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

工作关系,想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 Files\Microsoft SDKs\Windows\v6.0A\Include”和“C:\Program Files\Microsoft Visual Studio 9.0\VC\include”,即可编译通过。
  DllWorkspace下的两个项目,ANSI、UNICODE编译均可,既四种组合的编译,都可正常注入并HOOK。

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

  用CreateRemoteThread创建一个远程进程下的线程,开始执行pfnStartAddr(为LoadLibrary的地址)地址处的代码,传递的参数为pRemoteMem(远程进程下的一段内存空间,保存的是DllExport.dll的路径),创建的远程线程由LoadLibrary开始执行,然后加载DllExport.dll,在DllExport.dll中可执行我们的处理。注意:DllExport.dll的路径是相对于被注入进程的,因此被注入进程需要能找到这个Dll。程序中szDllPath保存路径。
  dll运行时可以和执行注入的进程交换数据,我使用file-mapping实现,代码如下:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// write infomation to file-mapping, the infomation includes witch apis need to be (un)hooked,
// and includes the (un)hook results and so on
LPVOID  WriteFileMapping( HANDLE  hMap, CONTENT_FILE_MAPPING content)
{
     LPVOID  pContent = NULL;
     do 
     {
         if (!hMap)
         {
             PrintMsg(_T( "WriteFileMapping fail : hMap is null, file : %s, line : %d\n" ), 
                 __FILE__, __LINE__);
             break ;
         }
         pContent = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0,  sizeof (content));
         if (!pContent)
         {
             PrintError(_T( "MapViewOfFile" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
         memcpy (pContent, &content,  sizeof (content));
     while  (FALSE);
     return  pContent;
}

  hMap是名字为宏NAME_FILE_MAPPING定义的一个file-mapping,然后向其中写入数据。dll执行时,再打开这个file-mapping,从中读取数据,再把结果写回。
  然后是卸载被注入进程中DllExport.dll的代码:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
// if no apis be hooked, we must free the library
BOOL  UnLoadModule( DWORD  dwProcesssId,  LPCTSTR  lpModuleName) 
     BOOL  bRet = FALSE;
     HANDLE  hModuleSnap = INVALID_HANDLE_VALUE; 
     MODULEENTRY32 me32; 
     HANDLE  hProcess = NULL;
     HMODULE    hModule = NULL;
      
     me32.dwSize =  sizeof (me32);
      
     hProcess = OpenProcess(PROCESS_ALL_ACCESS, TRUE, dwProcesssId);
     do 
     {
         if (!hProcess)
         {
             PrintError(_T( "OpenProcess" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
          
         hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcesssId);
         if (INVALID_HANDLE_VALUE == hModuleSnap)
         {
             PrintError(_T( "CreateToolhelp32Snapshot" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
  
         if (!Module32First(hModuleSnap, &me32)) 
        
             PrintError(_T( "Module32First" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
          
         int  nRefCount = 0;
         do 
         {     
             if (!StrCmpI(me32.szModule, lpModuleName) || !StrCmpI(me32.szExePath, lpModuleName))
             {
                 hModule = me32.hModule;
                 nRefCount = me32.ProccntUsage;
                 break ;
             }
         while (Module32Next(hModuleSnap, &me32));
  
         LPTHREAD_START_ROUTINE pfnStartAddr = 
                 (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T( "Kernel32" )),  "FreeLibrary" );
         if  (!pfnStartAddr)
         {
             PrintError(_T( "GetProcAddress" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
         for  ( int  i = 0; i < nRefCount; i++)
         {
             HANDLE  hThread = ::CreateRemoteThread(hProcess, NULL, 0, pfnStartAddr, hModule, 0, NULL);
             WaitForSingleObject(hThread, INFINITE);
             CLOSE_HANDLE(hThread);
         }
         PrintMsg(_T( "FreeLibrary %s in the process %d finished!\n" ), lpModuleName, dwProcesssId);
          
         bRet = TRUE;
     while  (FALSE);
      
     CLOSE_HANDLE(hModuleSnap);
     CLOSE_HANDLE(hProcess);
  
     return  bRet; 
}

  遍历被注入进程加载的模块,如果找到了DllExport.dll,得到被这个进程引用的次数,然后再循环创建远程线程使用FreeLibrary来卸载DllExport.dll。注意,DllExport.dll被加载几次,就必须执行几次FreeLibrary才能完全卸载。至此,DllExport.dll就可以任意操作了。
  HOOK代码简要说明:
  使用的是跳转法,先保存API的前几个字节,再把这几个字节设置为跳转到我们自己函数的地方去。我们自己的函数中,进行相应处理后,再执行保存的API前几个字节的代码,然后跳转到API相应的位置执行。代码如下:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// hook the specify api
// pRecallApiInfo : infomation of the api
BOOL  HookSpecifyApi(PRECALL_API_INFO pRecallApiInfo)
{
     BOOL  bRet = FALSE;    
      
     do 
     {
         if  (!pRecallApiInfo)
         {
             break ;
         }
         if  (pRecallApiInfo->pOrgfnMem)
         {
             bRet = TRUE;
             break ;
         }
         HMODULE  hModule = LoadLibrary(pRecallApiInfo->lpDllName);
         if  (!hModule)
         {
             PrintError(_T( "LoadLibrary" ), GetLastError(), __MYFILE__, __LINE__);
             break ;
         }
         USES_CONVERSION;
         FARPROC pfnStartAddr = (FARPROC)GetProcAddress(hModule, T2CA(pRecallApiInfo->lpFunctionName));
         pRecallApiInfo->lpApiAddr = pfnStartAddr;
         if  (!pfnStartAddr)
         {
             PrintError(_T( "GetProcAddress" ), GetLastError(), __MYFILE__, __LINE__);
             break  ;
         }
  
         // we must save the first few bytes of the api(at least five, and these few bytes must complete
         // the assembly codes), then make the 5 bytes in front of api to jump to our function, and our
         // function must execute the few bytes saved before, and then jump to the api to execute
         // the rest code in the api
         int  nSize = 0; 
         int  nDisassemblerLen = 0;
         while (nSize < 5) 
        
             // GetOpCodeSize can get the assembly code size
             nDisassemblerLen = GetOpCodeSize(( BYTE *)(pfnStartAddr) + nSize);
             nSize = nDisassemblerLen + nSize; 
        
          
         DWORD  dwProtect = 0;
         if  (!VirtualProtect(pfnStartAddr, nSize, PAGE_EXECUTE_READWRITE, &dwProtect))
         {
             PrintError(_T( "VirtualProtect" ), GetLastError(), __MYFILE__, __LINE__);
             break  ;
         }
  
         // be sure that we must change pOrgfnMem's protect, because the code in pOrgfnMem 
         // also need to execute 
         pRecallApiInfo->pOrgfnMem =  new  BYTE [5 + nSize];
         DWORD  dwMemProtect = 0;
         if  (!VirtualProtect(pRecallApiInfo->pOrgfnMem, 5 + nSize, PAGE_EXECUTE_READWRITE, &dwMemProtect))
         {
             delete  [] pRecallApiInfo->pOrgfnMem;
             pRecallApiInfo->pOrgfnMem = NULL;
             PrintError(_T( "VirtualProtect" ), GetLastError(), __MYFILE__, __LINE__);
             break  ;
         }
         pRecallApiInfo->nOrgfnMemSize = 5 + nSize;
  
         memcpy (pRecallApiInfo->pOrgfnMem, pfnStartAddr, nSize);
         *( BYTE *)(pRecallApiInfo->pOrgfnMem + nSize) = 0xE9;
         *( DWORD *)(pRecallApiInfo->pOrgfnMem + nSize + 1) 
             = ( DWORD )pfnStartAddr + nSize - ( DWORD )(pRecallApiInfo->pOrgfnMem + 5 + nSize);
         *( BYTE *)(pfnStartAddr) = 0xE9;
         *( DWORD *)(( BYTE *)pfnStartAddr + 1) = ( DWORD )pRecallApiInfo->lpRecallfn - (( DWORD )pfnStartAddr + 5);
         memset (( BYTE *)pfnStartAddr + 5, 0x90, nSize - 5);
         // be sure that we must set the rest to 0x90(assembly code for nop, do nothing, 
         // and occupy one byte), because we should't change the assembly code
  
         VirtualProtect(pfnStartAddr, nSize, dwProtect, &dwProtect);
  
         bRet = TRUE;
     while  (FALSE);
  
     return  bRet;
}

  需要注意的是: 保存的可能并不是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个字节修改为0x90,编译指令为NOP,不执行任何操作。否则,第6个字节可能和后面的几个字节组合成新的指令,也是不正确的。其实,这里也可以不修改,因为我们是直接跳到第7个字节执行的,既使第6、7个字节组合成一个新指令也没关系,因为不是从第6个指令开始执行的。但是,这样处理后,调试方便,打开汇编窗口,一目了解。

  还原就简单了,直接用之前保存的字节恢复即可。
  我们替换API的函数代码如下:
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int  WINAPI MyMessageBoxA(IN  HWND  hWnd, IN  LPCSTR  lpText, IN  LPCSTR  lpCaption, IN  UINT  uType)
{
     int  nOrderHookApi = ORDER_MESSAGEBOXA;
  
     int  nRet = 0;
     static  int  i = 1;
     if  (g_arHookAPIs[nOrderHookApi].pOrgfnMem)
     {
         USES_CONVERSION;
         PrintMsgA( "%-18s %08d : 0x%08x \"%s\" \"%s\" 0x%08x\n" ,
             T2CA(g_arHookAPIs[nOrderHookApi].lpFunctionName),
             i++, hWnd, VALID_CHAR(lpText), VALID_CHAR(lpCaption), uType);
  
         nRet = ((pfnMessageBoxA)( LPVOID )g_arHookAPIs[nOrderHookApi].pOrgfnMem)(
             hWnd,  "HelloWorld" "Caption" , MB_OKCANCEL);
     }
     return  nRet;
}

  最后一定要从保存的API的地址处开始执行。
  代码中已实现同时HOOK9个API(MessageBoxA、MessageBoxW、DeviceIoControl、CreateFileA、CreateFileW、ReadFile、ReadFileEx、WriteFile、WriteFileEx),稍加修改,即可实现HOOK更多的API。
  编译环境:Windows XP SP3、VC++ 6.0 SP6

  博客原文: 线程注入、HOOK APIs(附VC6源码)
  源码及DEMO下载地址: 线程注入、HOOK APIs(附VC6源码)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值