日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
IOS逆向-恢復(fù)Dyld的內(nèi)存加載方式

之前我們一直在使用由dyld及其NS Create Object File Image From Memory / NS Link Module API方法所提供的Mach-O捆綁包的內(nèi)存加載方式。雖然這些方法我們今天仍然還在使用,但是這個(gè)工具較以往有一個(gè)很大的區(qū)別......現(xiàn)在很多模塊都被持久化到了硬盤上。

瀾滄網(wǎng)站制作公司哪家好,找成都創(chuàng)新互聯(lián)公司!從網(wǎng)頁設(shè)計(jì)、網(wǎng)站建設(shè)、微信開發(fā)、APP開發(fā)、響應(yīng)式網(wǎng)站等網(wǎng)站項(xiàng)目制作,到程序開發(fā),運(yùn)營(yíng)維護(hù)。成都創(chuàng)新互聯(lián)公司從2013年開始到現(xiàn)在10年的時(shí)間,我們擁有了豐富的建站經(jīng)驗(yàn)和運(yùn)維經(jīng)驗(yàn),來保證我們的工作的順利進(jìn)行。專注于網(wǎng)站建設(shè)就選成都創(chuàng)新互聯(lián)公司。

@roguesys 在 2022 年 2 月發(fā)布公告稱,dyld 的代碼已經(jīng)被更新,傳遞給 NSLinkModule 的任何模塊都將會(huì)被寫入到一個(gè)臨時(shí)的位置中。

作為一個(gè)紅隊(duì)隊(duì)員,這對(duì)于我們的滲透工作并沒有好處。畢竟,NSLinkModule一個(gè)非常有用的api函數(shù),這個(gè)函數(shù)可以使得我們的有效載荷不被藍(lán)隊(duì)輕易的發(fā)現(xiàn)。

因此,在這篇文章中,我們來仔細(xì)看看dyld的變化,并看看我們能做些什么來恢復(fù)這一功能,讓我們的工具在內(nèi)存中多保存一段時(shí)間,防止被藍(lán)隊(duì)過早的發(fā)現(xiàn)。

NSLinkModule有何與眾不同

由于dyld是開源的,我們可以深入研究一下經(jīng)常使用的NSLinkModule方法的工作原理。

該函數(shù)的簽名為:

NSModule APIs::NSLinkModule(NSObjectFileImage ofi, const char* moduleName, uint32_t options) { ... }

該函數(shù)的第一個(gè)參數(shù)是ofi,它是用NSCreateObjectFileImageFromMemory創(chuàng)建的,它指向了存放Mach-O包的內(nèi)存。然后我們還有moduleName參數(shù)和options參數(shù),前者只是用于記錄語句,后者一般是被忽略不用的。

通過查看代碼發(fā)現(xiàn),最新版本的NSLinkModule,會(huì)將osi所指向的內(nèi)存寫入磁盤。

if ( ofi->memSource != nullptr ) {
...
char tempFileName[PATH_MAX];
const char* tmpDir = this->libSystemHelpers->getenv("TMPDIR");
if ( (tmpDir != nullptr) && (strlen(tmpDir) > 2) ) {
strlcpy(tempFileName, tmpDir, PATH_MAX);
if ( tmpDir[strlen(tmpDir) - 1] != '/' )
strlcat(tempFileName, "/", PATH_MAX);
}
else
strlcpy(tempFileName, "/tmp/", PATH_MAX);
strlcat(tempFileName, "NSCreateObjectFileImageFromMemory-XXXXXXXX", PATH_MAX);
int fd = this->libSystemHelpers->mkstemp(tempFileName);
if ( fd != -1 ) {
ssize_t writtenSize = ::pwrite(fd, ofi->memSource, ofi->memLength, 0);
}
...
}

通過分析可以發(fā)現(xiàn),代碼并不是真正的發(fā)生了 "新 "的變化。這段代碼一直存在于dyld3中,只不過是現(xiàn)在macOS也決定使用這段代碼路徑。所以我們知道內(nèi)存會(huì)被寫入磁盤,并且路徑會(huì)被傳遞給dlopen_from。

...
ofi->handle = dlopen_from(ofi->path, openMode, callerAddress);
...

因此,從本質(zhì)上講,這也就使得NSLinkModule成為了dlopen的一個(gè)封裝器。

① 網(wǎng)安學(xué)習(xí)成長(zhǎng)路徑思維導(dǎo)圖
② 60+網(wǎng)安經(jīng)典常用工具包
③ 100+SRC漏洞分析報(bào)告
④ 150+網(wǎng)安攻防實(shí)戰(zhàn)技術(shù)電子書
⑤ 最權(quán)威CISSP 認(rèn)證考試指南+題庫
⑥ 超1800頁CTF實(shí)戰(zhàn)技巧手冊(cè)
⑦ 最新網(wǎng)安大廠面試題合集(含答案)
⑧ APP客戶端安全檢測(cè)指南(安卓+IOS)

那我們能否恢復(fù)dyld之前的內(nèi)存加載特性呢?

我們知道磁盤 I/O 是被用來持久化和讀取我們的代碼的......那么,如果我們?cè)谡{(diào)用之前攔截它們,會(huì)發(fā)生什么呢?

使用dyld進(jìn)行hook

為了攔截 I/O 調(diào)用,我們首先需要了解如何對(duì)dyld進(jìn)行hook。

我們研究看看dyld是如何處理mmap調(diào)用的。啟動(dòng) Hopper 并加載 /usr/lib/dyld, 顯示mmap 是由 dyld 使用 svc 調(diào)用的。

知道了這一點(diǎn),如果我們找到內(nèi)存中存放這段代碼的位置,我們就應(yīng)該能夠覆蓋服務(wù)調(diào)用并將其重定向到我們控制的地方。但我們?cè)撚檬裁磥砀采w它呢?用下面的這段代碼就可以。

ldr x8, _value
br x8
_value: .ascii "\x41\x42\x43\x44\x45\x46\x47\x48" ; Update to our br location

在我們進(jìn)行操作之前,首先我們找到進(jìn)程地址空間中dyld的基址。這是通過調(diào)用task_info完成的,我們可以傳入TASK_DYLD_INFO來檢索dyld的基址信息。

void *getDyldBase(void) {
struct task_dyld_info dyld_info;
mach_vm_address_t image_infos;
struct dyld_all_image_infos *infos;

mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kern_return_t ret;

ret = task_info(mach_task_self_,
TASK_DYLD_INFO,
(task_info_t)&dyld_info,
&count);

if (ret != KERN_SUCCESS) {
return NULL;
}

image_infos = dyld_info.all_image_info_addr;

infos = (struct dyld_all_image_infos *)image_infos;
return infos->dyldImageLoadAddress;
}

只要我們有了dyld的基址,我們就可以為mmap服務(wù)的調(diào)用查找簽名。

bool searchAndPatch(char *base, char *signature, int length, void *target) {

char *patchAddr = NULL;
kern_return_t kret;

for(int i=0; i < 0x100000; i++) {
if (base[i] == signature[0] && memcmp(base+i, signature, length) == 0) {
patchAddr = base + i;
break;
}
}
...

當(dāng)我們找到一個(gè)匹配的簽名時(shí),我們可以在我們的ARM64的Stub中打補(bǔ)丁。由于我們要處理的是內(nèi)存的 "Read-Exec"頁,我們需要用以下方法來更新內(nèi)存保護(hù)。

kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_WRITE | VM_PROT_COPY);
if (kret != KERN_SUCCESS) {
return FALSE;
}

注意這里的VM_PROT, 這個(gè)是必須要設(shè)定的,因?yàn)樵搩?nèi)存頁在其最大內(nèi)存保護(hù)中沒有設(shè)置寫權(quán)限。

設(shè)置了寫權(quán)限后,我們可以用我們的補(bǔ)丁覆蓋內(nèi)存,然后將保護(hù)重新設(shè)定為Read-Exec。

// Copy our path
memcpy(patchAddr, patch, sizeof(patch));

// Set the br address for our hook call
*(void **)((char*)patchAddr + 16) = target;

// Return exec permission
kret = vm_protect(mach_task_self(), (vm_address_t)patchAddr, sizeof(patch), false, PROT_READ | PROT_EXEC);
if (kret != KERN_SUCCESS) {
return FALSE;
}

現(xiàn)在我們需要思考一下,當(dāng)我們?cè)谠噲D修改可執(zhí)行的內(nèi)存頁時(shí),在M1 macs上會(huì)發(fā)生什么。

由于macOS要確保每一頁可執(zhí)行內(nèi)存都有簽名,這也就意味著我們需要一個(gè)com.apple.security.cs.allow-unsigned-executable-memory的權(quán)限(com.apple.security.cs.disable-executable-page-protection也適用)來運(yùn)行我們的代碼。

那么,既然如此,我們?cè)撊绾翁幚砦覀兊膆ook程序呢?

API模擬調(diào)用

有了所有組件的映射,我們現(xiàn)在就可以開始模擬API的調(diào)用。根據(jù)dyld的代碼,我們需要對(duì)mmap、pread、fcntl的內(nèi)容進(jìn)行處理。

如果我們這樣做是正確的,我們可以在內(nèi)存指向空白Mach-O文件的情況下對(duì)NSLinkModule進(jìn)行調(diào)用,而該文件又將會(huì)被寫入磁盤。然后當(dāng)dyld正在從磁盤上讀入文件時(shí),我們就可以用內(nèi)存中的副本動(dòng)態(tài)地交換內(nèi)容。

首先研究mmap。我們首先檢查fd是否指向一個(gè)包含NSCreateObjectFileImageFromMemory的文件名,這是dyld寫入磁盤的臨時(shí)文件。

如果是這樣的話,我們就不需要從磁盤上映射內(nèi)存了,只要簡(jiǎn)單地分配一個(gè)新的內(nèi)存區(qū)域,然后復(fù)制到我們構(gòu)造的Mach-O包上。

#define FILENAME_SEARCH "NSCreateObjectFileImageFromMemory-"

const void* hookedMmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset) {
char *alloc;
char filePath[PATH_MAX];
int newFlags;

memset(filePath, 0, sizeof(filePath));

// Check if the file is our "in-memory" file
if (fcntl(fd, F_GETPATH, filePath) != -1) {
if (strstr(filePath, FILENAME_SEARCH) > 0) {

newFlags = MAP_PRIVATE | MAP_ANONYMOUS;
if (addr != 0) {
newFlags |= MAP_FIXED;
}

alloc = mmap(addr, len, PROT_READ | PROT_WRITE, newFlags, 0, 0);
memcpy(alloc, memoryLoadedFile+offset, len);
vm_protect(mach_task_self(), (vm_address_t)alloc, len, false, prot);
return alloc;
}
}

// If for another file, we pass through
return mmap(addr, len, prot, flags, fd, offset);
}

接下來是pread參數(shù),它會(huì)被dyld在加載時(shí)用來多次驗(yàn)證Mach-O的UUID。

ssize_t hookedPread(int fd, void *buf, size_t nbyte, int offset) {
char filePath[PATH_MAX];

memset(filePath, 0, sizeof(filePath));

// Check if the file is our "in-memory" file
if (fcntl(fd, F_GETPATH, filePath) != -1) {
if (strstr(filePath, FILENAME_SEARCH) > 0) {
memcpy(buf, memoryLoadedFile+offset, nbyte);
return nbyte;
}
}

// If for another file, we pass through
return pread(fd, buf, nbyte, offset);
}

最后我們處理fcntl。它會(huì)在很多地方被調(diào)用,可以在任何可能會(huì)失敗的mmap調(diào)用之前驗(yàn)證編碼的要求。

由于我們已經(jīng)完成了hook,我們可以使dyld正常運(yùn)行來繞過這些檢查。

int hookedFcntl(int fildes, int cmd, void* param) {

char filePath[PATH_MAX];

memset(filePath, 0, sizeof(filePath));

// Check if the file is our "in-memory" file
if (fcntl(fildes, F_GETPATH, filePath) != -1) {
if (strstr(filePath, FILENAME_SEARCH) > 0) {
if (cmd == F_ADDFILESIGS_RETURN) {
fsignatures_t *fsig = (fsignatures_t*)param;

// called to check that cert covers file.. so we'll make it cover everything ;)
fsig->fs_file_start = 0xFFFFFFFF;
return 0;
}

// Signature sanity check by dyld
if (cmd == F_CHECK_LV) {
// Just say everything is fine
return 0;
}
}
}

return fcntl(fildes, cmd, param);
}

有了以上這些,然后我們可以把這一切組合起來。

int main(int argc, const char * argv[], const char * argv2[], const char * argv3[]) {
@autoreleasepool {
char *dyldBase;
int fd;
int size;
void (*function)(void);
NSObjectFileImage fileImage;

// Read in our dyld we want to memory load... obviously swap this in prod with memory, otherwise we've just recreated dlopen :/
size = readFile("/tmp/loadme", &memoryLoadedFile);

dyldBase = getDyldBase();
searchAndPatch(dyldBase, mmapSig, sizeof(mmapSig), hookedMmap);
searchAndPatch(dyldBase, preadSig, sizeof(preadSig), hookedPread);
searchAndPatch(dyldBase, fcntlSig, sizeof(fcntlSig), hookedFcntl);

// Set up blank content, same size as our Mach-O
char *fakeImage = (char *)malloc(size);
memset(fakeImage, 0x41, size);

// Small hack to get around NSCreateObjectFileImageFromMemory validating our fake image
fileImage = (NSObjectFileImage)malloc(1024);
*(void **)(((char*)fileImage+0x8)) = fakeImage;
*(void **)(((char*)fileImage+0x10)) = size;

void *module = NSLinkModule(fileImage, "test", NSLINKMODULE_OPTION_PRIVATE);
void *symbol = NSLookupSymbolInModule(module, "runme");
function = NSAddressOfSymbol(symbol);
function();
}
}

當(dāng)我們執(zhí)行時(shí),可以看到在硬盤上就會(huì)創(chuàng)建我們的虛假文件。

但通過在運(yùn)行時(shí)的交換內(nèi)容來看,我們發(fā)現(xiàn)我們的內(nèi)存模塊加載完全正常。

其他

所以,最后一個(gè)階段讓我感到很困惑......我們使用了NSLinkModule,它生成了一個(gè)臨時(shí)文件,并且用垃圾字符對(duì)它進(jìn)行了填充。如果我們忽略這一點(diǎn),而只是使用操作系統(tǒng)中的任意一個(gè)庫來調(diào)用dlopen呢?這樣應(yīng)該就可以避免我們向磁盤中寫入任何文件。

事實(shí)證明,這個(gè)想法是正確的。比如:

void *a = dlopen("/usr/lib/libffi-trampolines.dylib", RTLD_NOW);
function = dlsym(a, "runme");
function();

而不是只是搜索NSCreateObjectFileImageFromMemory,我們只是在搜索任何加載libffi-trampolines.dylib的引用,并通過我們的代碼進(jìn)行了替換,我們得到了同樣的結(jié)果。

這里有一些注意事項(xiàng)。首先,我們需要確保庫比我們自己要加載的模塊大,否則當(dāng)涉及到pread和mmap時(shí),系統(tǒng)最終會(huì)截?cái)辔覀兊腗ach-O。


標(biāo)題名稱:IOS逆向-恢復(fù)Dyld的內(nèi)存加載方式
新聞來源:http://www.dlmjj.cn/article/cocoggs.html