0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

安全开发之堆分配内存加密简析

蛇矛实验室 来源:蛇矛实验室 2023-03-08 09:43 次阅读

前言

在安全研发的过程中,难免会使用内存分配函数 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函数分配),都可通过堆分配内存加密保护我们的数据。

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

下图是在挂钩了 Sleep 后,程序进入HookedSleep 函数未进行对加密之前我们在程序中分配的堆空间的原始数据。

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

在进行堆加密后,效果如下,可以看到,我们堆分配的内存已被加密。

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

目标线程堆分配空间加密

上文可以看到通过 HeapWalk 遍历了进程中所有的堆信息,并在堆加密之前比如挂起所有的线程,这样有一个缺点,就是只能针对自己写的程序。

有时候我们的需要执行的功能代码是通过注入到其它进程实现的,如果这样冒然的将所有线程挂起,这有太多的不可预料性,可能会导致宿主程序崩溃。

所以需要有一种方式来实现只加密我们执行功能线程代码中分配的堆内存。

由于通过注入 shellcode/dll 的方式执行功能,所以首要目标是获取执行功能的线程ID,之后就是在代码中 Hook 堆分配/释放相关的函数,RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap 函数,这样就可以跟踪线程代码中堆分配释放的状态了。

获取了线程堆分配释放的情况,目标线程堆分配内存加密的代码思路如下:

获取当前执行线程ID

Hook RtlAllocateHeap,RtlReAllocateHeap,RtlFreeHeap

在 HookedRtlAllocateHeap、HookedRtlReAllocateHeap 函数内部

执行原始 OldRtlAllocateHeap、OldRtlReAllocateHeap,并记录堆分配的地址和大小

筛选出我们的线程分配的堆内存:将堆分配信息添加进维护的堆分配信息数组中

HookedRtlFreeHeap 函数内部

得到需要释放的地址

执行原始 OldRtlFreeHeap

筛选出我们的线程释放的堆内存:将其移除维护的堆分配信息信息数组

当筛选出目标线程堆分配的内存信息时,就可以通过在 HookedSleep 函数中对填充好堆分配信息数组进行操作了。





审核编辑:刘清

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • crt
    crt
    +关注

    关注

    2

    文章

    80

    浏览量

    35674
  • 加解密
    +关注

    关注

    0

    文章

    16

    浏览量

    6476

原文标题:安全开发之堆分配内存加密

文章出处:【微信号:蛇矛实验室,微信公众号:蛇矛实验室】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Linux内存系统: Linux 内存分配算法

    内存管理算法:对讨厌自己管理内存的人来说是天赐的礼物。1、内存碎片1) 基本原理· 产生原因:内存分配较小,并且
    发表于 08-24 07:44

    【原创】内存的那些事

    作者:蔡琰老师(张飞实战电子高级工程师)上一篇我们分享了栈内存的概念,现在我们分享下内存的概念。在一般的编译系统中,内存
    发表于 07-12 09:48

    freertos与STM32如何分配堆栈空间

    freertos与STM32分栈、、全局区、常量区、代码区、RAM、ROM,及如何分配堆栈空间基于STM32分栈、、全局区、常量区、
    发表于 08-03 06:36

    请问使用动态内存分配安全吗?

    在使用完毕后,需要显式的释放,这就要求程序员对动态分配内存了然于胸。在非常重视安全(safety-critical)的嵌入式C语言程序开发
    发表于 12-15 06:10

    使用动态内存分配安全

    安全吗?”为了更加安全稳定,美国军方禁止在C语言程序中使用malloc()使用动态内存分配安全吗?在C语言程序
    发表于 12-15 07:44

    STM32内存结构介绍和FreeRTOS内存分配技巧

    。文章最后要解决的问题是,如何恰当地分配FreeRTOS中的、任务栈的空间。但是在概念的理解上,也需要知道STM32内存的相关知识。所以首先大致介绍一下STM32的内存结构。STM3
    发表于 02-14 07:38

    关于RT-Thread内存管理的内存

    这篇文章继续介绍 RT-Thread 内存管理剩下的部分——内存池。为何引入内存池?内存虽然方便灵活,但是存在明显的缺点:
    发表于 04-06 17:02

    关于RT-Thread的动态内存管理

    管理算法只能启用一个,但是提供给用户的接口完全相同。注意事项:内存管理为了满足多线程场景下的安全分配,考虑多线程间的互斥问题。因此,不要
    发表于 04-06 17:11

    有关RT-Thread操作系统的内存管理模块基本知识

    块大小将不能再做调整。  动态内存管理  动态内存管理是一个真实的(Heap)内存管理模块。动态内存管理,即在
    发表于 05-11 15:14

    Armv8.1-M PAC和BTI扩展

    1、Armv8.1-M PAC和 BTI 扩展Armv8-M通过Trustzone for Armv8-M, Memory Protection Unit (MPU) 和Privileged
    发表于 08-05 14:56

    陷阱:中断中分配内存

    版本中,你的内存管理不是从中断上下文(ISR)中调用的。这样就不能避免关键问题,而是可以在开发过程中发现,而不会进入最终产品。更简单的一种办法是确保内存分配函数是中断
    发表于 11-23 10:58

    用rt_memheap_init分配内存初始化失败是何原因?怎么解决?

    用rt_memheap_init分配内存,一到初始化任务分配内存的时候就会出现问题请教一下是什么原因导致这种情况
    发表于 02-13 14:18

    如何使用链接脚本删除分配

    的 heap_symbol),这对我来说会更好,因为否则这个符号会得到它的地址,并且可以使用它的地址覆盖内存。我的问题是,当我只是丢弃它时,我得到了期望此引用的 gcc 文件 (_cr_sbrk.c) 的引用错误。我想寻求帮助来实现我的目标,即安全地从链接器中删除
    发表于 03-23 07:05

    电子功能安全开发及汽车EPS电机控制设计

    实现认证并开始你的功能安全开发
    的头像 发表于 08-14 00:15 4657次阅读

    贸泽开售面向安全应用的英飞凌OPTIGA Trust M物联网安全开发套件

    2023 年 5 月 11 日 – 专注于引入新品的全球半导体和电子元器件授权代理商贸泽电子 (Mouser Electronics) 即日起供货英飞凌的OPTIGA™ Trust M物联网安全开发
    发表于 05-12 17:05 474次阅读
     贸泽开售面向<b class='flag-5'>安全</b>应用的英飞凌OPTIGA Trust M物联网<b class='flag-5'>安全开发</b>套件