写在前面
也做了一段时间的Windows逆向,编写了包含一系列API的WeChat机器人,将dll注入目标进程后,调用就成了问题,要调用,就要知道api函数的地址。
想要搞到地址,就要使用GetProcAddress这个函数,可是这个函数需要两个参数,CreateRemoteThread
只能传递一个参数,怎么办呢?这里提供两种解决方案。
获取RVA
这种方案比较简单,主要思路就是把dll加载到自身进程,再通过函数地址 - 模块句柄
的方式获取到目标函数的RVA。
然后使用GetModuleHandle
拿到远程进程中的模块句柄,加上RVA就得到了函数地址。
这种办法自然也有局限性,比如64位进程无法加载32位的dll,自然也获取不到RVA。
构造远程函数
是否可以构造一个,只接受一个参数,就能拿到目标函数地址的接口呢?先思考下用C++怎么写:
struct Param{
const wchar_t* dllname;
const char** func_name;
}
DWORD GetProcAddress(Param param){
HMODULE hd = GetModuleHandle(param.dllname);
DWORD func_addr = (DWORD)GetProcAddress(hd,param.func_name);
return func_addr;
}
转换成汇编呢?再来看一下:
_declspec(naked) DWORD GetProcAddress(LPVOID param)
{
__asm {
push ebp;
mov ebp, esp;
sub esp, 0x40;
push edi;
push ecx;
mov edi, dword ptr[ebp + 0x8];
mov eax,dword ptr[edi];
push eax;
call GetModuleHandleW;
add esp,0x4;
add edi,0x4;
mov ecx, dword ptr[edi];
push ecx;
push eax;
call GetProcAddress;
add esp, 0x8;
pop ecx;
pop edi;
mov esp, ebp;
pop ebp;
retn;
}
}
把汇编代码转换为机器码,申请一段可执行的内存,写进去后,就成功了:
static unsigned char GetProcAsmCode[] = {
0x55, // push ebp;
0x8B, 0xEC, // mov ebp, esp;
0x83, 0xEC, 0x40, // sub esp, 0x40;
0x57, // push edi;
0x51, // push ecx;
0x8B, 0x7D, 0x08, // mov edi, dword ptr[ebp + 0x8];
0x8B, 0x07, // mov eax,dword ptr[edi];
0x50, // push eax;
0xE8, 0x00, 0x00, 0x00, 0x00, // call GetModuleHandleW;
0x83, 0xC4, 0x04, // add esp,0x4;
0x83, 0xC7, 0x04, // add edi,0x4;
0x8B, 0x0F, // mov ecx, dword ptr[edi];
0x51, // push ecx;
0x50, // push eax;
0xE8, 0x00, 0x00, 0x00, 0x00, // call GetProcAddress;
0x83, 0xC4, 0x08, // add esp, 0x8;
0x59, // pop ecx;
0x5F, // pop edi;
0x8B, 0xE5, // mov esp, ebp;
0x5D, // pop ebp;
0xC3 // retn;
};
LPVOID GetAsmFunAddr()
{
// 这里仍然是位数相对应时的写法,如果是64位进程拿32位函数地址,要通过ntdll获取Windows API的地址,这块请自行探索
DWORD pGetModuleHandleW = (DWORD)GetModuleHandleW;
DWORD pGetProcAddress = (DWORD)GetProcAddress;
PVOID call1 = (PVOID)&GetProcAsmCode[15];
PVOID call2 = (PVOID)&GetProcAsmCode[30];
// 申请可执行内存
LPVOID pAsmFuncAddr = VirtualAllocEx(handle, NULL, 1, MEM_COMMIT, PAGE_EXECUTE);
if (!pAsmFuncAddr)
return 0;
// 计算目标函数与当前指令的距离
*(DWORD *)call1 = pGetModuleHandleW - (DWORD)pAsmFuncAddr - 14 - 5;
*(DWORD *)call2 = pGetProcAddress - (DWORD)pAsmFuncAddr - 29 - 5;
SIZE_T dwWriteSize;
WriteProcessMemory(handle, pAsmFuncAddr, GetProcAsmCode, sizeof(GetProcAsmCode), &dwWriteSize);
return pAsmFuncAddr;
}
最后再拿CreateRemoteThread
去调用GetAsmFunAddr
返回的函数地址就行啦,参数就是远程进程中的Param
结构体,这个也是要自己构造的。