今天是逆向開發的第5天內容–MachO文件(Mac 和 iOS 平台可執行的文件),在逆向開發中是比較重要的,下面我們着重講解一下MachO文件的基本內容和使用。
一、MachO概述
1. 概述
Mach-O是Mach Object文件格式的縮寫,iOS以及Mac上可執行的文件格式,類似Window的exe格式,Linux上的elf格式。Mach-O是一個可執行文件、動態庫以及目標代碼的文件格式,是a.out格式的替代,提供了更高更強的擴展性。
2.常見格式
Mach-O常見格式如下:
- .a
- .dylib
- .framework
通過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等的介紹。
通用二進制文件是蘋果自身發明的,基本內容如下
下面通過指令查看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位,對應的文件類型和處理器是什麼
- 包括區域的位置、動態符號表以及符號表等
- 一般包含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] = '\0'; // proc_regionfilename() does not zero terminate returned string
if ( strcmp(dyldPathBuffer, gProcessInfo->dyldPath) != 0 )
gProcessInfo->dyldPath = strdup(dyldPathBuffer);
}
// load any inserted libraries
// 第四步 加載插入的動態庫
if ( sEnv.DYLD_INSERT_LIBRARIES != NULL ) {
for (const char* const* lib = sEnv.DYLD_INSERT_LIBRARIES; *lib != NULL; ++lib)
loadInsertedDylib(*lib);
}
// record count of inserted libraries so that a flat search will look at
// inserted libraries, then main, then others.
// 記錄插入的動態庫數量
sInsertedDylibCount = sAllImages.size()-1;
// link main executable
// 第五步 鏈接主程序
gLinkContext.linkingMainExecutable = true;
#if SUPPORT_ACCELERATE_TABLES
if ( mainExcutableAlreadyRebased ) {
// previous link() on main executable has already adjusted its internal pointers for ASLR
// work around that by rebasing by inverse amount
sMainExecutable->rebase(gLinkContext, -mainExecutableSlide);
}
#endif
link(sMainExecutable, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
sMainExecutable->setNeverUnloadRecursive();
if ( sMainExecutable->forceFlat() ) {
gLinkContext.bindFlat = true;
gLinkContext.prebindUsage = ImageLoader::kUseNoPrebinding;
}
// link any inserted libraries
// do this after linking main executable so that any dylibs pulled in by inserted
// dylibs (e.g. libSystem) will not be in front of dylibs the program uses
// 第六步 鏈接插入的動態庫
if ( sInsertedDylibCount > 0 ) {
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
link(image, sEnv.DYLD_BIND_AT_LAUNCH, true, ImageLoader::RPathChain(NULL, NULL), -1);
image->setNeverUnloadRecursive();
}
// only INSERTED libraries can interpose
// register interposing info after all inserted libraries are bound so chaining works
for(unsigned int i=0; i < sInsertedDylibCount; ++i) {
ImageLoader* image = sAllImages[i+1];
image->registerInterposing();
}
}
// <rdar://problem/19315404> dyld should support interposition even without DYLD_INSERT_LIBRARIES
for (long i=sInsertedDylibCount+1; i < sAllImages.size(); ++i) {
ImageLoader* image = sAllImages[i];
if ( image->inSharedCache() )
continue;
image->registerInterposing();
}
...
// apply interposing to initial set of images
for(int i=0; i < sImageRoots.size(); ++i) {
sImageRoots[i]->applyInterposing(gLinkContext);
}
gLinkContext.linkingMainExecutable = false;
// <rdar://problem/12186933> do weak binding only after all inserted images linked
// 第七步 執行弱符號綁定
sMainExecutable->weakBind(gLinkContext);
// If cache has branch island dylibs, tell debugger about them
if ( (sSharedCacheLoadInfo.loadAddress != NULL) && (sSharedCacheLoadInfo.loadAddress->header.mappingOffset >= 0x78) && (sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset != 0) ) {
uint32_t count = sSharedCacheLoadInfo.loadAddress->header.branchPoolsCount;
dyld_image_info info[count];
const uint64_t* poolAddress = (uint64_t*)((char*)sSharedCacheLoadInfo.loadAddress + sSharedCacheLoadInfo.loadAddress->header.branchPoolsOffset);
// <rdar://problem/20799203> empty branch pools can be in development cache
if ( ((mach_header*)poolAddress)->magic == sMainExecutableMachHeader->magic ) {
for (int poolIndex=0; poolIndex < count; ++poolIndex) {
uint64_t poolAddr = poolAddress[poolIndex] + sSharedCacheLoadInfo.slide;
info[poolIndex].imageLoadAddress = (mach_header*)(long)poolAddr;
info[poolIndex].imageFilePath = "dyld_shared_cache_branch_islands";
info[poolIndex].imageFileModDate = 0;
}
// add to all_images list
addImagesToAllImages(count, info);
// tell gdb about new branch island images
gProcessInfo->notification(dyld_image_adding, count, info);
}
}
CRSetCrashLogMessage("dyld: launch, running initializers");
...
// run all initializers
// 第八步 執行初始化方法
initializeMainExecutable();
// notify any montoring proccesses that this process is about to enter main()
dyld3::kdebug_trace_dyld_signpost(DBG_DYLD_SIGNPOST_START_MAIN_DYLD2, 0, 0);
notifyMonitoringDyldMain();
// find entry point for main executable
// 第九步 查找入口點並返回
result = (uintptr_t)sMainExecutable->getThreadPC();
if ( result != 0 ) {
// main executable uses LC_MAIN, needs to return to glue in libdyld.dylib
if ( (gLibSystemHelpers != NULL) && (gLibSystemHelpers->version >= 9) )
*startGlue = (uintptr_t)gLibSystemHelpers->startGlueToCallExit;
else
halt("libdyld.dylib support not present for LC_MAIN");
}
else {
// main executable uses LC_UNIXTHREAD, dyld needs to let "start" in program set up for main()
result = (uintptr_t)sMainExecutable->getMain();
*startGlue = 0;
}
}
catch(const char* message) {
syncAllImages();
halt(message);
}
catch(...) {
dyld::log("dyld: launch failed\n");
}
...
return result;
}
View Code
摺疊開dyld main函數,步驟總結如下
對待dyld的講述,是非常不易的,因為本身過程是比較複雜的,上面僅僅是自身的抽出來的。下面再畫一張流程圖,幫助大家理解。
四、總結
MachO文件對於逆向開發是非常重要的,通過本次講解,希望對大家理解逆向開發有所幫助,也希望大家真正可以提高技術,應對iOS市場的大環境,下一篇我們將講述Hook原理–逆向開發。謝謝!!!
本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】
※帶您來了解什麼是 USB CONNECTOR ?
※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象
※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!
※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化
※廣告預算用在刀口上,網站設計公司幫您達到更多曝光效益