0%

堆加密技术抄写

堆加密技术抄写

逛街的时候看到了一个项目Caro-Kann,虽然这个项目中并没有使用堆加密,但是其后的一些OPSec improvement ideas中提到了堆加密,加上之前写geacon的时候也考虑过用堆加密,但是没有成功,所以这次仔细的看一下,简单学习一下

因为仔细研究后发现对于geacon来说堆加密技术的意义不是很大,所以并未进行部署,仅对网络文献的内容进行抄写复述加强记忆。并给我一个月未更新的博客水一篇文章。

堆加密意义

起因还是因为原生CS beacon中,关于C2Profile的内容是被存储在堆上的,导致针对C2Profile格式的内存扫描能够轻松的定位到被beacon感染的进程或是直接运行的CS beacon
The Anatomy of an APT Attack and CobaltStrike Beacon’s Encoded Configuration

为此,CS在高版本中推出了sleep mask功能,即在beacon sleep时对堆进行加密混淆,绕过内存扫描,在恢复运行前还原,防止进程崩溃。beacon每次运行的时间远短于sleep时间,内存扫描也就很难发现内存中的C2Profile特征,进而实现了绕过

然而geacon的C2Profile是通过config包定义了一堆变量export出去的,即使内容一致,其内存布局也会存在变化,起码原用于扫描beacon的规则是无法扫描geacon的。
又,不是很清楚go中export出去的常量是作为变量存储在代码段,还是作为全局变量存在bss段,还是由go自己分配在堆栈上呢?反正结论是在没有研究员检测出堆上内存特征前,geacon是不用做这类防护手段的。

其实我觉得恶意代码检测扫描代码段不是比扫描堆来的快许多?CS beacon的代码段应该已经固定的不能再固定了,扫描代码段不应该能更快更有效的检测原生beacon?不过扫描堆能够快速还原profile,可能更有利于溯源吧。但我个人认为扫描代码段定位的效率应该远大于堆。
还是说代码段扫描会存在迷之误报率问题?但是我感觉扫代码段特征应该和静态扫描特征相近,应该是很好检测的才对。

堆加密实现

前文提到,beacon的运行时间相较于sleep时间是非常短的,堆加密是在beacon停止运行的sleep期间防止内存扫描,所以只需要hook住原sleep函数,在调用原sleep前后进行加解密操作即可,伪代码如下

func HookedSleep(time int){
    SuspendThreads();
    heapEncrypt();
    sleep(time);
    heapDecrypt();
    ResumeThreads();
}

当然,在堆加密之前还需要把当前进程的所有线程都挂起来,不然仅仅是马线程挂起了,其他线程仍然会访问堆,然后堆是乱码就自动爆炸了。同样的,在挂起线程时,当前线程是不能挂起的,不然就全部挂起当场死锁了,当前线程需要进sleep挂起然后sleep结束恢复。

关于SuspendThread这个函数是否会直接挂起进程,这里有一篇不错的上古文章。What does SuspendThread really do?
(分析了半天看得头晕,最后直接给出了结论)

If the target thread is blocked in kernel mode, it may continue to do its kernel activity for a while. Before returning in user mode, the APC queue will be processed and thus the thread suspended.
If the target thread is running on a different processor, an IPI will interrupt it, and it becomes suspended.

SuspendThreads和TerminateThread都是往目标线程里面插APC,在APC中对线程实现挂起/终止。然后发起一个中断去触发APC,如果线程本身在用户态接受中断就立刻执行APC,如果在内核态,就可能忽略中断,等从内核态出来再处理APC

这篇文章中提到APC就是windows平台下的一种软件中断实现。
Windows APC机制

函数hook

这段是我想记录这个操作的主要原因,即跳板(Trampoline)形式hook函数的方法,函数hook有两种常见形式,一个是改IAT,另一个是搓汇编跳板。

IAT hook

直接去IAT里面找到目标函数对应的项,保留该地址为原函数地址后改为目标地址即可。该方法极其稳定,并且操作简单,唯一缺点是只能hook目标程序显式声明的import的函数,如果目标程序是用的loadLibrary+findProcAddr这种临时加载的方法,iat里面就找不到要hook的函数,无法完成hook。

跳板hook

跳板hook的好处是直接去目标函数的代码段魔改数据,能够hook任意的函数,不过实现起来更为复杂。

第一次遇到这种hook是在学杀软的用户层hook的时候,杀软通过将目标函数的前几个指令保存并覆写为long jump指令跳转到检测函数中,然后在检测函数末尾添加上被覆盖的原始指令再jump回原始函数实现对每个系统调用的hook和参数检测。当时只是认为是一个纯汇编操作,就等于使用jump指令控制了一下程序控制流,能够在原函数之前做一些其他操作。但现在的hook显然更加复杂,我们的hook函数需要在原函数的前后均添加操作,直接jump的方法就行不通了。

更加科学的实现方案也需要修改原函数的前几条指令为long jump跳转到hook函数上,然而,hook函数是一个独立的函数,而非汇编段。原函数同样被作为一个函数对待,而非long jump进行汇编层面的操作。通过生成一个跳板,内容为原函数被覆盖的指令和跳转回原函数剩余指令的long jump,使得其可以作为一个独立函数进行调用,实现完整的hook功能。

大概就是这么一个很丑陋的图。。。哪里有那种画风比较好看字体比较酷炫的画图网站。。。来点

image-20230918170149668

对原函数的调用通过long jump跳转到hook,而对于原函数的调用则转为对跳板的调用,因为走 的都是函数调用过程,return也能返回到hook函数的调用处,这样子就能更为自由的对hook函数进行发挥了。

如下是一个经典的跳板hook的实现以及其原理
MinHook - The Minimalistic x86/x64 API Hooking Library

跳板hook同样还需要对一些特殊情况进行考虑,比如被覆盖的指令是条件跳转,或者被覆盖的指令是相对地址跳转之类的,该库也一一进行了处理

堆加密

用heapWalk遍历所有堆,然后一个个加密就行,下面这篇文章详细的讲述了堆加密的实现流程
Hook Heaps and Live Free
LockdExeDemo

该文章还提出了更有意思的解决方案,常规beacon多被用于注入,若是注入beacon后对堆进行加密,需要挂起所有线程,会导致被注入的进程卡死。通过hook堆分配函数,在堆分配和销毁时进行记录即可做到在堆加密时只加密beacon分配的堆,这样子就不用挂起所有线程,可以保证beacon注入后原进程正常运行,且不会挂掉。