前言
在安全研發(fā)的過程中,難免會使用內(nèi)存分配函數(shù) malloc、重載的運(yùn)算符 new 開啟堆內(nèi)存用于長時間駐留一些數(shù)據(jù),但這些數(shù)據(jù)可能對于防御者來說比較敏感,比如有時候,這些堆內(nèi)存中可能會出現(xiàn)回連地址等。所以有必要對這些保留在堆內(nèi)存中的敏感數(shù)據(jù)進(jìn)行加密。
在進(jìn)入堆分配內(nèi)存加密之前,首先了解以下程序中堆棧的概念;
堆和棧是程序運(yùn)行時保存數(shù)據(jù)的方式。
棧:通常來說棧用于存儲臨時數(shù)據(jù),比如局部變量和函數(shù)調(diào)用的上下文信息,棧上的數(shù)據(jù)的生命周期隨著函數(shù)的調(diào)用和返回而自動管理,一般在作用域結(jié)束后被自動釋放;
堆:堆通常用于存儲想長期駐留在內(nèi)存中的數(shù)據(jù),比如一些需要長期存在的配置信息和一些需要共享的數(shù)據(jù),堆上的數(shù)據(jù)的生命周期不受作用域的限制,一般在整個程序運(yùn)行期間都存在,直到顯示地釋放它們。
由于堆上的數(shù)據(jù)的生命周期不受限制,除非顯式地釋放它們,否則它們將一直駐留在內(nèi)存中,如果我們想保護(hù)這些數(shù)據(jù),可以在內(nèi)存中將存儲這些數(shù)據(jù)的堆內(nèi)存進(jìn)行加密,防止數(shù)據(jù)暴露。
堆分配內(nèi)存加密
枚舉堆
在對堆進(jìn)行操作之前,首先需要先枚舉進(jìn)程內(nèi)存中堆的信息,可以通過 HeapWalk 函數(shù)枚舉指定堆中的內(nèi)存塊,其函數(shù)簽名如下:
BOOL HeapWalk( [in] HANDLE hHeap, [in, out] LPPROCESS_HEAP_ENTRY lpEntry );
可以使用以下代碼枚舉進(jìn)程內(nèi)存中堆的信息
void EnumHeaps() { PROCESS_HEAP_ENTRY entry; SecureZeroMemory(&entry, sizeof(entry)); while (HeapWalk(GetProcessHeap(), &entry)) { printf("heap addr: %p size: %d ", (char*)entry.lpData, entry.cbData); } }
獲取了進(jìn)程內(nèi)存中的堆信息,就可以對指定類型的堆進(jìn)行操作了,一般加密已分配的堆,需要注意的是,由于堆中的數(shù)據(jù)是線程共享的,所以,在加密堆數(shù)據(jù)之前,需要掛起所有線程(除了當(dāng)前執(zhí)行的線程),待操作結(jié)束后,恢復(fù)所有線程的執(zhí)行。
掛起線程
可以使用 SuspendThread 掛起指定線程,需要注意的是,需要排除當(dāng)前執(zhí)行的線程。
void DoSuspendThreads(DWORD targetProcessId, DWORD targetThreadId) { HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(h, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { // Suspend all threads EXCEPT the one we want to keep running if (te.th32ThreadID != targetThreadId && te.th32OwnerProcessID == targetProcessId) { HANDLE thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); if (thread != NULL) { SuspendThread(thread); CloseHandle(thread); } } } te.dwSize = sizeof(te); } while (Thread32Next(h, &te)); } CloseHandle(h); } }
恢復(fù)線程
使用 ResumeThread 函數(shù),可以讓掛起的線程恢復(fù)。
void DoResumeThreads(DWORD targetProcessId, DWORD targetThreadId) { HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0); if (h != INVALID_HANDLE_VALUE) { THREADENTRY32 te; te.dwSize = sizeof(te); if (Thread32First(h, &te)) { do { if (te.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(te.th32OwnerProcessID)) { // Suspend all threads EXCEPT the one we want to keep running if (te.th32ThreadID != targetThreadId && te.th32OwnerProcessID == targetProcessId) { HANDLE thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, te.th32ThreadID); if (thread != NULL) { ResumeThread(thread); CloseHandle(thread); } } } te.dwSize = sizeof(te); } while (Thread32Next(h, &te)); } CloseHandle(h); } }
在安全研發(fā)的過程中,為了降低自動化分析對代碼的敏感度,通常會使用延遲代碼的執(zhí)行速度,比如使用Sleep函數(shù)。
根據(jù)以上,進(jìn)程中堆分配內(nèi)存的步驟如下:
掛鉤 Sleep 函數(shù)
在 HookedSleep 函數(shù)內(nèi)部:
掛起當(dāng)前進(jìn)程內(nèi)所有線程(排除當(dāng)前執(zhí)行線程)
對已分配的堆進(jìn)行加密
調(diào)用原始 Sleep 進(jìn)行睡眠操作
恢復(fù)所有線程執(zhí)行
void WINAPI HookedSleep(DWORD dwMiliseconds) { DWORD time = dwMiliseconds; if (time > 1000) { printf("HookedSleep: suspend threads, strat encrypt heaps. "); DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId()); HeapEncryptDecrypt(); OldSleep(dwMiliseconds); HeapEncryptDecrypt(); DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId()); printf("HookedSleep: decrypt heaps success, resume threads run. "); } else { OldSleep(time); } }
當(dāng)代碼中存在使用堆相關(guān)函數(shù),比如 HeapAlloc 分配內(nèi)存時,再比如使用 CRT 函數(shù) malloc 和 重載的運(yùn)算符 new 開辟堆空間時(其最終也是通過HeapAlloc函數(shù)分配),都可通過堆分配內(nèi)存加密保護(hù)我們的數(shù)據(jù)。
下圖是在掛鉤了 Sleep 后,程序進(jìn)入HookedSleep 函數(shù)未進(jìn)行對加密之前我們在程序中分配的堆空間的原始數(shù)據(jù)。
在進(jìn)行堆加密后,效果如下,可以看到,我們堆分配的內(nèi)存已被加密。
目標(biāo)線程堆分配空間加密
上文可以看到通過 HeapWalk 遍歷了進(jìn)程中所有的堆信息,并在堆加密之前比如掛起所有的線程,這樣有一個缺點,就是只能針對自己寫的程序。
有時候我們的需要執(zhí)行的功能代碼是通過注入到其它進(jìn)程實現(xiàn)的,如果這樣冒然的將所有線程掛起,這有太多的不可預(yù)料性,可能會導(dǎo)致宿主程序崩潰。
所以需要有一種方式來實現(xiàn)只加密我們執(zhí)行功能線程代碼中分配的堆內(nèi)存。
由于通過注入 shellcode/dll 的方式執(zhí)行功能,所以首要目標(biāo)是獲取執(zhí)行功能的線程ID,之后就是在代碼中 Hook 堆分配/釋放相關(guān)的函數(shù),RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap 函數(shù),這樣就可以跟蹤線程代碼中堆分配釋放的狀態(tài)了。
獲取了線程堆分配釋放的情況,目標(biāo)線程堆分配內(nèi)存加密的代碼思路如下:
獲取當(dāng)前執(zhí)行線程ID
Hook RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap
在 HookedRtlAllocateHeap、HookedRtlReAllocateHeap 函數(shù)內(nèi)部
執(zhí)行原始 OldRtlAllocateHeap、OldRtlReAllocateHeap,并記錄堆分配的地址和大小
篩選出我們的線程分配的堆內(nèi)存:將堆分配信息添加進(jìn)維護(hù)的堆分配信息數(shù)組中
HookedRtlFreeHeap 函數(shù)內(nèi)部
得到需要釋放的地址
執(zhí)行原始 OldRtlFreeHeap
篩選出我們的線程釋放的堆內(nèi)存:將其移除維護(hù)的堆分配信息信息數(shù)組
當(dāng)篩選出目標(biāo)線程堆分配的內(nèi)存信息時,就可以通過在 HookedSleep 函數(shù)中對填充好堆分配信息數(shù)組進(jìn)行操作了。
審核編輯:劉清
-
crt
+關(guān)注
關(guān)注
2文章
82瀏覽量
36478 -
加解密
+關(guān)注
關(guān)注
0文章
18瀏覽量
6636
原文標(biāo)題:安全開發(fā)之堆分配內(nèi)存加密
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Linux應(yīng)用開發(fā)之內(nèi)存分配

【原創(chuàng)】堆內(nèi)存的那些事
freertos與STM32如何分配堆??臻g
請問使用動態(tài)內(nèi)存分配安全嗎?
使用動態(tài)內(nèi)存分配安全嗎
關(guān)于RT-Thread內(nèi)存管理的內(nèi)存池簡析
關(guān)于RT-Thread的動態(tài)內(nèi)存堆管理簡析
有關(guān)RT-Thread操作系統(tǒng)的內(nèi)存管理模塊基本知識簡析
Armv8.1-M PAC和BTI擴(kuò)展簡析
如何使用鏈接腳本刪除堆分配?
鼠標(biāo)HID例程(中)簡析
Java開發(fā)者必須了解的堆外內(nèi)存技術(shù)

什么是堆內(nèi)存?堆內(nèi)存是如何分配的?
貿(mào)澤開售面向安全應(yīng)用的英飛凌OPTIGA Trust M物聯(lián)網(wǎng)安全開發(fā)套件

評論