将代码注入到进程的三种方式
[源码下载]
介绍
在Code Project网站有许多关于password spy的文章,但是这些都是基于Windows Hooks的,还有没有其他方式能实现这种效果呢?是的,有,不过,先让我们简要的回顾一下。
要想读控件的内容,一般需要给它发送WM_GETTEXT 消息.edit控件就有这个特点。如果edit控件是在别的进程里,而且被设置成ES_PASSWORD类型,那么我们想要达到的目的就不会成功。这种情况下只有拥有password控件的进程才能实现WM_GETTEXT,所以我们的问题集中在了这一点上:
怎样才能在外部的进程空间内执行::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );
通常有三种方式解决这个问题:
1.把代码写到Dll内;然后,通过windows hooks将Dll映射到远程进程。
2.把代码写道Dll内;然后,通过CreateRemoteThread & LoadLibrary 技术将Dll映射到远程进程。
3.不单独写一个DLL;通过WriteProcessMemory将你的代码直接拷贝到远程进程。
I.Windows Hooks
Windows hools的主要用途是监控一些线程的消息。
1.Local hooks,监控属于你的进程的所有线程消息。
2.Remote hooks:
a.thread-specific,监控属于其他的进程的线程消息。
b.system-width,监控当前运行在系统中的所有线程。
如果hook的线程属于别的进程,你的hook程序必须放置在Dll内。然后系统将包含hook程序的dll映射到线程的地址空间,Windows映射全部的Dll,不仅仅是hook程序,这就是为什么Windows hooks可以被用来将代码注入到其他进程的地址空间中去。
这篇文章里,我不去深入讨论hooks(可以在MSDN内详细的看一下SetWindowHookEx API函数)我还会给你两个有用的线索,这在文档里是找不到的。
1.在调用SetWindowHookEx成功后,系统自动将Dll映射到了线程的地址空间,但不会立即执行。因为Windows hooks全部是基于消息,直到正确的事件返回Dll才被真正映射。例如:
如果你安装了一个Hook用来监控一些线程的所有消息队列(WH_CALLWNDPROC),在消息被发送到线程之前DLL不会被映射到进程的地址空间。如果在消息发送到线程之前,UnhoolWindowsHoolEx被调用,那么Dll永远都不会被映射到进程(尽管调用SetWondosHookEx成功了)。要想立即执行,在SetWondosHookEx成功之后立即给线程发送一个合适的event。
在调用UnhoolWindowsHoolEx取消Dll映射的时候,也不会立即取消DLL映射。
2.当你安装Hooks的时候,他们会影响系统的总性能(尤其是system-wide hooks).如果你将thread-specific hooks单独作为一个Dll映射机制,那么你很容易就避免这个缺点,看下面的代码片断:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { if( ul_reason_for_call == DLL_PROCESS_ATTACH ) { // Increase reference count via LoadLibrary char lib_name[MAX_PATH]; ::GetModuleFileName( hModule, lib_name, MAX_PATH ); ::LoadLibrary( lib_name ); // Safely remove hook ::UnhookWindowsHookEx( g_hHook ); } return TRUE; }
那么,发生了什么了?首先我们通过Windows hooks将Dll映射到远程进程。然后,在Dll被真正的映射之后,我们Unhook它,正常情况下,Dll将要被取消映射,同时,第一个到达被hook的线程的消息也被激活, 我们可以通过LoadLibrary增加Dll的引用计数来阻止取消映射。
随之而来的问题是:现在怎么去卸载Dll,一旦结速了,UnhookWindowsHookEx不会做这些事情的,因为我们已经unhook了线程,可以这样去做:
1.在你想要取消Dll映射之前,安装另一个Hook。
2.发送一个特殊的消息到远程线程
3.在你的hook程序中,捕获消息,在回复消息中, 调用FreeLibrary & UnhookWindowsHookEx
现在,hooks只在映射/取消映射的时候使用, 在这期间不会对"hooked"线程产生影响。 在第二部分中我们还会讨论比LoadLibrary更好的机制来使Dll映射不影响目标进程。然而,相对于LoadLibrary技术,这种解决方式只能在Winnt和Win9x下运行。
但是,什么时候才使用这些窍门呢?当Dll不得不被放到远程进程中很长时间时(例如,你将一个控件变成另一个进程的子类),你可能想尽量小的影响目标进程。我不在HookSpy中使用它,因为此时Dll只会注入片刻。
II.CreateRemoteThread & LoadLibrary技术
通常,所有的进程都能通过LoadLibrary动态的导入Dll。但是,怎么才能让外部进程调用这个函数?回答是用CreateRemoteThread.
首先,让我们看一下LoadLibrary and FreeLibrary
APIs 的声明:
HINSTANCE LoadLibrary( LPCTSTR lpLibFileName // address of filename of library module ); BOOL FreeLibrary( HMODULE hLibModule // handle to loaded library module );
现在,跟ThreadProc(线程程序,用来传给CreateRemoteThread)比较一下:
DWORD WINAPI ThreadProc(
LPVOID lpParameter // thread data
);
正如你所看到的,所有的函数都使用同样的调用转换,都接受一个32位的参数。返回值的大小也是一样的。换句话说,我们可以将LoadLibrary/FreeLibrary 作为线程函数传递给CreateRemoteThread.
然而,还有两个问题:
1.CreateRemoteThread 里的lpStartAddress参数必须是远程进程的线程程序的首地址。
2.如果lpParameter(传递给ThreadFunc)被认为是一个普通的32位值,(FreeLibrary 认为它是一个HMODULE),所有事情就好说了,然而,如果lpParameter 被描述成一个指针,(LoadLibraryA 将它描述成一个指向字符串的指针),那么它就必须指向远程进程的一些数据。
第二个问题好解决,用WriteProcessMemory拷贝Dll的模块名(需要LoadLibrary)。
下面是使用CreateRemoteThread &
LoadLibrary 技术的步骤:
1.得到远程进程的一个句柄(OpenPreocess).
2.在远程进程里给Dll名称分配内存(VirtualAllocEx).
3.将Dll名称,包括全路径,写到分配的内存里(WritePreocessMemory).
4.通过CreateRemoteThread & LoadLibrary将你的Dll映射到远程进程.
5.等待远程进程结束(WaitForSingleObject);也就是等到LoadLibrary返回.当我们的DllMain返回的时候线程就结束了(调用原因DLL_PROCESS_ATTACH).
6.返回远程线程的结束码,(GetExitCodeThread).注意这个值是LoadLibrary的返回值,
7.释放在步骤#2分配的内存(VirtualFreeEx).
8.从远程进程卸载DLL,将#6得到的返回句柄HMODULE传递给FreeLibrary(lpParameter in
CreateRemoteThread).
注意:如果你将Dll注入任何新的线程,确保在卸载的时候全部结束。
9.等待线程结束(WaitForSingleObject).
而且,一旦完成,别忘了关闭所有的句柄,对于在#4和#8创建的线程和在步骤#1返回的远程进程的句柄。
HANDLE hThread;
char szLibPath[_MAX_PATH]; // The name of our "LibSpy.dll" module
// (including full path!);
void* pLibRemote; // The address (in the remote process) where
// szLibPath will be copied to;
DWORD hLibModule; // Base address of loaded module (==HMODULE);
HMODULE hKernel32 = ::GetModuleHandle("Kernel32");
// initialize szLibPath
//...
// 1. Allocate memory in the remote process for szLibPath
// 2. Write szLibPath to the allocated memory
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
sizeof(szLibPath), NULL );
// Load "LibSpy.dll" into the remote process
// (via CreateRemoteThread & LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"LoadLibraryA" ),
pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// Get handle of the loaded module
::GetExitCodeThread( hThread, &hLibModule );
// Clean up
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );
III.The CreateRemoteThread & WriteProcessMemory 技术
这种方法是将代码拷贝到要注入进程的地址空间,然后在进程的上下文中执行这些代码,包括远程线程的使用。这就不必再使用分离的DLL了。可以用API函数WriteProcessMemory将代码直接拷贝到远程地址空间,用CreateRemoteThread启动代码执行。
先看一个CreateRemoteThread的声明
HANDLE CreateRemoteThread(
HANDLE hProcess, // handle to process to create thread in
LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security
// attributes
DWORD dwStackSize, // initial thread stack size, in bytes
LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread
// function
LPVOID lpParameter, // argument for new thread
DWORD dwCreationFlags, // creation flags
LPDWORD lpThreadId // pointer to returned thread identifier
);
用它和CreateThread的声明相比,有下面几点不同:
hProcess参数在CreateRemoteThread内是多加的。它是创建线程的进程句柄。
lpStartAddress参数是CreateRemoteThread 内描述远程地址空间内的线程的起始地址。函数必须存在于远程空间内。我们首先必须将代码拷贝到远程空间。
同样的, lpParameter指针必须存在于远程空间, 所以我们也要将它拷贝到那儿。
现在我们将这种技术总结为如下几步:
1.取得远程空间的句柄(OpenProcess).
2.在远程地址空间为注入的数据分配内存(VirtualAllocEx)
3.将自定义结构INJDATA拷贝到分配的内存里(WriteProcessMemory)
4.在远程地址空间为注入的代码分配内存。
5.将ThreadFunc拷贝到分配的内存里。
6.通过CreateRemoteThread启动远程的ThreadFunc。
7.等待远程线程的结束(WaitForSingleObject).
8.从远程进程返回结果(ReadProcessMemory or
GetExitCodeThread
).
9.释放在步骤2#和步骤#4分配的内存(VirtualFreeEx);
10.关闭在步骤#1和步骤#6返回的句柄(CloseHandle).
ThreadFunc必须服从下面的要求:
1.ThreadFunc 只能调用kernel32.dll 和 user32.dll内部的函数,只有kernel32.dll 和 user32.dll(注意user32没有被映射的每一个win32进程)能保证本地和目标进程有同样的加载地址。如果你需要其他库中的函数,将LoadLibrary and
GetProcAddress的地址传入注入代码,如果有其他有争议的DLL已经被映射到了进程空间你也可以用GetModuleHandle 代替of
LoadLibrary,
同样的,如果你想从ThreadFunc内调用你自己的子程序。可以将地址先通过INJDATA传入到ThreadFunc,再经过ThreadFunc加载到远程空间。
2.不要使用任何静态字符串,最好将所有的字符串通过INJDATA传递给ThreadFunc.
为什么呢?编译器将所有的静态字符串放入到了可执行文件的".data"段,并且只是在代码内引用。这样的话,远程空间中的ThreadFunc的拷贝将指向一些不存在的地址,(最少不在它的地址空间内).
3.将/GZ编译开关关掉,在debug里它是默认的。
4.要不将ThreadFunc and
AfterThreadFunc设为静态的,要不取消增量链接。
5.在ThreadFunc内本地变量必须小于page-worth(4kb)。注意在debug中有效的4kb中的大约10字节是内部变量使用的。
6.如果switch块大于3个case,就要把它象下面这样分开。
或者改用if-else if
switch( expression ) {
case constant1: statement1; goto END;
case constant2: statement2; goto END;
case constant3: statement2; goto END;
}
switch( expression ) {
case constant4: statement4; goto END;
case constant5: statement5; goto END;
case constant6: statement6; goto END;
}
END:
如果不使用这些规则,很可能使目标进程崩溃,记住,不要将目标进程的空间当成你自己进程的空间。
[源址]http://www.codeproject.com/KB/threads/winspy.aspx
// Unload "LibSpy.dll" from the target process // (via CreateRemoteThread & FreeLibrary) hThread = ::CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
"FreeLibrary" ),
(void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );
// Clean up ::CloseHandle( hThread );
将代码注入到进程的三种方式相关推荐
- python点击按钮创建进程_python-创建进程的三种方式
1,os.fork() 方法 import os ret = os.fork() if ret == 0: #子进程 print("Sub process pid = %d, Sub pro ...
- 用代码实现标签打印的三种方式
最近项目中要实现标签打印的功能,有几个条件 标签模板可以事先生成,用的是CodeSoft软件 标签模板里面有二维码 标签模板里面有一些变量,要求打印的时候自动填充 产线电脑上没有安装CodeSoft, ...
- SpringBoot中注入ApplicationContext对象的三种方式
在项目中,我们可能需要手动获取spring中的bean对象,这时就需要通过 ApplicationContext 去操作一波了! 1.直接注入(Autowired) @Component public ...
- 后台长期运行进程的三种方式
入门: nohup command > /var/log/test.log 2>&1 & 优雅: screen方式,通过screen 命令创建的环境下运行的终端命令,其父进 ...
- Spring依赖注入的三种方式(好的 坏的和丑的)
关于spring bean三种注入方式的优缺点对比,翻译自Spring DI Patterns: The Good, The Bad, and The Ugly,水平有限,如有错误请指正. Sprin ...
- 依赖注入的三种方式_ASP.NET Core技术研究-探秘依赖注入框架
ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务.提供服务.依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者. 毫不夸张的说,ASP.NET ...
- 直接将自身代码注入傀儡进程
EXE注入-傀儡进程 2008-08-16 16:38 直接将自身代码注入傀儡进程,不需要DLL.首先用CreateProcess来创建一个挂起的IE进程,创建时候就把它挂起.然后得到它的装载基址,使 ...
- 依赖注入的三种方式_一起学Spring之三种注入方式及集合类型注入
本文主要讲解Spring开发中三种不同的注入方式,以及集合数据类型的注入,仅供学习分享使用,如有不足之处,还请指正. 概述 Spring的注入方式一共有三种,如下所示: 通过set属性进行注入,即通过 ...
- Spring注解依赖注入的三种方式的优缺点以及优先选择
当我们在使用依赖注入的时候,通常有三种方式: 1.通过构造器来注入: 2.通过setter方法来注入: 3.通过filed变量来注入: 那么他们有什么区别吗?应该选择哪种方式更好? 代码示例: Con ...
最新文章
- hdu 5155(DP+排列组合)
- 保研计算机辅助翻译,本科2013级推免生寄语
- 边缘计算应用场景_云计算与边缘计算协同九大应用场景(2019年)发布(附PPT解读)...
- vue全局引入openlayers_vue中使用OpenLayers(一):引入谷歌地图
- 脚本化HTTP 取得响应 指定请求
- 这篇文章,专治MQ中间件各种疑难杂症
- Android功耗(8)---Camera功耗分析和拆解
- Java虚拟机的什么周期
- ucc编译器(语义分析)
- pip 安装GDAL
- nlp项目:搭建一个简单的问答系统
- jdbc连接数据库代码
- Masonry 布局 cell 高度适应的一种方案(实现类似朋友圈简单布局)
- 最新 iOS13 苹果登录
- 反转链表-就地逆置法
- 红米note4出厂系统版本_红米Note4官方出厂rom系统刷机包_升级包降级包回退包下载...
- Python实现京东价格监控
- 边缘保留滤波(EPF)
- 如何抓住风口,利用互联网赚钱?(內含三大商业模式推荐)建议收藏
- 常见网络端口_您应该知道的14个常见网络端口