我们知道Windows下的文件都是PE文件,同样在OS X和iOS中可执行文件是Mach-o
格式的。
所以我们如果要进行逆向分析,首先要熟悉Mach-o
文件结构。
Mach-o
包含三个基本区域:
- 头部(header structure)
- 加载命令(load command)
- 段(segment)。可以拥有多个`段(segment),每个段可以拥有零个或多个区域(section)。每一个段(segment)都拥有一段虚拟地址映射到进程的地址空间。
- 链接信息。一个完整的用户级Mach-o文件的末端是链接信息。其中包含了动态加载器用来链接可执行文件或者依赖库所需使用的符号表,字符串表等等。
你也可以在这里找到Mach-o的官方资料。
一、我们先使用otool工具来查看Mach-o的头部,看看都包含哪些信息。
头部的的结构如下(32位):
struct mach_header {
uint32_t magic ;
cpu_type_t cputype ;
cpu_subtype_t cpusubtype ;
uint32_t filetype ;
uint32_t ncmds ;
uint32_t sizeofcmds ;
uint32_t flags ;
}
magic
,是mach-o文件的魔数,0xfeedface代表的是32位,0xfeedfacf代表64位cputype
和cupsubtype
代表的是cpu的类型和其子类型,例子中分别是c和9,定义如下:
#define CPU_TYPE_ARM((cpu_type_t) 12)
#define CPU_SUBTYPE_ARM_V7((cpu_subtype_t) 9
即为:armv7
-
filetype
,2,代表可执行的文件
#defineMH_EXECUTE 0×2
ncmds
指的是加载命令(load commands)的数量,例子中一共23个,编号0-22sizeofcmds
表示23个load commands的总字节大小, load commands区域是紧接着header区域的。-
flags
,例子中是0×00200085,可以按文档分析之。
当然不用工具,直接使用UE看也是一样的。按照定义的结构来就行了。
二、头部之后就是加载命令。加载命令的数目以及总的大小在header
中已经给出。
cmd
是load command
的类型,本文中值=1就是LC_SEGMENT
,,LC_SEGMENT
的含义是(将文件中的段映射到进程地址空间)cmdsize
代表load command的大小(0×58个字节)。segname
16字节的段名字,当前是__PAGEZERO。vmaddr
段的虚拟内存起始地址vmsize
段的虚拟内存大小fileoff
段在文件中的偏移量filesize
段在文件中的大小maxprot
段页面所需要的最高内存保护(4=r,2=w,1=x)initprot
段页面初始的内存保护nsects
段中包含section的数量flags
其他杂项标志位
三、接下来就是 section了:
结构如下:
struct section {
char sectname [ 16 ] ;
char segname [ 16 ] ;
uint32_t addr ;
uint32_t size ;
uint32_t offset ;
uint32_t align ;
uint32_t reloff ;
uint32_t nreloc ;
uint32_t flags ;
uint32_t reserved1 ;
uint32_t reserved2 ;
} ;
sectname
第一个是__text ,就是主程序代码segname
该section所属的 segment名,第一个是__TEXTaddr
该section在内存的启始位置,0xa588。size
该section的大小,0x84aoffset
该section的文件偏移,28116 0x6dd4align
字节大小对齐 ,4reloff
重定位入口的文件偏移,0nreloc
需要重定位的入口数量,0flags
包含section的type和attributes
S_REGULAR—This section has no particular type. The standard tools create a __TEXT,__text section of this type.
结构中的最后2项保留用。
段的命名规则是两个下划线紧跟着大写字母(如__TEXT
),而section的命名则是两个下划线紧跟着小写字母(__text
)。
下面列出段中可能包含的section:
__TEXT段:
__text, __cstring, __picsymbol_stub, __symbol_stub, __const, __litera14, __litera18;
__DATA段
__data, __la_symbol_ptr, __nl_symbol_ptr, __dyld, __const, __mod_init_func, __mod_term_func, __bss, __commom;
__IMPORT段
__jump_table, __pointers;
其中__TEXT
段中的__text
是实际上的代码部分;__DATA
段的__data
是实际的初始数据。
绝大多数mach-o包括以下三个段(支持用户自定义Segment,但是很少使用):
-
__TEXT
代码段,只读,包括函数,和只读的字符串__TEXT
,__text
的都是代码段 -
__DATA
数据段,读写,包括可读写的全局变量等,__DATA
,__data
都是数据段 -
__LINKEDIT
__LINKEDIT
包含了方法和变量的元数据(位置,偏移量),以及代码签名等信息。
可以通过otool –s
查看某segment
的某个section
。
可以通过otool –t
直接查看代码段(__TEXT
)的反汇编代码:
最常见的加载命令如下:
LC_SEGMENT_64: 将该段(64位)映射到进程地址空间中
LC_DYLD_INFO_ONLY:加载动态链接库信息(重定向地址、弱引用绑定、懒加载绑定、开放函数等的偏移值等信息)
LC_SYMTAB:载入符号表地址
LC_DYSYMTAB:载入动态符号表地址
-
LC_LOAD_DYLINKER:加载动态加载库,可以看出示例使用的是/usr/lib/dyld
LC_UUID:确定文件的唯一标识,crash解析中也会有这个,去检测dysm文件和crash文件是否匹配
LC_VERSION_MIN_MACOSX/LC_VERSION_MIN_IPHONEOS:确定二进制文件要求的最低操作系统版本
LC_SOURCE_VERSION:构建该二进制文件使用的源代码版本
LC_MAIN:设置程序主线程的入口地址和栈大小
LC_ENCRYPTION_INFO_64:获取加密信息
LC_LOAD_DYLIB:加载额外的动态库
LC_FUNCTION_STARTS:定义一个函数起始地址表,使调试器和其他程序易于看到一个地址是否在函数内
LC_DATA_IN_CODE:定义在代码段内的非指令的表
LC_CODE_SIGNATURE:获取应用签名信息
作者:小可长江
链接:https://juejin.cn/post/6844903780853481479
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
其它的大家参考官方文档就行了。
相关文章