注入 DLL 之远程线程
大家都知道,在 Win32 中每个进程都拥有一个独立的 40 亿字节的内存空间,也就是 4 GB ,这个 4 GB 的内存空间相对于进程来说是独立的,也就是说,你不能在你的进程空间内直接操作别的进程的空间,因为内存相对于进程来说独立的,这样做的好处其实很多,比如,一个进程出现错误,不会影响到整个系统,还有就是可以防止一个进程破坏另一个进程,这些对于编程来说是非常有利的,因为自己的进程出错,并不会影响到其他的进程。
独立的进程空间虽然可以使进程之间不受影响,但是,当我们要想操作,或想控制别的进程就变得困难了。
前面说过,每个进程进程都有一个独立的 4 GB 空间,无论你在这块空间里乱写乱画,都不会影响到其他进程,最多就是自己的进程会死掉。独立的 4 GB  空间,似乎阻断了进程之间的通道了吗?不是的,虽然 Win32 里说不可以操作别的进程内存,但是仍然可以的。
Microsoft 并没有使这条路变成死路,在 Win32 API 中提供了 2 个函数,WriteProcessMemory 和 ReadProcessMemory,这两个函数可以在指定的进程的内存里做读写操作,有了这 2 个函数,我们就可以读写其他进程的内存了,进程之间的界限也就被打破,那么,我上面写的不就是废话了吗,呵呵,不是的,他们的毕竟还是 NT 架构和非 NT 架构的属性,在 NT 架构中打开进程是要权限的,不过仍然可以绕过?
有了 WriteProcessMemory 和 ReadProcessMemory函数,我们就可以读写其他进程的内存,不过呢,我们并不满足单纯的读与写,我们可能希望别的进程我们的代码,呵呵,你没有看错,我也没有写错,你可能会说,怎么可以让别的进程执行我们自己的代码的呢?莫非是把代码写到别的进程里吗?呵呵,当然可以的了,不过,如果是把代码写到别的进程的内存空间里的话,会很困难的,因为目标进程不可能也没必要知道你代码的入口点,设想一下,如果我们把自己的代码写到某个进程的内存空间里的话,再让那个进程来执行写入的代码,会发生什么呢,首先,不存在访问违规,第 2 ,因为你已经把代码写到别的进程去了,即使你的进程已经退出,只要那个进程不退出,那么你写入的代码句可以一直执行,也就是可以实现无进程,当然,如果那个进程退出了,那么写入的代码也就会退出。
那么现在的问题就是,如何写入代码到别的进程里和如何让别的进程执行我们写入的代码了,要解决这两个问题,有必要说说进程和线程。
进程,就是一个程序的执行实例,一个程序没有运行的时候,是保存在磁盘里的,此时他仅仅是一个 2 进制文件而已,但是如果你双击它的图标的时候,系统就会运行它,记住,是系统去运行它,而不是它自己运行,运行以后,系统就会为它创建一个映像(用 CreateProcess),也就是它的进程,也称为实例,不过进程是不可以做任何事情的,因为进程只是一个容器而已,一个被创建了的进程,系统会为它分配内存空间,分配句柄,分配 ID ,分配权限,还会为它加载需要的 DLL 文件,前面说过,进程不能做任何事情,是的,进程只是一个容器,它里面装着至少一个线程,而线程就是程序的执行单元,是用来做各种事情的。
线程,就是程序的执行单元,一个进程至少有一个线程,这是系统自动创建的,不过线程和子线程都可以创建线程。线程才是一个真正的程序,而进程只是组织这些线程的,还有线程之间是可以互相操作的,因为他们在同一个内存空间里,一般来说,当进程被结束时,系统会在该进程的每个线程中插入一段代码,这段代码会调用 ExitThread 函数来退出自己的线程,也就是说,线程是自己退出的,当最后一个线程退出后,进程也就退出了。
好了,理解了进程和线程后,我们就来把自己的代码写到别的进程空间里和让其执行吧!
首先呢,在 Win32 API 中,有一个叫做 CreateRemoteThread 的函数,这个函数可以在别的进程中创建一个线程,…可是不要高兴得太早,首先呢,线程是作为一个函数出现的,而且这个线程的函数一定要是在目标进程的内存空间中,因为指针不可能跨进程,其次,就是如何写入自己代码,说到写入代码,通常就是在目标进程中写入 LoadLiraryA ,让其来加载我们的 DLL 文件,也就是说,我们在目标进程中写入的代码,也就是创建的线程,功能仅仅是加载一个 DLL 文件而已,当加载完后,这个线程就会自动退出,而那个加载进来的 DLL 文件的 DllMain 函数就开始运行了,通常也是在 DllMain 中创建一个线程,用 CreateThread ,记住,这个线程是在目标进程中创建的了,因为这个 DLL 文件已经被加载到了目标进程中,这就叫做远程线程注入。
好了,也许你会问,既然指针不能跨进程,那么 LoadLiraryA 的参数就是一个指针字符,那么用 CreateRemoteThread 创建远线程时,传给LoadLibraryA 的参数是在本进程中的,那在目标进程中的 LoadLibraryA 又怎么可以找到传给自己的参数呢,是的,这确实找不到,因为指针在本进程中有效存到目标进程中就不再有效了,也就是说在目标进程创建的线程LoadLibraryA 将会失效,因为指针传到过来后就指到另一个地方了,传给 LoadLibraryA 的根本就不是一个 DLL 路径。
要解决这个问题,首先就先将 DLL 的路径名写到目标进程中,然后再创建远线程,传给 LoadLibraryA 的参数就是目标进程中存放 DLL 路径的地址,因为指针本来就是传地址的。好了,这个问题真的这么容易就解决了吗?当然不是,因为把 DLL 的路径名写到目标进程中,那么,因该写到那里去呢?呵呵,首先用 VirtualAllocEx 来在目标进程中分配一个内存空间,然后再在这个空间中把 DLL 的路径名写到目标进程中,然后创建远线程,把这个空间的地址传给LoadLibraryA ,因为这样 DLL 的路径名就在目标进程里了,LoadLibraryA 也就可以正确加载 DLL 了,然后 DLL 就可以响应 DLL_PROCESS_ATTACH,然后就可以创建线程了,然后那个 EXE 就可以退出了,只要被注入的进程不退出,那么 DLL 文件将一直运行。
呵,打中文打到手都累了,下面再折磨下自己,把实现代码也写出来吧!

const DWORD THREADSIZE=1024*4;
HANDLE pRemoteThread,hRemoteProcess;
PTHREAD_START_ROUTINE pfnAddr;
DWORD pId;
void *pFileRemote;

HWND hWinPro=::FindWindow("ProgMan",NULL); 
if(!hWinPro)
return 0;
else
{
::GetWindowThreadProcessId(hWinPro,&pId);  
hRemoteProcess=::OpenProcess(PROCESS_ALL_ACCESS,false,pId); 
pFileRemote=::VirtualAllocEx(hRemoteProcess,0,THREADSIZE,MEM_COMMIT|MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(!::WriteProcessMemory(hRemoteProcess,pFileRemote,"d:\\RemoteDll.dll",THREADSIZE,NULL))
return;
pfnAddr=(PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")),"LoadLibraryA");
pRemoteThread=::CreateRemoteThread(hRemoteProcess,NULL,0,pfnAddr,pFileRemote,0,NULL);
if(pRemoteThread==NULL)
return;
else MessageBox("success!");
}
}




好了,远程线程注入就说到这里,在开头我从进程内存空间,一直说到把 DLL 注入到进程的内存,也可以是比较详细的吧。