《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");
}

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

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

demo如下

demo1如下

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

4.庫文件
I.以.a文件結(jié)尾的動態(tài)庫靜態(tài)庫文件,查找.a文件,find /usr -name "*.a"
發(fā)現(xiàn)libpython3.9.a是動態(tài)庫可執(zhí)行文件

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

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

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

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


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


5.MachO文件架構(gòu)
MachO-Type類型,Build Settings中查看,生成的文件類型

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

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


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

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


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

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

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

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

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


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

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

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

Load Commands中能看到加載哪些庫

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

9.MachO Header
Header的數(shù)據(jù)結(jié)構(gòu),可在loader.h文件中查看
圖片.png
image
filetype文件類型
圖片.png
image
10.Load Commands

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

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

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


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

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ù)


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

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

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



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



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

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


0x1000065a0都指向開頭


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

外部符號表:調(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ù)

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











