iOSHook系统C函数(二):FishHook

在iOSHook系统C函数(一):使用动态库一文中,我们探讨了如何使用动态库去HooK系统的C函数,在本文中,我们将探索另外一种方式:fishhook,同样我们也去勾取getenv函数

实现

在fishhook中,主要的hook对象为:

     struct rebinding {
       const char *name; // 需要HOOK的函数名称,C字符串
       void *replacement; //新函数地址
       void **replaced; // 原始函数地址的指针
     };
  • 1,name: 需要HOOK的函数名称,C字符串。
  • 2,replacement: 新函数地址。
  • 3,replaced: 原始函数地址的指针。

为什么要获取原始函数地址指针?
通过保留一个变量来保存系统的函数,方便调用系统的函数。

我们在viewDidLoad里面来进行hook:

- (void)viewDidLoad {
    [super viewDidLoad];

    struct rebinding getEnvb; // 1
    getEnvb.name = "getenv"; //2
    getEnvb.replacement = my_getenv; //3
    getEnvb.replaced = (void *)&sys_getenv; //4

    struct rebinding rebs[1] = {getEnvb};
    rebind_symbols(rebs, 1); // 5
 m
    char * c = getenv("HOME");
    NSString *str = [NSString stringWithUTF8String:c];
    NSLog(@"%@",str);

    char * cTwo = getenv("PATH");
    NSString *strTwo = [NSString stringWithUTF8String:cTwo];

    NSLog(@"%@",strTwo);
}

static char *(* sys_getenv)(const char * str);  // 6

char  *my_getenv(const char * str){  // 7
    if (strcmp(str, "HOME") == 0 ) {
        return  "YAY";
    }else {
        return sys_getenv(str);
    }
}
  • 6: 根据getenv函数的签名,创建一个静态的函数名,用来保存系统的函数。

  • 7: 创建和系统函数交换自定义函数,签名需和系统函数保持一致。

  • 1: 创建 rebinding结构体。

  • 2: 需要hook的函数的名字。

  • 3: 自定义的函数,其方法签名需于被Hook函数的签名保持一致。

  • 4: 函数声明,用来存储系统原函数。

  • 5: 对符号进行重新绑定。

我们运行一下工程,运行结果如下:

2020-11-22 16:25:29.210773+0800 FishHookDemo[13208:8651857] YAY
2020-11-22 16:25:29.211194+0800 FishHookDemo[13208:8651857] /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/local/bin

是的,当我们获取HOME环境变量时,被我们自定义函数替换了值,获取PATH环境变量时,成功的调用了系统的函数方法。

原理分析

fishhook是怎样把系统的C函数替换的呢,我们应用加载的时候,使用的是 PIC技术:

  • 1,首先会在内存的数据段生成符号表
  • 2,dyld将真实地址赋值到数据段里面去,又叫做符号绑定

我们根据其文档我们可知dyld是通过更新符号表的内存指针来实现绑定的,符号表存在于Mach-O二进制文件的__DATA段 ,fishhook通过 rebind_symbols函数,对符号表进行重绑定,来实现响应替换

下面我们通过lldb指令来探索一下该过程。

我们修改一下代码

- (void)viewDidLoad {
    [super viewDidLoad];

    char * c = getenv("HOME");
     NSString *str = [NSString stringWithUTF8String:c];

     NSLog(@"%@",str);

    struct rebinding getEnvb; // 1
    getEnvb.name = "getenv"; //2
    getEnvb.replacement = my_getenv; //3
    getEnvb.replaced = (void *)&sys_getenv; //4


    struct rebinding rebs[1] = {getEnvb};
    rebind_symbols(rebs, 1); // 5

    char * cOne = getenv("HOME");
    NSString *strOne = [NSString stringWithUTF8String:cOne];

    NSLog(@"%@",strOne);

    char * cTwo = getenv("PATH");
    NSString *strTwo = [NSString stringWithUTF8String:cTwo];

    NSLog(@"%@",strTwo);
}

首先我们将程序 comand + B进行编译一下,得到 FishHookDemo.app文件,然后 显示包内容,得到 Mach-O 文件。

App包内容.png

然后我们将 Mach-O文件拖到Mach-O View中:
MachOView.jpg

符号表存在于 __DATA_CONST, __got__DATA,__la_symbol_ptr段中,分别为非懒加载符号表和懒加载符号表。这两种符号表的区别在于它们绑定的时机不一样,非懒加载符号表是在程序一启动就绑定了,懒加载符号表是在用到该符号时,才会进行绑定。_getenv符号存在于 Lazy Symbol Pointers,只有在用到时才会进行绑定。

我们在第一次调用的时候,设置一个断点,并运行程序,进入lldb模式

进入lldb.png

我们使用 image list命令,来查找当前进程加载的所有镜像,并找到FishHookDemo镜像的首地址

(lldb) image list
[  0] 19B79118-4135-3E49-BE15-A7C9B8EC16DF 0x00000001077e8000 /Users/ritamashin/Library/Developer/Xcode/DerivedData/FishHookDemo-ehajjrtywlbbfkfqujggbdpwoaos/Build/Products/Debug-iphonesimulator/FishHookDemo.app/FishHookDemo 
......

我们可以看到 0x00000001077e8000为该镜像的 内存地址(首地址) ,我们通过 首地址 + 偏移地址我们可以得到 符号所得地址值,通过 MachOView我们可以看到_getenv符号的偏移地址(offset)00005058,我们来查看一下其内存值

(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: 88 a4 7e 07 01 00 00 00 92 a4 7e 07 01 00 00 00  ..~.......~.....
0x1077ed068: 9c a4 7e 07 01 00 00 00 e8 a3 7e 07 01 00 00 00  ..~.......~.....
  • x: memory read的缩写,读取内存空间值。

因为我们是 64位的系统,我们从 0x1077ed058往右数8个字节,就读取到我们想要的值,因为 getenv函数还未调用,此时该值为系统的默认值,我们将断点移至第29行,

line29.png

然后查看内存值

(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: 67 b1 32 52 ff 7f 00 00 92 a4 7e 07 01 00 00 00  g.2R......~.....
0x1077ed068: 9c a4 7e 07 01 00 00 00 e8 a3 7e 07 01 00 00 00  ..~.......~.....

此时此刻,因为调用了getenv函数,对该符号进行了绑定,内存值已经发生变化。我们将该内存值使用汇编的形式进行查看

(lldb) dis -s 0x00007fff5232b167
libsystem_c.dylib`getenv:
    0x7fff5232b167 <+0>:  pushq  %rbp
    0x7fff5232b168 <+1>:  movq   %rsp, %rbp
    0x7fff5232b16b <+4>:  pushq  %r14
    0x7fff5232b16d <+6>:  pushq  %rbx
    0x7fff5232b16e <+7>:  subq   $0x10, %rsp
    0x7fff5232b172 <+11>: movq   %rdi, %rbx
    0x7fff5232b175 <+14>: leaq   0x37b7b494(%rip), %r14    ; __environ_lock_obj
    0x7fff5232b17c <+21>: movq   %r14, %rdi
    0x7fff5232b17f <+24>: movl   $0x10000, %esi            ; imm = 0x10000 
  • dis -s:使用汇编的形式进行查看

接下来,我们在 调用完rebind_symbols函数之后,我们来看下其符号表关联的内存情况是否有变化。

line42.png

(lldb) x 0x00000001077e8000+0x00005058
0x1077ed058: a0 94 7e 07 01 00 00 00 af 7a 43 52 ff 7f 00 00  ..~......zCR....
0x1077ed068: d4 8d 3b 52 ff 7f 00 00 e8 a3 7e 07 01 00 00 00  ..;R......~.....
(lldb) dis -s 0x00000001077e94a0
FishHookDemo`my_getenv:
    0x1077e94a0 <+0>:  pushq  %rbp
    0x1077e94a1 <+1>:  movq   %rsp, %rbp
    0x1077e94a4 <+4>:  subq   $0x10, %rsp
    0x1077e94a8 <+8>:  movq   %rdi, -0x10(%rbp)
    0x1077e94ac <+12>: movq   -0x10(%rbp), %rdi
    0x1077e94b0 <+16>: leaq   0x1d62(%rip), %rsi        ; "HOME"
    0x1077e94b7 <+23>: callq  0x1077ea3b8               ; symbol stub for: strcmp
    0x1077e94bc <+28>: cmpl   $0x0, %eax

此时此刻,_getenv符号绑定的地址已变为 FishHookDemo中的my_getenv函数了,当执行函数时,会执行我们自定义的my_getenv函数。这样我们证明到这里就结束了。

你可能感兴趣的:(iOSHook系统C函数(二):FishHook)