iOSHook系統(tǒng)C函數(shù)(一):使用動(dòng)態(tài)庫(kù)

Cocoa Touch Framework

我們將使用 Cocoa Touch Framework來Hook iOS 中系統(tǒng)的C函數(shù),首先我們先新創(chuàng)建一個(gè)工程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;
}

這段代碼是用來獲取系統(tǒng)的環(huán)境變量 HOME的值,我們運(yùn)行工程,運(yùn)行結(jié)果如下

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

該值是我們所運(yùn)行的模擬器中的 HOME環(huán)境變量值。如果我們想HOOK getEnv函數(shù),我們需要創(chuàng)建一個(gè)依賴于HooKDemo的Framework,來改變getEnv函數(shù)的實(shí)現(xiàn)。

Xcode中,我們通過 File -> New -> Target,并且選擇 Cocoa Touch Framework,選擇 HookingC為項(xiàng)目名,語(yǔ)言選擇Objective-C

frameWork.png

HookingC項(xiàng)目中,我們新建一個(gè)c語(yǔ)言文件,并命名為 getenvhook

hook c.png

我們?cè)?getenvhook.c文件中,添加以下頭文件

 #import <dlfcn.h>
 #import <assert.h>
 #import <stdio.h>
 #import <dispatch/dispatch.h>
 #import <string.h>
  • dlfcn.h中,我們主要用了兩個(gè)函數(shù) dlopendlsym。
  • assert.h中,我們主要來測(cè)試真實(shí)的 getEnv函數(shù)被真正加載。
  • stdio.h我們主要用里面的 printf函數(shù)。
  • string.h我們主要用里面的strcmp函數(shù)來比較兩個(gè)C字符串。

我們?cè)?getenvhook.c文件中,重寫getenv方法

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

然后我們運(yùn)行該工程,我們可以看到如下輸出

HOME env: YAY!

我們可以得知,已經(jīng)成功替換了系統(tǒng)的 getenv方法,但這肯定不是我們期望的,如果我們?cè)?getenv函數(shù)里面增加如下代碼會(huì)發(fā)生什么呢?

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

這樣的話,會(huì)造成遞歸循環(huán)調(diào)用,導(dǎo)致棧溢出,我們現(xiàn)在已經(jīng)替換了系統(tǒng)的getenv函數(shù),我們需要找到系統(tǒng)的getenv函數(shù),然后在調(diào)用它,我們重新啟動(dòng)工程,然后使工程進(jìn)入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個(gè)getenv實(shí)現(xiàn),一個(gè)在HOOKingC.framework中,一個(gè)在lib system_c.dylib中。它的全路徑為 /usr/lib/system/libsystem_c.dylib。

現(xiàn)在我們知道了這個(gè)函數(shù)是從哪里加載的了,下面我們將使用dlopen函數(shù),去加載該函數(shù)。其方法簽名如下

extern void * dlopen(const char * __path, int __mode); 
  • dlopen函數(shù)第一個(gè)參數(shù)為 一個(gè)全路徑,第二個(gè)參數(shù)為一個(gè)整數(shù)來決定了 dlopen函數(shù)以怎樣的模式去加載,如果加載成功,dlopen將會(huì)返回一個(gè)void *類型的數(shù)據(jù),否則將會(huì)返回NULL。

dlopen函數(shù)加載完之后,我們使用dlsym去得到 getenv函數(shù)的引用,dlsym函數(shù)的函數(shù)簽名如下:

extern void * dlsym(void * __handle, const char * __symbol);
  • dlsym的第一個(gè)參數(shù)為 dlopen函數(shù)得到的方法引用,第二個(gè)參數(shù)為方法的名字。如果成功,將返回第二個(gè)參數(shù) 符號(hào)名內(nèi)存地址。如果失敗了,則返回NULL。
    我們將我們自定義的getEnv函數(shù),進(jìn)行改進(jìn)一下。
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函數(shù)的意思是:讓該函數(shù)立即執(zhí)行,不使用懶加載的方式。
  • 2,我們使用assert函數(shù),確保 handle不為NULL。
  • 3,我們使用 dlsym函數(shù),來加載get_env的真實(shí)地址值。
    我們?cè)俅芜\(yùn)行工程,會(huì)得到類似的以下輸出:
Real getenv: 0x7fff5232b167
 Fake getenv: 0x107469de0
 HooKDemo[86264:7770900] HOME env: YAY!

現(xiàn)在我們已經(jīng)知道了getenv的函數(shù)簽名,接下來,我們將getenv函數(shù)進(jìn)行如下調(diào)整:

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,創(chuàng)建一個(gè)static變量 handle,因?yàn)槭庆o態(tài)的,在函數(shù)執(zhí)行完之后也不會(huì)被釋放,在getenv函數(shù)之外,我們也可以獲取該值。
  • 2,我們根據(jù)getenv函數(shù)的方法簽名,定義了一個(gè)靜態(tài)的real_getenv函數(shù)指針。我們將會(huì)把它賦值到真正的getenv方法實(shí)現(xiàn)中去。
  • 3,使用dispatch_once函數(shù),保證只執(zhí)行一次。
  • 4,使用 strcmp函數(shù),判斷當(dāng)前變量是否為HOME,來更改其輸出
  • 5, 如果不是HOME變量,則調(diào)用默認(rèn)的getenv函數(shù)。

AppDelegate中,我們分別獲取HOMEPATH環(huán)境變量

- (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;
}

其運(yùn)行結(jié)果如下:

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環(huán)境變量的值給替換掉,非HOME環(huán)境變量還是調(diào)用默認(rèn)的getenv函數(shù)。

如果我們想要調(diào)用 UIKit框架中的 getenv函數(shù),以我們現(xiàn)在的方式是不可以的,這是因?yàn)?,此時(shí)UIKit已經(jīng)加載到內(nèi)存中,我們自定義的getenv函數(shù)不會(huì)得到調(diào)用。

為了能夠調(diào)用UIKit中的getenv函數(shù),我們需要掌握 符號(hào)表的重定向知識(shí),然后去改變?cè)?code>__DATA.__la_symbol_ptr段中的getenv的地址。

備注:dlopen:打開動(dòng)態(tài)庫(kù),dlsym:獲得符號(hào)名。

總結(jié)

本篇文章,我們主要探討了使用 iOS中的frameworkHOOK系統(tǒng)中的C函數(shù),通過 dlopen函數(shù)得到系統(tǒng)中真實(shí)的函數(shù)地址,使用dlsym函數(shù)和方法簽名對(duì)真實(shí)的地址進(jìn)行符號(hào)綁定。從而達(dá)到調(diào)用默認(rèn)函數(shù)的目的。

最后附上本文代碼示例 HookDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容