首先,InjectDLL函数:
bool InjectDLL(HANDLE hProcess, const char *dllPath, int dllPathLength)
{
LOG("Injecting DLL: %s\n", dllPath);
HMODULE hModule = GetModuleHandle(L"kernel32.dll");
LPVOID loadLibraryAddr = (LPVOID)GetProcAddress(hModule, "LoadLibraryA");
if (loadLibraryAddr == NULL)
{
LOG("Unable to locate LoadLibraryA\n");
return false;
}
LOG("LoadLibrary found at address: 0x%x\n", loadLibraryAddr);
LPVOID loadLibraryArg = (LPVOID)VirtualAllocEx(hProcess, NULL, dllPathLength, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
if (loadLibraryArg == NULL)
{
LOG("Could not allocate memory in process\n");
return false;
}
int n = WriteProcessMemory(hProcess, loadLibraryArg, dllPath, dllPathLength, NULL);
if (n == 0)
{
LOG("Could not write to process's address space\n");
return false;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)loadLibraryAddr, loadLibraryArg, NULL, NULL);
if (hThread == INVALID_HANDLE_VALUE)
{
LOG("Failed to create remote thread in process\n");
return false;
}
DWORD waitReturnValue = WaitForSingleObject(hThread, INFINITE);
if (waitReturnValue != WAIT_OBJECT_0) {
LOG("Failed to wait for LoadLibrary to exit\n");
return false;
}
DWORD loadLibraryReturnValue;
GetExitCodeThread(hThread, &loadLibraryReturnValue);
if (loadLibraryReturnValue == NULL) {
LOG("LoadLibrary failed to return module handle\n");
return false;
}
return true;
}
GetModuleHandle:获取进程已经加载的模块句柄。
GetProcAddress:取得kernel32.dll的基地址
VirtualAllocEx:在远程进程空间中分配存储dll文件路径名的内存空间
WriteProcessMemory:复制dll文件路径名到远程刚分配的进程空间
CreateRemoteThread:创建远程线程,参数loadLibraryAddr为LoadLibraryA的地址,参数loadLibraryArg为要加载的dll路径名,
WaitForSingleObject:等待线程返回
远程线程进行dll注入流程:
1.取得远程进程的ID号
2.在远程进程空间中分配一段内存,用来存放要注入的dll的完整路径
3.将要注入的dll的路径写到第2步分配的远程进程空间
4.从kernel32.dll中取得LoadLibrary的地址
5.调用CreateRemoteThread函数以从Kernel32.dll中取得LoadLibrary函数的地址,作为线程函数的地址;用需要注入的dll文件名作为参数,创建远程线程
(创建LoadLibraryA线程来启动dll,LoadLibraryA存在系统的kernel32.dll中用来加载dll模块,该函数参数为dll文件的名称,包括路径。由于加载dll操作是在其他进程中进行,因此需要把dll文件名称拷贝到目标进程中的地址空间中)。使用VirtualAllocEx函数为dll文件名在目标进程中分配地址空间,在使用WriteProcessMemory将dll的文件名拷贝到分配的地址中。下一步取得LoadLibraryA函数的入口地址。由于kenrnel32.dll模块是系统的核心模块,因此,该模块中的函数地址在所有进程中都是一样的,LoadLibrary类似于此。
最后,使用CreateRemoteThread函数,将Load LibraryA函数的入口地址以及dll文件名作为参数,创建线程以加载自定义的dll文件,在dll文件中的DLL_PROCESS_ATTACH条件下可写入需要注入的操作。
此处注入了LibRevive64_1.dll或LibRevive32_1.dll 以及 openvr_api.dll文件,并创建它们对应的线程。
总结:
因此可知,Revive 整个流程及其原理大概为:
ReviveOverlay启动控制面板,系统例程中启动ReviveInject.exe并在后添加游戏路径名,以游戏路径名为参数,创建一个挂起子进程,再将LibRevive64_1.dll以及openvr_api.dll注入,再恢复启动该进程,之后,该游戏运行所需要的参数均来自于被注入的两个dll(而此时的LibRevive64_1.dll中的接口内部实现均使用openVR的API),而不再是它本来Oculus接口获取到的数据了。若检测到游戏在加载Oculus自身runtime的LibOVRRT64_1.dll库时(路径:C:\Program Files\Oculus\Support\oculus-runtime),则将其换成LibRevive64_1.dll库。
参考网址1
参考网址2