在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
文件。
然后我们将
Mach-O
文件拖到Mach-O View
中:
符号表
存在于 __DATA_CONST, __got
和__DATA,__la_symbol_ptr
段中,分别为非懒加载
符号表和懒加载
符号表。这两种符号表的区别在于它们绑定的时机不一样,非懒加载符号表是在程序一启动就绑定了,懒加载符号表是在用到该符号时,才会进行绑定。_getenv
符号存在于 Lazy Symbol Pointers
,只有在用到时才会进行绑定。
我们在第一次调用的时候,设置一个断点,并运行程序,进入lldb
模式
我们使用 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行,
然后查看内存值
(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
函数之后,我们来看下其符号表关联的内存情况是否有变化。
(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
函数。这样我们证明到这里就结束了。