在前一节中介绍了通过远线程不带参数的方式提前注入进程,现在介绍种远线程携带参数的方法。(转载请指明出处)
1.2 执行注入的进程需要传信息给被注入进程
因为同样采用的是远线程注入,所以大致的思路是一样的,只是在细节上要注意一些处理。总体来说分为以下几个步骤:
1 将需要传递的信息写入被注入进程的地址空间。
2 将远线程函数体写入被注入进程的空间。
3 在被注入进程中执行该远线程函数,让该线程利用我们之前写入的参数完成任务。
在被注入进程的地址空间中写入“需要传递”的信息不存在什么问题,因为该信息是”死的“”数据“,我们写入什么内容就是什么内容,它就是二进制数据。但是写入函数执行体就存在一定的问题。首先我们要考虑用什么语言来写这个函数?我是VC程序员,当然优先选择C++/C。可是使用这些语言往往会存在问题,因为我们不知道编译器对我们的代码可能做了什么手脚。下面我来验证下
- typedef struct _RemoteThreadRountineParam_
- {
- //LPLoadLibrary lpLoadLibraryW;
- //LPGetProcAddr lpGetProcAddress;
- WCHAR wszDllPath[MAX_PATH];
- CHAR szFuncName[MAX_FUNCNAMELENGTH];
- HANDLE hEvent;
- }RemoteThreadRountineParam,*pRemoteThreadRountineParam;
- DWORD WINAPI RemoteThreadRoutine_Error( LPVOID lpParam )
- {
- if ( NULL == lpParam )
- {
- return 0;
- }
- pRemoteThreadRountineParam lpRmtParam = (pRemoteThreadRountineParam) lpParam;
- if ( NULL == lpRmtParam->lpLoadLibraryW )
- {
- return 0;
- }
- if ( NULL == lpRmtParam->lpGetProcAddress )
- {
- return 0;
- }
- HMODULE hHookDll = LoadLibraryW(lpRmtParam->wszDllPath);
- if ( NULL == hHookDll )
- {
- return 0;
- }
- LPExportFun lpExportFunAddr = (LPExportFun)GetProcAddress( hHookDll, lpRmtParam->szFuncName );
- if ( NULL == lpExportFunAddr )
- {
- return 0;
- }
- lpExportFunAddr( lpRmtParam->hEvent );
- return 0;
- }
- do {
- // 写入线程例程代码
- // 分配内存空间
- pBufferRemoteFun = VirtualAllocEx( hProcess, NULL, dwFunMemSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE );
- if ( NULL == pBufferRemoteFun ) {
- break;
- }
- // 将信息写入傀儡进程的内存地址空间
- if ( FALSE == WriteProcessMemory( hProcess, pBufferRemoteFun, pRmtRoutine, dwFunMemSize, NULL ) ) {
- break;
- }
- do {
- pBufferParam = VirtualAllocEx( hProcess, NULL, dwParamMemSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE );
- if ( NULL == pBufferParam ) {
- break;
- }
- if ( FALSE == WriteProcessMemory( hProcess, pBufferParam, &RmtThdRtParam, dwParamMemSize, NULL ) ) {
- break;
- }
- do {
- // 注入线程
- pRemoteThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pBufferRemoteFun, pBufferParam, 0, NULL );
- if ( NULL == pRemoteThread ) {
- break;
- }
这段逻辑分别对应于刚介绍的三个步骤。其中dwFunMemSize是我们定义的一个”足够大“的空间大小,因为我这儿没有计算准确的函数执行体大小(其实我也不知道怎么去计算这个大小)。因为我们的函数执行体代码是要执行的,所以我们申请的空间是具有EXECUTE属性的。pBufferRemoteFun是指向远线程函数执行体的在”远程“的空间。pRmtRoutine是指向远线程函数执行体的在”本地“的空间。其他没有什么好介绍的,我们将主要的注意力放在pRmtRoutine。
最简单的方式是
- char* pRmtRoutine = (char*)RemoteThreadRoutine_Error;
- // 注入线程
- pRemoteThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pBufferRemoteFun, pBufferParam, 0, NULL );
- if ( NULL == pRemoteThread ) {
- break;
- }
- ResumeThread( hThread );
- // 等待远线程激活事件
- if ( WAIT_OBJECT_0 != WaitForSingleObject( hEvent, 10 * 1000 ) ) {
- // 等待出错,退出进程
- //::TerminateProcess( hProcess, 0 );
- }
- else {
- // 等待成功,恢复进程
- ResumeThread( hThread );
- }
1 用VC在CreateRemoteThread上下断点,F5,断到这个函数执行前。记下pBufferRemoteFun的值。
2 用windbg附加到被注入进程上。
3 在VC中F5,让被注入进程出现错误,以让windbg捕获。
4 在windbg中F5。
会出现以下信息
- (d20.8d8): Break instruction exception - code 80000003 (first chance)
- eax=002d1eb4 ebx=7ffdb000 ecx=00000003 edx=00000008 esi=002d1f48 edi=002d1eb4
- eip=7c92120e esp=0007fb20 ebp=0007fc94 iopl=0 nv up ei pl nz na po nc
- cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000202
- _no_process_!DbgBreakPoint:
- 7c92120e cc int 3
- 000a0000 e927430000 jmp 000a432c
- 000a0005 e902360000 jmp 000a360c
- 000a000a e9ad6d0000 jmp 000a6dbc
- ……
这是神马?这是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过滤掉。
- // JMP过渡
- if ( (char)0xE9 == *pRmtRoutine ) {
- DWORD dwOffset = 0;
- memcpy_s( &dwOffset, sizeof(DWORD), pRmtRoutine + 1, sizeof(DWORD) );
- // 偏移,1为0xE9,4为dwoffset大小
- pRmtRoutine = pRmtRoutine + 1 + 4 + dwOffset;
- }
- 000a0000 55 push ebp
- 000a0001 8bec mov ebp,esp
- 000a0003 81ece4000000 sub esp,0E4h
- 000a0009 53 push ebx
- 000a000a 56 push esi
- 000a000b 57 push edi
- 000a000c 8dbd1cffffff lea edi,[ebp-0E4h]
- 000a0012 b939000000 mov ecx,39h
- 000a0017 b8cccccccc mov eax,0CCCCCCCCh
- 000a001c f3ab rep stos dword ptr es:[edi]
- 000a001e 837d0800 cmp dword ptr [ebp+8],0
- 000a0022 7507 jne 000a002b
- 000a0024 33c0 xor eax,eax
- 000a0026 e983000000 jmp 000a00ae
- 000a002b 8b4508 mov eax,dword ptr [ebp+8]
- 000a002e 8945f8 mov dword ptr [ebp-8],eax
- 000a0031 8b45f8 mov eax,dword ptr [ebp-8]
- 000a0034 833800 cmp dword ptr [eax],0
- 000a0037 7504 jne 000a003d
- 000a0039 33c0 xor eax,eax
- 000a003b eb71 jmp 000a00ae
- 000a003d 8b45f8 mov eax,dword ptr [ebp-8]
- 000a0040 83780400 cmp dword ptr [eax+4],0
- 000a0044 7504 jne 000a004a
- 000a0046 33c0 xor eax,eax
- 000a0048 eb64 jmp 000a00ae
- 000a004a 8b45f8 mov eax,dword ptr [ebp-8]
- 000a004d 83c008 add eax,8
- 000a0050 8bf4 mov esi,esp
- 000a0052 50 push eax
- 000a0053 ff1588474200 call dword ptr ds:[424788h]
- 000a0059 3bf4 cmp esi,esp
- 000a005b e8c0bbffff call 0009bc20
- 000a0060 8945ec mov dword ptr [ebp-14h],eax
- 000a0063 837dec00 cmp dword ptr [ebp-14h],0
- 000a0067 7504 jne 000a006d
- 000a0069 33c0 xor eax,eax
- 000a006b eb41 jmp 000a00ae
- 000a006d 8b45f8 mov eax,dword ptr [ebp-8]
- 000a0070 0510020000 add eax,210h
- 000a0075 8bf4 mov esi,esp
- 000a0077 50 push eax
- 000a0078 8b4dec mov ecx,dword ptr [ebp-14h]
- 000a007b 51 push ecx
- 000a007c ff1504474200 call dword ptr ds:[424704h]
- 000a0082 3bf4 cmp esi,esp
- 000a0084 e897bbffff call 0009bc20
- 000a0089 8945e0 mov dword ptr [ebp-20h],eax
- 000a008c 837de000 cmp dword ptr [ebp-20h],0
- 000a0090 7504 jne 000a0096
- 000a0092 33c0 xor eax,eax
- 000a0094 eb18 jmp 000a00ae
- 000a0096 8bf4 mov esi,esp
- 000a0098 8b45f8 mov eax,dword ptr [ebp-8]
- 000a009b 8b8850020000 mov ecx,dword ptr [eax+250h]
- 000a00a1 51 push ecx
- 000a00a2 ff55e0 call dword ptr [ebp-20h]
- 000a00a5 3bf4 cmp esi,esp
- 000a00a7 e874bbffff call 0009bc20
- 000a00ac 33c0 xor eax,eax
- 000a00ae 5f pop edi
- 000a00af 5e pop esi
- 000a00b0 5b pop ebx
- 000a00b1 81c4e4000000 add esp,0E4h
- 000a00b7 3bec cmp ebp,esp
- 000a00b9 e862bbffff call 0009bc20
- 000a00be 8be5 mov esp,ebp
- 000a00c0 5d pop ebp
- 000a00c1 c20400 ret 4
- 0:002> u 0x7c80aeeb
- kernel32!LoadLibraryW:
- 7c80aeeb 8bff mov edi,edi
- 7c80aeed 55 push ebp
- 7c80aeee 8bec mov ebp,esp
- 7c80aef0 6a00 push 0
- 7c80aef2 6a00 push 0
- 7c80aef4 ff7508 push dword ptr [ebp+8]
- 7c80aef7 e8f96bffff call kernel32!LoadLibraryExW (7c801af5)
- 7c80aefc 5d pop ebp
- 0:002> u 0x7c801af5
- kernel32!LoadLibraryExW:
- 7c801af5 e9f2e7cf84 jmp 015002ec
- 7c801afa 807ce8d509 cmp byte ptr [eax+ebp*8-2Bh],9
- 7c801aff 0000 add byte ptr [eax],al
- 7c801b01 33ff xor edi,edi
- 7c801b03 897dd8 mov dword ptr [ebp-28h],edi
- 7c801b06 897dd4 mov dword ptr [ebp-2Ch],edi
- 7c801b09 897de0 mov dword ptr [ebp-20h],edi
- 7c801b0c 897de4 mov dword ptr [ebp-1Ch],edi
- RemoteThreadRountineParam RmtThdRtParam;
- RmtThdRtParam.lpGetProcAddress = GetProcAddress;
- RmtThdRtParam.lpLoadLibraryW = LoadLibraryW;
- wmemset( RmtThdRtParam.wszDllPath, 0, MAX_PATH );
- wcscpy_s( RmtThdRtParam.wszDllPath, MAX_PATH, lpDllPath );
- std::string strFuncName = "ExportFun";
- memset( RmtThdRtParam.szFuncName, 0 , MAX_FUNCNAMELENGTH );
- memcpy_s( RmtThdRtParam.szFuncName, MAX_FUNCNAMELENGTH, strFuncName.c_str(), strFuncName.length() );
- HMODULE hHookDll = (lpRmtParam->lpLoadLibraryW)(lpRmtParam->wszDllPath);
- if ( NULL == hHookDll )
- {
- return 0;
- }
- LPExportFun lpExportFunAddr = (LPExportFun)(lpRmtParam->lpGetProcAddress)( hHookDll, lpRmtParam->szFuncName );
- if ( NULL == lpExportFunAddr )
- {
- return 0;
- }
- lpExportFunAddr( lpRmtParam->hEvent );
F5。还是报错。这次的错误是“'0x0009bc20'指令引用的'0x0009bc20'内存。该内存不能为'written'”。继续使用之前的调试方法,发现我们注入的代码中有如下一行
- 000a0053 8b4df8 mov ecx,dword ptr [ebp-8]
- 000a0056 8b11 mov edx,dword ptr [ecx]
- 000a0058 ffd2 call edx
- 000a005a 3bf4 cmp esi,esp
- 000a005c e8bfbbffff call 0009bc20
- 000a0061 8945ec mov dword ptr [ebp-14h],eax
- 000a0064 837dec00 cmp dword ptr [ebp-14h],0
- 000a0068 7504 jne 000a006e
- 00415A2C E8 BF BB FF FF call @ILT+1515(__RTC_CheckEsp) (4115F0h)
E8BFBBFFFF这条指令是引起被注入进程崩溃的原因,这指令是RTC检查函数,默认情况下VC会给我们的代码做些手脚,这个就是个例子。我们对远线程代码关闭RTC检查。
- #pragma runtime_checks( "scu", off )
- DWORD WINAPI RemoteThreadRoutine_Error( LPVOID lpParam )
- {
- ……
- }
- #pragma runtime_checks( "scu", restore )
这个过程很忐忑,但是如果不想研究这个,可以选择内嵌汇编方式。因为RTC检查不会在Release版本中做,所以我们可以将远线程函数本地执行一次,在函数的入口处int 3一下,然后用windbg或ollydbg启动之,断在函数入口点,然后我们把其汇编东东扒拉下来就行了。当然会写汇编的同学就直接动手写汇编代码就行了。
- __declspec(naked) DWORD WINAPI RemoteThreadRoutineASM( LPVOID lpParam )
- {
- __asm
- {
- push esi
- // 检测指针参数
- mov esi, [esp+8]
- pushad
- pushfd
- test esi, esi
- jz short End
- // 检测参数第一个成员
- mov eax, [esi+4]
- test eax, eax
- jz short End
- // 检测参数第二个成员
- mov eax, [esi]
- test eax, eax
- jz short End
- lea ecx, [esi+8]
- push ecx
- call eax
- test eax,eax
- jz short End
- lea edx, [esi+210h]
- push edx
- push eax
- mov eax, [esi+4]
- call eax
- test eax,eax
- jz short End
- mov ecx, [esi+250h]
- push ecx
- call eax
- End:
- xor eax, eax
- popfd
- popad
- pop esi
- retn 8
- }
- }
(转载请指明出处)