iOS逆向之FISHHOOK介绍
-
上一篇文章地址: iOS逆向之Hook原理介绍
-
感谢小伙伴的关注我的文章,有什么疑问,大家就留言吧.
-
最近新写的文章正在更新中.
-
每一篇文章,我会亲自操作一遍,把操作的流程都记录下来.
-
写一篇文章希望他是有价值的,非常感谢朋友们的支持.
-
我之前也有遇到过有些iOS逆向文章介绍的很详细,通过实际操作,可以判断有些文章已经过时了.
-
因此,我把最新的实现结果记录及遇到的问题,解决的方式记录下来.
-
再一个,每个移动设备的系统版本不一样,也有可能我没有遇到这个问题,并没有记录.如果你愿意分享你的解决方式,可以联系我,我补充一下,把它分享给更多的朋友.
1.Fishhook
Fishhook上一篇文章,我们介绍了HOOK的原理,其中FISHHOOK是一种HOOK方式技术之一.
FISHHOOK地址: FISHHOOK
-
1.FISHHOOK
是 Facebook开源的一个非常小的重新绑定动态符号的库.
其实就是动态修改链接mach-O
文件的工具.
利用Mach-O
文件加载原理,通过修改懒加载和非懒加载
两个表的指针达到C函数进行HOOK的目的.
-
2.
FISHHOOK源码
fishhook代码加起来差不多200多行,代码量很少,轻量级代码.
fishhook.h
#ifndef fishhook_h
#define fishhook_h
#include <stddef.h>
#include <stdint.h>
#if !defined(FISHHOOK_EXPORT)
#define FISHHOOK_VISIBILITY __attribute__((visibility("hidden")))
#else
#define FISHHOOK_VISIBILITY __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif //__cplusplus
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
/*
* For each rebinding in rebindings, rebinds references to external, indirect
* symbols with the specified name to instead point at replacement for each
* image in the calling process as well as for all future images that are loaded
* by the process. If rebind_functions is called more than once, the symbols to
* rebind are added to the existing list of rebindings, and if a given symbol
* is rebound more than once, the later rebinding will take precedence.
*/
FISHHOOK_VISIBILITY
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel);
/*
* Rebinds as above, but only in the specified image. The header should point
* to the mach-o header, the slide should be the slide offset. Others as above.
*/
FISHHOOK_VISIBILITY
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel);
#ifdef __cplusplus
}
#endif //__cplusplus
#endif //fishhook_h
fishhook.c
#include "fishhook.h"
#include <dlfcn.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <mach/mach.h>
#include <mach/vm_map.h>
#include <mach/vm_region.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#ifdef __LP64__
typedef struct mach_header_64 mach_header_t;
typedef struct segment_command_64 segment_command_t;
typedef struct section_64 section_t;
typedef struct nlist_64 nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT_64
#else
typedef struct mach_header mach_header_t;
typedef struct segment_command segment_command_t;
typedef struct section section_t;
typedef struct nlist nlist_t;
#define LC_SEGMENT_ARCH_DEPENDENT LC_SEGMENT
#endif
#ifndef SEG_DATA_CONST
#define SEG_DATA_CONST "__DATA_CONST"
#endif
struct rebindings_entry {
struct rebinding *rebindings;
size_t rebindings_nel;
struct rebindings_entry *next;
};
static struct rebindings_entry *_rebindings_head;
static int prepend_rebindings(struct rebindings_entry **rebindings_head,
struct rebinding rebindings[],
size_t nel) {
struct rebindings_entry *new_entry = (struct rebindings_entry *) malloc(sizeof(struct rebindings_entry));
if (!new_entry) {
return -1;
}
new_entry->rebindings = (struct rebinding *) malloc(sizeof(struct rebinding) * nel);
if (!new_entry->rebindings) {
free(new_entry);
return -1;
}
memcpy(new_entry->rebindings, rebindings, sizeof(struct rebinding) * nel);
new_entry->rebindings_nel = nel;
new_entry->next = *rebindings_head;
*rebindings_head = new_entry;
return 0;
}
static vm_prot_t get_protection(void *sectionStart) {
mach_port_t task = mach_task_self();
vm_size_t size = 0;
vm_address_t address = (vm_address_t)sectionStart;
memory_object_name_t object;
#if __LP64__
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
vm_region_basic_info_data_64_t info;
kern_return_t info_ret = vm_region_64(
task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object);
#else
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT;
vm_region_basic_info_data_t info;
kern_return_t info_ret = vm_region(task, &address, &size, VM_REGION_BASIC_INFO, (vm_region_info_t)&info, &count, &object);
#endif
if (info_ret == KERN_SUCCESS) {
return info.protection;
} else {
return VM_PROT_READ;
}
}
static void perform_rebinding_with_section(struct rebindings_entry *rebindings,
section_t *section,
intptr_t slide,
nlist_t *symtab,
char *strtab,
uint32_t *indirect_symtab) {
const bool isDataConst = strcmp(section->segname, "__DATA_CONST") == 0;
uint32_t *indirect_symbol_indices = indirect_symtab + section->reserved1;
void **indirect_symbol_bindings = (void **)((uintptr_t)slide + section->addr);
vm_prot_t oldProtection = VM_PROT_READ;
if (isDataConst) {
oldProtection = get_protection(rebindings);
mprotect(indirect_symbol_bindings, section->size, PROT_READ | PROT_WRITE);
}
for (uint i = 0; i < section->size / sizeof(void *); i++) {
uint32_t symtab_index = indirect_symbol_indices[I];
if (symtab_index == INDIRECT_SYMBOL_ABS || symtab_index == INDIRECT_SYMBOL_LOCAL ||
symtab_index == (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) {
continue;
}
uint32_t strtab_offset = symtab[symtab_index].n_un.n_strx;
char *symbol_name = strtab + strtab_offset;
bool symbol_name_longer_than_1 = symbol_name[0] && symbol_name[1];
struct rebindings_entry *cur = rebindings;
while (cur) {
for (uint j = 0; j < cur->rebindings_nel; j++) {
if (symbol_name_longer_than_1 &&
strcmp(&symbol_name[1], cur->rebindings[j].name) == 0) {
if (cur->rebindings[j].replaced != NULL &&
indirect_symbol_bindings[i] != cur->rebindings[j].replacement) {
*(cur->rebindings[j].replaced) = indirect_symbol_bindings[I];
}
indirect_symbol_bindings[i] = cur->rebindings[j].replacement;
goto symbol_loop;
}
}
cur = cur->next;
}
symbol_loop:;
}
if (isDataConst) {
int protection = 0;
if (oldProtection & VM_PROT_READ) {
protection |= PROT_READ;
}
if (oldProtection & VM_PROT_WRITE) {
protection |= PROT_WRITE;
}
if (oldProtection & VM_PROT_EXECUTE) {
protection |= PROT_EXEC;
}
mprotect(indirect_symbol_bindings, section->size, protection);
}
}
static void rebind_symbols_for_image(struct rebindings_entry *rebindings,
const struct mach_header *header,
intptr_t slide) {
Dl_info info;
if (dladdr(header, &info) == 0) {
return;
}
segment_command_t *cur_seg_cmd;
segment_command_t *linkedit_segment = NULL;
struct symtab_command* symtab_cmd = NULL;
struct dysymtab_command* dysymtab_cmd = NULL;
uintptr_t cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_LINKEDIT) == 0) {
linkedit_segment = cur_seg_cmd;
}
} else if (cur_seg_cmd->cmd == LC_SYMTAB) {
symtab_cmd = (struct symtab_command*)cur_seg_cmd;
} else if (cur_seg_cmd->cmd == LC_DYSYMTAB) {
dysymtab_cmd = (struct dysymtab_command*)cur_seg_cmd;
}
}
if (!symtab_cmd || !dysymtab_cmd || !linkedit_segment ||
!dysymtab_cmd->nindirectsyms) {
return;
}
// Find base symbol/string table addresses
uintptr_t linkedit_base = (uintptr_t)slide + linkedit_segment->vmaddr - linkedit_segment->fileoff;
nlist_t *symtab = (nlist_t *)(linkedit_base + symtab_cmd->symoff);
char *strtab = (char *)(linkedit_base + symtab_cmd->stroff);
// Get indirect symbol table (array of uint32_t indices into symbol table)
uint32_t *indirect_symtab = (uint32_t *)(linkedit_base + dysymtab_cmd->indirectsymoff);
cur = (uintptr_t)header + sizeof(mach_header_t);
for (uint i = 0; i < header->ncmds; i++, cur += cur_seg_cmd->cmdsize) {
cur_seg_cmd = (segment_command_t *)cur;
if (cur_seg_cmd->cmd == LC_SEGMENT_ARCH_DEPENDENT) {
if (strcmp(cur_seg_cmd->segname, SEG_DATA) != 0 &&
strcmp(cur_seg_cmd->segname, SEG_DATA_CONST) != 0) {
continue;
}
for (uint j = 0; j < cur_seg_cmd->nsects; j++) {
section_t *sect =
(section_t *)(cur + sizeof(segment_command_t)) + j;
if ((sect->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
if ((sect->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS) {
perform_rebinding_with_section(rebindings, sect, slide, symtab, strtab, indirect_symtab);
}
}
}
}
}
static void _rebind_symbols_for_image(const struct mach_header *header,
intptr_t slide) {
rebind_symbols_for_image(_rebindings_head, header, slide);
}
int rebind_symbols_image(void *header,
intptr_t slide,
struct rebinding rebindings[],
size_t rebindings_nel) {
struct rebindings_entry *rebindings_head = NULL;
int retval = prepend_rebindings(&rebindings_head, rebindings, rebindings_nel);
rebind_symbols_for_image(rebindings_head, (const struct mach_header *) header, slide);
if (rebindings_head) {
free(rebindings_head->rebindings);
}
free(rebindings_head);
return retval;
}
int rebind_symbols(struct rebinding rebindings[], size_t rebindings_nel) {
int retval = prepend_rebindings(&_rebindings_head, rebindings, rebindings_nel);
if (retval < 0) {
return retval;
}
// If this was the first call, register callback for image additions (which is also invoked for
// existing images, otherwise, just run on existing images
if (!_rebindings_head->next) {
_dyld_register_func_for_add_image(_rebind_symbols_for_image);
} else {
uint32_t c = _dyld_image_count();
for (uint32_t i = 0; i < c; i++) {
_rebind_symbols_for_image(_dyld_get_image_header(i), _dyld_get_image_vmaddr_slide(i));
}
}
return retval;
}
注意
如果需要下载源码,只需要访问上面的链接即可查看.
-
3.
FISHHOOK如何工作?
dyld
通过更新Mach-O二进制文件中特定的_DATA段
来绑定懒加载和非懒加载符号.
fishhook
通过更新这些符号位置进行重新绑定,然后进行相应的替换,从而重新绑定这些符号.
-
4.
我们继续使用TEST 项目
ViewController.m
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic,strong)UIButton *revealBtn;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
NSLog(@"hello");
[self.view addSubview:self.revealBtn];
}
-(UIButton*)revealBtn{
if (!_revealBtn) {
_revealBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_revealBtn.frame = CGRectMake(100,100, 100,40);
_revealBtn.backgroundColor = [UIColor redColor];
[_revealBtn setTitle:@"测试" forState:UIControlStateNormal];
[_revealBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_revealBtn addTarget:self action:@selector(revealClick) forControlEvents:UIControlEventTouchUpInside];
}
return _revealBtn;
}
-(void)revealClick{
NSLog(@"revealClick");
}
@end
4.1
我们首先编译/打包项目,生成ipa包.我们找到ipa包中mach-O文件.
4.2
我们就可以找到通过MachOView打开TEST_MachO
4.3
找到懒加载符号表,找到NSLog的偏移地址.
这里是Offset(Ox3020
)
4.4
我们通过在viewDidLoad进行断点,查看 NSLog 的汇编代码
4.5
我们通过上一节中LLDB的介绍,使用lldb 命令进行查看MachO的偏移地址,再找到NSLog的偏移地址,就是通过DYLD动态绑定,将MachO中的_DATA段的指针,指向外部函数,也就是NSLog函数.
-
5.
怎么使用fishhook呢
文章前面有提到fishhook的github下载地址,我们只需要把文件下载fishhook.c,fishhook.h
下载好后,放到TEST项目目录下,我们对NSLog进行绑定修改.
ViewController.m
#import "ViewController.h"
#import "fishhook.h"
@interface ViewController ()
@property (nonatomic,strong)UIButton *revealBtn;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
[self.view addSubview:self.revealBtn];
struct rebinding logReBind;
logReBind.name = "NSLog";
logReBind.replacement = newNSlog;
logReBind.replaced = (void *)&OriginalNSLog;
struct rebinding rebs[] ={ logReBind };
rebind_symbols(rebs, 1);
}
-(UIButton*)revealBtn{
if (!_revealBtn) {
_revealBtn = [UIButton buttonWithType:UIButtonTypeCustom];
_revealBtn.frame = CGRectMake(100,100, 100,40);
_revealBtn.backgroundColor = [UIColor redColor];
[_revealBtn setTitle:@"测试" forState:UIControlStateNormal];
[_revealBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_revealBtn addTarget:self action:@selector(revealClick) forControlEvents:UIControlEventTouchUpInside];
}
return _revealBtn;
}
-(void)revealClick{
NSLog(@"hello");
}
static void (*OriginalNSLog)(NSString *format, ...);
void newNSlog(NSString *format, ...){
OriginalNSLog([NSString stringWithFormat:@"newNSLog : %@",format]);
}
@end
结果: 2020-03-18 16:43:17.060093+0800 TEST[5261:198913] newNSLog : hello
/*
* A structure representing a particular intended rebinding from a symbol
* name to its replacement
*/
struct rebinding {
const char *name;
void *replacement;
void **replaced;
};
name -> hook函数名称
replacement -> 替换新的函数
replaced 保存原始函数指针变量的指针
最后绑定符号表
-
6.
通过符号表查找字符串
6.1
我们这里分析一下如何查找字符串表.
我们打开MachOView
我们依然使用NSLog为例
6.2
我们找到的是懒加载表,NSLog
6.3
通过懒加载表(NSLog
) 找到 Indirect Symbols,图中红色箭头所指方向.
6.4
找到间接符号表,如上图.
间接符号表 与 懒加载表一一对应
6.5
我们通过间接表获取的Data值进行机制转换,获取NSLog在(Symbol Tables -> Symbols
)中的位置,如下图
6.6
我们找到符号表中的NSLog的data值 Ox9C
注意
查找字符串,通过符号表偏移量 + 字符串表基地址进行获取.
字符串表及地址: 0x61F0
符号表偏移值 : 0x9C
计算得到是Ox628C
这样我们通过符号表就查找到了字符串.
2.总结
通过本篇文章的学习,我们知道了FISHHOOK的作用,如何使用及查找字符串原理.
大家有什么疑问,可以在文章下方进行留言.
感谢关注iOS逆向文章.