MachO文件详解--逆向开发
Posted guohai-stronger
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MachO文件详解--逆向开发相关的知识,希望对你有一定的参考价值。
今天是逆向开发的第5天内容--MachO文件(Mac 和 ios 平台可执行的文件),在逆向开发中是比较重要的,下面我们着重讲解一下MachO文件的基本内容和使用。
一、MachO概述
1. 概述
Mach-O是Mach Object文件格式的缩写,iOS以及Mac上可执行的文件格式,类似Window的exe格式,Linux上的elf格式。Mach-O是一个可执行文件、动态库以及目标代码的文件格式,是a.out格式的替代,提供了更高更强的扩展性。
2.常见格式
Mach-O常见格式如下:
- 目标文件 .o
- 库文件
- .a
- .dylib
- .framework
- 可执行文件
- dyld
- .dsym
通过file文件路径查看文件类型
我们通过部分实例代码来简单研究一下。
2.1目标文件.o
通过test.c 文件,可以使用clang命令将其编译成目标文件.o
我们再通过file命令(如下)查看文件类型
是个Mach-O文件。
2.2 dylib
通过cd /usr/lib命令查看dylib
通过file命令查看文件类型
2.3 .dsym
下面是一个截图来说明.dsym是也是Mach-O文件格式
以上只是Mach-O常见格式的某一种,大家可以通过命令来尝试。
3. 通用二进制文件
希望大家在了解App二进制架构的时候,可以先读一下本人写的另一篇博客关于armv7,armv7s以及arm64等的介绍。https://www.cnblogs.com/guohai-stronger/p/9447364.html
通用二进制文件是苹果自身发明的,基本内容如下
下面通过指令查看Macho文件来看下通用二进制文件
然后通过file指令查看文件类型
上面该MachO文件包含了3个架构分别是arm v7,arm v7s 以及arm 64 。
针对该MachO文件我们做几个操作,利用lipo命令拆分合并架构
3.1 利用lipo-info查看MachO文件架构
3.2 瘦身MachO文件,拆分
利用lipo-thin瘦身架构
查看一下结果如下,多出来一个新建的MachO_armv7
3.3 增加架构,合并
利用lipo -create 合并多种架构
发现多出一种框架,合并成功多出Demo可执行文件。结果如下:
整理出lipo命令如下:
二、MachO文件
2.1 文件结构
下面是苹果官方图解释MachO文件结构图
MachO文件的组成结构如上,看包括了三个部分
- Header包含了该二进制文件的一般信息,信息如下:
- 字节顺序、加载指令的数量以及架构类型
- 快速的确定一些信息,比如当前文件是32位或者64位,对应的文件类型和处理器是什么
- Load commands 包含很多内容的表
- 包括区域的位置、动态符号表以及符号表等
- Data一般是对象文件的最大部分
- 一般包含Segement具体数据
2.2 Header的数据结构
在项目代码中,按下Command+ 空格,然后输入loader.h
然后查看loader.h文件,找到mach_header
上面是mach_header,对应结构体的意义如下:
通过MachOView查看Mach64 Header头部信息
2.3 LoadCommands
LoadCommand包含了很多内容的表,通过MachOView查看LoadCommand的信息,图如下:
但是大家看的可能并不了解内容,下面有图进行注解,可以看下主要的意思
2.4 Data
Data包含Segement,存储具体数据,通过MachOView查看,地址映射内容
三、DYLD
3.1 dyld概述
dyld(the dynamic link editor)是苹果动态链接器,是苹果系统一个重要的组成部分,系统内核做好准备工作之后,剩下的就会交给了dyld。
3.2 dyld加载过程
程序的入口一般都是在main函数中,但是比较少的人关心main()函数之前发生了什么?这次我们先探索dyld的加载过程。(但是比在main函数之前,load方法就在main函数之前)
3.2.1 新建项目,在main函数下断
main()之前有个libdyld.dylib start入口,但是不是我们想要的,根据dyld源码找到__dyld_start函数
3.2.2 dyld main()函数
dyld main()函数是关键函数,下面是函数实现内容。(此时的main实现函数和程序App的main 函数是不一样的,因为dyld也是一个可执行文件,也是具有main函数的)
// // Entry point for dyld. The kernel loads dyld and jumps to __dyld_start which // sets up some registers and call this function. // // Returns address of main() in target program which __dyld_start jumps to // uintptr_t _main(const macho_header* mainExecutableMH, uintptr_t mainExecutableSlide, int argc, const char* argv[], const char* envp[], const char* apple[], uintptr_t* startGlue) { // Grab the cdHash of the main executable from the environment // 第一步,设置运行环境 uint8_t mainExecutableCDHashBuffer[20]; const uint8_t* mainExecutableCDHash = nullptr; if ( hexToBytes(_simple_getenv(apple, "executable_cdhash"), 40, mainExecutableCDHashBuffer) ) // 获取主程序的hash mainExecutableCDHash = mainExecutableCDHashBuffer; // Trace dyld‘s load notifyKernelAboutImage((macho_header*)&__dso_handle, _simple_getenv(apple, "dyld_file")); #if !TARGET_IPHONE_SIMULATOR // Trace the main executable‘s load notifyKernelAboutImage(mainExecutableMH, _simple_getenv(apple, "executable_file")); #endif uintptr_t result = 0; // 获取主程序的macho_header结构 sMainExecutableMachHeader = mainExecutableMH; // 获取主程序的slide值 sMainExecutableSlide = mainExecutableSlide; CRSetCrashLogMessage("dyld: launch started"); // 设置上下文信息 setContext(mainExecutableMH, argc, argv, envp, apple); // Pickup the pointer to the exec path. // 获取主程序路径 sExecPath = _simple_getenv(apple, "executable_path"); // <rdar://problem/13868260> Remove interim apple[0] transition code from dyld if (!sExecPath) sExecPath = apple[0]; if ( sExecPath[0] != ‘/‘ ) { // have relative path, use cwd to make absolute char cwdbuff[MAXPATHLEN]; if ( getcwd(cwdbuff, MAXPATHLEN) != NULL ) { // maybe use static buffer to avoid calling malloc so early... char* s = new char[strlen(cwdbuff) + strlen(sExecPath) + 2]; strcpy(s, cwdbuff); strcat(s, "/"); strcat(s, sExecPath); sExecPath = s; } } // Remember short name of process for later logging // 获取进程名称 sExecShortName = ::strrchr(sExecPath, ‘/‘); if ( sExecShortName != NULL ) ++sExecShortName; else sExecShortName = sExecPath; // 配置进程受限模式 configureProcessRestrictions(mainExecutableMH); // 检测环境变量 checkEnvironmentVariables(envp); defaultUninitializedFallbackPaths(envp); // 如果设置了DYLD_PRINT_OPTS则调用printOptions()打印参数 if ( sEnv.DYLD_PRINT_OPTS ) printOptions(argv); // 如果设置了DYLD_PRINT_ENV则调用printEnvironmentVariables()打印环境变量 if ( sEnv.DYLD_PRINT_ENV ) printEnvironmentVariables(envp); // 获取当前程序架构 getHostInfo(mainExecutableMH, mainExecutableSlide); //-------------第一步结束------------- // load shared cache // 第二步,加载共享缓存 // 检查共享缓存是否开启,iOS必须开启 checkSharedRegionDisable((mach_header*)mainExecutableMH); if ( gLinkContext.sharedRegionMode != ImageLoader::kDontUseSharedRegion ) { mapSharedCache(); } ... try { // add dyld itself to UUID list addDyldImageToUUIDList(); // instantiate ImageLoader for main executable // 第三步 实例化主程序 sMainExecutable = instantiateFromLoadedImage(mainExecutableMH, mainExecutableSlide, sExecPath); gLinkContext.mainExecutable = sMainExecutable; gLinkContext.mainExecutableCodeSigned = hasCodeSignatureLoadCommand(mainExecutableMH); // Now that shared cache is loaded, setup an versioned dylib overrides #if SUPPORT_VERSIONED_PATHS checkVersionedPaths(); #endif // dyld_all_image_infos image list does not contain dyld // add it as dyldPath field in dyld_all_image_infos // for simulator, dyld_sim is in image list, need host dyld added #if TARGET_IPHONE_SIMULATOR // get path of host dyld from table of syscall vectors in host dyld void* addressInDyld = gSyscallHelpers; #else // get path of dyld itself void* addressInDyld = (void*)&__dso_handle; #endif char dyldPathBuffer[MAXPATHLEN+1]; int len = proc_regionfilename(getpid(), (uint64_t)(long)addressInDyld, dyldPathBuffer, MAXPATHLEN); if ( len > 0 ) { dyldPathBuffer[len] = ‘