您的位置:首页 > 编程语言 > C语言/C++

基于c++进程注入的实现

2008-12-07 00:32 246 查看
示范工程:http://download.csdn.net/user/kissyfish

要实现对一个程序的进程注入,然后对被注入的进程进行控制,首先需要查找到要注入的进程ID。如何获取的进程ID呢?windows提供了一个API只要知道了这个进程里面的一个窗口句柄,就可以找到找到该进程ID。函数形式如下:DWORD GetWindowThreadProcessId(
HWND hWnd,
LPDWORD lpdwProcessId
);

那如何获取这个窗口的句柄呢?很自然我们可以想到这么一个函数,函数形式如下:

HWND FindWindowEx(
HWND hwndParent,
HWND hwndChildAfter,
LPCTSTR lpszClass,
LPCTSTR lpszWindow
);

hwndParent:指向一个待搜索窗口的父窗。

hwndChildAfter:子窗口的句柄。

lpszClass:窗口的类名。

lpszWindow:窗口的标题名。

例如,本示例工程要找到一个对话框里的编辑框,并对这个编辑框进行注入。查找过程如下:

HWND hWndChild = NULL;

while(1)

{

// 查找对话框窗口,且这个对话框窗口的标题名为“TestDlg”

HWND hDlg = FindWindowEx(0, NULL, "#32770", "TestDlg");

if(hDlg == NULL)

{

printf("没有找到该对话框窗口!/n");

exit(1);

}

else

{

// 查找这个对话框窗口中的edit控件

hWndChild = FindWindowEx(hDlg, NULL, "Edit",NULL);

if(hWndChild != NULL)

{

break; // 找到这个编辑框,现在要对这个编辑框进行注入

}

}

}

// 根据查找出的窗口,查询进程

DWORD dwQQGameId;

HANDLE hProcessQQGame;

if(!GetWindowThreadProcessId(hWndChild,&dwQQGameId))

{

printf("Error in GetWindowThreadProcessId():%d/n",GetLastError());

exit(1);

}

找到这个进程后,然后就要对这个进程进行注入。但是,你别忘记了,当你在其他进程中获取另外进程的窗口句柄,你是没有办法操作这个句柄的。为什么呢?每个进程都被赋予它自己的虚拟地址空间。对于3 2位进程来说,这个地址空间是4 G B,因
为3 2位指针可以拥有从0 x 0 0 0 0 0 0 0 0至0 x F F F F F F F F之间的任何一个值。这使得一个指针能够拥有4 294 967 296个值中的一个值,它覆盖了一个进程的4 G B虚拟空间的范围。对于6 4位进程来说,这个地址空间是1 6 E B(1 01 8字节),因为6 4位指针可以拥有从0 x 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0至0 x F F F F F F F F F F F F F F F F之间的任何值。这使得一个指针可以拥有18 446 744 073 709 551 616个值中的一个值,它覆盖了一个进程的1 6 E B虚拟空间的范围。这是相当大的一个范围。由于每个进程可以接收它自己的私有的地址空间,因此当进程中的一个线程正在运行时,该线程可以访问只属于它的进程的内存。属于所有其他进程的内存则隐藏着,并且不能被正在运行的线程访问。注意在Windows 2000中,属于操作系统本身的内存也是隐藏的,正在运行的线程无法访问。这意味着线程常常不能访问操作系统的数据。Windows 98中,属于操作系统的内存是不隐藏的,正在运行的线程可以访问。因此,正在运行的线程常常可以访问操作系统的数据,也可以破坏操作系统(从而有可能导致操作系统崩溃)。在Windows 98中,一个进程的线程不可能访问属于另一个进程的内存。前面说过,每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0 x 1 2 3 4 5 6 7 8,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0 x 1 2 3 4 5 6 7 8。当进程A中运行的线程访问地址为0 x 1 2 3 4 5 6 7 8的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0 x 1 2 3 4 5 6 7 8的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。

这样看来,若想对这个窗口句柄进行操作,得想方设法使我们能够进入到原宿主进程中,然后执行我们的操作。这个就好象一个寄生虫想要破坏人体的机能,必须得进入我们的体内,寄生在我们的组织上才能够产生作用。现在关键是如何寄生到宿主中呢?

通常,任何进程都可以通过LoadLibrary动态地加载DLL,但是我们如何强制一个外部进程调用该函数呢?答案是CreateRemoteThread。
让我们先来看看LoadLibrary和FreeLibrary的函数声明:

HINSTANCE LoadLibrary(
LPCTSTR lpLibFileName // address of filename of library module
);

BOOL FreeLibrary(
HMODULE hLibModule // handle to loaded library module
);

再和CreateRemoteThread的线程过程(thread procedure)ThreadProc比较一下:
DWORD WINAPI ThreadProc(
LPVOID lpParameter // thread data
);

你会发现所有的函数都有同样的调用约定(calling convention)、都接受一个32位的参数并且返回值类型的大小也一样。也就是说,我们可以把LoadLibrary/FreeLibrary的指针作为参数传递给CrateRemoteThread。

然而,还有两个问题(参考下面对CreateRemoteThread的说明)

1. 传递给ThreadProc的lpStartAddress 参数必须为远程进程中的线程过程的起始地址。
2. 如果把ThreadProc的lpParameter参数当做一个普通的32位整数(FreeLibrary把它当做HMODULE)那么没有如何问题,但是如果把它当做一个指针(LoadLibrary把它当做一个char*),它就必须指向远程进程中的内存数据。

第一个问题其实已经迎刃而解了,因为LoadLibrary和FreeLibrary都是存在于kernel32.dll中的函数,而kernel32可以保证任何“正常”进程中都存在,且其加载地址都是一样的。(参看附录A)于是LoadLibrary/FreeLibrary在任何进程中的地址都是一样的,这就保证了传递给远程进程的指针是个有效的指针。

第二个问题也很简单:把DLL的文件名(LodLibrary的参数)用WriteProcessMemory复制到远程进程。

所以,使用CreateRemoteThread和LoadLibrary技术的步骤如下:
1. 得到远程进程的HANDLE(使用OpenProcess)。
2. 在远程进程中为DLL文件名分配内存(VirtualAllocEx)。
3. 把DLL的文件名(全路径)写到分配的内存中(WriteProcessMemory)
4. 使用CreateRemoteThread和LoadLibrary把你的DLL映射近远程进程。
5. 等待远程线程结束(WaitForSingleObject),即等待LoadLibrary返回。也就是说当我们的DllMain(是以DLL_PROCESS_ATTACH为参数调用的)返回时远程线程也就立即结束了。
6. 取回远程线程的结束码(GetExitCodeThtread),即LoadLibrary的返回值――我们DLL加载后的基地址(HMODULE)。
7. 释放第2步分配的内存(VirtualFreeEx)。
8. 用CreateRemoteThread和FreeLibrary把DLL从远程进程中卸载。调用时传递第6步取得的HMODULE给FreeLibrary(通过CreateRemoteThread的lpParameter参数)。
9. 等待线程的结束(WaitSingleObject)。

主要代码如下:

#include<stdio.h>

#include<conio.h>

#include<windows.h>

void main()

{

HWND hWndChild = NULL;

while(1)

{

// 查找对话框窗口,且这个对话框窗口的标题名为“TestDlg”

HWND hDlg = FindWindowEx(0, NULL, "#32770", "TestDlg");

if(hDlg == NULL)

{

printf("没有找到该对话框窗口!/n");

exit(1);

}

else

{

// 查找这个对话框窗口中的edit控件

hWndChild = FindWindowEx(hDlg, NULL, "Edit",NULL);

if(hWndChild != NULL)

{

break; // 找到这个编辑框,现在要对这个编辑框进行注入

}

}

}

// 根据查找出的窗口,查询进程

DWORD dwQQGameId;

HANDLE hProcessQQGame;

if(!GetWindowThreadProcessId(hWndChild,&dwQQGameId))

{

printf("Error in GetWindowThreadProcessId():%d/n",GetLastError());

exit(1);

}

HINSTANCE hDll = NULL;

typedef void(*LP_SET_HEDIT_FUN)(HWND);

LP_SET_HEDIT_FUN m_SethEdit = NULL;

char DllPath[MAX_PATH] = "D://进程注入测试工程//Bin//automessagedll.dll";

hDll = LoadLibrary(DllPath);

if(hDll)

{

m_SethEdit = (LP_SET_HEDIT_FUN)GetProcAddress(hDll,"SethEdit");

}

if(m_SethEdit)

{

m_SethEdit(hWndChild);

}

else

{

printf("Can not load SethEdit in the dll(%d)./n",GetLastError());

}

if( (hProcessQQGame = OpenProcess(PROCESS_ALL_ACCESS,FALSE,dwQQGameId)) == NULL)

{

printf("Error in OpenProcess():%d/n",GetLastError());

getch();

exit(1);

}

int cb = (1+lstrlen(DllPath))* sizeof(char);

char* RemoteLibFile = (char*)VirtualAllocEx(hProcessQQGame,NULL,cb,MEM_COMMIT,PAGE_READWRITE);

if(RemoteLibFile == NULL)

{

printf("Error in VirtualAllocEx():%d/n",GetLastError());

getch();

exit(1);

}

if( (WriteProcessMemory(hProcessQQGame,RemoteLibFile,(LPVOID)DllPath,cb,NULL)) == 0)

{

printf("Error in WriteProcessMemory():%d/n",GetLastError());

getch();

exit(1);

}

PTHREAD_START_ROUTINE pfnStartAddr;

pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("Kernel32"),"LoadLibraryA");

HANDLE hThread = CreateRemoteThread(hProcessQQGame,NULL,0,pfnStartAddr,RemoteLibFile,0,NULL);

if(hThread == NULL)

{

printf("Error in CreateRemoteThread():%d",GetLastError());

getch();

exit(1);

}

WaitForSingleObject(hThread,INFINITE);

CloseHandle(hThread);

VirtualFreeEx(hProcessQQGame,RemoteLibFile,0,MEM_RELEASE);

CloseHandle(hProcessQQGame);

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: