线程注入代码算是很老的技术,但是涉及的东西还是挺多的,所以不失其重要性。以前走了很多弯路,一句话,工具黑客坑害求学之人。而后很冲动的转了专业,投奔了“妓院”。正儿八经的学编程已经有一年多半了,来看雪就更有年头了(还真忘了怎么弄到的注册号)。坛子多是是ring0的东西,也在偶尔写写驱动代码。貌似走题了,赶紧转正。头一次发原创帖,貌似没啥新东西,就是总结一下,权当给和我一样的小菜看。不喜勿批,嘿嘿!
  代码注入的重要思想是自定义函数的代码在远程进程空间执行。要做到这点,首先要在远程空间注入自己的代码和参数。所以,我们首先要明确我们分开注入代码和参数,当然参数也可以在代码里,下边会给予提示。我们已最简单的在远程线程执行MessageBox为例
  1,参数的注入
  我们很容易的定义一个结构体
  #pragma pack(1)
  typedef struct _RemoteParam
  {
    GetModuleHandle_Fun reGetModuleHandle; 
    LoadLibrary_Fun reLoadLibrary;
    GetProcAddress_Fun reGetProcAddress;
    char szUser32Dll[11];
    char szMessageBox[12];
    char szText[20];
    char szCaption[20];
  }RemoteParam,*PRemoteParam;
  #pragma pack()
   对于GetModuleHandle,GetProcAddress和LoadLibrary这三个函数,可以在目标进程的kernel32.dll的导出表内搜索,本例从简,直接写入(由于kernel32.dll在不同的进程中GetModuleHandle得到的hMod都是一样的,所以,它的GetModuleHandle,GetProcAddress和LoadLibrary的实现部分在不同进程的地址也是一样的)。为了告诉编译器这三个地址是三个如它们定义一样的函数,所以我们姑且定义一下,
   typedef FARPROC (__stdcall * GetProcAddress_Fun)(
             HMODULE hModule,    // handle to DLL module
             LPCSTR lpProcName   // name of function
             );
typedef HINSTANCE (__stdcall *LoadLibrary_Fun)(
            LPCTSTR lpLibFileName   // address of filename of executable module
            );
typedef HMODULE (__stdcall *GetModuleHandle_Fun)(
            LPCTSTR lpModuleName   // address of module name to return handle 
            // for
            );
如果你不想自己定义,那么完全可以,在你自定义函数体中的嵌入汇编,用压栈call调用的方式也可以。在此不再赘述。函数的定义这里有一点特别要注意的,就是参数的调用约定为题 __stdcall不能丢弃,否则破坏了堆栈,生成的debug程序运行会出错,而release虽然不报错但关掉注入程序,母体程序也会关闭。如果你od玩转的很好的,我想你很容易处理一下,在出现出错的程序弹出框后完全可以不关闭它,用od附加,定位到错误代码,自己看看植入代码出错问题。有一点要说的是debug版本有检测堆栈,汇编代码是cmp esi,esp,这个经常是拦路虎,在inline hook中经常因为它debug版本程序不能正常运行,嘿嘿。
00401000   .  56            push esi
00401001   .  8B7424 08     mov esi,dword ptr ss:[esp+0x8]  //通过esp寻参
00401005   .  8D46 0C       lea eax,dword ptr ds:[esi+0xC]  //esi指向的就是我们注入的
00401008   .  50            push eax                    //参数的开始地址
00401009   .  FF16          call dword ptr ds:[esi]     //GetModuleHandle
0040100B   .  8D4E 17       lea ecx,dword ptr ds:[esi+0x17]
0040100E   .  51            push ecx
0040100F   .  50            push eax
00401010   .  FF56 08       call dword ptr ds:[esi+0x8]  //GetProcAddress
00401013   .  6A 00         push 0x0
00401015   .  6A 00         push 0x0
00401017   .  6A 00         push 0x0
00401019   .  6A 00         push 0x0
0040101B   .  FFD0          call eax
0040101D   .  83C4 1C       add esp,0x1C
00401020   .  5E            pop esi
00401021   .  C3            retn
如果你不加__stdcall往往是上边贴出那样(release版本)的,add esp,0x1c明显是错误的,我们调用的是api,除了wsprintf等少数函数外,通常api都是在函数内部实现堆栈平衡的。
上边已经讲解了参数所能遇到的问题。开头我提过,我们完全不将函数参数和代码分开写入,对于这点,在汇编内很容易实现,我们可以在函数内分配空间,分配空间的伪指令就是 db,dd 我们可以 dd出GetProcAddress等三个函数,可以db出szString等变量名。在ide中,以vc为例,我们可以通过 _emit伪指令实现db功能,加个标签就是函数变量的地址,例如bingle: __emit 0
        __emit 0
        __emit 0
        __emit 0这样就在函数内部定义了一个存储GetProcAddress等函数地址的变量,
当然,你想这样的话,你得对vc中汇编操作比较熟练。
2,代码部分的注入,此部分比较关键。
CreateRemoteThread函数允许我们把我们的参数部分传入,当然我看到过另一种定位参数部分的方法。待会给出。
 首先也是在远程空间分配空间,VirtualAllocEx出代码部分大小的空间。这里涉及到函数长度的问题,貌似我在坛子里问过,大牛说反汇编引擎最精确,可以帮我实现这点,可本人还没研究到那块,所以,我们也不估量了直接分配一页4*1024大小的代码空间,足够用。当然节省空间粗略计算的方法我也给出吧。代码的自定位和staic内存紧密排布。
Static void InjectCode(RemoteParam *param)
{
 
}
Static dword __declspec(naked) GetBeginAddr()
{
    __asm
    {
        call lbl_Next
lbl_Next:
        pop eax
        sub eax, 5 
        ret
    }
}
函数的长度就是GetBeginAddr的return值减去InjectCode。裸函数内部不能定义变量,否则可以直接把InjectCode定义为裸函数,那么计算函数的长度就容易多了,哈哈!
另一种定位参数的方法是将代码部分和参数部分放在一页里边,这完全可以通过Virtualalloc这个函数实现。然后通过自定位的方法在定位参数。给出代码。
static LRESULT CALLBACK NewProc(
  HWND hwnd,      // handle to window
  UINT uMsg,      // message identifier
  WPARAM wParam,  // first message parameter
  LPARAM lParam )  // second message parameter
{  
    
  INJDATA* pData;  
  _asm {        
    call  dummy
dummy:
    pop    ecx      // <- ECX contains the current EIP (instruction pointer);
    sub    ecx, OFFSET  // <- ECX contains the address of NewProc;
    mov    pData, ecx
  }
  pData--;  
}
内存排布:Data数据,NewProc函数。pData--直接定位到参数部分,是不是很神奇呀,哈哈。
接下来是注入的函数体部分的注意问题了。我们不可以在自己实现的要注入的函数内部定义全局变量和保留字符串等操作。原因就是全局变量和字符串是在你程序的进程空间而不是在母体(宿主体内)中的。
测试的注入代码(注入的是notepad和酷我等,所以没有loadlibrary入user32.dll):
void bingle(RemoteParam * remoteParam)
{
  HMODULE hMod = remoteParam->reGetModuleHandle(remoteParam->szUser32Dll);
    MessageBox_Fun myMessageBox =     (MessageBox_Fun)remoteParam->reGetProcAddress(hMod,remoteParam->szMessageBox);
    myMessageBox(NULL,remoteParam->szText,remoteParam->szCaption,NULL);
  }
对应的汇编代码:
004010A0   .  56            push esi
004010A1   .  8B7424 08     mov esi,dword ptr ss:[esp+0x8] 
//[esp] = 原esi,[esp+4] = eip,[esp+8] = remoteParam 
004010A5   .  8D46 0C       lea eax,dword ptr ds:[esi+0xC]
//eax = esi+0xc = RemoteParam的第四个变量
004010A8   .  50            push eax
004010A9   .  FF16          call dword ptr ds:[esi]
//调用GetModuleHandle
004010AB   .  8D4E 17       lea ecx,dword ptr ds:[esi+0x17]
004010AE   .  51            push ecx
004010AF   .  50            push eax
004010B0   .  FF56 08       call dword ptr ds:[esi+0x8]
//调用GetProcAddress
004010B3   .  8D56 37       lea edx,dword ptr ds:[esi+0x37]
004010B6   .  6A 00         push 0x0
004010B8   .  83C6 23       add esi,0x23
004010BB   .  52            push edx
004010BC   .  56            push esi
004010BD   .  6A 00         push 0x0
004010BF   .  FFD0          call eax
//调用MessageBox
004010C1   .  5E            pop esi
004010C2   .  C3            retn

这里要说的是想要玩转注入代码,这部分汇编代码的分析跟外重要。纠错就是从此处下手。上边给出简略分析,很容易知道,完全是对应RemoteParam结构体的。
还有要注意的问题是api函数的宽字符和窄字符的问题,比如GetProcAddress(hMod,"GetProcAddress");可见GetProcAddress没有对应的宽窄函数(貌似这么称呼很不专业呀,呵呵,理解万岁),我的处理方法是。在od的cmdline里边测试一下,比如你dd LoadLibrary会显示“未知标示符”,可见它是LoadLibraryA 和 LoadLibraryW两种的。这是我处理的方法,其它函数一样。坛友有好的方法,可以告诉我一下,谢谢啦。
3,参数也注入了,代码也注入了,剩下的就是CreateRemoteThread,执行一下子啦。呵呵,这个不消说,观者应该没问题的。还有就是QueueUserApc也可以执行注入代码,只要你考虑了远程空间是够有你要用的参数问题,还有堆栈问题,一切都简单了,哈哈!
测试结果,od打开枚举进程那块的酷狗活动窗口是test(MessageBox的标题),MessageBox的任务栏图标icon也换成它爹kugou的图标了,呵呵!