如何在 iOS 上 hook 一個 C 函數(shù)

在 iOS 上,Objective-C runtime 提供了一系列函數(shù)熊杨,可以很容易地 hook Objective-C 的方法上炎。因?yàn)?Objective-C 的動態(tài)性很高荡澎,每個 Objective-C 的方法(SEL)都是對應(yīng)一個匿名 C 函數(shù)的實(shí)現(xiàn)(IMP),只要去修改這個 Objective-C 方法 與 C 實(shí)現(xiàn)的映射關(guān)系局装,就可以很容易地做到 hook 的功能坛吁。但是對于 C 函數(shù)本身,就不是那么簡單的事情了贼邓。

Mach-O 的映像結(jié)構(gòu)

要想了解如何 hook C 函數(shù)阶冈,需要先了解下 iOS 下 Mach-O 可執(zhí)行文件載入的過程。一個 iOS app 進(jìn)程可以包含多個映像(image)塑径,可執(zhí)行文件自己的代碼是一個 image女坑,它所鏈接的每個動態(tài)庫也各分配了一個 image。每個映像分為三個區(qū)域统舀,mach header, load commands 和 data匆骗,圖示如下(以下說明都以 64 位架構(gòu)為準(zhǔn),32 位也是差不多的):

Mach-O 的映像結(jié)構(gòu)

(圖片來自seriot.ch - Hello Mach-O)

Mach header 用來記錄映像的元信息誉简,比如 CPU 架構(gòu)等碉就,具體細(xì)節(jié)我們不關(guān)心。load commands 區(qū)域是由若干個長度不等的 load command 排在一起闷串,每個 load command 用來告訴加載器進(jìn)行一些加載工作瓮钥,其中最主要的 load command 類型是 segment command,目前我們只關(guān)心這一種命令烹吵,該命令讓加載器在把指定的數(shù)據(jù)(由文件偏移量fileoff和大小filesize決定)加載到指定的地址里碉熄,地址為 vmaddr+slide,slide 后文再說肋拔。知道了地址锈津,就能對指定段和節(jié)的數(shù)據(jù)進(jìn)行操作了。

struct segment_command_64 { /* for 64-bit architectures */
    uint32_t    cmd;        /* LC_SEGMENT_64 */
    uint32_t    cmdsize;    /* includes sizeof section_64 structs */
    char        segname[16];    /* segment name */
    uint64_t    vmaddr;     /* memory address of this segment */
    uint64_t    vmsize;     /* memory size of this segment */
    uint64_t    fileoff;    /* file offset of this segment */
    uint64_t    filesize;   /* amount to map from the file */
    vm_prot_t   maxprot;    /* maximum VM protection */
    vm_prot_t   initprot;   /* initial VM protection */
    uint32_t    nsects;     /* number of sections in segment */
    uint32_t    flags;      /* flags */
};

一個 segment 可以有0或多個 section凉蜂,從 nsects里可以獲得琼梆,這些 section 就是緊接著 segment 后面指定性誉。load commands 后面就是實(shí)際的數(shù)據(jù)了。

遍歷方法

由于這三個部分是緊密地排在一起的茎杂,因此只要知道映像的首址和每個部分的大小错览,就可以通過指針?biāo)銛?shù)獲取每個區(qū)塊的內(nèi)容。比如我們通過 _dyld_get_image_header 可以獲得映像的 header 的地址蛉顽,然后加上一個偏移量sizeof(struct mach_header_64)蝗砾,就是 load commands 區(qū)域的首址,header 中會有一個名為ncmds的字段記錄該區(qū)域有幾條 load command携冤,每個 load command 最前面必定是有兩個字段:

struct load_command {
    uint32_t cmd;       /* type of load command */
    uint32_t cmdsize;   /* total size of command in bytes */
};

cmd 用來表示 load command 的類型,cmdsize 表示該命令所占的空間大小闲勺,這樣結(jié)合前面 header 提供的命令數(shù)和計算出來的 load commands 區(qū)域的首址曾棕,就可以遍歷該區(qū)域的所有 load command 了。

ASLR

ASLR 是 Address Space Layout Randomization 的縮寫菜循,這個概念在業(yè)界由來已久翘地,并非蘋果原創(chuàng)。由于 vmaddr 是鏈接器鏈接的時候?qū)懭?Mach-O 文件的癌幕,對于一個進(jìn)程來說是靜態(tài)不變的衙耕,因此給黑客攻擊帶來了便利,iOS 4.3 以后引入了 ASLR勺远,給每個 image 在 vmaddr 的基礎(chǔ)上再加一個隨機(jī)的偏移量 slide橙喘,因此每段數(shù)據(jù)的真實(shí)的虛擬地址是 vmaddr + slide。

開始 hook

兩個函數(shù)表

__DATA 段有兩個特殊的節(jié):__nl_symbol_ptr__la_symbol_ptr胶逢,這兩個節(jié)都是一個函數(shù)數(shù)組厅瞎,前者存儲非懶加載解析的 C 函數(shù)地址,后者存儲懶加載存儲的函數(shù)地址初坠。

對于以非懶加載的動態(tài)庫和簸,加載動態(tài)庫映像的時候,將所有的符號全部解析出來填入該表碟刺,而對于懶加載的動態(tài)庫锁保,則默認(rèn)用一個特殊的函數(shù) dyld_stub_helper填充之,懶加載的函數(shù)第一次調(diào)用的時候半沽,從映像中解析出地址爽柒,然后填充調(diào)用之。因此只要我們修改這兩個表的內(nèi)容抄囚,就可以替換原先函數(shù)的實(shí)現(xiàn)了霉赡。但問題是,這兩個節(jié)存儲的都是函數(shù)地址幔托,沒有函數(shù)名穴亏,那么我們怎樣通過函數(shù)名找到對應(yīng)的函數(shù)地址呢蜂挪?

__LINKEDIT 段

Mach-O 文件里另有一個特殊的段,這個段存儲了很多符號信息嗓化,與我們 hook C 函數(shù)有關(guān)的有三個數(shù)組:

  1. 間接符號表
  2. 符號表
  3. 字符串表
    間接符號表記錄了前面函數(shù)表里的函數(shù)所對應(yīng)的符號表下標(biāo)棠涮,比如說某個函數(shù)表里分別表示的是 A, B, C, D 四個函數(shù)的地址,而對應(yīng)的符號表里四個函數(shù)的順序?yàn)?B, D, C, A刺覆,那么這個函數(shù)表所對應(yīng)的間接符號表的元素就是 3, 0, 2, 1严肪。我們通過間接符號表就從函數(shù)地址查到函數(shù)在符號表的索引,然后通過這個索引再查符號表谦屑,符號表的每個表項(xiàng)是 struct nlist_64
struct nlist_64 {
    union {
        uint32_t  n_strx; /* index into the string table */
    } n_un;
    uint8_t n_type;        /* type flag, see below */
    uint8_t n_sect;        /* section number or NO_SECT */
    uint16_t n_desc;       /* see <mach-o/stab.h> */
    uint64_t n_value;      /* value of this symbol (or stab offset) */
};

這個結(jié)構(gòu)體的 n_un.n_strx 就是該函數(shù)的名字在字符串表中的索引驳糯,通過這個索引查字符串表就能得到函數(shù)名字。流程總結(jié)一下:

  1. 從間接地址表得到符號表索引
  2. 通過符號表和符號表索引得到函數(shù)對應(yīng)的符號表表項(xiàng)
  3. 通過符號表表項(xiàng)得到函數(shù)名在字符串表的索引
  4. 通過字符串表和字符串表索引找到函數(shù)名
    然后比較函數(shù)名是否是要 hook 的函數(shù)氢橙,是的話酝枢,就用新的函數(shù)替換原先的表項(xiàng),當(dāng)然在替換之前最好把原先的地址拿出來悍手,供新函數(shù)使用帘睦。

Facebook 的開源庫 fishhook

以上的流程實(shí)際上編碼起來是很繁瑣的,好在 Facebook 已經(jīng)幫我們做好了一個庫:fishhook坦康,這個庫進(jìn)行 hook 的原理就是上面所說的這些竣付,F(xiàn)acebook 自己的循環(huán)引用檢測庫 FBRetainCycleDetector 就基于 fishhook 實(shí)現(xiàn)的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末滞欠,一起剝皮案震驚了整個濱河市古胆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌仑撞,老刑警劉巖赤兴,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異桶良,居然都是意外死亡陨帆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門妆够,熙熙樓的掌柜王于貴愁眉苦臉地迎上來颓哮,“玉大人冕茅,你說我怎么就攤上這事哨坪。” “怎么了?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵串绩,是天一觀的道長高氮。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮碾牌,結(jié)果婚禮上翘瓮,老公的妹妹穿的比我還像新娘资盅。我一直安慰自己筐带,他們只是感情好蓝晒,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帖鸦,像睡著了一般芝薇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上作儿,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天洛二,我揣著相機(jī)與錄音,去河邊找鬼攻锰。 笑死晾嘶,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的娶吞。 我是一名探鬼主播垒迂,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寝志!你這毒婦竟也來了娇斑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤材部,失蹤者是張志新(化名)和其女友劉穎毫缆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體乐导,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡苦丁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了物臂。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旺拉。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡产上,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蛾狗,到底是詐尸還是另有隱情晋涣,我是刑警寧澤,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布沉桌,位于F島的核電站谢鹊,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏留凭。R本人自食惡果不足惜佃扼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔼夜。 院中可真熱鬧兼耀,春花似錦、人聲如沸求冷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽匠题。三九已至尽超,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間梧躺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工傲绣, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掠哥,地道東北人。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓秃诵,卻偏偏與公主長得像续搀,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子菠净,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內(nèi)容