iOS-逆向12-MachO文件

《iOS底層原理文章匯總》
上一篇文章iOS-逆向11-代碼注入得知,要想動態(tài)注入,必須要修改MachO文件,通過工具yololib使其中能增加一行自定義的動態(tài)庫的路徑,才能動態(tài)注入自己的代碼,從而達(dá)到hook的目的,MachO文件的結(jié)構(gòu)是什么樣的呢?

圖片.png

圖片.png

1.MachO文件

Mach-O其實是Mach Object文件格式的縮寫,是mac以及iOS上可執(zhí)行文件的格式, 類似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)

2.MachO文件格式

Mach-O為Mach object文件格式的縮寫,它是一種用于可執(zhí)行文件、目標(biāo)代碼、動態(tài)庫的文件格式。作為a.out格式的替代,Mach-O提供了更強的擴展性。
屬于MachO格式的常見文件
目標(biāo)文件.o
庫文件
.a
.dylib
Framework
可執(zhí)行文件
dyld
.dsym
File指令
通過 $file 文件路徑 查看文件類型

3.目標(biāo)文件.o

I.單個c文件通過clang(llvm的前端)編譯為.o文件

  • 打開Xcode->Cmd+N新建test.c文件,編寫main函數(shù)


    圖片.png
#include <stdio.h>
int main(){
    printf("test\n");
    return 0;
}
  • 通過clang -o test.c生成test.o文件,通過file指令查看test.o的文件結(jié)構(gòu)屬于Mach-O文件,架構(gòu)為x86-64位處理器


    圖片.png
  • 再次執(zhí)行clang test.o將.o文件編譯為可執(zhí)行文a.out,是可執(zhí)行文件executable,不再是object文件


    image
  • 也可以通過clang -o test2 test.c一次性將test.c編譯生成可執(zhí)行文件
    也可以通過clang -o test3 test.o將.o文件編譯生成可執(zhí)行文件
    a.out、test2、test3屬于同一個文件,哈希值相等只是文件名不相同,改后綴名后哈希值仍然相等


    image

    源文件到可執(zhí)行文件的中間產(chǎn)物是.o文件

II.項目中會存在多個.c文件,兩個.c文件,test.c和test1.c文件,test.c中會調(diào)用test1.c中的方法

執(zhí)行clang -o demo test1.c test.c
./demo輸入
test
test1

//  test.c
#include <stdio.h>
void test1();
int main(){
    printf("test\n");
    test1();
    return 0;
}
//  test1.c
#include <stdio.h>
void test1(){
    printf("test1\n");
}

圖片.png

執(zhí)行clang -c test1.c test.c生成.o文件,執(zhí)行clang -o demo1 test.o test1.o生成demo1可執(zhí)行文件
但兩次文件不一樣,因為編譯鏈接順序發(fā)生變化,一次test1.c在前,一次test1.c在前,生成的可執(zhí)行文件哈希值不相同
圖片.png

通過objdump --macho -d demo2查看可執(zhí)行文件順序,發(fā)現(xiàn)demo2(和demo相同)與demo1的文件順序不一致,準(zhǔn)確的說是text段不一致,demo2中_main函數(shù)在前,_test1函數(shù)在后,demo中_main函數(shù)在后,_test1函數(shù)在前
image

demo2如下
image

demo如下
image

demo1如下
image

相當(dāng)于Xcode工程Build Phases目錄下的Compile Sources的文件順序,文件順序不一致,編譯生成的二進制文件排列不一致
圖片.png

4.庫文件

I.以.a文件結(jié)尾的動態(tài)庫靜態(tài)庫文件,查找.a文件,find /usr -name "*.a"

發(fā)現(xiàn)libpython3.9.a是動態(tài)庫可執(zhí)行文件


image

發(fā)現(xiàn)libx264.a、libSDL2.a、libfdk-aac.a是靜態(tài)庫可執(zhí)行文件


image

II.以.dylib結(jié)尾的dylib也是MachO文件

圖片.png

III.dyld動態(tài)鏈接器文件,系統(tǒng)內(nèi)核觸發(fā)dyld

image

IV.dsym文件,App打包時生成,edit schemes中修改為release模式下,build編譯,App包統(tǒng)計目錄下生成Demo.app.dSYM,若遇到崩潰根據(jù)堆棧信息,要用此符號文件進行排查分析拿到方法名稱

圖片.png

圖片.png

Demo.app.dSYM也是一個包,右鍵顯示包內(nèi)容,有一個Demo的MachO文件


圖片.png

image

5.MachO文件架構(gòu)

MachO-Type類型,Build Settings中查看,生成的文件類型


圖片.png

iOS11.0以上的系統(tǒng)只支持arm64架構(gòu),32位的架構(gòu)在11.0的系統(tǒng)安裝不了了,無法適配
若將DeploymentInfo改為支持iOS9.0以上,則會出現(xiàn)armv7和arm64架構(gòu)


image

若要添加支持的系統(tǒng)架構(gòu),可在Build Settings中Architectures添加架構(gòu)類型如armv7s,armv7s支持iPhone5和iPhone5c
image

圖片.png

6.通用二進制文件(Universal binary)

蘋果公司提出的一種程序代碼。能同時適用多種架構(gòu)的二進制文件
同一個程序包中同時為多種架構(gòu)提供最理想的性能。
因為需要儲存多種代碼,通用二進制應(yīng)用程序通常比單一平臺二進制的程序要大。
但是 由于兩種架構(gòu)有共通的非執(zhí)行資源(代碼以外的),所以并不會達(dá)到單一版本的兩倍之多。
而且由于執(zhí)行中只調(diào)用一部分代碼,運行起來也不需要額外的內(nèi)存。

支持armv7s、armv7和arm64文件架構(gòu)的二進制文件是通用二進制文件


圖片.png

當(dāng)用hopper打開Demo可執(zhí)行文件時,會提示是Fat archive,表示是通用二進制文件,選擇一種,hopper會分析選擇的其中一種


image

圖片.png
  • lipo命令
    使用lifo -info 可以查看MachO文件包含的架構(gòu)
    lipo -info MachO文件 使用lifo –thin 拆分某種架構(gòu)lipo MachO文件 –thin 架構(gòu) –output 輸出文件路徑
    使用lipo -create 合并多種架構(gòu)
    $lipo -create MachO1 MachO2 -output 輸出文件路徑
image

7.MachO文件結(jié)構(gòu)

image

Mach-O 的組成結(jié)構(gòu)如圖所示包括了
Header 包含該二進制文件的一般信息
字節(jié)順序、架構(gòu)類型、加載指令的數(shù)量等。
使得可以快速確認(rèn)一些信息,比如當(dāng)前文件用于32位還是64位,對應(yīng)的處理器是什么、文件類型是什么
Load commands 一張包含很多內(nèi)容的表
內(nèi)容包括區(qū)域的位置、符號表、動態(tài)符號表等。
Data 通常是對象文件中最大的部分
包含Segement的具體數(shù)據(jù)

  • 通用二進制文件會有多個上述圖中的結(jié)構(gòu),armv7一個,arm64一個,armv7s一個
  • 可通過otool查看MachO中的數(shù)據(jù),otool指令如下
Usage: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool [-arch arch_type] [-fahlLDtdorSTMRIHGvVcXmqQjCP] [-mcpu=arg] [--version] <object file> ...
    -f print the fat headers
    -a print the archive header
    -h print the mach header
    -l print the load commands
    -L print shared libraries used
    -D print shared library id name
    -t print the text section (disassemble with -v)
    -x print all text sections (disassemble with -v)
    -p <routine name>  start dissassemble from routine name
    -s <segname> <sectname> print contents of section
    -d print the data section
    -o print the Objective-C segment
    -r print the relocation entries
    -S print the table of contents of a library (obsolete)
    -T print the table of contents of a dynamic shared library (obsolete)
    -M print the module table of a dynamic shared library (obsolete)
    -R print the reference table of a dynamic shared library (obsolete)
    -I print the indirect symbol table
    -H print the two-level hints table (obsolete)
    -G print the data in code table
    -v print verbosely (symbolically) when possible
    -V print disassembled operands symbolically
    -c print argument strings of a core file
    -X print no leading addresses or headers
    -m don't use archive(member) syntax
    -B force Thumb disassembly (ARM objects only)
    -q use llvm's disassembler (the default)
    -Q use otool(1)'s disassembler
    -mcpu=arg use `arg' as the cpu for disassembly
    -j print opcode bytes
    -P print the info plist section as strings
    -C print linker optimization hints
    --version print the version of /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/otool
image

otool -f Demo,查看MachO文件頭信息,Demo是通用二進制文件支持三種架構(gòu)


image

8.MachOView查看MachO文件

Fat Binary中有三種不同的架構(gòu)armv7,armv7s,arm64
首先是Fat Header,cputype=12表示ARM架構(gòu)CPU_TYPE_ARM,
cpusubtype=9(小端模式00000009)表示CPU_SUBTYPE_ARM_V7
cpusubtype=11(小端模式0000000B)表示CPU_SUBTYPE_ARM_V7S
cpusubtype=0(小端模式00000000)表示CPU_SUBTYPE_ARM64_ALL


image

image

每種架構(gòu)的偏移地址Offset和Size相加后會有間隔,是因為分頁的原因,iOS中每頁的大小為16k,MacOS中每頁大小為4K


圖片.png

從Load Commands開始一直到底都是DATA數(shù)據(jù)段
DATA數(shù)據(jù)段中Section分為兩部分,TEXT和DATA
image

類名和方法名會有聯(lián)系,工具classdump會dump出類的名稱和方法的列表
image

Load Commands中能看到加載哪些庫
圖片.png

64位環(huán)境中PAGEZERO占用4G的空間0xffffffff,后面的所有指令都從0xffffffff00000001開始,目的是和32位指令隔開,插入PAGEZERO后和32位指令不會有重疊,這是PAGEZERO的目的,64位和32位做區(qū)分,在內(nèi)存中執(zhí)行時和32位完全隔離,是一個空區(qū)域分割區(qū),讓內(nèi)存地址加上0xffffffff=4294967296,所有的數(shù)據(jù)往后移,和32位架構(gòu)的指令不重復(fù),如果有數(shù)據(jù)指向PAGEZERO為空,里面是不放數(shù)據(jù)的
早期的架構(gòu)armv7和armv7s的PAGEZERO為16384

64位大小的地址
0x12345678a2345678
32位大小的地址
0x12345678
0xffffffff=4G 
image

9.MachO Header

  • Header的數(shù)據(jù)結(jié)構(gòu),可在loader.h文件中查看


    圖片.png

    image

    filetype文件類型


    圖片.png

    image

10.Load Commands

image

DATA部分由三部分組成,SECTION TEXT代碼段,SECTION DATA數(shù)據(jù)段,LINKEDIT,指明起始位置,偏移位置


image

ASLR,操作系統(tǒng)為每一個進程分配隨機的ASLR,
應(yīng)用程序加載進內(nèi)存中時,實際地址=ASLR+Rebase Info Size,在MachO指定后,方便應(yīng)用程序
加載進內(nèi)存后調(diào)用,編譯時期生成的是偏移地址Rebase Info
Offset,表示在整個MachO文件中偏移多少,程序在第一次加載進內(nèi)存中時根據(jù)ASLR+Rebase Info Offset


image

匯編跳轉(zhuǎn)bl,在編譯時期生成的是偏移地址,在文件中偏移多少,運行時期的地址每次都變化,重定向改變的是匯編代碼


image

image

重定向改變的是匯編代碼,地址前的0x1表示PAGEZERO


image

Binding Info offset和Binding Info Size,外部的符號將地址綁定上去,Weak Binding Info Offset和Weak Binding Info Size弱綁定,Lazy Binding Info offset和Lazy Binding Info Size懶綁定,用到的時候再去綁定,Export Info Offset和Export Info Size對外開放的函數(shù)
圖片.png

image

每一段數(shù)據(jù)以頁為單位


圖片.png

Load Commands和Section之間會有空間,之前動態(tài)注入修改成功是因為有空間,才能插入一條
圖片.png

Size of load Commands + 2720 = 0002E734和S
ection64 Text段的地址00032374中間有一段地址隔開,故動態(tài)注入時能插入一條新的內(nèi)容在Load Commands的最底部,若沒有空間則無法插入
image

圖片.png

image

上篇文章動態(tài)注入時,我們看到WeChat可執(zhí)行文件自己的動態(tài)庫andromedo的路徑@rpath/andromeda.framework/andromeda,@rpath路徑在Load Command中已經(jīng)指明為@executable_path/Frameworks,才能找到動態(tài)庫andromedo
image

image

image

11.DATA數(shù)據(jù)段

Load Commands中已經(jīng)指明了DATA數(shù)據(jù)段包括三部分內(nèi)容


image

TEXT段可以通過工具objdump --macho -d Demo查看到


image

圖片.png

0x1000065a0都指向開頭
圖片.png

image

Symbol Stubs和Assembly結(jié)合起來做符號綁定


image

外部符號表:調(diào)用外部函數(shù),只有在運行那一刻,才能找到
啟動時刻就綁定了,應(yīng)用程序一啟動,外部函數(shù)和符號表進行綁定
Lazy Symbol Pointers中的函數(shù)做綁定時會調(diào)用Non-Lazy Symbol Pointers的dyld_stub_binder做綁定,綁定符號的目的,將外部函數(shù)的真實地址告訴MachO文件,方便調(diào)用,先綁定專門用來綁定的函數(shù),之后用此函數(shù)去綁定所有的函數(shù)
圖片.png

調(diào)用Non-Lazy Symbol Pointers中的dyld_stub_binder去綁定
image

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

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

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