在 MSVC 做相对比较麻烦,因为一不小心就会用到 cpp 自带的实现(引用宿主程序地址)。
勉强改了个能用的版本,顺便在纯 MSVC 版本实现了个比较基础的跨进程交互(IPC)。
#include <Windows.h>
#include <cstdint>
#include <string>
#include <vector>
#include <cassert>
// true: 使用新的 msvc payload
// false: 使用之前 fasm 编译的 payload
constexpr bool kUseNewPayload = true;
// 使用 FASM 编译前文的汇编代码
unsigned char msgbox_shell_bin[] = {
0xeb, 0x3e, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x41, 0x00,
0x55, 0x73, 0x65, 0x72, 0x33, 0x32, 0x2e, 0x64, 0x6c, 0x6c, 0x00, 0x53,
0x65, 0x72, 0x69, 0x61, 0x6c, 0x00, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
0x90, 0x90, 0x90, 0x90, 0x6a, 0x40, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x58,
0x83, 0xe8, 0x18, 0x50, 0xff, 0x75, 0xf4, 0x6a, 0x00, 0xe8, 0x00, 0x00,
0x00, 0x00, 0x58, 0x83, 0xe8, 0x3e, 0x50, 0xe8, 0x00, 0x00, 0x00, 0x00,
0x58, 0x83, 0xe8, 0x3c, 0x50, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x58, 0x83,
0xe8, 0x5a, 0xff, 0x10, 0x50, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x58, 0x83,
0xe8, 0x62, 0xff, 0x10, 0xff, 0xd0, 0x8b, 0x55, 0xe8, 0x8b, 0x45, 0xf4,
0xff, 0x34, 0x24, 0x81, 0x2c, 0x24, 0xcc, 0xe8, 0x01, 0x00, 0xc3
};
unsigned int msgbox_shell_bin_len = 143;
typedef decltype(&LoadLibraryA) tLoadLibraryA;
typedef decltype(&GetProcAddress) tGetProcAddress;
typedef decltype(&GetModuleHandleA) tGetModuleHandleA;
typedef decltype(&MessageBoxA) tMessageBoxA;
typedef decltype(&OpenProcess) tOpenProcess;
typedef decltype(&VirtualAllocEx) tVirtualAllocEx;
typedef decltype(&calloc) tcalloc;
typedef decltype(&WriteProcessMemory) tWriteProcessMemory;
typedef decltype(&free) tfree;
typedef decltype(&CreateRemoteThread) tCreateRemoteThread;
typedef decltype(&WaitForSingleObject) tWaitForSingleObject;
typedef decltype(&VirtualFreeEx) tVirtualFreeEx;
typedef decltype(&CloseHandle) tCloseHandle;
// 必须关闭优化,否则会向量化一些变量
#pragma optimize( "", off )
__declspec(safebuffers)
void* __cdecl injected_fn_start_inner(const char* user_serial, const char* real_serial);
__declspec(naked)
void* injected_fn_start() {
// 因为 naked 函数不能使用变量,所以要跳到下一个函数
__asm {
// 备份寄存器,顺便作为参数传给下一个函数
push eax
push edx
call injected_fn_start_inner
// 返回值其实是下一个要调用的地址
mov ecx, eax
// 还原现场
pop edx
pop eax
call ecx
ret
}
}
__declspec(safebuffers)
void* __cdecl injected_fn_start_inner(const char* user_serial, const char* real_serial) {
// ShellCode 参数
auto pLoadLibraryA = (tLoadLibraryA)0x12345678;
auto pGetProcAddress = (tGetProcAddress)0x22345678;
auto pGetModuleHandleA = (tGetModuleHandleA)0x32345678;
auto pHostProcessId = (DWORD)0x42345678;
auto pHostProcessHandler = (DWORD)0x52345678;
char szUser32Dll[] = { 'U', 's', 'e', 'r', '3', '2', '.', 'd', 'l', 'l', 0 };
char szMessageBoxA[] = { 'M', 'e', 's', 's', 'a', 'g', 'e', 'B', 'o', 'x', 'A', 0 };
char szKernel32Dll[] = { 'K', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0 };
char szGetModuleHandleA[] = { 'G', 'e', 't', 'M', 'o', 'd', 'u', 'l', 'e', 'H', 'a', 'n', 'd', 'l', 'e', 'A', 0 };
char szOpenProcess[] = { 'O', 'p', 'e', 'n', 'P', 'r', 'o', 'c', 'e', 's', 's', 0 };
char szVirtualAllocEx[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'A', 'l', 'l', 'o', 'c', 'E', 'x', 0 };
char szWriteProcessMemory[] = { 'W', 'r', 'i', 't', 'e', 'P', 'r', 'o', 'c', 'e', 's', 's', 'M', 'e', 'm', 'o', 'r', 'y', 0 };
char szCreateRemoteThread[] = { 'C', 'r', 'e', 'a', 't', 'e', 'R', 'e', 'm', 'o', 't', 'e', 'T', 'h', 'r', 'e', 'a', 'd', 0 };
char szWaitForSingleObject[] = { 'W', 'a', 'i', 't', 'F', 'o', 'r', 'S', 'i', 'n', 'g', 'l', 'e', 'O', 'b', 'j', 'e', 'c', 't', 0 };
char szVirtualFreeEx[] = { 'V', 'i', 'r', 't', 'u', 'a', 'l', 'F', 'r', 'e', 'e', 'E', 'x', 0 };
char szCloseHandle[] = { 'C', 'l', 'o', 's', 'e', 'H', 'a', 'n', 'd', 'l', 'e', 0 };
char szMsvcrtDll[] = { 'm', 's', 'v', 'c', 'r', 't', '.', 'd', 'l', 'l', 0 };
char szcalloc[] = { 'c', 'a', 'l', 'l', 'o', 'c', 0 };
char szfree[] = { 'f', 'r', 'e', 'e', 0 };
char szTitleSerial[] = { 'S', 'e', 'r', 'i', 'a', 'l', ' ', '(', 'F', 'r', 'o', 'm', ' ', 'C', 'r', 'a', 'c', 'k', 'm', 'e', ')', 0 };
DWORD unused{};
// 定义几个方便用的宏
auto hKernel32 = pLoadLibraryA(szKernel32Dll);
auto hMsvcrt = pLoadLibraryA(szMsvcrtDll);
#define k32(name) t##name(pGetProcAddress(hKernel32, sz##name))
#define vcrt(name) t##name(pGetProcAddress(hMsvcrt, sz##name))
// 实现几个简单的文本操作函数
auto strlen_inline = [](const char* src) {
int result = 0;
while (*src++) {
result++;
}
return result;
};
auto strcpy_inline = [](char* dst, const char* src) {
while(*src) {
*dst++ = *src++;
}
*dst = 0;
};
// 传递信息到宿主程序
// 这是一个简单粗暴的 IPC 实现,效率不一定高
auto hProcess = k32(OpenProcess)(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pHostProcessId);
if (hProcess != INVALID_HANDLE_VALUE) {
// payload 格式
// [command_id] [dword_sizeof_data] [data]
// cmd_01 [sizeof("serial")] "serial"
auto payload_data_size = strlen_inline(real_serial) + 1;
auto payload_size = 1 + sizeof(uint32_t) + payload_data_size;
auto p_payload = (char*)vcrt(calloc)(payload_size, sizeof(char));
if (p_payload != nullptr) {
// 填充载荷
p_payload[0] = 0x01;
*(uint32_t*)&p_payload[1] = payload_data_size;
strcpy_inline(&p_payload[5], real_serial);
// 拷贝载荷到宿主程序
auto p_remote_data = k32(VirtualAllocEx)(hProcess, nullptr, payload_size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (p_remote_data != nullptr) {
k32(WriteProcessMemory)(hProcess, p_remote_data, p_payload, payload_size, &unused);
// 建立新线程通知,并等待结束
DWORD thread_id{};
auto hThread = k32(CreateRemoteThread)(hProcess, nullptr, 0, LPTHREAD_START_ROUTINE(pHostProcessHandler), p_remote_data, 0, &unused);
if (hThread != nullptr) {
// 等待运行结束
k32(WaitForSingleObject)(hThread, INFINITE);
k32(CloseHandle)(hThread);
// 此处使用 "GetExitCodeThread" 还可以获取返回值,与宿主程序进一步交互
}
k32(VirtualFreeEx)(hProcess, p_remote_data, 0, MEM_RELEASE);
}
vcrt(free)(p_payload);
}
k32(CloseHandle)(hProcess);
}
// 弹出信息框
auto hUser32 = pLoadLibraryA(szUser32Dll);
tMessageBoxA(pGetProcAddress(hUser32, szMessageBoxA))(nullptr, real_serial, szTitleSerial, MB_ICONINFORMATION);
// 找回原始的函数
auto pImageBase = (uint8_t*)k32(GetModuleHandleA)(nullptr);
return pImageBase + 0x00403474 - 0x00400000;
#undef k32
#undef vcrt
}
void injected_fn_end() {
// do nothing
}
#pragma optimize( "", on )
std::wstring A2W(const char* text) {
std::wstring result;
if (text && *text) {
auto new_size = 1 + MultiByteToWideChar(CP_ACP, 0, text, -1, NULL, 0);
result.resize(new_size + 1);
auto chr_written = MultiByteToWideChar(CP_ACP, 0, text, -1, &result.at(0), new_size);
result.resize(chr_written);
}
return result;
}
void hexdump(const void* data, size_t size) {
char ascii[17];
size_t i, j;
ascii[16] = '\0';
for (i = 0; i < size; ++i) {
printf("%02X ", ((unsigned char*)data)[i]);
if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') {
ascii[i % 16] = ((unsigned char*)data)[i];
}
else {
ascii[i % 16] = '.';
}
if ((i + 1) % 8 == 0 || i + 1 == size) {
printf(" ");
if ((i + 1) % 16 == 0) {
printf("| %s \n", ascii);
}
else if (i + 1 == size) {
ascii[(i + 1) % 16] = '\0';
if ((i + 1) % 16 <= 8) {
printf(" ");
}
for (j = (i + 1) % 16; j < 16; ++j) {
printf(" ");
}
printf("| %s \n", ascii);
}
}
}
}
void __stdcall thread_callback(uint8_t* payload) {
printf("recv payload: %p\n", payload);
if (payload == nullptr) {
return;
}
auto cmd_id = payload[0];
uint32_t data_len = *(uint32_t*)&payload[1];
auto p_data = &payload[5];
printf("cmd_id(%02x) data_len(0x%04x)\n", cmd_id, data_len);
hexdump(p_data, data_len);
switch (cmd_id) {
case 0x01: {
// 处理 01 指令
std::wstring serial;
serial += L"序列号是: ";
serial += A2W((const char*)p_data);
MessageBoxW(nullptr, serial.c_str(), L"序列号信息 (来自宿主程序)", MB_ICONINFORMATION);
break;
}
}
}
std::vector<uint8_t> get_shellcode_payload() {
assert(("调试模式无法正常使用,你需要切换至编译模式运行", !_DEBUG));
auto p_begin = (uint8_t*)injected_fn_start;
auto p_end = (uint8_t*)injected_fn_end;
std::vector<uint8_t> data(p_begin, p_end);
auto find_and_replace_u32 = [&](uint32_t search, uint32_t replacement) {
for (size_t i = 0; i < data.size() - sizeof(uint32_t); i++) {
if (*(uint32_t*)&data.at(i) == search) {
*(uint32_t*)&data.at(i) = replacement;
}
}
};
find_and_replace_u32(0x12345678, (uint32_t)LoadLibraryA);
find_and_replace_u32(0x22345678, (uint32_t)GetProcAddress);
find_and_replace_u32(0x32345678, (uint32_t)GetModuleHandleA);
find_and_replace_u32(0x42345678, (uint32_t)GetProcessId(GetCurrentProcess()));
find_and_replace_u32(0x52345678, (uint32_t)(thread_callback));
return data;
}
int main()
{
wchar_t szProcessPath[] = L"Dope2112.1.exe";
STARTUPINFOW si{};
PROCESS_INFORMATION pi{};
if (!CreateProcessW(nullptr, szProcessPath, nullptr, nullptr, FALSE, CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
MessageBoxW(nullptr, L"建立进程失败", L"失败", MB_ICONERROR);
return 1;
}
std::vector<uint8_t> payload;
if (kUseNewPayload) {
payload = get_shellcode_payload();
}
else {
payload.assign(&msgbox_shell_bin[0], &msgbox_shell_bin[msgbox_shell_bin_len]);
}
DWORD unused{};
auto p_shellcode = reinterpret_cast<uint8_t*>(VirtualAllocEx(pi.hProcess, nullptr, payload.size(), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE));
if (p_shellcode == nullptr) {
TerminateProcess(pi.hProcess, 1);
MessageBoxW(nullptr, L"开辟空间失败", L"失败", MB_ICONERROR);
return 1;
}
// 写出 ShellCode
WriteProcessMemory(pi.hProcess, p_shellcode, &payload.front(), payload.size(), &unused);
if (!kUseNewPayload) {
void* ptr_temp;
WriteProcessMemory(pi.hProcess, p_shellcode + 0x10, &(ptr_temp = &LoadLibraryA), sizeof(void*), &unused);
WriteProcessMemory(pi.hProcess, p_shellcode + 0x14, &(ptr_temp = &GetProcAddress), sizeof(void*), &unused);
}
// 读取进程基地址
uint8_t* module_base_addr{};
CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_INTEGER;
GetThreadContext(pi.hThread, &context);
ReadProcessMemory(pi.hProcess, (void*)(context.Ebx + 8), &module_base_addr, sizeof(PVOID), NULL);
// 写新的跳转
auto hook_loc = module_base_addr + 0x00421D3B - 0x00400000;
uint8_t inst_call_shellcode[5] = { 0xE8 };
*reinterpret_cast<uint32_t*>(&inst_call_shellcode[1]) = p_shellcode - (hook_loc + 5);
WriteProcessMemory(pi.hProcess, hook_loc, inst_call_shellcode, sizeof(inst_call_shellcode), &unused);
// 将 ShellCode 区域的内存改为可执行
VirtualProtectEx(pi.hProcess, p_shellcode, payload.size(), PAGE_EXECUTE_READ, &unused);
// 继续执行
ResumeThread(pi.hThread);
// 等待进程结束,然后清理
WaitForSingleObject(pi.hProcess, INFINITE);
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
编译好的示例(解压密码 52pojie
):
https://pan.baidu.com/s/1FZClTK1f8z4gBpW8_zIKWQ?pwd=g81d