转自:
这是一个简单的Win32 DLL程序,它仅由一个入口函数DllMain组成:
- BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved )
- {
- switch ( fdwReason )
- {
- case DLL_PROCESS_ATTACH:
- {
- MessageBox( NULL, _T("DLL已进入目标进程。"), _T("信息"), MB_ICONINFORMATION );
- }
- break;
- case DLL_PROCESS_DETACH:
- {
- MessageBox( NULL, _T("DLL已从目标进程卸载。"), _T("信息"), MB_ICONINFORMATION );
- }
- break;
- }
- return TRUE;
- }
- DWORD FindTarget( LPCTSTR lpszProcess )
- {
- DWORD dwRet = 0;
- HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
- PROCESSENTRY32 pe32;
- pe32.dwSize = sizeof( PROCESSENTRY32 );
- Process32First( hSnapshot, &pe32 );
- do
- {
- if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 )
- {
- dwRet = pe32.th32ProcessID;
- break;
- }
- } while ( Process32Next( hSnapshot, &pe32 ) );
- CloseHandle( hSnapshot );
- return dwRet;
- }
这里我使用了Tool Help函数库,当然如果你是NT系统的话,也可以选择PSAPI函数库。这段代码的目的就是通过给定的进程名称来在当前系统中查找相应的进程,并返回该进程的ID。得到进程ID后,就可以调用OpenProcess来打开目标进程了:
- // 打开目标进程
- HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwProcessID );
现在有必要说一下OpenProcess第一个参数所指定的三种权限。在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都相互独立。如果一个进程需要完成跨进程的工作的话,那么它必须拥有目标进程的相应操作权限。在这里,PROCESS_CREATE_THREAD表示我可以通过返回的进程句柄在该进程中创建新的线程,也就是调用CreateRemoteThread的权限;同理,PROCESS_VM_OPERATION则表示在该进程中分配/释放内存的权限,也就是调用VirtualAllocEx/VirtualFreeEx的权限;PROCESS_VM_WRITE表示可以向该进程的地址空间写入数据,也就是调用WriteProcessMemory的权限。
至此目标进程已经打开,那么我们该如何来将DLL注入其中呢?在这之前,我请你看一行代码,是如何在本进程内显式加载DLL的:
- HMODULE hDll = LoadLibrary( "DLL.dll" );
那么,如果能控制目标进程调用LoadLibrary,不就可以完成DLL的远程注入了么?的确是这样,我们可以通过CreateRemoteThread将LoadLibrary作为目标进程的一个线程来启动,这样就可以完成“控制目标进程调用LoadLibrary”的工作了。到这里,也许你会想当然地写下类似这样的代码:
- DWORD dwID;
- LPVOID pFunc = LoadLibraryA;
- HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, (LPVOID)"DLL.dll", 0, &dwID );
不过结果肯定会让你大失所望——注入DLL失败!
嗯嗯,那么现在让我们来分析一下失败的原因吧。我是前说过,在Win32系统下,每个进程都拥有自己的4G虚拟地址空间,各个进程之间都是相互独立的。在这里,我们当作参数传入的字符串"DLL.dll"其实是一个数值,它表示这个字符串位于Virus.exe地址空间之中的地址,而这个地址在传给Target.exe之后,它指向的东西就失去了有效性。举个例子来说,譬如A、B两栋大楼,我住在A楼的401;那么B楼的401住的是谁我当然不能确定——也就是401这个门牌号在B楼失去了有效性,而且如果我想要入住B楼的话,我就必须请B楼的楼长为我在B楼中安排新的住处(当然这个新的住处是否401也就不一定了)。
由此看来,我就需要做这么一系列略显繁杂的手续——首先在Target.exe目标进程中分配一段内存空间,然后向这段空间写入我要加载的DLL名称,最后再调用CreateRemoteThread。这段代码就成了这样:
- // 向目标进程地址空间写入DLL名称
- DWORD dwSize, dwWritten;
- dwSize = lstrlenA( lpszDll ) + 1;
- LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, dwSize, MEM_COMMIT, PAGE_READWRITE );
- if ( NULL == lpBuf )
- {
- CloseHandle( hProcess );
- // 失败处理
- }
- if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, dwSize, &dwWritten ) )
- {
- // 要写入字节数与实际写入字节数不相等,仍属失败
- if ( dwWritten != dwSize )
- {
- VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
- CloseHandle( hProcess );
- // 失败处理
- }
- }
- else
- {
- CloseHandle( hProcess );
- // 失败处理
- }
- // 使目标进程调用LoadLibrary,加载DLL
- DWORD dwID;
- LPVOID pFunc = LoadLibraryA;
- HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID );
需要说的有两点,一是由于我要在目标进程中为ANSI字符串来分配内存空间,所以这里凡是和目标进程相关的部分,都明确使用了后缀为“A”的API函数——当然,如果要使用Unicode字符串的话,可以换作后缀是“W”的API;第二,在这里LoadLibrary的指针我是取的本进程的LoadLibraryA的地址,这是因为LoadLibraryA/LoadLibraryW位于kernel32.dll之中,而Win32下每个应用程序都会把kernel32.dll加载到进程地址空间中一个固定的地址,所以这里的函数地址在Target.exe中也是有效的。
在调用LoadLibrary完毕之后,我们就可以做收尾工作了:
- // 等待LoadLibrary加载完毕
- WaitForSingleObject( hThread, INFINITE );
- // 释放目标进程中申请的空间
- VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
- CloseHandle( hThread );
- CloseHandle( hProcess );
在此解释一下WaitForSingleObject一句。由于我们是通过CreateRemoteThread在目标进程中另外开辟了一个LoadLibrary的线程,所以我们必须等待这个线程运行完毕才能够释放那段先前申请的内存。
好了,现在你可以尝试着整理这些代码并编译运行。运行Target.exe,然后开启一个有模块查看功能的进程查看工具(在这里我使用我的July)来查看Target.exe的模块,你会发现在注入DLL之前,Target.exe中并没有DLL.dll的存在:
我自己的实现:
/********************************** 时间:07-30 11-26 功能:完成对一个已知进程名的进程进行注入 注意:使用 asc编码 整理:by wht ***********************************/ #include <iostream> #include <Windows.h> #include <TlHelp32.h> #include <string.h> using std::cin; using std::cout; using std::endl; DWORD FindTarget( LPCTSTR lpszProcess ) { DWORD dwRet = 0; // cout<<lpszProcess; //获取系统快照:内存中当前进程线程的副本 HANDLE hSnapshot = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); //进程 当前 //该参数只有在设置了TH32CS_SNAPHEAPLIST或TH32CS_SNAPMOUDLE后才有效,在其他情况下该参数被忽略,所有的进程都会被快照。 PROCESSENTRY32 pe32; //用来存放快照进程信息的一个结构体 pe32.dwSize = sizeof( PROCESSENTRY32 ); //找出目标进程id Process32First( hSnapshot, &pe32 ); do { if ( lstrcmpi( pe32.szExeFile, lpszProcess ) == 0 ) { dwRet = pe32.th32ProcessID; break; } } while ( Process32Next( hSnapshot, &pe32 ) ); CloseHandle( hSnapshot ); // cout<<dwRet<<endl; return dwRet; } int main() { char procName[20] = ""; cout<<"input process name"<<endl; cin>>procName; // cout<<procName<<endl; int pid = FindTarget((LPCTSTR)procName); cout<<pid<<endl; // 打开目标进程 HANDLE hProcess = OpenProcess( PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, pid ); if(hProcess == NULL) { cout<<"geterror"<<endl; } DWORD dwSize, dwWritten; LPCSTR lpszDll = "07301021mydll.dll"; dwSize = lstrlenA( lpszDll ) + 1; cout<<dwSize<<endl; //在进程中申请虚拟内存 //dwsize LPVOID lpBuf = VirtualAllocEx( hProcess, NULL, 27648, MEM_COMMIT, PAGE_READWRITE ); if ( NULL == lpBuf ) { CloseHandle( hProcess ); // 失败处理 cout<<"1"<<endl; } printf("%p\n",lpBuf); //dwsize if ( WriteProcessMemory( hProcess, lpBuf, (LPVOID)lpszDll, 27648, &dwWritten ) )//(LPVOID)lpszDll { //进程句柄,起始地址,指针,大小,实际写入大小 // 要写入字节数与实际写入字节数不相等,仍属失败 if ( dwWritten != dwSize ) { VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT ); CloseHandle( hProcess ); // 失败处理 cout<<"2"<<endl; } cout<<"2.1"<<endl; cout<<"dwWritton="<<dwWritten<<endl; cout<<"dwsize="<<dwSize<<endl; } else { CloseHandle( hProcess ); // 失败处理 cout<<"3"<<endl; } // 使目标进程调用LoadLibrary,加载DLL cout<<"4"<<endl; DWORD dwID; LPVOID pFunc = LoadLibraryA("07301021mydll.dll"); HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwID ); WaitForSingleObject( hThread, INFINITE ); // 释放目标进程中申请的空间 VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT ); CloseHandle( hThread ); CloseHandle( hProcess ); return 0; }