存映射機(jī)制)
1. Mach-O文件格式與加載命令基礎(chǔ)Mach-O作為macOS和iOS系統(tǒng)可執(zhí)行文件的標(biāo)準(zhǔn)格式其核心結(jié)構(gòu)由三部分組成頭部Header、加載命令Load Commands和段數(shù)據(jù)Segment Data。其中加載命令作為連接頭部與段數(shù)據(jù)的橋梁直接決定了內(nèi)核如何將二進(jìn)制文件映射到內(nèi)存空間。在分析Mach-O文件時(shí)我們通常使用otool或MachOView這類工具。以otool為例執(zhí)行otool -l binary命令可以完整顯示加載命令列表。每個(gè)加載命令都以固定的數(shù)據(jù)結(jié)構(gòu)開頭struct load_command { uint32_t cmd; // 命令類型 uint32_t cmdsize; // 命令總大小 };注意32位和64位架構(gòu)下的Mach-O文件其頭部結(jié)構(gòu)略有差異。64位頭部使用mach_header_64結(jié)構(gòu)其中包含額外的保留字段。2. 核心加載命令深度解析2.1 LC_SEGMENT/LC_SEGMENT_64這是最基礎(chǔ)的段加載命令定義了如何將文件中的段映射到內(nèi)存。其數(shù)據(jù)結(jié)構(gòu)包含關(guān)鍵信息struct segment_command_64 { char segname[16]; // 段名稱如__TEXT uint64_t vmaddr; // 虛擬內(nèi)存地址 uint64_t vmsize; // 虛擬內(nèi)存大小 uint64_t fileoff; // 文件偏移量 uint64_t filesize; // 文件大小 vm_prot_t maxprot; // 最大內(nèi)存保護(hù) vm_prot_t initprot; // 初始內(nèi)存保護(hù) uint32_t nsects; // 包含的節(jié)數(shù)量 uint32_t flags; // 標(biāo)志位 };實(shí)際案例__TEXT段通常設(shè)置maxprot為VM_PROT_READ|VM_PROT_EXECUTE表示可讀可執(zhí)行但不可寫這是代碼段的基本保護(hù)策略。2.2 LC_LOAD_DYLIB動(dòng)態(tài)庫(kù)加載命令記錄了二進(jìn)制依賴的所有動(dòng)態(tài)庫(kù)。通過otool -L查看時(shí)實(shí)際就是在解析這些命令。其結(jié)構(gòu)包含struct dylib { union lc_str name; // 動(dòng)態(tài)庫(kù)路徑 uint32_t timestamp; // 時(shí)間戳 uint32_t current_version; // 當(dāng)前版本 uint32_t compatibility_version;// 兼容版本 };經(jīng)驗(yàn)iOS應(yīng)用中常見的rpath處理就發(fā)生在這個(gè)階段。如果鏈接時(shí)指定了rpath加載器會(huì)在此階段進(jìn)行路徑替換。2.3 LC_CODE_SIGNATURE代碼簽名命令指向文件的簽名數(shù)據(jù)區(qū)域。在iOS上沒有有效簽名的應(yīng)用將無(wú)法運(yùn)行。其結(jié)構(gòu)相對(duì)簡(jiǎn)單struct linkedit_data_command { uint32_t cmd; // LC_CODE_SIGNATURE uint32_t cmdsize; // 結(jié)構(gòu)大小 uint32_t dataoff; // 數(shù)據(jù)偏移 uint32_t datasize; // 數(shù)據(jù)大小 };簽名驗(yàn)證流程內(nèi)核加載Mach-O時(shí)檢查簽名命令存在性根據(jù)dataoff定位簽名數(shù)據(jù)驗(yàn)證CMS格式簽名與證書鏈檢查entitlements與權(quán)限聲明3. 加載命令處理全流程3.1 內(nèi)核處理階段當(dāng)用戶執(zhí)行Mach-O文件時(shí)內(nèi)核按以下順序處理驗(yàn)證魔數(shù)和CPU類型遍歷加載命令表為每個(gè)LC_SEGMENT創(chuàng)建內(nèi)存映射處理動(dòng)態(tài)鏈接器路徑LC_LOAD_DYLINKER傳遞環(huán)境變量和主函數(shù)地址3.2 動(dòng)態(tài)鏈接器階段動(dòng)態(tài)鏈接器dyld接管后遞歸加載所有LC_LOAD_DYLIB依賴處理符號(hào)重定位運(yùn)行初始化函數(shù)如load方法準(zhǔn)備ObjC運(yùn)行時(shí)環(huán)境跳轉(zhuǎn)到主入口4. 高級(jí)調(diào)試技巧4.1 自定義加載命令注入通過在鏈接階段添加-sectcreate參數(shù)可以插入自定義段ld -sectcreate __MYSEC __mycmd mydata.bin ...生成的Mach-O會(huì)包含額外的LC_SEGMENT命令指向這個(gè)自定義數(shù)據(jù)。4.2 加載命令修改實(shí)踐使用install_name_tool工具可以修改LC_LOAD_DYLIB路徑install_name_tool -change /old/path executable_path/new libdemo.dylib對(duì)于更復(fù)雜的修改推薦使用開源的MachOEditor或自行編寫解析工具。需要注意修改后必須重新計(jì)算代碼簽名。5. 典型問題排查指南問題現(xiàn)象可能原因解決方案dyld: Library not loaded依賴庫(kù)路徑錯(cuò)誤使用otool -L檢查路徑無(wú)效的Mach-O文件加載命令損壞用MachOView驗(yàn)證結(jié)構(gòu)代碼簽名無(wú)效LC_CODE_SIGNATURE偏移錯(cuò)誤重建簽名或調(diào)整偏移段權(quán)限沖突LC_SEGMENT保護(hù)設(shè)置錯(cuò)誤檢查maxprot/initprot在逆向工程中經(jīng)常需要手動(dòng)解析加載命令。這里分享一個(gè)快速定位段信息的技巧import macholib def print_segments(path): m macholib.MachO(path) for header in m.headers: for seg, sections in header.segments: print(f{seg.segname} {hex(seg.vmaddr)})最后需要提醒的是iOS15之后蘋果引入了新的LC_DYLD_CHAINED_FIXUPS和LC_DYLD_EXPORTS_TRIE命令用于優(yōu)化啟動(dòng)性能這是二進(jìn)制分析時(shí)需要特別注意的變化點(diǎn)。