线程注入、HOOK APIs(附VC6源码)

作者: littlesu 分类: C++ 发布时间: 2013-01-17 15:00 ė3,996 浏览数 6没有评论
文章转自王牌软件
站长推荐:NSetup一键部署软件
一键式完成美化安装包制作,自动增量升级,数据统计,数字签名。应对各种复杂场景,脚本模块化拆分,常规复杂的脚本代码,图形化设置。无需专业的研发经验,轻松完成项目部署。(www.nsetup.cn)

工作关系,想HOOK并修改一些API,使得不支持某些设备的第三方工具可以正常运行,因此花时间写了这么个工具。比如ReadFile时,某些设备不支持指定的缓存大小(如512KB),可以HOOK ReadFile,把缓存大小修改为更小,可能ReadFile就能正常工作,第三方工具也能正常使用。其实,只是想借工作这个契机,学习远程线程注入和HOOK API。工作上测试的设备和第三方工具运行在64位机上,还没有时间在64位机上修改并编译。

  运行DEMO说明:

  首先进入TestExe目录,打开MFCDialogApplication.exe,8个按钮分别简单的调用8个API,可一一点击查看效果,标题栏显示进程ID:

  打开DllImport.exe,弹出Console,以下显示的是我输入并HOOK完的界面:

  首先输入需要HOOK的进程ID,这里输入对话框进程ID12600。然后提示选择需HOOK的API,每个API有FLAG值,占一位,可以用位组合,这里输入511,即9个API都需HOOK。然后选择是HOOK还是UNHOOK,这里当然输入1。然后这个程序向对话框程序注入线程,调用DllExport.dll,DllExport.dll中HOOK这9个API。结果全部成功,见上图。HOOK对话框后,会在对话框进程中弹出一个Console窗口,用以显示相关信息,见下图:

  然后在对话框上一一点击各个按钮,并随时查看弹出的Console窗口中内容,单击完后,见下图:

  每点击一个按钮后,在Console中会显示相关信息。这些信息是HOOK时打印的,我只设置了打印简单的信息。还要注意,HOOK MessageBoxA和MessageBoxW时,改变了弹出消息框的标题、文字等。

  然后再打开DllImport.exe,把HOOK的API还原,并从对话框进程中卸载DllExport.dll,输入顺序与HOOK一致,只是第三步时需指定0,见下图:

  UNHOOK API时我选择的全部还原。完后,9个API不再被HOOK,正常执行,之前显示信息的Console关闭,对话框正常运行。所有API UNHOOK后,DllExport.dll被卸载,可改名、删除等。此时,再点击对话框按钮,就不再有任何显示显示。注意点击两个MessageBox后,消息框的标题与文字。

  编译说明:

  MFCDialogApplication是生成被注入的对话框工程;DllWorkspace中,DllExport生成需注入的DllExport.dll,DllImport生成执行注入操作的DllImport.exe。

  DllWorkspace工作空间中,两个项目都是用的安全字符串操作函数,如果出现找不到头文件,需要更新SDK,我的VC6 Include路径包括“C:Program FilesMicrosoft SDKsWindowsv6.0AInclude”和“C:Program FilesMicrosoft Visual Studio 9.0VCinclude”,即可编译通过。

  DllWorkspace下的两个项目,ANSI、UNICODE编译均可,既四种组合的编译,都可正常注入并HOOK。

  注入及HOOK代码简要说明:
  注入主要代码如下:

 

  1. // create a remote thread, and start LoadLibrary to load dll that we make, in the dll, we can  
  2. // hook apis and can do everything we want  
  3. BOOL CreateRemoteThread(HANDLE hProcess, LPTHREAD_START_ROUTINE pfnStartAddr, LPVOID pRemoteMem)  
  4. {  
  5.     BOOL bRet = FALSE;  
  6.     HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, pRemoteMem, 0, NULL);  
  7.     do   
  8.     {  
  9.         if(!hThread)  
  10.         {  
  11.             PrintError(_T("CreateRemoteThread"), GetLastError(), __MYFILE__, __LINE__);  
  12.             break;  
  13.         }  
  14.         TCOUT << _T("Waiting for the end of the remote thread…") << endl;  
  15.         WaitForSingleObject(hThread, INFINITE);  
  16.         CloseHandle(hThread);  
  17.         hThread = NULL;  
  18.         bRet = TRUE;  
  19.     } while (FALSE);  
  20.     return bRet;  
  21. }  

  用CreateRemoteThread创建一个远程进程下的线程,开始执行pfnStartAddr(为LoadLibrary的地址)地址处的代码,传递的参数为pRemoteMem(远程进程下的一段内存空间,保存的是DllExport.dll的路径),创建的远程线程由LoadLibrary开始执行,然后加载DllExport.dll,在DllExport.dll中可执行我们的处理。注意:DllExport.dll的路径是相对于被注入进程的,因此被注入进程需要能找到这个Dll。程序中szDllPath保存路径。

  dll运行时可以和执行注入的进程交换数据,我使用file-mapping实现,代码如下:

  1. // write infomation to file-mapping, the infomation includes witch apis need to be (un)hooked,  
  2. // and includes the (un)hook results and so on  
  3. LPVOID WriteFileMapping(HANDLE hMap, CONTENT_FILE_MAPPING content)  
  4. {  
  5.     LPVOID pContent = NULL;  
  6.     do   
  7.     {  
  8.         if(!hMap)  
  9.         {  
  10.             PrintMsg(_T("WriteFileMapping fail : hMap is null, file : %s, line : %dn"),   
  11.                 __FILE__, __LINE__);  
  12.             break;  
  13.         }  
  14.         pContent = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, sizeof(content));  
  15.         if(!pContent)  
  16.         {  
  17.             PrintError(_T("MapViewOfFile"), GetLastError(), __MYFILE__, __LINE__);  
  18.             break;  
  19.         }  
  20.         memcpy(pContent, &content, sizeof(content));  
  21.     } while (FALSE);  
  22.     return pContent;  
  23. }  

  hMap是名字为宏NAME_FILE_MAPPING定义的一个file-mapping,然后向其中写入数据。dll执行时,再打开这个file-mapping,从中读取数据,再把结果写回。

  然后是卸载被注入进程中DllExport.dll的代码:

 

 

  1. // if no apis be hooked, we must free the library  
  2. BOOL UnLoadModule(DWORD dwProcesssId, LPCTSTR lpModuleName)   
  3. {   
  4.     BOOL bRet = FALSE;  
  5.     HANDLE hModuleSnap = INVALID_HANDLE_VALUE;   
  6.     MODULEENTRY32 me32;   
  7.     HANDLE hProcess = NULL;  
  8.     HMODULE hModule = NULL;  
  9.       
  10.     me32.dwSize = sizeof(me32);  
  11.       
  12.     hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwProcesssId);  
  13.     do   
  14.     {  
  15.         if(!hProcess)  
  16.         {  
  17.             PrintError(_T("OpenProcess"), GetLastError(), __MYFILE__, __LINE__);  
  18.             break;  
  19.         }  
  20.           
  21.         hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcesssId);  
  22.         if(INVALID_HANDLE_VALUE == hModuleSnap)  
  23.         {  
  24.             PrintError(_T("CreateToolhelp32Snapshot"), GetLastError(), __MYFILE__, __LINE__);  
  25.             break;  
  26.         }  
  27.   
  28.         if(!Module32First(hModuleSnap, &me32))   
  29.         {   
  30.             PrintError(_T("Module32First"), GetLastError(), __MYFILE__, __LINE__);  
  31.             break;  
  32.         }  
  33.           
  34.         int nRefCount = 0;  
  35.         do   
  36.         {     
  37.             if(!StrCmpI(me32.szModule, lpModuleName) || !StrCmpI(me32.szExePath, lpModuleName))  
  38.             {  
  39.                 hModule = me32.hModule;  
  40.                 nRefCount = me32.ProccntUsage;  
  41.                 break;  
  42.             }  
  43.         } while(Module32Next(hModuleSnap, &me32));  
  44.   
  45.         LPTHREAD_START_ROUTINE pfnStartAddr =   
  46.                 (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "FreeLibrary");  
  47.         if (!pfnStartAddr)  
  48.         {  
  49.             PrintError(_T("GetProcAddress"), GetLastError(), __MYFILE__, __LINE__);  
  50.             break;  
  51.         }  
  52.         for (int i = 0; i < nRefCount; i++)  
  53.         {  
  54.             HANDLE hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, hModule, 0, NULL);  
  55.             WaitForSingleObject(hThread, INFINITE);  
  56.             CLOSE_HANDLE(hThread);  
  57.         }  
  58.         PrintMsg(_T("FreeLibrary %s in the process %d finished!n"), lpModuleName, dwProcesssId);  
  59.           
  60.         bRet = TRUE;  
  61.     } while (FALSE);  
  62.       
  63.     CLOSE_HANDLE(hModuleSnap);  
  64.     CLOSE_HANDLE(hProcess);  
  65.   
  66.     return bRet;   
  67. }  

  遍历被注入进程加载的模块,如果找到了DllExport.dll,得到被这个进程引用的次数,然后再循环创建远程线程使用FreeLibrary来卸载DllExport.dll。注意,DllExport.dll被加载几次,就必须执行几次FreeLibrary才能完全卸载。至此,DllExport.dll就可以任意操作了。

  HOOK代码简要说明:

  使用的是跳转法,先保存API的前几个字节,再把这几个字节设置为跳转到我们自己函数的地方去。我们自己的函数中,进行相应处理后,再执行保存的API前几个字节的代码,然后跳转到API相应的位置执行。代码如下:

  1. // hook the specify api  
  2. // pRecallApiInfo : infomation of the api  
  3. BOOL HookSpecifyApi(PRECALL_API_INFO pRecallApiInfo)  
  4. {  
  5.     BOOL bRet = FALSE;    
  6.       
  7.     do   
  8.     {  
  9.         if (!pRecallApiInfo)  
  10.         {  
  11.             break;  
  12.         }  
  13.         if (pRecallApiInfo->pOrgfnMem)  
  14.         {  
  15.             bRet = TRUE;  
  16.             break;  
  17.         }  
  18.         HMODULE hModule = LoadLibrary(pRecallApiInfo->lpDllName);  
  19.         if (!hModule)  
  20.         {  
  21.             PrintError(_T("LoadLibrary"), GetLastError(), __MYFILE__, __LINE__);  
  22.             break;  
  23.         }  
  24.         USES_CONVERSION;  
  25.         FARPROC pfnStartAddr = (FARPROC)GetProcAddress(hModule, T2CA(pRecallApiInfo->lpFunctionName));  
  26.         pRecallApiInfo->lpApiAddr = pfnStartAddr;  
  27.         if (!pfnStartAddr)  
  28.         {  
  29.             PrintError(_T("GetProcAddress"), GetLastError(), __MYFILE__, __LINE__);  
  30.             break ;  
  31.         }  
  32.   
  33.         // we must save the first few bytes of the api(at least five, and these few bytes must complete  
  34.         // the assembly codes), then make the 5 bytes in front of api to jump to our function, and our  
  35.         // function must execute the few bytes saved before, and then jump to the api to execute  
  36.         // the rest code in the api  
  37.         int nSize = 0;   
  38.         int nDisassemblerLen = 0;  
  39.         while(nSize < 5)   
  40.         {   
  41.             // GetOpCodeSize can get the assembly code size  
  42.             nDisassemblerLen = GetOpCodeSize((BYTE*)(pfnStartAddr) + nSize);  
  43.             nSize = nDisassemblerLen + nSize;   
  44.         }   
  45.           
  46.         DWORD dwProtect = 0;  
  47.         if (!VirtualProtect(pfnStartAddr, nSize, PAGE_EXECUTE_READWRITE, &dwProtect))  
  48.         {  
  49.             PrintError(_T("VirtualProtect"), GetLastError(), __MYFILE__, __LINE__);  
  50.             break ;  
  51.         }  
  52.   
  53.         // be sure that we must change pOrgfnMem's protect, because the code in pOrgfnMem   
  54.         // also need to execute   
  55.         pRecallApiInfo->pOrgfnMem = new BYTE[5 + nSize];  
  56.         DWORD dwMemProtect = 0;  
  57.         if (!VirtualProtect(pRecallApiInfo->pOrgfnMem, 5 + nSize, PAGE_EXECUTE_READWRITE, &dwMemProtect))  
  58.         {  
  59.             delete [] pRecallApiInfo->pOrgfnMem;  
  60.             pRecallApiInfo->pOrgfnMem = NULL;  
  61.             PrintError(_T("VirtualProtect"), GetLastError(), __MYFILE__, __LINE__);  
  62.             break ;  
  63.         }  
  64.         pRecallApiInfo->nOrgfnMemSize = 5 + nSize;  
  65.   
  66.         memcpy(pRecallApiInfo->pOrgfnMem, pfnStartAddr, nSize);  
  67.         *(BYTE*)(pRecallApiInfo->pOrgfnMem + nSize) = 0xE9;  
  68.         *(DWORD*)(pRecallApiInfo->pOrgfnMem + nSize + 1)   
  69.             = (DWORD)pfnStartAddr + nSize - (DWORD)(pRecallApiInfo->pOrgfnMem + 5 + nSize);  
  70.         *(BYTE*)(pfnStartAddr) = 0xE9;  
  71.         *(DWORD*)((BYTE*)pfnStartAddr + 1) = (DWORD)pRecallApiInfo->lpRecallfn - ((DWORD)pfnStartAddr + 5);  
  72.         memset((BYTE*)pfnStartAddr + 5, 0×90, nSize - 5);  
  73.         // be sure that we must set the rest to 0×90(assembly code for nop, do nothing,   
  74.         // and occupy one byte), because we should't change the assembly code  
  75.   
  76.         VirtualProtect(pfnStartAddr, nSize, dwProtect, &dwProtect);  
  77.   
  78.         bRet = TRUE;  
  79.     } while (FALSE);  
  80.   
  81.     return bRet;  
  82. }  

  需要注意的是:保存的可能并不是API的前5个字节,因为前5个字节可能不是一个或几个完整的汇编指令,比如第5、6个字节合起来才是一个指令,我们就不能只保存前5个字节,最后执行这5个字节,再跳转到第6个字节处执行。这样破坏了指令,必然造成崩溃。这时需要保存前6个字节才行。程序中,我使用了从网上找到的一段代码GetOpCodeSize,GetOpCodeSize可以得到当前地址处的汇编指令长度。然后保存API前至少5个字节,并且这些字节可以组成完整的汇编指令。实际也可以不用这样,可以用另一方式,我们函数中先恢复API的前5个字节,然后再调用API,调用完后再改API前5个字节为跳转到我们函数的指令。但是,这种方式并不好,如果调用API时,API的前5个字节正常,如果再有进程中其他线程调用API,这时流程完全正常,没有被HOOK。
  另外,还需要修改保存API前几个字节内存的属性,因为这些内存是需要执行的,因此修改为可读、可写、可执行。代码修改pRecallApiInfo->pOrgfnMem段内存在属性。
  最后,如果保存的API前5个以上的字节,比如保存的6个字节,还需要把第6个字节修改为0×90,编译指令为NOP,不执行任何操作。否则,第6个字节可能和后面的几个字节组合成新的指令,也是不正确的。其实,这里也可以不修改,因为我们是直接跳到第7个字节执行的,既使第6、7个字节组合成一个新指令也没关系,因为不是从第6个指令开始执行的。但是,这样处理后,调试方便,打开汇编窗口,一目了解。


  还原就简单了,直接用之前保存的字节恢复即可。

  我们替换API的函数代码如下:

 

  1. int WINAPI MyMessageBoxA(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType)  
  2. {  
  3.     int nOrderHookApi = ORDER_MESSAGEBOXA;  
  4.   
  5.     int nRet = 0;  
  6.     static int i = 1;  
  7.     if (g_arHookAPIs[nOrderHookApi].pOrgfnMem)  
  8.     {  
  9.         USES_CONVERSION;  
  10.         PrintMsgA("%-18s %08d : 0x%08x "%s" "%s" 0x%08xn",  
  11.             T2CA(g_arHookAPIs[nOrderHookApi].lpFunctionName),  
  12.             i++, hWnd, VALID_CHAR(lpText), VALID_CHAR(lpCaption), uType);  
  13.   
  14.         nRet = ((pfnMessageBoxA)(LPVOID)g_arHookAPIs[nOrderHookApi].pOrgfnMem)(  
  15.             hWnd, "HelloWorld""Caption", MB_OKCANCEL);  
  16.     }  
  17.     return nRet;  
  18. }  

  最后一定要从保存的API的地址处开始执行。

  代码中已实现同时HOOK9个API(MessageBoxA、MessageBoxW、DeviceIoControl、CreateFileA、CreateFileW、ReadFile、ReadFileEx、WriteFile、WriteFileEx),稍加修改,即可实现HOOK更多的API。

  编译环境:Windows XP SP3、VC++ 6.0 SP6

  源码及DEMO下载地址:线程注入、HOOK APIs(附VC6源码)



只回答业务咨询点击这里给我发消息 点击这里给我发消息

学习日记,兼职软件设计,软件修改,毕业设计。

本文出自 学习日记,转载时请注明出处及相应链接。

本文永久链接: https://www.softwareace.cn/?p=80

发表评论

电子邮件地址不会被公开。 必填项已用*标注

您可以使用这些HTML标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">


Ɣ回顶部

无觅相关文章插件,快速提升流量