另類(lèi)iOS上的C函數(shù)hook

hook C函數(shù)

先拿一個(gè)簡(jiǎn)單的c函數(shù)getenv上手。這個(gè)函數(shù)接受一個(gè)char *類(lèi)型的參數(shù)(得是null terminated string)并返回對(duì)應(yīng)的環(huán)境變量。

實(shí)際上在你的可執(zhí)行文件啟動(dòng)時(shí)這個(gè)函數(shù)會(huì)被多次調(diào)用的。

用Xcode打開(kāi)Watermark項(xiàng)目。創(chuàng)建一個(gè)symbolic breakpoint到getenv,并添加action:
po (char *)$rdi
并勾選Automatically continue after evaluating actions。

getenv斷點(diǎn).png

構(gòu)建并運(yùn)行app在模擬器上,你可以得到類(lèi)似這樣的輸出:

"DYLD_INSERT_LIBRARIES"
"NSZombiesEnabled"
"OBJC_DEBUG_POOL_ALLOCATION"
"MallocStackLogging"
"MallocStackLoggingNoCompact"
"OBJC_DEBUG_MISSING_POOLS"
"LIBDISPATCH_DEBUG_QUEUE_INVERSIONS"
"LIBDISPATCH_CONTINUATION_ALLOCATOR"
... etc ...

(注意,更優(yōu)雅的打印app的所有環(huán)境變量的方式是使用DYLD_PRINT_ENV。在Product\Manage Scheme中添加這個(gè)到Environment variables里面去??梢院?jiǎn)單地添加這個(gè)名字,不需要值,就可以了。啟動(dòng)app后控制臺(tái)就可以看到所有環(huán)境變量及其對(duì)應(yīng)值了。)

這里需要注意的一點(diǎn)是,所有這些getenv的調(diào)用都發(fā)生在你的可執(zhí)行文件開(kāi)始執(zhí)行之前。你可以在getenv添加一個(gè)斷點(diǎn)然后查看調(diào)用棧來(lái)驗(yàn)證(你將找不到任何main入口的蛛絲馬跡)。這意味著你不能夠修改這個(gè)函數(shù)除非你在dyld加載動(dòng)態(tài)庫(kù)之前定義了getenv函數(shù)。

既然C并沒(méi)有使用方法動(dòng)態(tài)分配(objc_msgsend),要hook一個(gè)函數(shù)你必須得在它被加載之前攔截它。不過(guò)C函數(shù)也并不是棘手得徹底,C函數(shù)相對(duì)容易獲取。所有你需要的只是函數(shù)的方法名,并不需要任何參數(shù)和所在動(dòng)態(tài)庫(kù)的名字。

c函數(shù)的hook有很多復(fù)雜度都不一樣的方式。如果你只是想在你的可執(zhí)行文件內(nèi)進(jìn)行hook,那要做到事情并不多。但是如果你想在main函數(shù)前hook一個(gè)方法,復(fù)雜度就提升了一個(gè)等級(jí)。

一旦你的可執(zhí)行文件在main函數(shù)加載完畢,load commands里面指定的所有的動(dòng)態(tài)庫(kù)也都已加載完畢。dyld以深度優(yōu)先的方式遞歸加載動(dòng)態(tài)庫(kù)。一旦鏈接器獲取到一個(gè)特定函數(shù)的地址,它將會(huì)用這個(gè)函數(shù)的實(shí)際內(nèi)存地址替換所有這個(gè)函數(shù)的引用。

這意味著如果你想在你的app啟動(dòng)前hook一個(gè)c函數(shù),你需要?jiǎng)?chuàng)建一個(gè)動(dòng)態(tài)庫(kù)并把hook邏輯放到里面去,這樣它在main函數(shù)被調(diào)用之前將會(huì)是可用的。

理論扯了一大堆,回到project中來(lái)。打開(kāi)**AppDelegate.swift并用以下替換application(_:didFinishLaunchingWithOptions:)

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
  if let cString = getenv("HOME") {
    let homeEnv = String(cString: cString)
    print("HOME env: \(homeEnv)")
  }
return true
}

取消上面創(chuàng)建的符號(hào)斷點(diǎn)并運(yùn)行,大概得到如下輸出:
HOME env: /Users/username/Library/Developer/CoreSimulator/Devices/3EAAC880-B34B-4966-AE34-5C617769E54B/data/Containers/Data/Application/31F483BA-D23F-4DEC-8C0A-227CF6934A75
這個(gè)就是你正使用的模擬器的HOME環(huán)境變量。

通過(guò)以下命令獲取getenv所在的動(dòng)態(tài)庫(kù):

(lldb) image lookup -s getenv
1 symbols match 'getenv' in /Users/gogleyin/Library/Developer/Xcode/DerivedData/Watermark-grqfiuuagfwxlhgqetwiuaqnqiwn/Build/Products/Debug-iphonesimulator/Watermark.app/Frameworks/HookingC.framework/HookingC:
        Address: HookingC[0x0000000000000d00] (HookingC.__TEXT.__text + 0)
        Summary: HookingC`getenv at getenvhook.c:30
1 symbols match 'getenv' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//usr/lib/system/libsystem_c.dylib:
        Address: libsystem_c.dylib[0x000000000005ca26] (libsystem_c.dylib.__TEXT.__text + 375174)
        Summary: libsystem_c.dylib`getenv

現(xiàn)在假設(shè)你要這樣對(duì)getenv進(jìn)行hook:邏輯不變,只是當(dāng)參數(shù)為HOME時(shí)返回點(diǎn)不一樣的東西。

正如前面所述,你需要?jiǎng)?chuàng)建一個(gè)動(dòng)態(tài)庫(kù)。getenv本將會(huì)在main可執(zhí)行文件中被解析,在這之前你的app的可執(zhí)行文件必須獲取到getenv的地址并改變它。

File/New/Target/Cocoa Touch Framework,設(shè)若Product Name為HookingC,Project和Embed in Application選擇與你的app對(duì)應(yīng)。然后新建一個(gè)c文件叫g(shù)etenvhook.c(取消勾選Also create a header file),內(nèi)容如下:

#import <dlfcn.h>
#import <assert.h>
#import <stdio.h>
#import <dispatch/dispatch.h>
#import <string.h>

char * getenv(const char *name) {
  static void *handle;
  static char * (*real_getenv)(const char *);
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    handle = dlopen("/usr/lib/system/libsystem_c.dylib", RTLD_NOW);
    assert(handle);
    real_getenv = dlsym(handle, "getenv");
  });
  
  if (strcmp(name, "HOME") == 0) {
    return "/";
  }
  
  return real_getenv(name);
}

下面來(lái)解釋一下。dlopen函數(shù)簽名如下:
extern void * dlopen(const char * __path, int __mode);
dlopen接受一個(gè)char *類(lèi)型的路徑,和一個(gè)整型來(lái)決定它如何加載模塊。如果成功返回一個(gè)handle(類(lèi)型為void *),否則NULL。

dlopen返回一個(gè)對(duì)模塊的引用后,你就可以使用dlsym來(lái)獲取對(duì)函數(shù)getenv的引用了:
extern void * dlsym(void * __handle, const char * __symbol);
第一個(gè)參數(shù)為dlopen返回的handle,第二個(gè)參數(shù)為要獲取的函數(shù)的名字。一切正常的話,它就會(huì)返回第二個(gè)指定的函數(shù)的地址,否則返回NULL。

需要注意的是,如果你調(diào)用了一個(gè)UIKit方法,然后UIKit調(diào)用了getenv,那么新的getenv方法并不會(huì)被調(diào)用,因?yàn)?code>getenv的地址在UIKit的代碼被加載的時(shí)候已經(jīng)被解析了。

最后編輯于
?著作權(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)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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