0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

安全開發(fā)之堆分配內(nèi)存加密簡析

蛇矛實(shí)驗(yàn)室 ? 來源:蛇矛實(shí)驗(yàn)室 ? 2023-03-08 09:43 ? 次閱讀

前言

在安全研發(fā)的過程中,難免會(huì)使用內(nèi)存分配函數(shù) malloc、重載的運(yùn)算符 new 開啟堆內(nèi)存用于長時(shí)間駐留一些數(shù)據(jù),但這些數(shù)據(jù)可能對于防御者來說比較敏感,比如有時(shí)候,這些堆內(nèi)存中可能會(huì)出現(xiàn)回連地址等。所以有必要對這些保留在堆內(nèi)存中的敏感數(shù)據(jù)進(jìn)行加密。

在進(jìn)入堆分配內(nèi)存加密之前,首先了解以下程序中堆棧的概念;

堆和棧是程序運(yùn)行時(shí)保存數(shù)據(jù)的方式。

棧:通常來說棧用于存儲(chǔ)臨時(shí)數(shù)據(jù),比如局部變量和函數(shù)調(diào)用的上下文信息,棧上的數(shù)據(jù)的生命周期隨著函數(shù)的調(diào)用和返回而自動(dòng)管理,一般在作用域結(jié)束后被自動(dòng)釋放;

堆:堆通常用于存儲(chǔ)想長期駐留在內(nèi)存中的數(shù)據(jù),比如一些需要長期存在的配置信息和一些需要共享的數(shù)據(jù),堆上的數(shù)據(jù)的生命周期不受作用域的限制,一般在整個(gè)程序運(yùn)行期間都存在,直到顯示地釋放它們。

由于堆上的數(shù)據(jù)的生命周期不受限制,除非顯式地釋放它們,否則它們將一直駐留在內(nèi)存中,如果我們想保護(hù)這些數(shù)據(jù),可以在內(nèi)存中將存儲(chǔ)這些數(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ā)的過程中,為了降低自動(dòng)化分析對代碼的敏感度,通常會(huì)使用延遲代碼的執(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)存時(shí),再比如使用 CRT 函數(shù) malloc 和 重載的運(yùn)算符 new 開辟堆空間時(shí)(其最終也是通過HeapAlloc函數(shù)分配),都可通過堆分配內(nèi)存加密保護(hù)我們的數(shù)據(jù)。

c460339a-bcca-11ed-bfe3-dac502259ad0.png

下圖是在掛鉤了 Sleep 后,程序進(jìn)入HookedSleep 函數(shù)未進(jìn)行對加密之前我們在程序中分配的堆空間的原始數(shù)據(jù)。

c4790410-bcca-11ed-bfe3-dac502259ad0.png

在進(jìn)行堆加密后,效果如下,可以看到,我們堆分配的內(nèi)存已被加密。

c49c4e66-bcca-11ed-bfe3-dac502259ad0.png

目標(biāo)線程堆分配空間加密

上文可以看到通過 HeapWalk 遍歷了進(jìn)程中所有的堆信息,并在堆加密之前比如掛起所有的線程,這樣有一個(gè)缺點(diǎn),就是只能針對自己寫的程序。

有時(shí)候我們的需要執(zhí)行的功能代碼是通過注入到其它進(jìn)程實(shí)現(xiàn)的,如果這樣冒然的將所有線程掛起,這有太多的不可預(yù)料性,可能會(huì)導(dǎo)致宿主程序崩潰。

所以需要有一種方式來實(shí)現(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)存信息時(shí),就可以通過在 HookedSleep 函數(shù)中對填充好堆分配信息數(shù)組進(jìn)行操作了。





審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • crt
    crt
    +關(guān)注

    關(guān)注

    2

    文章

    80

    瀏覽量

    35776
  • 加解密
    +關(guān)注

    關(guān)注

    0

    文章

    17

    瀏覽量

    6500

原文標(biāo)題:安全開發(fā)之堆分配內(nèi)存加密

文章出處:【微信號:蛇矛實(shí)驗(yàn)室,微信公眾號:蛇矛實(shí)驗(yàn)室】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    Linux應(yīng)用開發(fā)內(nèi)存分配

    是長度可變的連續(xù)虛擬內(nèi)存,始于進(jìn)程未初始化數(shù)據(jù)段的末尾,將當(dāng)前的內(nèi)存邊界稱為 "program break"。
    發(fā)表于 05-08 11:17 ?494次閱讀
    Linux應(yīng)用<b class='flag-5'>開發(fā)</b>之<b class='flag-5'>內(nèi)存</b><b class='flag-5'>分配</b>

    【原創(chuàng)】內(nèi)存的那些事

    作者:蔡琰老師(張飛實(shí)戰(zhàn)電子高級工程師)上一篇我們分享了棧內(nèi)存的概念,現(xiàn)在我們分享下內(nèi)存的概念。在一般的編譯系統(tǒng)中,內(nèi)存
    發(fā)表于 07-12 09:48

    freertos與STM32如何分配堆??臻g

    freertos與STM32分棧、、全局區(qū)、常量區(qū)、代碼區(qū)、RAM、ROM,及如何分配堆棧空間基于STM32分棧、、全局區(qū)、常量區(qū)、
    發(fā)表于 08-03 06:36

    請問使用動(dòng)態(tài)內(nèi)存分配安全嗎?

    在使用完畢后,需要顯式的釋放,這就要求程序員對動(dòng)態(tài)分配內(nèi)存了然于胸。在非常重視安全(safety-critical)的嵌入式C語言程序開發(fā)
    發(fā)表于 12-15 06:10

    使用動(dòng)態(tài)內(nèi)存分配安全

    安全嗎?”為了更加安全穩(wěn)定,美國軍方禁止在C語言程序中使用malloc()使用動(dòng)態(tài)內(nèi)存分配安全嗎?在C語言程序
    發(fā)表于 12-15 07:44

    關(guān)于RT-Thread內(nèi)存管理的內(nèi)存

    這篇文章繼續(xù)介紹 RT-Thread 內(nèi)存管理剩下的部分——內(nèi)存池。為何引入內(nèi)存池?內(nèi)存雖然方便靈活,但是存在明顯的缺點(diǎn):
    發(fā)表于 04-06 17:02

    關(guān)于RT-Thread的動(dòng)態(tài)內(nèi)存管理

    管理算法只能啟用一個(gè),但是提供給用戶的接口完全相同。注意事項(xiàng):內(nèi)存管理為了滿足多線程場景下的安全分配,考慮多線程間的互斥問題。因此,不要
    發(fā)表于 04-06 17:11

    有關(guān)RT-Thread操作系統(tǒng)的內(nèi)存管理模塊基本知識(shí)

    塊大小將不能再做調(diào)整?! ?dòng)態(tài)內(nèi)存管理  動(dòng)態(tài)內(nèi)存管理是一個(gè)真實(shí)的(Heap)內(nèi)存管理模塊。動(dòng)態(tài)內(nèi)存管理,即在
    發(fā)表于 05-11 15:14

    Armv8.1-M PAC和BTI擴(kuò)展

    1、Armv8.1-M PAC和 BTI 擴(kuò)展Armv8-M通過Trustzone for Armv8-M, Memory Protection Unit (MPU) 和Privileged
    發(fā)表于 08-05 14:56

    如何使用鏈接腳本刪除分配?

    的 heap_symbol),這對我來說會(huì)更好,因?yàn)榉駝t這個(gè)符號會(huì)得到它的地址,并且可以使用它的地址覆蓋內(nèi)存。我的問題是,當(dāng)我只是丟棄它時(shí),我得到了期望此引用的 gcc 文件 (_cr_sbrk.c) 的引用錯(cuò)誤。我想尋求幫助來實(shí)現(xiàn)我的目標(biāo),即安全地從鏈接器中刪除
    發(fā)表于 03-23 07:05

    鼠標(biāo)HID例程(中)

    鼠標(biāo) HID 例程 緊接《鼠標(biāo) HID 例程(上)》一文,繼續(xù)向大家介紹鼠 標(biāo) HID 例程的未完的內(nèi)容。
    發(fā)表于 07-26 15:18 ?0次下載

    Java開發(fā)者必須了解的內(nèi)存技術(shù)

    先來看一個(gè) Demo:在 Demo 中分配內(nèi)存用的是 allocateDirect 方法,但其內(nèi)部調(diào)用的是 DirectByteBuffer,換言之,DirectByteBuffer 才是實(shí)際操作
    發(fā)表于 07-01 10:19 ?3733次閱讀
    Java<b class='flag-5'>開發(fā)</b>者必須了解的<b class='flag-5'>堆</b>外<b class='flag-5'>內(nèi)存</b>技術(shù)

    什么是內(nèi)存內(nèi)存是如何分配的?

    在一般的編譯系統(tǒng)中,內(nèi)存分配方向和棧內(nèi)存是相反的。當(dāng)棧內(nèi)存從高地址向低地址增長的時(shí)候,
    的頭像 發(fā)表于 07-05 17:58 ?9794次閱讀

    貿(mào)澤開售面向安全應(yīng)用的英飛凌OPTIGA Trust M物聯(lián)網(wǎng)安全開發(fā)套件

    2023 年 5 月 11 日 – 專注于引入新品的全球半導(dǎo)體和電子元器件授權(quán)代理商貿(mào)澤電子 (Mouser Electronics) 即日起供貨英飛凌的OPTIGA? Trust M物聯(lián)網(wǎng)安全開發(fā)
    發(fā)表于 05-12 17:05 ?562次閱讀
     貿(mào)澤開售面向<b class='flag-5'>安全</b>應(yīng)用的英飛凌OPTIGA Trust M物聯(lián)網(wǎng)<b class='flag-5'>安全開發(fā)</b>套件

    什么是內(nèi)存?存儲(chǔ)方式是什么樣的?

    只有在內(nèi)存里面才會(huì)發(fā)生內(nèi)存泄漏的問題,在棧內(nèi)存中不會(huì)發(fā)生內(nèi)存泄漏。因?yàn)闂?b class='flag-5'>內(nèi)存在自動(dòng)
    的頭像 發(fā)表于 06-22 10:29 ?1037次閱讀
    什么是<b class='flag-5'>堆</b><b class='flag-5'>內(nèi)存</b>?存儲(chǔ)方式是什么樣的?