iOS中的Mach-O&重定向&符號綁定&符號重綁定

Mach-O

什么Mach-O

Mach-O為Mach Object文件格式的縮寫怒见,它是一種用于可執(zhí)行文件,目標代碼禽车,動態(tài)庫寇漫,內核轉儲的文件格式刊殉。作為a.out格式的替代,Mach-O提供了更強的擴展性州胳,并提升了符號表中信息的訪問速度记焊。Mach-O是iOS、mac系統(tǒng)中的可執(zhí)行文件格式栓撞。

  • MachO格式的常見文件

目標文件.o
庫文件
.a
.dylib
Framework
可執(zhí)行文件
dyld
.dsym

  • 常用命令

使用lifo -info 可以查看MachO文件包含的架構
lipo -info MachO文件
使用lifo –thin 拆分某種架構
lipo MachO文件 –thin 架構 –output 輸出文件路徑
使用lipo -create 合并多種架構
lipo -create MachO1 MachO2 -output 輸出文件路徑

Mach-O文件結構

每個Mach-O文件包括一個Mach-O頭遍膜,然后是一系列的載入命令,再是一個或多個瓤湘,每個塊包括0到255個瓢颅。Mach-O使用REL再定位格式控制對符號的引用。Mach-O在兩級命名空間中將每個符號編碼成“對象-符號名”對岭粤,在查找符號時則采用線性搜索法惜索。以下是蘋果官方關于Mach-O的結構圖:

蘋果官方圖片

通過MachOView可以查看更為詳細的Mach-O的結構特笋。如下圖所示:

mac-o.jpg
  • Mach64 Header: 包含該二進制文件的一般信息剃浇。包括字節(jié)順序、架構類型猎物、加載指令的數(shù)量等虎囚。使得可以快速確認一些信息,比如當前文件用于32位還是64位蔫磨,對應的處理器是什么淘讥、文件類型是什么。以下就是Header的數(shù)據結構:
struct mach_header_64 {
    uint32_t    magic;      /* mach-o 格式的標識符 */
    cpu_type_t  cputype;    /* cpu區(qū)分符 */
    cpu_subtype_t   cpusubtype; /* machine區(qū)分符 */
    uint32_t    filetype;   /* 文件類型 */
    uint32_t    ncmds;      /* 加載命令的個數(shù) */
    uint32_t    sizeofcmds; /* 加載命令的字節(jié)數(shù) */
    uint32_t    flags;      /* 程序的標識位 */
    uint32_t    reserved;   /* 保留字段 */
};
  • Load Commands:是加載指令堤如,描述的是文件的加載信息蒲列,內容包括區(qū)域的位置、符號表搀罢、動態(tài)符號表等蝗岖。這個部分信息還是比較有用的,我們可以從這里獲取到符號表和字符串表的偏移量等榔至。通過MachOView可以查看Load Commands詳情:
load_commands_detail.jpg

字段解析

LC_SEGMENT_64 將文件中(32位或64位)的段映射到進程地址空間中
LC_DYLD_INFO_ONLY 動態(tài)鏈接相關信息
LC_SYMTAB 符號地址
LC_DYSYMTAB 動態(tài)符號表地址
LC_LOAD_DYLINKER 使用誰加載抵赢,我們使用dyld
LC_UUID 文件的UUID
LC_VERSION_MIN_MACOSX 支持最低的操作系統(tǒng)版本
LC_SOURCE_VERSION 源代碼版本
LC_MAIN 設置程序主線程的入口地址和棧大小
LC_LOAD_DYLIB 依賴庫的路徑,包含三方庫
LC_FUNCTION_STARTS 函數(shù)起始地址表
LC_CODE_SIGNATURE 代碼簽名

  • 數(shù)據區(qū):除了Header和Load Commands外所有的原始數(shù)據唧取。數(shù)據區(qū)分為很多段(Section)铅鲤。

text段是代碼段。它用來放程序代碼(code)枫弟。它通常是只讀的邢享。
data段是數(shù)據段。它用來存放初始化了的(initailized)全局變量(global)和初始化了的靜態(tài)變量(static)淡诗。它是可讀可寫的骇塘。
bss段是全局變量數(shù)據段掸犬。它用來存放未初始化的(uninitailized)全局變量(global)和未初始化的靜態(tài)變量

接下來先介紹數(shù)據區(qū)幾個比較重要的模塊:

  • (__TEXT,__text)
    這里存放的是匯編后的代碼,當我們進行編譯時绪爸,每個.m文件會經過預編譯->編譯->匯編形成.o文件湾碎,稱之為目標文件。匯編后奠货,所有的代碼會形成匯編指令存儲在.o文件的(__TEXT,__text)區(qū)介褥。鏈接后,所有的.o文件會合并成一個文件递惋,所有.o文件的(__TEXT,__text)數(shù)據都會按鏈接順序存放到應用文件的(__TEXT,__text)中柔滔。
_TEXT_text.jpg
  • (__DATA,__data)
    存儲數(shù)據的section,static在進行非零賦值后會存儲在這里萍虽,如果static 變量沒有賦值或者賦值為0睛廊,那么它會存儲在(__DATA,__bss)中。
_DATA_data.jpg
  • Symbol Table
    符號表杉编,這個是重點中的重點超全,符號表是將地址和符號聯(lián)系起來的橋梁。符號表并不能直接存儲符號邓馒,而是存儲符號位于字符串表的位置嘶朱。
SymbolTable.jpg
  • String Table
    字符串表所有的變量名、函數(shù)名等光酣,都以字符串的形式存儲在字符串表中疏遏。
StringTable.jpg
  • Dynamic Symbol Table
    動態(tài)符號表存儲的是動態(tài)庫函數(shù)位于符號表的偏移信息。(__DATA,__la_symbol_ptr) section 可以從動態(tài)符號表中獲取到該section位于符號表的索引數(shù)組救军。動態(tài)符號表并不存儲符號信息财异,而是存儲其位于符號表的偏移信息。
DynamicSymbolTable.jpg
  • Lazy Symbol Pointers
    Lazy Symbol Pointers懶加載符號表唱遭。所謂懶加載是指在程序運行時需要訪問這些符號的時候再去綁定戳寸。這些符號一般來自程序依賴的動態(tài)庫。
LazySymbolPointers.jpg
  • Non Lazy Symbol Pointers
    Non Lazy Symbol Pointers 非懶加載符號表胆萧。所謂非懶加載是指在程序一加載就綁定好的庆揩。這些符號一般來自程序依賴的動態(tài)庫。


    Non-LazySymbolPointers.jpg
  • Symbol Stubs
    翻譯過來就是符號樁跌穗。它與Lazy Symbol Pointers是一一對應的订晌,每次訪問外部符號時都會先訪問Symbol Stubs,然后執(zhí)行樁代碼蚌吸,最后去Lazy Symbol Pointers找到相應的符號地址執(zhí)行下一步操作锈拨。


    SymbolStubs.jpg
  • Assembly
    這里面其實就是符號綁定執(zhí)行的匯編代碼。后面到符號綁定的時候再詳解羹唠。

Assembly.jpg

符號重定向

對于iOS程序來說奕枢,由于ASLR安全機制的原因娄昆,每次啟動程序系統(tǒng)都會分配一個隨機偏移值,程序啟動時會根據偏移值進行符號地址修正缝彬。假設程序首地址的0x00000000, 隨機偏移值是0x00008000, 那程序的首地址就變成0x00000000 + 0x00008000, 程序內某個符號的偏移地址是0x00001000萌焰,那么它的內存地址是0x00001000 + 0x00008000 = 0x00009000。符號重定向實際上就是在程序運行時谷浅,把符號的偏移地址加上啟動時的隨機偏移值得到符號的內存地址的一個過程扒俯。符號重定向針對的是程序內的符號。接下來我們通過下面的demo來進行驗證一疯。首先我們新建一個類MyObject, 然后定義
一個方法doSomething撼玄,然后運行:

demo.jpeg

獲取編譯后的可執(zhí)行文件:

1.jpeg

2.jpeg

3.jpeg

這個黑色icon的文件就是我們的可執(zhí)行文件,把這個文件拖入MachOView中:

mach-o.jpeg

接下來演示一下重定向的過程墩邀。首先在運行前- (void)doSomething入口打個斷點掌猛,然后在xcode->Debug-> Debug WorkFlow -> Always show disassembly 進入匯編模式,運行然后就進入如下畫面:

重定向-獲取ASLR值.png

打開剛才的MachOView查看符號表眉睹,如下:

符號表.png

可以看到- (void)doSomething放的的符號-[MyObject doSomething]在文件中的偏移地址為5E28荔茬。接下來我們驗證一下重定向的過程:

重定向.png

由圖中可知方法doSomething的內存地址與程序的首地址的差值是不變的,而且是等于-[MyObject doSomething]在文件中的偏移值辣往。

符號綁定

相對于符號重定向針對的是程序內的符號兔院,符號綁定針對的是程序外的符號殖卑,比如所以依賴的動態(tài)庫等站削。由于編譯時并沒有把動態(tài)庫編譯到程序內,只是在連接階段生成動態(tài)符號表孵稽。但是生成這個動態(tài)符表的地址并不是符號的真是地址许起,只有在訪問這個符號時才會調用系統(tǒng)的符號綁定函數(shù)進行進行綁定,并獲取符號地址更新到符號表中菩鲜。這個過程就叫符號綁定园细。

綁定流程

當程序首次訪問外部函數(shù)的時候,它首先會訪問外部函數(shù)的樁Symbol Stubs接校,并執(zhí)行樁代碼(Symbol Stubs中的Data字段對應的值)猛频,而這個樁代碼執(zhí)行后,最后會跳轉到Lazy Symbol Pointers對應符號的地址蛛勉。首次訪問會根據這個地址在Assembly文件中找到相應的代碼執(zhí)行鹿寻,最后調用dyld_stub_binder函數(shù)進行符號綁定。綁定完成之后就會更新Lazy Symbol Pointers表中的值诽凌,將符號地址直接寫入到表中毡熏,再次訪問的時候就可以直接訪問這個地址而不需要在執(zhí)行Assembly中的代碼。 大致流程圖如下:

綁定流程.jpg

下面我們利用NSLog(NSLog來自系統(tǒng)動態(tài)庫Foundation)的例來對符號綁定整個過程進行演示侣诵。我們同時通過MachOView工具以及程序運行會的匯編調試來展示這個過程痢法。同樣的狱窘,以下面的demo為例:

demo.jpeg
  • 進入匯編調試
    首先,在NSLog入口處打個斷點财搁,進入匯編調試蘸炸,如下圖所示:
訪問SymbolStubs.jpg

紅圈部分可以看到確實是訪問了Simbol Stubs。

  • 執(zhí)行執(zhí)行Symbol Stubs中的代碼
    通過匯編調試執(zhí)行bl指令進入如下頁面:
執(zhí)行SymbolStubs中的代碼.jpg

這一步可以看到實際上是執(zhí)行Symbol Stubs中的Data斷的代碼尖奔,這個段代碼最后會跳轉到0x000000010015651c幻馁,這個地址是通過Lazy Symbol Pointers獲取的。

訪問Lazy Symbol Pointers.jpg
  • 執(zhí)行Assembly中的代碼
    通過image list命令可以獲取程序的偏移地址(首地址)為0x0000000100150000越锈。偏移地址0x000000010015651c -0x0000000100150000 = 0x000000000000651c仗嗦。然后根據這個偏移值到Assembly執(zhí)行相應的代碼。執(zhí)行br指令甘凭,跳轉到x16中的地址(實際上就是剛才0x000000010015651c)稀拐,進入如下頁面:
訪問Assembly.png

Assembly.jpg
  • 調用符號綁定函數(shù)dyld_stub_binder
    在這里執(zhí)行兩行代碼然后跳轉到6504這個這個地方。這個地方實際上就是調用符號綁定函數(shù)dyld_stub_binder的地方丹弱。以下通過匯編調試和Assembly中查看他們的匯編指令就可以看得出來德撬。
dyld_stub_binder.jpeg

為了進一步驗證,執(zhí)行br指令跳轉到x16中的地址(實際上就是dyld_stub_binder函數(shù)地址)躲胳。進入如下頁面:

dyld_stub_binder.jpeg
  • Non-Lazy Symbol Pointers
    這里有個疑問蜓洪,就是dyld_stub_binder本身也是外部符號。那它是怎么綁定的呢坯苹?又是什么時候綁定的呢隆檀。實際上dyld_stub_binder是非懶加載符號,是在程序一開始運行就綁定的粹湃,它存儲在Non-Lazy Symbol Pointers里面恐仑,所以不需要再走一遍符號綁定流程。
Assembly尋找函數(shù)dyld_stub_binder過程.png
Non-Lazy Symbol pointers.png

獲取程序首地址.png

讀取Non-Lazy Symbol Pointers中的運行時地址.jpeg

至此为鳄,NSlog函數(shù)第一次調用過程中的符號綁定流程就走完了裳仆。其他外部符號訪問流程都是一樣的。綁定成功之后會修改Lazy Symbol Pointers中的值為符號的內存地址孤钦,下次訪問時不需要再次綁定歧斟,可以直接在Lazy Symbol Pointers進行訪問。

第二次調用NSLog函數(shù)

查看Lazy Symbol Pointers中的編譯時偏移地址:

第二次調用的Lazy Symbol Pointers.png

接下來我們進行第二次訪問的驗證偏形。首先在第二次調用處打個斷點静袖,進入匯編調試,進入如下頁面:

第二次調用.png

繼續(xù)執(zhí)行bl指令壳猜,跳轉一個地址勾徽,進入如下頁面:

第二次訪問NSLog時的內存地址.jpeg

第二次直接跳轉NSLog函數(shù)實現(xiàn).png

最終看到第二次進入的時候Lazy Symbol Pointers中的值就已經是NSLog函數(shù)的地址了,程序就可以直接訪問了。

符號重綁定

有前面知道符號的綁定過程喘帚,我們知道符號綁定的本質就是將外部符號的地址跟新到Lazy Symbol Pointers表中畅姊。符號重綁定實際上也就是修改Lazy Symbol Pointers中的值。常用的第三方庫Fishhook之所以能夠hook系統(tǒng)代碼就是利用這個原理吹由,直接修改了Lazy Symbol Pointers對應符號的地址值實現(xiàn)若未。這里我們以Hook系統(tǒng)函數(shù)NSLog為例,演示Fishhook工作的大致流程如下:

  • 通過字符換找到符號倾鲫。去Mach-O中尋找String Table(String Table中通過“.”字符將符號分割)粗合,得到偏移值(String Table Index)
重綁定—StringTable.jpeg
  • 通過String Table Index去找Symbols(符號表),得到符號表的偏移值(Symbol Index)
重綁定-Symbols.jpeg
  • 通過Symbol Index 去找indirect Symbols乌昔,得到符號的偏移值(Indirect Symbol Index)
重綁定Indirect Symbol.png
  • 最后去修改Lazy Symbol Pointers 里面的值(因為有前面的分析可知隙疚,外部符號調用都在找樁,樁去尋找Lazy Symbol Pointers 里面的地址)
重綁定Lazy Symbol Pointers.png

接下來通過匯編調試驗證:
首先獲取NSLog符號在Lazy Symbol Pointers中的偏移值:

重綁定_NSLog偏移值.png

拿到符號偏移地址為0xC000,開始進入匯編調試:

重綁定-demo.jpeg

重綁定-首地址.png

重綁定-第一次調用NSLog.png

重綁定-NSLog后hook前.png

重綁定-hook之后地址.png

hook之后符號地址就改成了我們自定義的函數(shù)myNSlog的地址磕道。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末供屉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子溺蕉,更是在濱河造成了極大的恐慌伶丐,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疯特,死亡現(xiàn)場離奇詭異哗魂,居然都是意外死亡,警方通過查閱死者的電腦和手機漓雅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門录别,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人故硅,你說我怎么就攤上這事庶灿。” “怎么了吃衅?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長腾誉。 經常有香客問我徘层,道長,這世上最難降的妖魔是什么利职? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任趣效,我火速辦了婚禮,結果婚禮上猪贪,老公的妹妹穿的比我還像新娘跷敬。我一直安慰自己,他們只是感情好热押,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布西傀。 她就那樣靜靜地躺著斤寇,像睡著了一般。 火紅的嫁衣襯著肌膚如雪拥褂。 梳的紋絲不亂的頭發(fā)上娘锁,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音饺鹃,去河邊找鬼莫秆。 笑死,一個胖子當著我的面吹牛悔详,可吹牛的內容都是我干的镊屎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼茄螃,長吁一口氣:“原來是場噩夢啊……” “哼杯道!你這毒婦竟也來了?” 一聲冷哼從身側響起责蝠,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤党巾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后霜医,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體齿拂,經...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年肴敛,在試婚紗的時候發(fā)現(xiàn)自己被綠了署海。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡医男,死狀恐怖砸狞,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情镀梭,我是刑警寧澤刀森,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站报账,受9級特大地震影響研底,放射性物質發(fā)生泄漏。R本人自食惡果不足惜透罢,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一榜晦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧羽圃,春花似錦乾胶、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽斩郎。三九已至,卻和暖如春腕扶,著一層夾襖步出監(jiān)牢的瞬間孽拷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工半抱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脓恕,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓窿侈,卻偏偏與公主長得像炼幔,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子史简,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容