《iOS底层原理文章汇总》
图片.png
上一篇文章iOS-逆向11-代码注入得知,要想动态注入,必须要修改MachO文件,通过工具yololib使其中能增加一行自定义的动态库的路径,才能动态注入自己的代码,从而达到hook的目的,MachO文件的结构是什么样的呢?
图片.png
1.MachO文件
Mach-O其实是Mach Object文件格式的缩写,是mac以及iOS上可执行文件的格式, 类似于windows上的PE格式 (Portable Executable ), linux上的elf格式 (Executable and Linking Format)
2.MachO文件格式
Mach-O为Mach object文件格式的缩写,它是一种用于可执行文件、目标代码、动态库的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性。
属于MachO格式的常见文件
目标文件.o
库文件
.a
.dylib
Framework
可执行文件
dyld
.dsym
File指令
通过 $file 文件路径 查看文件类型
3.目标文件.o
I.单个c文件通过clang(llvm的前端)编译为.o文件
打开Xcode->Cmd+N新建test.c文件,编写main函数
图片.png
#include <stdio.h>
int main(){
printf("test\n");
return 0;
}
通过clang -o test.c生成test.o文件,通过file指令查看test.o的文件结构属于Mach-O文件,架构为x86-64位处理器
图片.png再次执行clang test.o将.o文件编译为可执行文a.out,是可执行文件executable,不再是object文件
image也可以通过clang -o test2 test.c一次性将test.c编译生成可执行文件
也可以通过clang -o test3 test.o将.o文件编译生成可执行文件
a.out、test2、test3属于同一个文件,哈希值相等只是文件名不相同,改后缀名后哈希值仍然相等
image
源文件到可执行文件的中间产物是.o文件
II.项目中会存在多个.c文件,两个.c文件,test.c和test1.c文件,test.c中会调用test1.c中的方法
执行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
执行
clang -c test1.c test.c
生成.o文件,执行clang -o demo1 test.o test1.o
生成demo1可执行文件但两次文件不一样,因为编译链接顺序发生变化,一次test1.c在前,一次test1.c在前,生成的可执行文件哈希值不相同
图片.png
通过
objdump --macho -d demo2
查看可执行文件顺序,发现demo2(和demo相同)与demo1的文件顺序不一致,准确的说是text段不一致,demo2中_main函数在前,_test1函数在后,demo中_main函数在后,_test1函数在前image
demo2如下
image
demo如下
image
demo1如下
image
相当于Xcode工程Build Phases目录下的Compile Sources的文件顺序,文件顺序不一致,编译生成的二进制文件排列不一致
图片.png
4.库文件
I.以.a文件结尾的动态库静态库文件,查找.a文件,find /usr -name "*.a"
发现libpython3.9.a是动态库可执行文件
image
发现libx264.a、libSDL2.a、libfdk-aac.a是静态库可执行文件
image
II.以.dylib结尾的dylib也是MachO文件
图片.pngIII.dyld动态链接器文件,系统内核触发dyld
imageIV.dsym文件,App打包时生成,edit schemes中修改为release模式下,build编译,App包统计目录下生成Demo.app.dSYM,若遇到崩溃根据堆栈信息,要用此符号文件进行排查分析拿到方法名称
图片.png图片.png
Demo.app.dSYM也是一个包,右键显示包内容,有一个Demo的MachO文件
图片.png
image
5.MachO文件架构
MachO-Type类型,Build Settings中查看,生成的文件类型
图片.png
iOS11.0以上的系统只支持arm64架构,32位的架构在11.0的系统安装不了了,无法适配
若将DeploymentInfo改为支持iOS9.0以上,则会出现armv7和arm64架构
image
若要添加支持的系统架构,可在Build Settings中Architectures添加架构类型如armv7s,armv7s支持iPhone5和iPhone5c
image
图片.png
6.通用二进制文件(Universal binary)
苹果公司提出的一种程序代码。能同时适用多种架构的二进制文件
同一个程序包中同时为多种架构提供最理想的性能。
因为需要储存多种代码,通用二进制应用程序通常比单一平台二进制的程序要大。
但是 由于两种架构有共通的非执行资源(代码以外的),所以并不会达到单一版本的两倍之多。
而且由于执行中只调用一部分代码,运行起来也不需要额外的内存。
支持armv7s、armv7和arm64文件架构的二进制文件是通用二进制文件
图片.png
当用hopper打开Demo可执行文件时,会提示是Fat archive,表示是通用二进制文件,选择一种,hopper会分析选择的其中一种
image
图片.png
image
- lipo命令
使用lifo -info 可以查看MachO文件包含的架构
lipo MachO文件 –thin 架构 –output 输出文件路径
使用lipo -create 合并多种架构
$lipo -create MachO1 MachO2 -output 输出文件路径
7.MachO文件结构
imageMach-O 的组成结构如图所示包括了
Header 包含该二进制文件的一般信息
字节顺序、架构类型、加载指令的数量等。
使得可以快速确认一些信息,比如当前文件用于32位还是64位,对应的处理器是什么、文件类型是什么
Load commands 一张包含很多内容的表
内容包括区域的位置、符号表、动态符号表等。
Data 通常是对象文件中最大的部分
包含Segement的具体数据
- 通用二进制文件会有多个上述图中的结构,armv7一个,arm64一个,armv7s一个
- 可通过otool查看MachO中的数据,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是通用二进制文件支持三种架构
image
8.MachOView查看MachO文件
Fat Binary中有三种不同的架构armv7,armv7s,arm64
首先是Fat Header,cputype=12表示ARM架构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
每种架构的偏移地址Offset和Size相加后会有间隔,是因为分页的原因,iOS中每页的大小为16k,MacOS中每页大小为4K
图片.png
从Load Commands开始一直到底都是DATA数据段
DATA数据段中Section分为两部分,TEXT和DATA
image
类名和方法名会有联系,工具classdump会dump出类的名称和方法的列表
image
Load Commands中能看到加载哪些库
图片.png
64位环境中PAGEZERO占用4G的空间0xffffffff,后面的所有指令都从0xffffffff00000001开始,目的是和32位指令隔开,插入PAGEZERO后和32位指令不会有重叠,这是PAGEZERO的目的,64位和32位做区分,在内存中执行时和32位完全隔离,是一个空区域分割区,让内存地址加上0xffffffff=4294967296,所有的数据往后移,和32位架构的指令不重复,如果有数据指向PAGEZERO为空,里面是不放数据的
早期的架构armv7和armv7s的PAGEZERO为16384
64位大小的地址
0x12345678a2345678
32位大小的地址
0x12345678
0xffffffff=4G
image
9.MachO Header
Header的数据结构,可在loader.h文件中查看
图片.png
image
filetype文件类型
图片.png
image
10.Load Commands
imageDATA部分由三部分组成,SECTION TEXT代码段,SECTION DATA数据段,LINKEDIT,指明起始位置,偏移位置
image
ASLR,操作系统为每一个进程分配随机的ASLR,
应用程序加载进内存中时,实际地址=ASLR+Rebase Info Size,在MachO指定后,方便应用程序
加载进内存后调用,编译时期生成的是偏移地址Rebase Info
Offset,表示在整个MachO文件中偏移多少,程序在第一次加载进内存中时根据ASLR+Rebase Info Offset
image
汇编跳转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对外开放的函数
图片.png
image
每一段数据以页为单位
图片.png
Load Commands和Section之间会有空间,之前动态注入修改成功是因为有空间,才能插入一条
图片.png
Size of load Commands + 2720 = 0002E734和S
ection64 Text段的地址00032374中间有一段地址隔开,故动态注入时能插入一条新的内容在Load Commands的最底部,若没有空间则无法插入
image
图片.png
image
上篇文章动态注入时,我们看到WeChat可执行文件自己的动态库andromedo的路径@rpath/andromeda.framework/andromeda,@rpath路径在Load Command中已经指明为@executable_path/Frameworks,才能找到动态库andromedo
image
image
image
11.DATA数据段
Load Commands中已经指明了DATA数据段包括三部分内容
image
TEXT段可以通过工具objdump --macho -d Demo查看到
image
图片.png
0x1000065a0都指向开头
图片.png
image
Symbol Stubs和Assembly结合起来做符号绑定
image
外部符号表:调用外部函数,只有在运行那一刻,才能找到
启动时刻就绑定了,应用程序一启动,外部函数和符号表进行绑定
Lazy Symbol Pointers中的函数做绑定时会调用Non-Lazy Symbol Pointers的dyld_stub_binder做绑定,绑定符号的目的,将外部函数的真实地址告诉MachO文件,方便调用,先绑定专门用来绑定的函数,之后用此函数去绑定所有的函数
图片.png
调用Non-Lazy Symbol Pointers中的dyld_stub_binder去绑定
image
image
网友评论