• 进程动态拦截注入API HOOK


    最近工作中遇到一个问题,需要通过程序界面进行判断程序的运行状态,刚开始认为很简单,不就是一个窗体控件获取,获取Button的状态和Text。刚好去年干过该事情,就没太在意,就把优先级排到后面了,随着项目交付时间的临近,就准备开始解决问题,一下懵逼了,这次软件作者也聪明了,居然换了花样。

    从图中可以发现,此处的按钮上的文字等信息不是通过Button信息进行操作。静想猜测是通过Windows API直接将信息进行写入,那么哪个API可以进行写入呢,想想不难发现,其实就是窗口程序中在窗口中写入文字的方法DrawText,同时隐约记得SetWindowsText也具备该功能。既然有猜想了那就实践看看,验证下猜想。

    开干之前,还需要理理思路:

    1. 既然程序会调用DrawText方法,那我要获取文本,就必须截获到该方法
    2. 截获方法不就是使用Windows上大名鼎鼎的钩子(Hook)函数。
    3. 说到钩子函数,这又分钩窗口消息钩API函数。对于本文来说当然就是后者了。实现API HOOK主要有两个重要环节:
      • 如何把代码注入到目标地址空间
      • 如何让自己的代码被调用
    4. 稍稍查询下资料,发现钩窗口函数貌似就复杂了,如果要研究细节请参考该文
    5. 进一步资料查询,我发现以牛逼函数库(居然还是微软自己开发的):Detours(当然我会提供下载链接)Detours它用于实现拦截Win32二进制代码中的API函数。它使用一个JMP指令替换了目标函数的前面几个字节,使得控制直接调用实现的Detours函数。并通过一个trampoline函数保留了原来函数的功能调用。
    6. 到目前为止就是对该库的使用了。从文档上来看应该没什么问题了。但是文档上是通过创建进程进行注入DetourCreateProcessWithDll,对目前我的应用场景就不太匹配了。客户程序一直处于运行状态,我需要获取动态注入,那么问题来了,如何解决了?
    BOOL WINAPI DetourCreateProcessWithDll(LPCSTR lpApplicationName,
                                            LPSTR lpCommandLine,
                                            ...);
    
    1. 能提出问题,基本上问题就解决了一般。果不然,通过百度和Google的不懈努力,终于发现原来Detours1.5版本中有个方法DetourContinueProcessWithDll该方法就是进行动态注入的,这下就可以开干了(至于Detours的原理本文就不再赘述,请大家自行查询资料,本文以解决实际问题问题主)
    BOOL WINAPI DetourContinueProcessWithDll(HANDLE hProcess, LPCSTR lpDllName);
    

    经过上面几个步骤下来,已经有了完整的思路,下文主要结合实践,进行代码实践。本文主要从如下几个方面进行时间:

    • 首先,居然是要截取函数DrawTextSetWindowText那么首先的先实现自己的函数(通过DLL封装)
    • 然后,就是动态注入我们的DLL文件指定进程
    • 最后,拿出来溜溜(本文为了简便,仅将相关信息打印到DebugView中)

    实现HookWindowTextDll

    首先,按照Detours的编程规范,需要在加载HookWindowTextDll时通过方法DetourFunctionWithTrampoline进行注册。在卸载的时候通过DetourRemove卸载。

    该处主要分以下几步:

    1. 需要Hook的方法声明和实现
    2. 安装和卸载注入方法

    Hook方法声明和实现

    首先需要声明我们的方法

    BOOL WINAPI MySetWindowTextA( HWND hWnd, LPCTSTR lpString ); 
    BOOL WINAPI MySetWindowTextW( HWND hWnd, LPCWSTR lpString ); 
    
    int WINAPI MyDrawTextA( HDC hDC, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat );
    int WINAPI MyDrawTextW( HDC hDC, LPCWSTR lpString, int nCount, LPRECT lpRect, UINT uFormat );
    
    //该方法主要是用Real_SetWindowTextA保存原来函数SetWindowTextA地址,方便后面调用
    DETOUR_TRAMPOLINE( BOOL WINAPI Real_SetWindowTextA( HWND a0, LPCTSTR a1 ), SetWindowTextA );
    DETOUR_TRAMPOLINE( BOOL WINAPI Real_SetWindowTextW( HWND a0, LPCWSTR a1 ), SetWindowTextW );
    
    DETOUR_TRAMPOLINE( int WINAPI Real_DrawTextA( HDC a0, LPCTSTR a1, int a2, LPRECT a3, UINT a4 ), DrawTextA );
    DETOUR_TRAMPOLINE( int WINAPI Real_DrawTextW( HDC a0, LPCWSTR a1, int a2, LPRECT a3, UINT a4 ), DrawTextW );
    
    //代码实现,限于篇幅,仅列出MySetWindowTextA
    BOOL WINAPI MySetWindowTextA( HWND hWnd, LPCTSTR lpString )
    {
    #ifdef _DEBUG
    	char strMsg[ 1024 ]={0};
    	wsprintf( strMsg, "SetWindowTextA : %s. size = %ld
    ", lpString, strlen(lpString) );
    	OutputDebugString( strMsg );
    #endif
    
    	return Real_SetWindowTextA( hWnd, lpString );
    }
    

    Hook方法的安装和卸载

    BOOL APIENTRY DllMain( HANDLE hModule, 
    	DWORD  ul_reason_for_call, 
    	LPVOID lpReserved )
    {
    	if( DLL_PROCESS_ATTACH == ul_reason_for_call )//dll加载
    	{
    		InstallProbes();
    	}
    	else if( DLL_PROCESS_DETACH == ul_reason_for_call )//dll卸载
    	{
    		UninstallProbes();
    	}
    	else;
    
    	return TRUE;
    }
    

    拦截注入通过方法DetourFunctionWithTrampoline进行,该方法原型如下:

    BOOL  WINAPI DetourFunctionWithTrampoline(PBYTE pbTrampoline,
                                              PBYTE pbDetour);
    

    这个函数有两个参数,pbTrampoline和一个指向pbDetour函数的指针。目标函数Target之所以没有作
    为一个参数,是因为它已经编码到pbTrampoline函数之中(上文中进行编码DETOUR_TRAMPOLINE)。

    BOOL InstallProbes()
    {
    	DetourFunctionWithTrampoline( (PBYTE)Real_SetWindowTextA, (PBYTE)MySetWindowTextA );
    	DetourFunctionWithTrampoline( (PBYTE)Real_SetWindowTextW, (PBYTE)MySetWindowTextW );
    
    	DetourFunctionWithTrampoline( (PBYTE)Real_DrawTextA, (PBYTE)MyDrawTextA );
    	DetourFunctionWithTrampoline( (PBYTE)Real_DrawTextW, (PBYTE)MyDrawTextW );
    
    	OutputDebugString("InstallProbesA ok.
    ");
    	return TRUE;
    }
    
    BOOL UninstallProbes()
    {
    	DetourRemove( (PBYTE)Real_SetWindowTextA, (PBYTE)MySetWindowTextA );
    	DetourRemove( (PBYTE)Real_SetWindowTextW, (PBYTE)MySetWindowTextW );
    
    	OutputDebugString("UNInstallProbesB ok.
    ");
    	return TRUE;
    }
    

    至此拦截注入的方法就完成。下一小节就是如何进行动态注入了。

    动态注入HookWindowTextDll

    既然要动态注入,那么就先要看看动态注入方法DetourContinueProcessWithDll方法的使用方法

    //把一个动态链接库注入到一个新的进程中
    BOOL WINAPI DetourContinueProcessWithDllA(HANDLE hProcess, LPCSTR lpDllName)
    

    该方法有两个参数,一看看就明白了

    • hProcess:需要注入的原进程句柄
    • lpDllName:需要注入的Dll路径,本文即HookWindowTextDll.dll

    那么此时应该先获取进程句柄,获取进程句柄通过如下方法即可:

    通过进程名称获取进程ID

    DWORD	GetProcessIdFromProcessName(std::string processname)
    {
    	DWORD dwRet = 0;
    	PROCESSENTRY32 pe32;
    	pe32.dwSize = sizeof(pe32);
    	HANDLE hProcessSnap = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    	if (hProcessSnap != INVALID_HANDLE_VALUE)
    	{
    		BOOL bMore = ::Process32First(hProcessSnap, &pe32);
    		while (bMore)
    		{
    			if (boost::iequals(pe32.szExeFile, processname))
    			{
    				dwRet = pe32.th32ProcessID;
                  	 break;
    			}
    			bMore = ::Process32Next(hProcessSnap, &pe32);
    		}
    		::CloseHandle(hProcessSnap);
    	}
    	return dwRet;
    }
    

    调用测试:

    std::string str1 = "WireCut.EXE";
    DWORD dwProcessId = GetProcessIdFromProcessName(str1);
    std::cout << dwProcessId << std::endl; //
    

    获取到了进程,那就进入下一节,获取句柄。

    通过进程ID获取进程句柄

    OpenProcess 函数用来打开一个已存在的进程对象,并返回进程的句柄

    HANDLE OpenProcess(
    		DWORD dwDesiredAccess, 
    		BOOL bInheritHandle, 
    		DWORD dwProcessId
    );
    

    参数:

    • dwDesiredAccess:想拥有的该进程访问权限
      • PROCESS_ALL_ACCESS:所有能获得的权限
      • PROCESS_CREATE_PROCESS:需要创建一个进程
      • PROCESS_CREATE_THREAD:需要创建一个线程
      • PROCESS_DUP_HANDLE:重复使用DuplicateHandle句柄
      • PROCESS_QUERY_INFORMATION:获得进程信息的权限,如它的退出代码、优先级
      • PROCESS_SET_INFORMATION :设置某些信息的权限,如进程优先级
      • PROCESS_SET_QUOTA :设置内存限制的权限,使用SetProcessWorkingSetSize
      • PROCESS_SUSPEND_RESUME :暂停或恢复进程的权限
      • PROCES_TERMINATE :终止一个进程的权限,使用TerminateProcess
      • PROCESS_VM_OPERATION :操作进程内存空间的权限(可用VirtualProtectEx和WriteProcessMemory)
      • PROCESS_VM_READ :读取进程内存空间的权限,可使用ReadProcessMemory
      • PROCESS_VM_WRITE :读取进程内存空间的权限,可使用WriteProcessMemory
      • SYNCHRONIZE :等待进程终止
    • bInheritHandle:表示所得到的进程句柄是否可以被继承
    • dwProcessId:被打开进程的PID

    返回类型:

    • 如成功,返回值为指定进程的句柄。
    • 如失败,返回值为NULL,可调用GetLastError()获得错误代码。
    HANDLE GetProcessHandle(DWORD nID)
    {
    	//PROCESS_ALL_ACCESS 所有能获得的权限
    	return OpenProcess(PROCESS_ALL_ACCESS, FALSE, nID);
    }
    
    DWORD dwProcessId = GetProcessIdFromProcessName("WireCut.EXE");
    if (dwProcessId != 0)
    {
    	bRet = DetourContinueProcessWithDllW(GetProcessHandle(dwProcessId), szDllFilePath);	
    }
    

    到此就完成了所有工作,后面提供项目代码和库文件

    链接:https://pan.baidu.com/s/1c09LWg9zo5NIVwR2htJYZA
    提取码:f0kt

    欢迎关注交流共同进步
    奔跑阿甘
    博客地址:wqliceman.top

  • 相关阅读:
    Android OCR 之 tesseract
    抛砖引玉 之 谁动了我的流量(0权限上传数据)
    退伍一年了
    android 通过 Hessian 与 j2ee 服务端交互
    Arduino 入手
    抛砖引玉 之 谁动了我的隐私(android用户隐私窥探)
    如何关注那些有价值的微博
    关于培训和外包20111027
    ASP.NET实现Cookie功能的三个基本操作(写入,读取,删除)
    提高你开发效率的十五个Visual Studio 2010使用技巧
  • 原文地址:https://www.cnblogs.com/wqliceman/p/10924958.html
Copyright © 2020-2023  润新知