Windows核心编程(二十一)远程线程注入DLL

1、windows内的各个进程有各自的地址空间。当我们用指针来引用内存时,指针的值表示的是进程自己的地址空间中的一个内存地址。进程不能创建一个指针来引用属于其他进程的内存。它们相互独立互不干扰保证了系统的安全性。


2、应用程序需要跨越进程边界来访问另一个进程的地址空间的情况如下:

1)我们想要从另一个进程创建的窗口派生子类窗口。

2)我们需要一些手段来辅助调试。比如我们需要确定另一个进程正在使用哪些DLL。

3)我们想要给另一个进程安装挂钩。


3、windows也为调试器或是其他工具设计了一些函数,这些函数可以让一个进程对另一个进程进行操作。虽然他们是为调试器设计的,但是任何应用程序都可以调用它们 。


4、使用远程线程来注入DLL。

从根本上说,DLL注入就是将某一DLL注入到某一进程的地址空间。该进程中的一个线程调用LoadLibrary来载入想要注入的DLL。由于我们不能直接控制其他进程内的线程,因此我们必须在其他进程内创建一个我们自己的线程。我们可以对新创建的线程加以控制,让他调用LoadLibrary来载入DLL。

在其他进程内创建的线程被称为:远程线程,该进程被称为远程进程。

windows提供了一个函数,可以让我们在其他进程内创建一个线程:     

[cpp]  view plain copy
  1.  HANDLE WINAPI CreateRemoteThread(  
  2.            HANDLE hProcess,  
  3.            LPSECURITY_ATTRIBUTES lpThreadAttributes,  
  4.            SIZE_T dwStackSize,  
  5.            LPTHREAD_START_ROUTINE lpStartAddress,  
  6.            LPVOID lpParameter,  
  7.            DWORD dwCreationFlags,  
  8.            LPDWORD lpThreadId  
  9.            );  

该函数除了第一个参数hProcess,标识要创建的线程所属的进程外,其他参数与CreateThread的参数完全相同。

---lpstartAddress是线程函数的内存地址。由于是在远程进程创建的,所以该函数一定必须在远程进程的地址空间内。


5、如何让该线程载入我们的DLL?让线程调用LoadLibrary函数即可。

HMODULE  LoadLibrary( PCTSTR  pszLibFile);


6、如何让线程运行起来,即为线程选择线程函数。

因为线程是在其他进程内运行的,所以该线程函数必须符合以下条件:

1)该函数符合线程函数的原型,

2)存在于远程线程地址空间内。

线程函数的函数原型:

DWORD WINAPI ThreadFunc( PVOID pvParam);

即远程线程的任务只有一个,就是调用LoadLibray加载DLL。


7、查看MSDN可以发现LoadLibrary并不是一个API,它其实是一个宏。

在WinBase.h中有:

#ifdef UNICODE

#define LoadLibrary LoadLibraryW

#else

#define LoadLibrary LoadLibraryA

#endif

实际上有两个Load*函数,唯一区别就是参数类型不同。如果DLL文件名是以ANSI形式保存的,我们就必须调用LoadLibraryA,如果是UNICODE形式保存的我们就必须调用LoadLibraryW。


8、接下来只需调用CreateThread函数,传给标识线程函数的参数LoadLibraryA或是LoadLibraryW。然后将我们要远程进程加载的DLL的路径名的地址作为参数传给它。

注意:传给线程函数的参数是DLL路径名的地址。但是该地址是在我们进程内的。如果远程进程引用此地址的数据,很可能会导致访问违规,远程进程被终止。

为了解决这个问题,我们应该将该字符串放到远程地址的地址空间去。首先应该在远程进程的地址空间分配一块儿内存。VirtualAllocEx可以解决这个问题。

[cpp]  view plain copy
  1.  LPVOID WINAPI VirtualAllocEx(  
  2.       __in      HANDLE hProcess,  
  3.       __in_opt  LPVOID lpAddress,  
  4.       __in      SIZE_T dwSize,  
  5.       __in      DWORD flAllocationType,  
  6.       __in      DWORD flProtect  
  7.     );  

---hProcess标识想在那个进程的地址空间申请内存的进程句柄。

---其他参数跟VirtualAlloc完全相同。此处不再介绍。

释放申请的内存:VirtualFreeEx

[cpp]  view plain copy
  1. BOOL WINAPI VirtualFreeEx(  
  2.       __in  HANDLE hProcess,  
  3.       __in  LPVOID lpAddress,  
  4.       __in  SIZE_T dwSize,  
  5.       __in  DWORD dwFreeType  
  6.     );    

与VirtualFree的区别只是多了一个进程句柄。


9、现在申请空间的任务完成了,要怎么样将本进程的数据复制到另外一个进程呢?

可以使用ReadProcessMemory和WriteProcessMemory。

[cpp]  view plain copy
  1.  BOOL WINAPI ReadProcessMemory(  
  2.       __in   HANDLE hProcess,  
  3.       __in   LPCVOID lpBaseAddress,  
  4.       __out  LPVOID lpBuffer,  
  5.       __in   SIZE_T nSize,  
  6.      __out  SIZE_T *lpNumberOfBytesRead  
  7.     );  

[cpp]  view plain copy
  1.   BOOL WINAPI WriteProcessMemory(  
  2.       __in   HANDLE hProcess,  
  3.       __in   LPVOID lpBaseAddress,  
  4.       __in   LPCVOID lpBuffer,  
  5.       __in   SIZE_T nSize,  
  6.       __out  SIZE_T *lpNumberOfBytesWritten  
  7.     );  

由于他们签名类似,此处放在一块介绍。

---hProcess是用来标识远程进程的。

---lpBaseAddress是在远程进程地址空间的地址,是VirtualAllocEx的返回值。

---lpBuffer是在本进程的内存地址。此处也就是DLL路径名的地址。

---nSize为要传输的字符串。

---lpNumberOfByteRead和lpNumberOfByteWrite为实际传输的字节数。

注意:当调用WriteProcessMemory时有时会导致失败。此时可以尝试调用VirtualProtect来修改写入页面的属性,写入之后再改回来。


10、我们知道导入函数的真实地址是在DLL加载的时候获得的。

加载程序从导入表取得每一个导入函数的函数名(字符串),然后在被加载到进程地址空间的DLL中查询之后,填到导入表的相应位置(IAT)的。也就是说在运行之前我们并不知道导入函数的地址(当然模块绑定过得除外)。

1)那么程序代码中是如何表示对导入函数的调用呢?

或许我们觉得应该是:CALL DWORD PTR[004020108]  ( [ ]内仅表示导入函数地址,无实际意义)。

由于程序的代码在经过编译连接之后就已经确定,而导入表的地址如00402010是在程序运行的时候获得的。所以程序在调用导入函数的时候并不能这样实现。那到底是如何实现的呢?

2)[ ]内有一个确定的地址这是毋庸置疑的,但是他的值并不是导入函数的地址,而是一个子程序的地址。该子程序被称为转换函数(thunk)。这些转换函数用来跳转到导入函数。当程序调用导入函数时,先会调用转换函数,转换函数从导入表的IAT获得导入函数的真实地址时在调用相应地址。

3)导入函数的调用形式:   

[cpp]  view plain copy
  1.          CALL  00401164 ; 转换函数的地址。  
  2.   
  3.             ...... 
  4.   
  5.          :00401164  
  6.   
  7.             ......
  8.   
  9.          CALL DWORD PTR [00402010];  调用导入函数。  

4)至此我们会明白为什么在声明一个导出函数的时候要加上_decllpec(dllimport)前缀。

原因是:编译器无法区分应用程序是对一般函数的调用还是对导入函数的调用。当我们在一个函数前加上此前缀就是告诉编译器此函数来自导入函数,编译器就会产生如上的指令。而不是CALL XXXXXXXX的形式。

所以在写一个输出函数的时候一定要在函数声明前加上修饰符:_decllpec(dllimport)。

 

11、我们传给CreateRemoteThread的线程函数LoadLibrary*,会被解析成我们进程内的转换函数的地址。如果把这个转换函数的地址作为线程函数的起始地址很可能导致访问违规。

解决方法是:强制代码略过转换函数而直接调用LoadLibrary*。这可以通过GetProAddress来实现。

[cpp]  view plain copy
  1.  FARPROC WINAPI GetProcAddress(  
  2.       __in  HMODULE hModule,  
  3.       __in  LPCSTR lpProcName  
  4.      );  

---hModule是模块句柄。标志某一模块。

---lpProcName是该模块内某一函数的函数名。

它返回该函数在模块所属进程地址空间的地址。

如GetProcAddress(GetModuleHandle("Kernel.dll","LoadLibraryW"));

此语句取得LoadLibrary在Kernel.dll所在进程空间的真实地址。

注意此时仅仅是取得在本进程Kernel.dll的地址和LoadLibraryW的地址。


12、例子:通过远程线程向explorer.exe进程注入DLL。

explorer.exe:资源管理器进程。随系统启动而启动,且一直运行。因此它经常被用来被当做远程线程的寄主。

步骤:1)获得explorer进程的句柄。

通过调用CreatehlpSnapshot获得此时系统的一个快照。然后遍历该快照。找到进程名称为explorer.exe的进程。并得到起进程对象句柄。

[cpp]  view plain copy
  1.     PROCESSENTRY32 pe32;  
  2.     pe32.dwSize=sizeof(pe32);  
  3.     HANDLE hSnapshot=CreateToolhelp32Snapshot(TH32CS_SNAPALL,0);  
  4.     int ret=Process32First(hSnapshot,&pe32);  
  5.     CString a;  
  6.     UpdateData();  
  7.     if(-1==m_processToFind.Find(".exe",0))  
  8.       m_processToFind+=".exe";  
  9.     while(ret)  
  10.     {  
  11.     if(pe32.szExeFile==m_processToFind)  
  12.         {  
  13.               
  14.         a.Format("进程:%s找到,它的进程ID为:%d",m_processToFind,pe32.th32ProcessID);  
  15.             MessageBox(a);  
  16.             break ;  
  17.         }  
  18.         ret=Process32Next(hSnapshot,&pe32);  
  19.     }

[cpp]  view plain copy
  1. HANDLE WINAPI CreateToolhelp32Snapshot(  
  2.   __in  DWORD dwFlags,  
  3.   __in  DWORD th32ProcessID  
  4. );    

该函数用于获取指定进程的快照,以及该进程使用的堆,线程等。

---dwFlags用来表示此快照中包含的项目。具体参考MSDN。

此处传入TH32CS_SNAPALL,表示此快照包括系统中所有的进程和线程,以及在th32ProcessID中指定的进程的各模块和线程的信息。

---th32ProessID指定要包括到此快照的进程ID,当传入0时表示当前进程。

函数执行成功返回快照句柄。否则返回INVALID_HANDLE_VALUE。可以调用GetLastError查看更多错误信息。

[cpp]  view plain copy
  1. BOOL WINAPI Process32First(  
  2.   __in     HANDLE hSnapshot,  
  3.   __inout  LPPROCESSENTRY32 lppe  
  4. );   
BOOL WINAPI Process32Next(
  __in   HANDLE hSnapshot,
  __out  LPPROCESSENTRY32 lppe
);

以上两个函数,用于在CreateHlpSnapshot中遍历各项。用法参考上例。

步骤:2)获得explorer的进程ID之后,还要调用OpenProcess来获得该进程的句柄。函数执行成功返回进程句柄。

[cpp]  view plain copy
  1. HANDLE hProcess=OpenProcess(PROCESS_ALL_ACCESS,0,pe32.th32ProcessID);
  2.    

步骤:3)在explorer的地址空间中申请存储要注入的DLL的路径名的空间。

[cpp]  view plain copy
  1.  PVOID addr=VirtualAllocEx(hProcess,NULL,50,MEM_COMMIT,  PAGE_READWRITE);  
  2.       if(addr==NULL)  
  3.       {  
  4.         CString a;  
  5.         int ret=GetLastError();  
  6.         a.Format("在远程进程申请空间失败!错误码为:%d",ret);  
  7.   
  8.         MessageBox(a);  
  9.       }  
  10.        else  
  11.        {  
  12.          MessageBox("远程进程地址空间中申请空间成功!");  
  13.   
  14.        }                 

步骤:4)将路径名写入在explorer进程申请的空间。

[cpp]  view plain copy
  1. char path[50]="F:\\injectDll.dll";  
  2. int retval=WriteProcessMemory(hProcess,addr,(LPVOID)path,sizeof(path),NULL);  
  3.     if(retval)  
  4.     {  
  5.         MessageBox("写入成功!");  
  6.     }  
  7.     else  
  8.         MessageBox("写入失败!");  

步骤:5)创建远程线程。

//获得LoadLibraryA在远程进程中的地址。(与本进程的地址相同。)

[cpp]  view plain copy
  1. PTHREAD_START_ROUTINE pfnThread=(PTHREAD_START_ROUTINE)  
  2.      GetProcAddress(GetModuleHandle("kernel32.DLL"),"LoadLibraryA");
[cpp]  view plain copy
  1. HANDLE hRemoteThread=CreateRemoteThread(    
  2.       hProcess,//in   HANDLE hProcess,    
  3.       NULL,  
  4.       0,//__in   SIZE_T dwStackSize,    
  5.       pfnThread,  
  6.       addr,   
  7.       0,   
  8.       NULL); 
[cpp]  view plain copy
  1. if(hRemoteThread==INVALID_HANDLE_VALUE)  
  2.  {  
  3.   MessageBox("远程线程穿件失败!");  
  4.  }  
  5.  else  
  6.  {  
  7.   MessageBox("远程线程创建成功!");  
  8.  }  

步骤:6)创建要注入到远程进程的DLL。(可参考DLL基础篇)

如果在注入的DLL创建一个线程,就可以执行我们想让它做的工作。

比如监控某程序的运行,一旦程序运行,就将另一个DLL加载到此进程。此DLL会挂在全局钩子,获得用户键盘的动作。

以上部分参考自http://blog.csdn.net/ithzhang/article/details/7042613 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值