通过修改程序入口点实现dll注入

    前两节中介绍了通过远线程进行注入的方法。现在换一种方法——修改进程入口点。 (转载请指明出处)

        在PE文件中,其中有个字段标识程序入口点位置。我们通过这个字段,到达程序入口点。PE文件的结构我这儿不讨论(我会在之后写关于PE文件的介绍和研究),我只列出一些和程序入口点有关的数据结构

  1. typedef struct _IMAGE_NT_HEADERS {  
  2.     DWORD Signature;  
  3.     IMAGE_FILE_HEADER FileHeader;  
  4.     IMAGE_OPTIONAL_HEADER32 OptionalHeader;  
  5. } IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;  
  1. typedef struct _IMAGE_OPTIONAL_HEADER {  
  2.     //  
  3.     // Standard fields.  
  4.     //  
  5.   
  6.     WORD    Magic;  
  7.     BYTE    MajorLinkerVersion;  
  8.     BYTE    MinorLinkerVersion;  
  9.     DWORD   SizeOfCode;  
  10.     DWORD   SizeOfInitializedData;  
  11.     DWORD   SizeOfUninitializedData;  
  12.     DWORD   AddressOfEntryPoint;  
  13.     DWORD   BaseOfCode;  
  14.     DWORD   BaseOfData;  
  15.   
  16.     //  
  17.     // NT additional fields.  
  18.     //  
  19.   
  20.     DWORD   ImageBase;  
  21.     ……  
  22. }  
其中ImageBase是程序加载的基址,AddressOfEntryPoint是代码执行的入口偏移。于是我们的程序入口点是
  1. PIMAGE_DOS_HEADER lpstDosHeader = (PIMAGE_DOS_HEADER)(LPSTR)lpMapFile;  
  2. PIMAGE_NT_HEADERS lpstNtHeaders = (PIMAGE_NT_HEADERS)((LPSTR)lpMapFile + lpstDosHeader->e_lfanew );  
  3. dwPEEntry = lpstNtHeaders->OptionalHeader.AddressOfEntryPoint + lpstNtHeaders->OptionalHeader.ImageBase;  
我们将从程序入口点开始搜索Call这个指令。这个地方存在一个经验,就是一般(如果没动手脚)我们代码第一个Call指令跟随的是一个函数偏移地址。我们用ollydbg打开mspaint.exe这个我们要注入的进程文件
[plain] view plain copy
  1. 01034BD7 > $  6A 70         push    70  
  2. 01034BD9   .  68 00740001   push    01007400  
  3. 01034BDE   .  E8 09040000   call    01034FEC  
用IDA打开之,可以看到
[plain] view plain copy
  1. public _wWinMainCRTStartup  
  2. .text:01034BD7 _wWinMainCRTStartup proc near  
  3. ……  
  4. .text:01034BD7                 push    70h  
  5. .text:01034BD9                 push    offset stru_1007400  
  6. .text:01034BDE                 call    __SEH_prolog  
这个特性很重要,我们在找到被注入进程的第一个call指令后,将之后的偏移量记下来,并计算出真实函数的起始地址。我们程序结束后再jmp到这个真实地址。当然不否认这个方案存在很大风险,比如第一call的不是偏移地址,而是一个寄存器中保存的地址,那么我们这个方案就挂了!
        我们得到第一个Call指令位置和Call的地址后,我们就可以考虑将我们的代码注入到傀儡中。因为我们这次要在代码中动态地修改注入的代码,于是我们需要使用ShellCode,毕竟汇编和01之间还是隔一层的。ShellCode也很好得到,我们写完汇编后,查看该处的16进制码即可。
  1. /* 
  2.             $ ==>    >  60              pushad 
  3.             $+1      >  9C              pushfd 
  4.             $+2      >  68 11111111     push    11111111  //加载的dll名称 
  5.             $+7      >  E8 444288A5     call    LoadLibraryW  //LoadLibraryW地址 
  6.             $+C      >  50              push    eax 
  7.             $+D      >  E8 444288A5     call    FreeLibrary  //FreeLibrary地址 
  8.             $+12     >  9D              popfd 
  9.             $+13     >  61              popad 
  10.             $+14     >- E9 495399B6     jmp     33333333  //跳转到第一个call函数开始 
  11.             */  
  12.             BYTE lpShellCode[] = {  
  13.             0x60,  
  14.             0x9c,  
  15.             0x68,0xcc,0xcc,0xcc,0xcc,  
  16.             0xe8,0xcc,0xcc,0xcc,0xcc,  
  17.             0x50,  
  18.             0xe8,0xcc,0xcc,0xcc,0xcc,  
  19.             0x9d,  
  20.             0x61,  
  21.             0xe9,0xcc,0xcc,0xcc,0xcc  
  22.             };  
OK,此处我们预留了4个地址,分别是LoadLibrary加载的DLL的路径的地址,LoadLibrary 和FreeLibrary的地址,以及真实Call函数地址的在ShellCode中的偏移量。下步我们填充这些数据。
  1. DWORD dwCallAddrOffset = 0;  
  2. if( FALSE == ReadProcessMemory( hProcess, lpFirstCallAddr, &dwCallAddrOffset, 4, NULL ) ) {  
  3.     break;  
  4. }  
  5.   
  6. // 计算call的目标函数地址  
  7. LPSTR lpRealFuncAddr = lpFirstCallAddr + 4 + dwCallAddrOffset;  
  8.   
  9. DWORD dwDllPathSize =  ( (DWORD) wcslen( lpDllPath ) ) * sizeof(WCHAR);  
  10. LPVOID lpDllPathAddr = VirtualAllocEx( hProcess, NULL, dwDllPathSize, MEM_COMMIT, PAGE_READWRITE );  
  11.   
  12. if( NULL == lpDllPathAddr ) {  
  13.     break;  
  14. }  
  15. if( FALSE == WriteProcessMemory( hProcess, lpDllPathAddr, lpDllPath, dwDllPathSize, NULL) ) {  
  16.     break;  
  17. }  
  18. LPLoadLibrary lpFuncLoadLibraryAddr = LoadLibraryW;  
  19. LPFreeLibrary lpFuncFreeLibraryAddr = FreeLibrary;  
  20. if ( NULL == lpFuncLoadLibraryAddr || NULL == FreeLibrary ) {  
  21.     break;  
  22. }  
  23.   
  24. size_t ShellCodeSize = strlen( (char*)lpShellCode ) + 1;  
  25. LPVOID lpShellCodeAddr = VirtualAllocEx( hProcess, NULL, ShellCodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE );  
  26.   
  27. if ( NULL == lpShellCodeAddr  ) {  
  28.     break;  
  29. }  
  30.   
  31. // 修改shellcode  
  32. // 填充DLL路径  
  33. memcpy( lpShellCode + 0x03, &lpDllPathAddr, 4 );  
  34.   
  35. // 填充LoadLibrary偏移  
  36. DWORD dwCallLoadLibraryOffset = (*(LPDWORD)&lpFuncLoadLibraryAddr) -( (*(LPDWORD)&lpShellCodeAddr) + 0x0C );  
  37. memcpy( lpShellCode + 0x08, &dwCallLoadLibraryOffset, 4 );  
  38.   
  39. // 填充FreeLibrary偏移  
  40. DWORD dwCallFreeLibraryOffset = (*(LPDWORD)&lpFuncFreeLibraryAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x12 );  
  41. memcpy( lpShellCode + 0x0E, &dwCallFreeLibraryOffset, 4 );  
  42.   
  43. // 填充返回地址偏移,即原来的Call地址  
  44. DWORD dwRealFuncOffset = (*(LPDWORD)&lpRealFuncAddr) - ( (*(LPDWORD)&lpShellCodeAddr) + 0x19 );  
  45. memcpy( lpShellCode + 0x15, &dwRealFuncOffset, 4  );  
  46.   
  47. // 写入shellcode  
  48. if( FALSE == WriteProcessMemory( hProcess, lpShellCodeAddr, lpShellCode, ShellCodeSize, NULL ) ) {  
  49.     break;  
  50. }  
在我们写入ShellCode后,我们可以拿到ShellCode的地址,然后我们算出它在第一个Call指令的偏移
  1. //计算call的新地址  
  2. DWORD dwNewCallAddrOffset = (*(LPDWORD)&lpShellCodeAddr) - ((*(LPDWORD)&lpFirstCallAddr) + 4 );  
之后就要做个非常重要的事情——将新Call地址写入已经载入内存中的傀儡代码中。因为一般PE文件的代码段的内存页是EXECUTE|READ,但是不具备WRITE属性。于是我们要将第一个Call指令所在的内存页的页属性改为PAGE_EXECUTE_READWRITE,我们写入新Call地址后,再将原来的页属性设置回去,善始善终。
  1. MEMORY_BASIC_INFORMATION stMemBasicInfor = {0};  
  2. if ( FALSE == VirtualQueryEx( hProcess, lpFirstCallAddr, &stMemBasicInfor, sizeof(MEMORY_BASIC_INFORMATION) ) )  
  3. {  
  4.     break;  
  5. }  
  6. DWORD dwOldProtect = 0;  
  7.   
  8. if ( FALSE == VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, PAGE_EXECUTE_READWRITE, &dwOldProtect ) )  
  9. {  
  10.     break;  
  11. }  
  12.   
  13. if ( FALSE == WriteProcessMemory( hProcess, lpFirstCallAddr, &dwNewCallAddrOffset, 4, NULL ) )  
  14. {  
  15.     break;  
  16. }  
  17.   
  18. VirtualProtectEx( hProcess, stMemBasicInfor.BaseAddress, stMemBasicInfor.RegionSize, dwOldProtect, NULL );  

最简单的修改程序入口点进行注入的方法就是如此。


转自:http://blog.csdn.net/breaksoftware/article/details/7474817


  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值