VC下提前注入进程的一些方法2——远线程带参数

在前一节中介绍了通过远线程不带参数的方式提前注入进程,现在介绍种远线程携带参数的方法。(转载请指明出处)

  1.2 执行注入的进程需要传信息给被注入进程 

        因为同样采用的是远线程注入,所以大致的思路是一样的,只是在细节上要注意一些处理。总体来说分为以下几个步骤:

        1 将需要传递的信息写入被注入进程的地址空间。

        2 将远线程函数体写入被注入进程的空间。

        3 在被注入进程中执行该远线程函数,让该线程利用我们之前写入的参数完成任务。

        在被注入进程的地址空间中写入“需要传递”的信息不存在什么问题,因为该信息是”死的“”数据“,我们写入什么内容就是什么内容,它就是二进制数据。但是写入函数执行体就存在一定的问题。首先我们要考虑用什么语言来写这个函数?我是VC程序员,当然优先选择C++/C。可是使用这些语言往往会存在问题,因为我们不知道编译器对我们的代码可能做了什么手脚。下面我来验证下

[cpp]  view plain copy
  1. typedef struct _RemoteThreadRountineParam_  
  2. {  
  3.     //LPLoadLibrary lpLoadLibraryW;  
  4.     //LPGetProcAddr lpGetProcAddress;  
  5.     WCHAR wszDllPath[MAX_PATH];  
  6.     CHAR szFuncName[MAX_FUNCNAMELENGTH];  
  7.     HANDLE hEvent;  
  8. }RemoteThreadRountineParam,*pRemoteThreadRountineParam;  
[cpp]  view plain copy
  1. DWORD WINAPI RemoteThreadRoutine_Error( LPVOID lpParam )  
  2.     {  
  3.         if ( NULL == lpParam )  
  4.         {  
  5.             return 0;  
  6.         }  
  7.         pRemoteThreadRountineParam lpRmtParam = (pRemoteThreadRountineParam) lpParam;  
  8.   
  9.         if ( NULL == lpRmtParam->lpLoadLibraryW )  
  10.         {  
  11.             return 0;  
  12.         }  
  13.   
  14.         if ( NULL == lpRmtParam->lpGetProcAddress )  
  15.         {  
  16.             return 0;  
  17.         }  
  18.   
  19.         HMODULE hHookDll = LoadLibraryW(lpRmtParam->wszDllPath);  
  20.         if ( NULL == hHookDll )  
  21.         {  
  22.             return 0;  
  23.         }  
  24.   
  25.         LPExportFun lpExportFunAddr = (LPExportFun)GetProcAddress( hHookDll, lpRmtParam->szFuncName );  
  26.         if ( NULL == lpExportFunAddr )  
  27.         {  
  28.             return 0;  
  29.         }  
  30.   
  31.         lpExportFunAddr( lpRmtParam->hEvent );  
  32.   
  33.         return 0;  
  34.     }  
        回想前一节中,我们将DLL的绝对路径写入被注入进程的空间作为远线程的唯一参数,而本节的远线程需要很多参数,所以我们要定义一个结构体RemoteThreadRountineParam。它包含的成员是:要注入的DLL的绝对路径、这DLL中的导出函数名,以及这个导出函数需要的参数——Event句柄。远线程执行的函数体是RemoteThreadRoutine_Error,其参数是一个指向RemoteThreadRountineParam结构体对象的一个指针,正如其名字——它是Error的。其执行的逻辑也是很简单的:加载DLL,寻找导出函数和执行导出函数。之后我们所有带参数的注入逻辑都将采用这个最基本的处理流程,只是细节处理上存在一定的区别。

[cpp]  view plain copy
  1. do {  
  2.                 // 写入线程例程代码  
  3.                 // 分配内存空间  
  4.                 pBufferRemoteFun = VirtualAllocEx( hProcess, NULL, dwFunMemSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );         
  5.                 if ( NULL == pBufferRemoteFun ) {  
  6.                     break;  
  7.                 }  
  8.                 // 将信息写入傀儡进程的内存地址空间  
  9.                 if ( FALSE == WriteProcessMemory( hProcess, pBufferRemoteFun, pRmtRoutine, dwFunMemSize, NULL ) ) {  
  10.                     break;  
  11.                 }  
  12.   
  13.                 do {  
  14.                     pBufferParam = VirtualAllocEx( hProcess, NULL, dwParamMemSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE );  
  15.                     if ( NULL == pBufferParam ) {  
  16.                         break;  
  17.                     }  
  18.                     if ( FALSE == WriteProcessMemory( hProcess, pBufferParam, &RmtThdRtParam, dwParamMemSize, NULL ) ) {  
  19.                         break;  
  20.                     }  
  21.   
  22.                     do {  
  23.                         // 注入线程  
  24.                         pRemoteThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pBufferRemoteFun, pBufferParam, 0, NULL );  
  25.                         if ( NULL == pRemoteThread ) {  
  26.                             break;  
  27.                         }  

这段逻辑分别对应于刚介绍的三个步骤。其中dwFunMemSize是我们定义的一个”足够大“的空间大小,因为我这儿没有计算准确的函数执行体大小(其实我也不知道怎么去计算这个大小)。因为我们的函数执行体代码是要执行的,所以我们申请的空间是具有EXECUTE属性的。pBufferRemoteFun是指向远线程函数执行体的在”远程“的空间。pRmtRoutine是指向远线程函数执行体的在”本地“的空间。其他没有什么好介绍的,我们将主要的注意力放在pRmtRoutine。

        最简单的方式是

[cpp]  view plain copy
  1. char* pRmtRoutine = (char*)RemoteThreadRoutine_Error;  
但是我们debug的结果是 “‘0x000a432c’指令引用的‘0x0000a432c’内存。该内存不能为‘written’”。可以见得我们写入的远线程代码存在问题。现在我们用windbg调试下。在调试前,我们先调整下VC代码为

[cpp]  view plain copy
  1. // 注入线程  
  2.                         pRemoteThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pBufferRemoteFun, pBufferParam, 0, NULL );  
  3.                         if ( NULL == pRemoteThread ) {  
  4.                             break;  
  5.                         }  
  6.                         ResumeThread( hThread );  
  7.                         // 等待远线程激活事件  
  8.                         if ( WAIT_OBJECT_0 != WaitForSingleObject( hEvent, 10 * 1000 ) ) {  
  9.                             // 等待出错,退出进程  
  10.                             //::TerminateProcess( hProcess, 0 );  
  11.                         }  
  12.                         else {  
  13.                             // 等待成功,恢复进程  
  14.                             ResumeThread( hThread );  
  15.                         }  
注意此处,我不会在线程执行失败后立即TerminateProcess,否则我们windbg准备调试被注入进程时,被注入进程可能已经就被杀掉了。而且因为之前我们是以挂起方式创建被注入进程的,所以在执行完创建远线程后,要ResumeThread主线程。否则我们在远线程挂了后,windbg没法挂到任何一个线程上。调试的过程是:

       1 用VC在CreateRemoteThread上下断点,F5,断到这个函数执行前。记下pBufferRemoteFun的值。

       2 用windbg附加到被注入进程上。

       3 在VC中F5,让被注入进程出现错误,以让windbg捕获。

       4 在windbg中F5。

会出现以下信息

[plain]  view plain copy
  1. (d20.8d8): Break instruction exception - code 80000003 (first chance)  
  2. eax=002d1eb4 ebx=7ffdb000 ecx=00000003 edx=00000008 esi=002d1f48 edi=002d1eb4  
  3. eip=7c92120e esp=0007fb20 ebp=0007fc94 iopl=0         nv up ei pl nz na po nc  
  4. cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000202  
  5. _no_process_!DbgBreakPoint:  
  6. 7c92120e cc              int     3  
看不出什么信息。但是我们可以借助pBufferRemoteFun的值(假设为0x000a0000 ),在windbg命令框中输入0x000a0000 0x000a0100。出现如下信息

[plain]  view plain copy
  1. 000a0000 e927430000      jmp     000a432c  
  2. 000a0005 e902360000      jmp     000a360c  
  3. 000a000a e9ad6d0000      jmp     000a6dbc  
  4. ……  

这是神马?这是debug环境下增量编译(incremental linking)的一种表现。这儿说说增量编译,增量编译如同在“调用”和“函数执行逻辑”之间插入一个“地址转换层”。比如我们有个函数A,我们调用A的汇编是Call 0x00ABCDEF,那么修改A函数代码后编译,这个0x00ABCDEF地址会发生改变。因为Call指令分为两步,其中第二步是jmp到A函数逻辑的入口点,jmp的偏移是需要计算的。于是我们频繁修改A的函数逻辑,会导致频繁的计算A函数逻辑偏移地址(想想整个PE文件中所有调用都要再算一次jmp偏移是不是很浪费)。于是一种解决方案是,调用A时就Call一个固定地址,该地址指令是jmp到一个固定的地址,这个地址保存的是真实调用A的代码。这样每次编译只用修改“转换层”中的jmp偏移即可。

        回到我们的问题,用u 0x000a432c可以发现这个内存空间不存在汇编代码。因为我们写错内容了,我们要写入的远线程函数的逻辑代码。那么我们将jmp过滤掉。

[cpp]  view plain copy
  1. // JMP过渡  
  2. if ( (char)0xE9 == *pRmtRoutine ) {  
  3.     DWORD dwOffset = 0;  
  4.     memcpy_s( &dwOffset, sizeof(DWORD), pRmtRoutine + 1, sizeof(DWORD) );  
  5.     // 偏移,1为0xE9,4为dwoffset大小  
  6.     pRmtRoutine = pRmtRoutine + 1 + 4 + dwOffset;  
  7. }  
再试一下我们程序。还是报错,这次错误是“'0x000a0053'指令引用的'0x00424788'内存。该内存不能为'read'”。使用刚才的调试方法,我们发现我们注入的代码如下

[plain]  view plain copy
  1. 000a0000 55              push    ebp  
  2. 000a0001 8bec            mov     ebp,esp  
  3. 000a0003 81ece4000000    sub     esp,0E4h  
  4. 000a0009 53              push    ebx  
  5. 000a000a 56              push    esi  
  6. 000a000b 57              push    edi  
  7. 000a000c 8dbd1cffffff    lea     edi,[ebp-0E4h]  
  8. 000a0012 b939000000      mov     ecx,39h  
  9. 000a0017 b8cccccccc      mov     eax,0CCCCCCCCh  
  10. 000a001c f3ab            rep stos dword ptr es:[edi]  
  11. 000a001e 837d0800        cmp     dword ptr [ebp+8],0  
  12. 000a0022 7507            jne     000a002b  
  13. 000a0024 33c0            xor     eax,eax  
  14. 000a0026 e983000000      jmp     000a00ae  
  15. 000a002b 8b4508          mov     eax,dword ptr [ebp+8]  
  16. 000a002e 8945f8          mov     dword ptr [ebp-8],eax  
  17. 000a0031 8b45f8          mov     eax,dword ptr [ebp-8]  
  18. 000a0034 833800          cmp     dword ptr [eax],0  
  19. 000a0037 7504            jne     000a003d  
  20. 000a0039 33c0            xor     eax,eax  
  21. 000a003b eb71            jmp     000a00ae  
  22. 000a003d 8b45f8          mov     eax,dword ptr [ebp-8]  
  23. 000a0040 83780400        cmp     dword ptr [eax+4],0  
  24. 000a0044 7504            jne     000a004a  
  25. 000a0046 33c0            xor     eax,eax  
  26. 000a0048 eb64            jmp     000a00ae  
  27. 000a004a 8b45f8          mov     eax,dword ptr [ebp-8]  
  28. 000a004d 83c008          add     eax,8  
  29. 000a0050 8bf4            mov     esi,esp  
  30. 000a0052 50              push    eax  
  31. 000a0053 ff1588474200    call    dword ptr ds:[424788h]  
  32. 000a0059 3bf4            cmp     esi,esp  
  33. 000a005b e8c0bbffff      call    0009bc20  
  34. 000a0060 8945ec          mov     dword ptr [ebp-14h],eax  
  35. 000a0063 837dec00        cmp     dword ptr [ebp-14h],0  
  36. 000a0067 7504            jne     000a006d  
  37. 000a0069 33c0            xor     eax,eax  
  38. 000a006b eb41            jmp     000a00ae  
  39. 000a006d 8b45f8          mov     eax,dword ptr [ebp-8]  
  40. 000a0070 0510020000      add     eax,210h  
  41. 000a0075 8bf4            mov     esi,esp  
  42. 000a0077 50              push    eax  
  43. 000a0078 8b4dec          mov     ecx,dword ptr [ebp-14h]  
  44. 000a007b 51              push    ecx  
  45. 000a007c ff1504474200    call    dword ptr ds:[424704h]  
  46. 000a0082 3bf4            cmp     esi,esp  
  47. 000a0084 e897bbffff      call    0009bc20  
  48. 000a0089 8945e0          mov     dword ptr [ebp-20h],eax  
  49. 000a008c 837de000        cmp     dword ptr [ebp-20h],0  
  50. 000a0090 7504            jne     000a0096  
  51. 000a0092 33c0            xor     eax,eax  
  52. 000a0094 eb18            jmp     000a00ae  
  53. 000a0096 8bf4            mov     esi,esp  
  54. 000a0098 8b45f8          mov     eax,dword ptr [ebp-8]  
  55. 000a009b 8b8850020000    mov     ecx,dword ptr [eax+250h]  
  56. 000a00a1 51              push    ecx  
  57. 000a00a2 ff55e0          call    dword ptr [ebp-20h]  
  58. 000a00a5 3bf4            cmp     esi,esp  
  59. 000a00a7 e874bbffff      call    0009bc20  
  60. 000a00ac 33c0            xor     eax,eax  
  61. 000a00ae 5f              pop     edi  
  62. 000a00af 5e              pop     esi  
  63. 000a00b0 5b              pop     ebx  
  64. 000a00b1 81c4e4000000    add     esp,0E4h  
  65. 000a00b7 3bec            cmp     ebp,esp  
  66. 000a00b9 e862bbffff      call    0009bc20  
  67. 000a00be 8be5            mov     esp,ebp  
  68. 000a00c0 5d              pop     ebp  
  69. 000a00c1 c20400          ret     4  
我们查看之前报错的0x000a0053行call    dword ptr ds:[424704h],这个函数地址不是被注入进程空间的函数地址,像之后000a007c ff1504474200    call    dword ptr ds:[424704h]也是会报错的。那么这两个函数是啥?我在VC中Alt+8查看远线程函数的汇编代码,可以发现call    dword ptr ds:[424704h]对应于LoadLibraryW这个函数。0x00424704h保存的是0x7c80aeeb。我们用windbg加载并运行注入进程的PE文件,break后查看相关地址命令

[plain]  view plain copy
  1. 0:002> u 0x7c80aeeb  
  2. kernel32!LoadLibraryW:  
  3. 7c80aeeb 8bff            mov     edi,edi  
  4. 7c80aeed 55              push    ebp  
  5. 7c80aeee 8bec            mov     ebp,esp  
  6. 7c80aef0 6a00            push    0  
  7. 7c80aef2 6a00            push    0  
  8. 7c80aef4 ff7508          push    dword ptr [ebp+8]  
  9. 7c80aef7 e8f96bffff      call    kernel32!LoadLibraryExW (7c801af5)  
  10. 7c80aefc 5d              pop     ebp  
[plain]  view plain copy
  1. 0:002> u 0x7c801af5  
  2. kernel32!LoadLibraryExW:  
  3. 7c801af5 e9f2e7cf84      jmp     015002ec  
  4. 7c801afa 807ce8d509      cmp     byte ptr [eax+ebp*8-2Bh],9  
  5. 7c801aff 0000            add     byte ptr [eax],al  
  6. 7c801b01 33ff            xor     edi,edi  
  7. 7c801b03 897dd8          mov     dword ptr [ebp-28h],edi  
  8. 7c801b06 897dd4          mov     dword ptr [ebp-2Ch],edi  
  9. 7c801b09 897de0          mov     dword ptr [ebp-20h],edi  
  10. 7c801b0c 897de4          mov     dword ptr [ebp-1Ch],edi  
可以想到,0x7c80aeeb是Kernel32.dll文件在该进程中LoadLibrary的函数入口地址。所以我们call    dword ptr ds:[424704h]时,被注入进程中424704h保存的是啥是不确定的。但是,如我在前一节介绍的,windows程序加载kernel32.dll的基地址一般是一样的,于是我们要是将0x7c80aeeb这个值直接传给远线程,应该就可以了。同样的问题存在于我们之前对GetProcAddress的调用,于是我们将这些函数地址以参数形式传入被注入进程。

[cpp]  view plain copy
  1. RemoteThreadRountineParam RmtThdRtParam;  
  2.         RmtThdRtParam.lpGetProcAddress = GetProcAddress;  
  3.         RmtThdRtParam.lpLoadLibraryW = LoadLibraryW;  
  4.         wmemset( RmtThdRtParam.wszDllPath, 0, MAX_PATH );  
  5.         wcscpy_s( RmtThdRtParam.wszDllPath, MAX_PATH, lpDllPath );  
  6.         std::string strFuncName = "ExportFun";  
  7.         memset( RmtThdRtParam.szFuncName, 0 , MAX_FUNCNAMELENGTH );  
  8.         memcpy_s( RmtThdRtParam.szFuncName, MAX_FUNCNAMELENGTH, strFuncName.c_str(), strFuncName.length() );  
远线程的代码改为

[cpp]  view plain copy
  1. HMODULE hHookDll = (lpRmtParam->lpLoadLibraryW)(lpRmtParam->wszDllPath);  
  2. if ( NULL == hHookDll )  
  3. {  
  4.     return 0;  
  5. }  
  6.   
  7. LPExportFun lpExportFunAddr = (LPExportFun)(lpRmtParam->lpGetProcAddress)( hHookDll, lpRmtParam->szFuncName );  
  8. if ( NULL == lpExportFunAddr )  
  9. {  
  10.     return 0;  
  11. }  
  12.   
  13. lpExportFunAddr( lpRmtParam->hEvent );  

F5。还是报错。这次的错误是“'0x0009bc20'指令引用的'0x0009bc20'内存。该内存不能为'written'”。继续使用之前的调试方法,发现我们注入的代码中有如下一行

[plain]  view plain copy
  1. 000a0053 8b4df8          mov     ecx,dword ptr [ebp-8]  
  2. 000a0056 8b11            mov     edx,dword ptr [ecx]  
  3. 000a0058 ffd2            call    edx  
  4. 000a005a 3bf4            cmp     esi,esp  
  5. 000a005c e8bfbbffff      call    0009bc20  
  6. 000a0061 8945ec          mov     dword ptr [ebp-14h],eax  
  7. 000a0064 837dec00        cmp     dword ptr [ebp-14h],0  
  8. 000a0068 7504            jne     000a006e  
我们再在VS中查看我们的远线程反汇编代码有如下

[plain]  view plain copy
  1. 00415A2C E8 BF BB FF FF   call        @ILT+1515(__RTC_CheckEsp) (4115F0h)   

E8BFBBFFFF这条指令是引起被注入进程崩溃的原因,这指令是RTC检查函数,默认情况下VC会给我们的代码做些手脚,这个就是个例子。我们对远线程代码关闭RTC检查。

[cpp]  view plain copy
  1. #pragma runtime_checks( "scu", off )  
  2.   DWORD WINAPI RemoteThreadRoutine_Error( LPVOID lpParam )  
  3.     {  
  4.       ……  
  5.     }  
  6. #pragma runtime_checks( "scu", restore )  
运行之,OK了。
这个过程很忐忑,但是如果不想研究这个,可以选择内嵌汇编方式。因为RTC检查不会在Release版本中做,所以我们可以将远线程函数本地执行一次,在函数的入口处int 3一下,然后用windbg或ollydbg启动之,断在函数入口点,然后我们把其汇编东东扒拉下来就行了。当然会写汇编的同学就直接动手写汇编代码就行了。

[plain]  view plain copy
  1. __declspec(naked) DWORD WINAPI RemoteThreadRoutineASM( LPVOID lpParam )  
  2.     {  
  3.         __asm  
  4.         {  
  5.             push esi  
  6.             // 检测指针参数  
  7.             mov     esi, [esp+8]  
  8.             pushad  
  9.             pushfd  
  10.             test    esi, esi  
  11.             jz      short End  
  12.             // 检测参数第一个成员  
  13.             mov     eax, [esi+4]  
  14.             test    eax, eax  
  15.             jz      short End  
  16.             // 检测参数第二个成员  
  17.             mov     eax, [esi]  
  18.             test    eax, eax  
  19.             jz      short End  
  20.               
  21.             lea     ecx, [esi+8]  
  22.             push    ecx  
  23.             call    eax  
  24.   
  25.             test    eax,eax  
  26.             jz      short End  
  27.   
  28.             lea     edx, [esi+210h]  
  29.             push    edx  
  30.             push    eax  
  31.             mov     eax, [esi+4]  
  32.             call    eax  
  33.   
  34.             test    eax,eax  
  35.             jz      short End  
  36.   
  37.             mov     ecx, [esi+250h]  
  38.             push    ecx  
  39.             call    eax  
  40.   
  41. End:  
  42.             xor     eax, eax  
  43.             popfd  
  44.             popad  
  45.             pop     esi  
  46.             retn    8  
  47.   
  48.         }  
  49.           
  50.     }  
一定要加__declspec(naked),否则起不来哦!
(转载请指明出处)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值