前言
在安全研發的過程中,難免會使用內存分配函數 malloc、重載的運算符 new 開啟堆內存用于長時間駐留一些數據,但這些數據可能對于防御者來說比較敏感,比如有時候,這些堆內存中可能會出現回連地址等。所以有必要對這些保留在堆內存中的敏感數據進行加密。
在進入堆分配內存加密之前,首先了解以下程序中堆棧的概念;
堆和棧是程序運行時保存數據的方式。
棧:通常來說棧用于存儲臨時數據,比如局部變量和函數調用的上下文信息,棧上的數據的生命周期隨著函數的調用和返回而自動管理,一般在作用域結束后被自動釋放;
堆:堆通常用于存儲想長期駐留在內存中的數據,比如一些需要長期存在的配置信息和一些需要共享的數據,堆上的數據的生命周期不受作用域的限制,一般在整個程序運行期間都存在,直到顯示地釋放它們。
由于堆上的數據的生命周期不受限制,除非顯式地釋放它們,否則它們將一直駐留在內存中,如果我們想保護這些數據,可以在內存中將存儲這些數據的堆內存進行加密,防止數據暴露。
堆分配內存加密
枚舉堆
在對堆進行操作之前,首先需要先枚舉進程內存中堆的信息,可以通過 HeapWalk 函數枚舉指定堆中的內存塊,其函數簽名如下:
BOOL HeapWalk( [in] HANDLE hHeap, [in, out] LPPROCESS_HEAP_ENTRY lpEntry );
可以使用以下代碼枚舉進程內存中堆的信息
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); } }
獲取了進程內存中的堆信息,就可以對指定類型的堆進行操作了,一般加密已分配的堆,需要注意的是,由于堆中的數據是線程共享的,所以,在加密堆數據之前,需要掛起所有線程(除了當前執行的線程),待操作結束后,恢復所有線程的執行。
掛起線程
可以使用 SuspendThread 掛起指定線程,需要注意的是,需要排除當前執行的線程。
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); } }
恢復線程
使用 ResumeThread 函數,可以讓掛起的線程恢復。
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); } }
在安全研發的過程中,為了降低自動化分析對代碼的敏感度,通常會使用延遲代碼的執行速度,比如使用Sleep函數。
根據以上,進程中堆分配內存的步驟如下:
掛鉤 Sleep 函數
在 HookedSleep 函數內部:
掛起當前進程內所有線程(排除當前執行線程)
對已分配的堆進行加密
調用原始 Sleep 進行睡眠操作
恢復所有線程執行
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); } }
當代碼中存在使用堆相關函數,比如 HeapAlloc 分配內存時,再比如使用 CRT 函數 malloc 和 重載的運算符 new 開辟堆空間時(其最終也是通過HeapAlloc函數分配),都可通過堆分配內存加密保護我們的數據。
下圖是在掛鉤了 Sleep 后,程序進入HookedSleep 函數未進行對加密之前我們在程序中分配的堆空間的原始數據。
在進行堆加密后,效果如下,可以看到,我們堆分配的內存已被加密。
目標線程堆分配空間加密
上文可以看到通過 HeapWalk 遍歷了進程中所有的堆信息,并在堆加密之前比如掛起所有的線程,這樣有一個缺點,就是只能針對自己寫的程序。
有時候我們的需要執行的功能代碼是通過注入到其它進程實現的,如果這樣冒然的將所有線程掛起,這有太多的不可預料性,可能會導致宿主程序崩潰。
所以需要有一種方式來實現只加密我們執行功能線程代碼中分配的堆內存。
由于通過注入 shellcode/dll 的方式執行功能,所以首要目標是獲取執行功能的線程ID,之后就是在代碼中 Hook 堆分配/釋放相關的函數,RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap 函數,這樣就可以跟蹤線程代碼中堆分配釋放的狀態了。
獲取了線程堆分配釋放的情況,目標線程堆分配內存加密的代碼思路如下:
獲取當前執行線程ID
Hook RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap
在 HookedRtlAllocateHeap、HookedRtlReAllocateHeap 函數內部
執行原始 OldRtlAllocateHeap、OldRtlReAllocateHeap,并記錄堆分配的地址和大小
篩選出我們的線程分配的堆內存:將堆分配信息添加進維護的堆分配信息數組中
HookedRtlFreeHeap 函數內部
得到需要釋放的地址
執行原始 OldRtlFreeHeap
篩選出我們的線程釋放的堆內存:將其移除維護的堆分配信息信息數組
當篩選出目標線程堆分配的內存信息時,就可以通過在 HookedSleep 函數中對填充好堆分配信息數組進行操作了。
審核編輯:劉清
-
crt
+關注
關注
2文章
82瀏覽量
36302 -
加解密
+關注
關注
0文章
18瀏覽量
6609
原文標題:安全開發之堆分配內存加密
文章出處:【微信號:蛇矛實驗室,微信公眾號:蛇矛實驗室】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
freertos與STM32如何分配堆棧空間
請問使用動態內存分配安全嗎?
關于RT-Thread內存管理的內存池簡析
關于RT-Thread的動態內存堆管理簡析
Armv8.1-M PAC和BTI擴展簡析
如何使用鏈接腳本刪除堆分配?
Java開發者必須了解的堆外內存技術

貿澤開售面向安全應用的英飛凌OPTIGA Trust M物聯網安全開發套件

評論