目前,在iOS中很多dylib是通過DYLDINSERTLIBRARIES來動態注入的,關于這種注入的防范方式最好的方法是在程序編譯時,添加一個空的restict區段,因為dyld在load動態庫時會檢查區段中是否存在restict,如果存在那么就跳過加載。詳情參見:防止tweak依附,App有高招;破解App保護,tweak留一手
前段時間,發現自己寫的一個demo加了restict之后,仍然能夠被cycript掛載,順勢分析了cycript的注入原理。
cycript是大神saurik開發的一個非常強大的工具,可以讓開發者在命令行下和應用交互,在運行時查看和修改應用。其中的底層實現,是通過蘋果的JavaScriptCore.framework來打通iOS與javascript的橋梁。具體詳解,參考
在安裝cycript過后,通過cydia可以看到安裝cycript之后影響的目錄結構
雖然只提到這幾個文件,但是在/usr/bin下,還有一個cynject的可執行文件和cycript相關,后面分析會發現,這個程序才是注入的核心文件。我這里,通過scp把這些文件都下載到本地,方便分析。
分析Cycript主程序時,觀察到里面有一個InjectLibrary的函數被調用
#!c
void InjectLibrary(int pid, int argc, const char *argv[]) {
auto cynject(LibraryFor(reinterpret_cast<void *>(&main)));
auto slash(cynject.rfind('/'));
_assert(slash != std::string::npos);
cynject = cynject.substr(0, slash) + "/cynject";
auto library(LibraryFor(reinterpret_cast<void *>(&MSmain0)));
bool ios(false);
for (decltype(header->ncmds) i(0); i != header->ncmds; ++i) {
if (command->cmd == LC_VERSION_MIN_IPHONEOS)
ios = true;
command = shift(command, command->cmdsize);
}
_syscall(munmap(map, size)); // XXX: _scope
auto length(library.size());
_assert(length >= 6);
length -= 6;
_assert(library.substr(length) == ".dylib");
library = library.substr(0, length);
library += ios ? "-sim" : "-sys";
library += ".dylib";
#endif
std::ostringstream inject;
inject << cynject << " " << std::dec << pid << " " << library;
for (decltype(argc) i(0); i != argc; ++i)
inject << " " << argv[i];
_assert(system(inject.str().c_str()) == 0);
}
程序在解析過程中,注入代碼前會對會環境進行檢查,并且進行字符串格式化拼接,構造一個shell命令,用來做調用system函數的參數,來執行cynject注入功能。
接下來分析cynject,在函數subb308的位置可以看到,通過調用taskfor_pid獲取到進程句柄結構。通過該結構,可以對進程能夠有訪問權限。
舉個栗子:
#!c
mach_port_t rtask;
task_for_pid(mach_task_self(), pid, &rtask);
程序為了讓內存中的dylib有執行能力,把dylib通過線程的方式來加載。繼續往下看,就發現進程創建一個被掛起的線程
拿到句柄結構,在進程的空間中申請內存,將dylib映射之后,寫入到這片申請的空間里面。
所以,代碼邏輯大概是這樣的:
#!c
vm_size_t codeSize = 124;
vm_address_t rAddress;
vm_allocate(rtask, &rAddress, codeSize, TRUE);
vm_write(rtask, rAddress, &code, (mach_msg_type_number_t)codeSize);
vm_protect(rtask, rAddress, codeSize, FALSE, VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE);
最后,等dylib加載完全后,為dylib恢復啟動并執行使其開始運行
梳理一下大體的流程:
( 1 )獲取 PID 的進程句柄
( 2 )在 PID 中創建一個被掛起的線程
( 3 )在 PID 進程中申請一片用于加載 DYLIB 的內存
( 4 )調用 RESUME ,恢復線程
OVER~
說到這里,流程差不多梳理完了,拿一個dylib測試一下
通過lldb連上程序,看一下內存加載模塊:
可以發現,我們惡意的dylib已經注入到進程中了。
cycript里面還有一些比較有意思的東西,大家可以深挖一下。另外,上面的內容不要用到自己企業的app產品中,按照蘋果appStore原則,調用私有api以及動態注入代碼的操作,是會被下架的。