zoukankan      html  css  js  c++  java
  • 进程动态拦截注入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

  • 相关阅读:
    Fix Installing .NET Framework 3.5 failed Error Code 0x800F0954 on Windows 10
    RHEL8安装五笔输入法
    Enable EPEL and Local Repository on RHEL8
    Why is Yum Replaced by DNF?
    检查Linux服务器是否被攻击的常用命令及方法
    IDEA 主题
    IDEA 如何显示一个类中所有的方法
    Appium 安装以及安装过程中遇到的问题
    Maven 如何发布 jar 包到 Nexus 私库
    java泛型的基本使用
  • 原文地址:https://www.cnblogs.com/wqliceman/p/10924958.html
Copyright © 2011-2022 走看看