我对 shellcode 执行做了一些实验,其中我编写了自己的 shellcode,将其写入我希望执行它的目标程序的内存中,并使用新线程或线程劫持来执行它。
这很有效,但是手动编写 shellcode 相当耗时,因此我正在寻找一种能够用 C 或 C++ 编写函数的方法,该函数在编译后将完全独立。这意味着任何编译的函数都应该可以独立执行。这样我就可以直接将其写入目标程序,准备使用 WriteProcessMemory 执行。因此,推送 shellcode 将使用如下代码来完成:
#include <Windows.h>
#include <iostream>
using namespace std;
BOOL MakeABeep() {
return Beep(0x500, 0x500);
}
DWORD MakeABeepEnd() { return 0; }
int main() {
DWORD pid = 0;
cout << "PID: ";
cin >> dec >> pid;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProcess) { cout << "OpenProcess failed GLE = " << dec << GetLastError() << endl; return EXIT_FAILURE; }
void* buf = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!buf) { cout << "VirtualAllocEx failed GLE = " << dec << GetLastError() << endl; return EXIT_FAILURE; }
SIZE_T size = (DWORD64)MakeABeep - (DWORD64)MakeABeepEnd;
BOOL wpmStatus = WriteProcessMemory(hProcess, buf, MakeABeep, size, NULL);
if (!wpmStatus) { cout << "WriteProcessMemory failed GLE = " << dec << GetLastError() << endl; return EXIT_FAILURE; }
HANDLE hThread = CreateRemoteThread(hProcess, NULL, NULL, (LPTHREAD_START_ROUTINE)buf, NULL, NULL, NULL);
if (!hThread) { cout << "CreateRemoteThread failed GLE = " << dec << GetLastError() << endl; return EXIT_FAILURE; }
WaitForSingleObject(hThread, INFINITE);
VirtualFreeEx(hProcess, buf, 0, MEM_RELEASE);
return EXIT_SUCCESS;
}
如果用MSVC编译器的默认选项编译,只会复制一堆jmp
指令,看起来像是一个跳转表。为了避免这个问题,我在编译器选项中禁用了增量链接,现在函数 MakeABeep
中的所有代码都被正确复制,但对导入函数的调用除外。
在我的 shellcode 中,我按照调用约定传递参数,然后将要调用的函数的地址放入寄存器 rax
中,最后使用 调用该函数调用rax
。
是否有可能让编译器生成类似的东西? 关键是生成的二进制文件必须具有可以独立执行的自包含子例程。
例如,这是为函数 MakeABeep 生成的汇编代码:
为了能够直接运行它,编译器应该将 Beep 函数的完整地址移动到 rax 中,而不是 mov rax, QWORD PTR [rip+0x?]
。
请忽略与目标程序中可能未加载或加载到不同地址的模块相关的问题,我只想调用 kernel32 和 ntdll 中的函数,这些函数肯定已加载并且在不同进程中位于相同的地址。
感谢您的帮助。
最佳答案
编译器不知道 Beep 函数的完整地址。 Beep 函数位于 kernel32.dll 中,该 DLL 被标记为 ASLR兼容,理论上可以在每次运行程序时更改其地址。没有编译器功能可以让您生成 .DLL 中函数的真实地址,因为这样的功能非常无用。
我能想到的一个选择是使用 magic cookie,在运行时将其替换为正确的函数地址:
SIZE_T beepaddr = 0xff77ffffffff7001ull; // Magic value
((BOOL(WINAPI*)(DWORD,DWORD))beepaddr)(0x500, 0x500); // Call Beep()
编译为
00011 b9 00 05 00 00 mov ecx, 1280 ; 00000500H
00016 48 b8 01 70 ff ff ff ff 77 ff mov rax, -38280596832686079 ; ff77ffffffff7001H
00020 8b d1 mov edx, ecx
00022 ff d0 call rax
然后,您必须围绕 WriteProcessMemory
编写一个包装器,它知道如何查找这些神奇值并将其替换为正确的地址。
某些 shell 代码将有自己的 GetModuleHandle
和 GetProcAddress
迷你实现,它在 PEB 模块列表中查找模块,然后搜索导出目录。他们经常对名称使用迷你哈希函数,这样他们就不必处理字符串。
如果您要注入(inject)大量代码,您可能会厌倦这些黑客行为,并像其他人一样在远程进程中加载 .DLL。
关于c++ - 通过从 C/C++ 调用 Windows API 函数来生成独立的汇编子例程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49879368/