Detours是微软开发的一个函数库,可用于捕获系统API。可以从github下载源码并编译。Detours通过更改被拦截的API函数的跳转地址为用户自定义的函数地址来实现拦截。比如我们知道要拦截API函数A,我们需要自定义一个函数B,在实现拦截之前,此时函数A所在的线程正常调用A。开始拦截的时候,Detours库函数将A的入口地址修改为B的函数指针。此前线程对函数A的调用将会被替换为被函数B的调用,当然前提是函数A和函数B的函数签名完全一致。在将函数A的入口地址修改函数B的地址时已经保留了函数A的原始地址,所以函数B的函数形式形如
void* funcB(arg1,arg2,...){
void* retPtr= funcA(arg1,arg2,...)
}
所以我们在函数B中能做的事情就是拦截并修改目标函数参数或者修改原函数的返回值。那么我们如何拦截一个进程的命令行参数呢。在windows系统中,进程是存在父子关系的树状结构。每一个子进程都由它的父进程所创建。一般的 我们都是在资源管理器中点击程序的exe可执行文件来启动进程,那么此时资源管理器进程(explorer.exe) 就是被启动进程的父进程,还有我们熟悉的命令行程序cmd.exe,还有开发者们常用的开发工具如idea,visual studio,eclipse 等等都扮演着父进程的角色。父进程一般通过CreateProcess来创建子程,CreateProcess函数通过命令行参数即可以启动子进程。所以拦截进程的创建和启动就是通过拦截父进程中CreateProcess函数的参数和返回值来实现。我们要实现的函数B需要在一个独立的dll中实现,这个dll可以通过detours工具setdll.exe插入目标父进程的导入表
或者detours工具withdll.exe启动时被目标父进程加载到进程空间,
以下的例子程序通过拦截idea64.exe中的CreateProcess函数 实现打印CreateProcess函数的命令行参数。如果原函数CreateProcess为A,那么MyCreateProcessW则为B。
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "stdafx.h"
#include <stdio.h>
#include <windows.h>
#include "detours.h"
#pragma comment(lib,"detours.lib")
typedef BOOL(WINAPI* PMyCreateProcessW)(
_In_opt_ LPCWSTR lpApplicationName,
_Inout_opt_ LPWSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCWSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
);
PMyCreateProcessW gOldCreateProcessW = NULL;
BOOL
WINAPI
MyCreateProcessW(
_In_opt_ LPCWSTR lpApplicationName,
_Inout_opt_ LPWSTR lpCommandLine,
_In_opt_ LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ BOOL bInheritHandles,
_In_ DWORD dwCreationFlags,
_In_opt_ LPVOID lpEnvironment,
_In_opt_ LPCWSTR lpCurrentDirectory,
_In_ LPSTARTUPINFOW lpStartupInfo,
_Out_ LPPROCESS_INFORMATION lpProcessInformation
) {
printf("lpApplicationName:【%ls】,lpCommandLine【%ls】\n\n", lpApplicationName, lpCommandLine);
return gOldCreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes,
bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation
);
}
void Hook(){
AllocConsole();
freopen("CON", "r", stdin);
freopen("CON", "w", stdout);
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
gOldCreateProcessW = (PMyCreateProcessW)DetourFindFunction("kernel32.dll", "CreateProcessW");
DetourAttach(&gOldCreateProcessW,MyCreateProcessW);
LONG ret = DetourTransactionCommit();
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
//printf("命令行参数:%s\n", GetCommandLineA());
//MessageBoxA(NULL, GetCommandLineA(),GetCommandLineA(),MB_OK);
Hook();
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
extern "C" _declspec(dllexport) void _stdcall add() {
printf("zzz\n");
}
//CreateProcessW
编译后得到DetourJavaCDemo.dll
此时 我们使用setdll.exe将编译好的dll插入idea64.exe的导入表
D:\test4\Detours\bin.X64\setdll /d:D:\Users\DELL\source\repos\DetourJavaCDemo\x64\Release\DetourJavaCDemo.dll "C:\Program Files\JetBrains\IntelliJ IDEA 2017.3.5\bin\idea64.exe"
当然最好是使用管理员权限运行命令、否则报出无权访问(error:5)
成功将dll插入idea64.exe的导入表之后,生成了idea64.exe~,idea64.exe~则是 idea64.exe被插入前的备份
下面再以withdll启动idea64.exe
由于上面代码中使用
AllocConsole();
freopen("CON", "r", stdin);
freopen("CON", "w", stdout);
为idea64分配了一个控制台窗口。可以看出由idea64启动的子进程命令行参数被打印了出来。