iOSHook系统C函数(一):使用动态库

Cocoa Touch Framework

我们将使用 Cocoa Touch Framework来Hook iOS 中系统的C函数,首先我们先新创建一个工程HooKDemo,在AppDelegate.h文件中,在 application(_:didFinishLaunchingWithOptions:)方法后面,我们写下如下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Override point for customization after application launch.
   char *str = getenv("HOME");
   NSString *envStr = [[NSString alloc] initWithUTF8String:str];

   NSLog(@"HOME env: %@", envStr);

   return YES;
}

这段代码是用来获取系统的环境变量 HOME的值,我们运行工程,运行结果如下

2020-11-20 HooKDemo[76436:7339554] HOME env: /Users/ritamashin/Library/Developer/CoreSimulator/Devices/928FDC1A-5119-406D-872E-6B605E24ED40/data/Containers/Data/Application/0F44D2B0-C3F5-42A3-8661-96DEC5578ECD

该值是我们所运行的模拟器中的 HOME环境变量值。如果我们想HOOK getEnv函数,我们需要创建一个依赖于HooKDemo的Framework,来改变getEnv函数的实现

Xcode中,我们通过 File -> New -> Target,并且选择 Cocoa Touch Framework,选择 HookingC为项目名,语言选择Objective-C

frameWork.png

HookingC项目中,我们新建一个c语言文件,并命名为 getenvhook

hook c.png

我们在 getenvhook.c文件中,添加以下头文件

 #import <dlfcn.h>
 #import <assert.h>
 #import <stdio.h>
 #import <dispatch/dispatch.h>
 #import <string.h>
  • dlfcn.h中,我们主要用了两个函数 dlopendlsym
  • assert.h中,我们主要来测试真实的 getEnv函数被真正加载。
  • stdio.h我们主要用里面的 printf函数。
  • string.h我们主要用里面的strcmp函数来比较两个C字符串。

我们在 getenvhook.c文件中,重写getenv方法

char * getenv(const char *name) {
    return  "YAY!";
}

然后我们运行该工程,我们可以看到如下输出

HOME env: YAY!

我们可以得知,已经成功替换了系统的 getenv方法,但这肯定不是我们期望的,如果我们在 getenv函数里面增加如下代码会发生什么呢?

char  * getenv(const char *name) {
  return getenv(name);
  return "YAY"!
}

这样的话,会造成递归循环调用,导致栈溢出,我们现在已经替换了系统的getenv函数,我们需要找到系统的getenv函数,然后在调用它,我们重新启动工程,然后使工程进入lldb模式,在镜像文件中查找getenv方法

(lldb) image lookup -s getenv
1 symbols match 'getenv' in /Users/ritamashin/Library/Developer/Xcode/DerivedData/HooKDemo-gosjkkcckyouwldjttqgdrtoaovl/Build/Products/Debug-iphonesimulator/HooKDemo.app/Frameworks/HOOKingC.framework/HOOKingC:
        Address: HOOKingC[0x0000000000000f60] (HOOKingC.__TEXT.__text + 0)
        Summary: HOOKingC`getenv at getenvhook.c:12
1 symbols match 'getenv' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libsystem_c.dylib:
        Address: libsystem_c.dylib[0x000000000005a167] (libsystem_c.dylib.__TEXT.__text + 364823)
        Summary: libsystem_c.dylib`getenv
1 symbols match 'getenv' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/AppleAccount.framework/AppleAccount:

我们一共找到3个getenv实现,一个在HOOKingC.framework中,一个在lib system_c.dylib中。它的全路径为 /usr/lib/system/libsystem_c.dylib

现在我们知道了这个函数是从哪里加载的了,下面我们将使用dlopen函数,去加载该函数。其方法签名如下

extern void * dlopen(const char * __path, int __mode); 
  • dlopen函数第一个参数为 一个全路径,第二个参数为一个整数来决定了 dlopen函数以怎样的模式去加载,如果加载成功,dlopen将会返回一个void *类型的数据,否则将会返回NULL

dlopen函数加载完之后,我们使用dlsym去得到 getenv函数的引用,dlsym函数的函数签名如下:

extern void * dlsym(void * __handle, const char * __symbol);
  • dlsym的第一个参数为 dlopen函数得到的方法引用,第二个参数为方法的名字。如果成功,将返回第二个参数 符号名内存地址。如果失败了,则返回NULL
    我们将我们自定义的getEnv函数,进行改进一下。
char * getenv(const char *name) {
    void *handle = dlopen("/usr/lib/system/libsystem_c.dylib", RTLD_NOW);
    assert(handle);

    void *real_getenv = dlsym(handle, "getenv");

    printf("Real getenv: %p\n Fake getenv: %p\n", real_getenv, getenv);

    return  "YAY!";
}
  • 1,我们使用 RTLD_NOW模式使用 dlopen函数的意思是:让该函数立即执行,不使用懒加载的方式。
  • 2,我们使用assert函数,确保 handle不为NULL
  • 3,我们使用 dlsym函数,来加载get_env的真实地址值。
    我们再次运行工程,会得到类似的以下输出:
Real getenv: 0x7fff5232b167
 Fake getenv: 0x107469de0
 HooKDemo[86264:7770900] HOME env: YAY!

现在我们已经知道了getenv的函数签名,接下来,我们将getenv函数进行如下调整:

char * getenv(const char *name) {
  static void *handle;      // 1 
  static char * (*real_getenv)(const char *); // 2
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{  // 3 
    handle = dlopen("/usr/lib/system/libsystem_c.dylib",
                    RTLD_NOW); 
    assert(handle);
    real_getenv = dlsym(handle, "getenv");
  });
  
  if (strcmp(name, "HOME") == 0) { // 4
    return "/WOOT";
  }
  
  return real_getenv(name); // 5
}.
  • 1,创建一个static变量 handle,因为是静态的,在函数执行完之后也不会被释放,在getenv函数之外,我们也可以获取该值。
  • 2,我们根据getenv函数的方法签名,定义了一个静态的real_getenv函数指针。我们将会把它赋值到真正的getenv方法实现中去。
  • 3,使用dispatch_once函数,保证只执行一次。
  • 4,使用 strcmp函数,判断当前变量是否为HOME,来更改其输出
  • 5, 如果不是HOME变量,则调用默认的getenv函数。

AppDelegate中,我们分别获取HOMEPATH环境变量

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    char *str = getenv("HOME");
    NSString *envStr = [[NSString alloc] initWithUTF8String:str];

    NSLog(@"HOME env: %@", envStr);
    char *pathchar = getenv("PATH");
    NSString *pathStr = [[NSString alloc] initWithUTF8String:pathchar];
    NSLog(@"HOME env: %@", pathStr);
    return YES;
}

其运行结果如下:

2020-11-21 14:38:03.325656+0800 HooKDemo[86696:7792671] HOME env: /WOOT
2020-11-21 14:38:03.325885+0800 HooKDemo[86696:7792671] HOME env: /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环境变量的值给替换掉,非HOME环境变量还是调用默认的getenv函数。

如果我们想要调用 UIKit框架中的 getenv函数,以我们现在的方式是不可以的,这是因为,此时UIKit已经加载到内存中,我们自定义的getenv函数不会得到调用。

为了能够调用UIKit中的getenv函数,我们需要掌握 符号表的重定向知识,然后去改变在__DATA.__la_symbol_ptr段中的getenv的地址。

备注:dlopen:打开动态库,dlsym:获得符号名。

总结

本篇文章,我们主要探讨了使用 iOS中的frameworkHOOK系统中的C函数,通过 dlopen函数得到系统中真实的函数地址,使用dlsym函数和方法签名对真实的地址进行符号绑定。从而达到调用默认函数的目的。

最后附上本文代码示例 HookDemo

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343