iOS之fishhook

fishhook

fishHook是Facebook提供的一个动态修改链接mach-O文件的工具。利用MachO文件加载原理,通过重新绑定(rebind_symbols)懒加载表(Lazy Symbol Pointers)和非懒加载表(Non-Lazy Symbol Pointers)这两个表的指针达到C函数HOOK的目的。

dyld加载macho文件和系统框架,对函数地址复制的过程,成为 “符号绑定“

调试分析fishHook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
//
// ViewController.m
// fishhook
//
// Created by Cola on 2020/5/24.
// Copyright © 2020 Cola. All rights reserved.
//

#import "ViewController.h"
#import "fishhook.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
[super viewDidLoad];

//一个rebinding hook一个函数,想要hook多个函数,可以将rebinding添加至数组
NSLog(@"1");
//更改系统的NSLoga函数
struct rebinding nslog;
nslog.name = "NSLog";
nslog.replacement = myNSLog;
nslog.replaced = &sys_nslog;

struct rebinding rebs[1] = {nslog};


rebind_symbols(rebs, 1);

}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{

NSLog(@"点击");
}

//函数指针
static void (*sys_nslog)(NSString * _Nonnull format, ...);

//定义一个新的函数
void myNSLog(NSString * _Nonnull format, ...){
format = [format stringByAppendingFormat:@"\n HOOK成功🐂"];
sys_nslog(format);
}

@end
  1. 通过machoView查看符号表(Lazy Symbol Pointers)的偏移量(比如NSLog的偏移量是) 0x00005000

  2. rebind_symbols(rebs, 1);下断点,

  3. 通过image list查看加载的macho文件和所有的框架

    1
    2
    3
    4
    5
    6
    7
    8
    9
    (lldb) image list
    [ 0] E10158F5-413E-35E1-A349-8AFA71FFC955 0x0000000103db1000 /Users/cola/Library/Developer/Xcode/DerivedData/fishhook-dduxiymrdkidvtcxqmuqqwanbexr/Build/Products/Debug-iphonesimulator/fishhook.app/fishhook
    [ 1] EEA931D0-403E-3BC8-862A-CBA037DE4A74 0x00000001048e3000 /usr/lib/dyld
    [ 2] 75369F31-702D-364A-95C3-8AFA9DD4B3A2 0x0000000103dbf000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/dyld_sim
    [ 3] 56E47800-2CCB-3B7D-B94B-CCF5F13D6BCF 0x00007fff256b8000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/Foundation.framework/Foundation
    [ 4] 3EC683F6-36EF-33E1-8B98-C95E12BA38D2 0x00007fff513f6000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libobjc.A.dylib
    [ 5] 7881AD7F-524C-3CFA-9595-02ED549166AA 0x00007fff4ff15000 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/libSystem.B.dylib

    .......
  4. macho文件的首地址+第一步的偏移量,就是NSLog的真实地址,读NSLog的符号表地址

    memory read 简写 x 0x0000000103db1000+0x00005000

    1
    2
    3
    (lldb) memory read 0x0000000103db1000+0x00005000
    0x103db6000: fa 2d 76 25 ff 7f 00 00 f8 d1 74 25 ff 7f 00 00 .-v%......t%....
    0x103db6010: f8 26 09 48 ff 7f 00 00 44 34 db 03 01 00 00 00 .&.H....D4......
  5. 其中前八个字节为NSLog的地址
    查看4中的前八个字节的地址的汇编代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    (lldb) dis -s 0x7fff25762dfa
    Foundation`NSLog:
    0x7fff25762dfa <+0>: pushq %rbp
    0x7fff25762dfb <+1>: movq %rsp, %rbp
    0x7fff25762dfe <+4>: subq $0xd0, %rsp
    0x7fff25762e05 <+11>: testb %al, %al
    0x7fff25762e07 <+13>: je 0x7fff25762e2f ; <+53>
    0x7fff25762e09 <+15>: movaps %xmm0, -0xa0(%rbp)
    0x7fff25762e10 <+22>: movaps %xmm1, -0x90(%rbp)

    从中可以看见NSLog的地址

  6. 单步执行,执行fishhook的rebind_symbols方法后查看NSLog的真实地址

    和4相比地址已经改变

    1
    2
    3
    (lldb) memory read 0x000000010f550000+0x00005000
    0x10f555000: 50 14 55 0f 01 00 00 00 f8 d1 74 25 ff 7f 00 00 P.U.......t%....
    0x10f555010: f8 26 09 48 ff 7f 00 00 54 99 34 52 ff 7f 00 00 .&.H....T.4R....
  7. 查看前八个字节对应地址的汇编代码,发现已经被替换成我们的myNSlog方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    (lldb) dis -s 010f551450
    fishhook`myNSLog:
    0x10f551450 <+0>: pushq %rbp
    0x10f551451 <+1>: movq %rsp, %rbp
    0x10f551454 <+4>: subq $0x30, %rsp
    0x10f551458 <+8>: movq $0x0, -0x8(%rbp)
    0x10f551460 <+16>: leaq -0x8(%rbp), %rax
    0x10f551464 <+20>: movq %rdi, -0x10(%rbp)
    0x10f551468 <+24>: movq %rax, %rdi
    0x10f55146b <+27>: movq -0x10(%rbp), %rsi
    (lldb)
  8. 调用原方法

    因为系统默认绑定的地址被保存到了,自定义的变量中,可以通过地址调用该方法。

    1
    2
    //函数指针
    static void (*sys_nslog)(NSString * _Nonnull format, ...);

    调用原方法

    1
    2
    3
    4
    5
    //定义一个新的函数
    void myNSLog(NSString * _Nonnull format, ...){
    format = [format stringByAppendingFormat:@"\n HOOK成功🐂"];
    sys_nslog(format);
    }

fishHOOK原理

重新绑定符号表

懒加载符号表与indirect symbols表一一对应

indirect symbols表中NSLog的Data值为A6

A6转为10进制为166,其中166对应着Symbols Table下的Symbols

其中 #166在String Table Index表的偏移为D7,符号表基址为83AC,NSLog的地址为 83AC+D7=8483

而fishHook则是逆过程,通过String Table表NSLog的地址,找到Lazy Symbols Pointers(懒加载符号表)中的NSLog符号表地址,获取懒加载符号表的偏移.

fishHOOK能做什么

  1. 应用安全防护:

    将系统敏感函数,真实地址保存,并将该方法设置为宏定义,以后使用宏定义设置的方法,别人就无法HOOK该方法了,因为你使用的是函数的真实地址,并不是调用符号表中的函数地址,这样别人修改符号表是不影响程序的。

    注意:

    三方框架有可能会用到该函数,如果fishhook加载的过早会导致三方框架失效。

  1. 启动优化、埋点、AOP

    • 定位所有方法的调用,hook objc_msgSend函数?
    • 二进制重排(layout)的目的在于将hot code聚合在一起,即使得最经常执行的代码或最需要关键执行的代码(如启动阶段的顺序调用)聚合在一起,形成一个更紧凑的__TEXT段。