在目标进程中构造函数


写在前面

也做了一段时间的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结构体,这个也是要自己构造的。