创建傀儡进程时,一般需要以挂起模式创建进程,然后调用未导出NT函数NtQueryInformationProcess
来获取目标进程的PEB地址,拿到PEB地址后,再通过ReadProcessMemory
获取目标进程的PEB数据,最后NtUnMapViewOfSection,传入PEB结构中的ImageBaseAddress来卸载目标进程的内存。
其实可以直接通过GetThreadContext
这个函数来直接获取PEB地址,获取的CONTEXT.Ebx
寄存器中保存的就是PEB地址。
typedef struct __PEB {
BYTE InheritedAddressSpace;
BYTE ReadImageFileExecOptions;
BYTE BeingDebugged;
BYTE SpareBool;
void* Mutant;
void* ImageBaseAddress; // offset 0x08
_PEB_LDR_DATA* Ldr;
/*....*/
}MYPEB, *PMYPEB;
得知PEB结构偏移0x8的位置就是进程加载基址,在获取线程上下文后,直接用ebx的内容+8,再通过ReadProcessMemory
读取4个字节,就可以得到被挂起进程的基址了。
测试代码:
// 获取线程上下文
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_ALL;
if (!GetThreadContext(pi.hThread, &ctx))
{
printf("GetThreadContext failed (%d).\n", GetLastError());
}
// 拿到目标进程主线程上下文后,在Ebx寄存器中保存的就是PEB的地址,
// 而PEB结构偏移0x8的位置是AddressOfImageBase字段,
// 所以直接来读取ctx.Ebx+0x8,就可以获取到目标进程的加载基址
DWORD dwImageBase = 0;
DWORD lpNumberOfBytesRead = 0;
if (!ReadProcessMemory(pi.hProcess, (LPCVOID)(ctx.Ebx + 0x8), &dwImageBase, sizeof(DWORD), &lpNumberOfBytesRead))
{
printf("ReadProcessMemory failed (%d).\n", GetLastError());
return;
}