逆向工程核心原理-第二十三章-DLL注入
里面函数分析部分很多的内容都参考了SYJ学长的看雪博客: https://bbs.pediy.com/thread-266901.htm
23.1 DLL注入
DLL注入是指向运行中的其他进程强制插入特定的DLL文件.
从技术细节来说, DLL注入命令其他进程自行调用LoadLibrary()API, 加载(Loading)用户指定的DLL文件.
DLL注入与一般DLL加载的区别在于, 加载的目标进程是其自身或其他进程.
如上图所示, myhack.dll被注入到notepad.exe中, 关键在于myhack.dll拥有与其他DLL一样的权限, 比如访问notepad进程内存, 这样就可以做任何想做的事情了.
关于DllMain()函数的分析
1 | BOOL WINAPI DllMain(HINSTANCE instDLL, DWORD dwReason, LPVOID lpvReserved) |
参数
- instDLL 指向自身的句柄
- dwReason 调用的原因
- lpvReserved 隐式加载和显式加载
关键参数-dwReason的四种类型
- DLL_PROCESS_ATTACH(进程映射(附加)): 当一个程序需要调用一个DLL中的函数, 首先要把DLL文件映射到进程的地址空间(前面钩子处理函数KeyboardProc()就是这样). 要把一个DLL文件映射到进程的地址空间, 有两种方法: 静态链接和动态链接的LoadLibrary或LoadLibraryEx. 同时, 当一个DLL文件被映射到进程的地址空间时, 系统会调用该DLL的DllMain函数, 此时dwReason的值为DLL_PROCESS_ATTACH, 这种调用只发生在第一次映射时, 如果同一个进程后来为已经映射进来的DLL再次调用LoadLibrary或LoadLibraryEx, 操作系统只会增加DLL的使用次数, 不会再用DLL_PROCESS_ATTACH调用DLL的DllMain函数.不同进程用LoadLibrary同一个DLL时, 每个进程的第一次映射都会用DLL_PROCESS_ATTACH调用DllMain函数. 总而言之, 以DLL_PROCESS_ATTACH调用DllMain只发生在一个进程第一次调用该DLL时发生
- DLL_PROCESS_DETACH(进程卸载): 当DLL从进程的地址空间中被解除映射时, 系统使用参数DLL_PROCESS_DETACH调用DllMain(), 使用该值的DllMain执行的是DLL的清理工作. DLL被解除映射的两种情况: 1. FreeLibrary解除DLL映射 2. 进程结束而解除DLL映射. 注意: 当使用DLL_PROCESS_ATTACH调用DLL的DllMain时, 如果返回FALSE, 说明初始化失败, 系统会调用DLL_PROCESS_DETACH再次调用DllMain进行清理, 因此, 必须保证清理那些没有成功初始化的东西(会占用资源)
- DLL_THREAD_ATTACH(线程映射): 当进程创建一个线程的时候, 系统查看当前进程地址空间中的所有DLL文件映像, 并用DLL_THREAD_ATTACH调用DLL的DllMain函数, 新创建的线程负责执行这次DLL的DllMain函数, 只有所有的DLL都处理完这一个通知后, 系统才允许进程开始执行它的线程函数. 注意跟DLL_PROCESS_ATTACH的区别, DLL_PROCESS_ATTACH只执行一次, 但是DLL_PROCESS_ATTACH只要有一个新线程在该进程中创建都会执行, 哪怕在线程中建立线程也是一样的.
- DLL_THREAD_DETACH(线程卸载): 如果线程调用ExitThread来结束线程(线程函数返回时, 系统也会自动调用ExitThread), 系统查看当前映射到进程空间中的所有DLL文件映像, 并用DLL_THREAD_DETACH来调用DllMain函数, 并通知所有的DLL去执行线程级的清理工作.
23.2 DLL注入示例
使用LoadLibrary()API加载某个DLL时, 该DLL中的DllMain()函数就会调用执行.
DLL注入的工作原理就是从外部促使目标进程调用LoadLibrary()API, 所以会强制调用执行DLL的DLLMain()函数.
- 改善功能和修复Bug
- 消息钩取
- API钩取
- 其他应用程序
- 恶意代码
23.3 DLL注入的实现方法
三种方法:
- 创建远程线程(CreateRemoteThread()API)
- 使用注册表(AppInit_DLLs值)
- 消息钩取(SetWindowsHookEx()API)
23.4 CreateRemoteThread()创建远程线程
23.4.1 练习示例myhack.dll
过程时将myhack.dll注入notepad.exe进程, 被注入的没有hack.dll用来联网并下载一个html文件.
首先把文件都复制到一个文件夹中
运行notepad.exe
使用ProcessExplore查看
可以看到notepad的PID为2008
运行DebugView
myhack.dll注入
InjectDll.exe用来向目标进程注入DLL文件的实用小程序.打开命令行输入相应参数即可运行InjectDll.exe
确定DLL注入成功
我们查看debugview
然后查看ProcessExplore
可以看到myhack.dll已经注入到了notepad.exe中
结果确认
可以看到网站已经下载
23.4.2 分析示例源代码
myhack.cpp
1 |
|
InjectDll.cpp
用到的函数和指针分析
1 |
|
下面逐个分析API
OpenProcess()
用来打开一个**已经存在的(注意这个条件)**进程对象, 并返回进程的句柄
原型
1 | HANDLE OpenProcess( |
参数
dwDesiredAccess
- PROCESS_ALL_ACCESS:获取所有权限
- PROCESS_CREATE_PROCESS:创建进程
- PROCESS_CREATE_THREAD:创建线程
- PROCESS_DUP_HANDLE:使用DuplicateHandle()函数复制一个新句柄
- PROCESS_QUERY_INFORMATION:获取进程的令牌、退出码和优先级等信息
- PROCESS_QUERY_LIMITED_INFORMATION:获取进程特定的某个信息
- PROCESS_SET_INFORMATION:设置进程的某种信息
- PROCESS_SET_QUOTA:使用SetProcessWorkingSetSize函数设置内存限制
- PROCESS_SUSPEND_RESUME:暂停或者恢复一个进程
- PROCESS_TERMINATE:使用Terminate函数终止进程
- PROCESS_VM_OPERATION:在进程的地址空间执行操作
- PROCESS_VM_READ:使用ReadProcessMemory函数在进程中读取内存
- PROCESS_VM_WRITE:使用WriteProcessMemory函数在进程中写入内存
- SYNCHRONIZE:使用wait函数等待进程终止
bInheritHandle
- TRUE
- FALSE
dwProcessld
- 已存在进程的PID
返回值
- 成功, 则返回指定进程的句柄
- 失败, 则返回NULL, 并可用GetLastError获取错误代码
VirtualAllocEx
在指定进程(注意是指定, 在这里我们所指定的就是notepad的内存区域, 而不是我们运行的Injectdll)的虚拟空间保留或提交内存区域, 除非指定MEM_RESET参数, 否则将该内存区域初始化为0
函数原型
1 | LPVOID VirtualAllocEx( |
参数
hProcess
- 申请内存所在的句柄
lpAddress
- 保留页面的内存地址, 一般设为NULL让系统自动分配
dwSize
- 想分配的内存大小, 以字节为单位, 实际分配的内存大小是业内存大小的整数倍
flAllocationType
- MEM_COMMIT:为特定的页面区域分配内存中或磁盘的页面文件中的物理存储
- MEM_PHYSICAL :分配物理内存(仅用于地址窗口扩展内存
- MEM_RESERVE:保留进程的虚拟地址空间,而不分配任何物理存储。保留页面可通过继续调用VirtualAlloc()而被占用
- MEM_RESET :指明在内存中由参数lpAddress和dwSize指定的数据无效
- MEM_TOP_DOWN:在尽可能高的地址上分配内存(Windows 98忽略此标志)
- MEM_WRITE_WATCH:必须与MEM_RESERVE一起指定,使系统跟踪那些被写入分配区域的页面(仅针对Windows 98)
flProtect
- PAGE_READONLY: 该区域为只读。如果应用程序试图访问区域中的页的时候,将会被拒绝访问
- PAGE_READWRITE: 区域可被应用程序读写
- PAGE_EXECUTE: 区域包含可被系统执行的代码。试图读写该区域的操作将被拒绝。
- PAGE_EXECUTE_READ :区域包含可执行代码,应用程序可以读该区域
- PAGE_EXECUTE_READWRITE: 区域包含可执行代码,应用程序可以读写该区域。
- PAGE_GUARD: 区域第一次被访问时进入一个STATUS_GUARD_PAGE异常,这个标志要和其他保护标志合并使用,表明区域被第一次访问的权限
- PAGE_NOACCESS: 任何访问该区域的操作将被拒绝
- PAGE_NOCACHE: RAM中的页映射到该区域时将不会被微处理器缓存(cached)
返回值
- 成功, 则返回分配的内存的首地址
- 是被, 则返回NULL
WriteProcessMemory
将数据写入内存的函数, 但是前提是这段内存是可写的, 这就是前面的VirtualAllocEx的参数flProtect的值PAGE_READWRITE的作用了, 它使得我们分配的那段内存是可读可写的.
原型
1 | BOOL WriteProcessMemory( |
返回值
- 成功, 则返回一个非零值
- 失败, 则返回0, 可以用GetLastError获取跟多错误详细信息
CreateRemoteThread, 关键函数
创建一个在其他进程(注意是在其他进程中)地址空间中运行(注意是运行的)的线程(即远程线程)
原型
1 | HANDLE WINAPI CreateRemoteThread( |
参数
hProcess
创建的要执行的线程所属的进程的句柄
lpThreadAttributes
一个指向SECURITY_ATTRIBUTES结构的指针, 该结构指定了线程的安全属性.
dwStackSize
线程栈初始大小,以字节为单位,如果该值设为0,那么使用系统默认大小.
lpStartAddress
在远程进程的地址空间中, 该线程的线程函数的起始地址
在这里传入的是LoadLibraryW库函数的地址
lpParameter
传给线程函数的参数
在这里传入的是myhack.dll的路径
dwCreationFlags
线程的创建表示
lpThreadId
指向所创建线程ID的指针, 如果创建失败, 该参数为NULL
返回值
- 成功, 则返回新线程的句柄
- 失败, 则返回NULL
再回来分析InjectDll.exe
- 首先获取目标进程句柄, 使用OpenProcess()实现
- 在notepad的地址空间中分配内存来存储myhack.dll的路径, 通过VirtualAllocEx()实现
- 将myhack.dll的路径写入notepad的内存空间中并获取该地址, 通过WriteProcessMemory实现
- 获取LoadLibraryW的地址, 由于DLL共享所以可以直接使用GetProcAddress获得
- 使用CreateRemoteThread()在notepad中创建一个进程调用LoadLibraryW()从而加载myhack.dll
- 在myhack.dll中执行了DllMain函数, 在其中调用ThreadProc()(及线程过程函数)下载了网页
CreateRemoteThread()实现DLL注入的原理
首先我们一定一定要知道的是InjectDll.exe调用了CreateRemoteThread(), 而CreateRemoteThread()作用的进程却是notepad.exe
而CreateRemoteThread()对notepad.exe做了什么呢, 就是在notepad.exe中创建了一个线程, 且这个线程调用了LoadLibraryW()这个API, 而这个LoadLibraryW()加载的就是myhack.dll. 在这里停下, DLL就是这样注入到了notepad当中.
再具体一点就是, 我们先来看看具体的Inject DLL.cpp源代码
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
注意第四个参数和第五个参数
- pThreadProc, 这个参数是线程函数的起始地址, 意即线程开始时在那个函数那里执行, 我们传入的是LoadLibraryW()函数的地址, 所以该线程会执行该函数在notepad.exe中注入某个DLL, 但是我们现在不知道要注入的是哪个DLL, 所以还需要给LoadLibraryW()一个参数
- pRemoteBuf, 这个参数就是为了弥补上面的空缺, 告诉线程中执行的LoadLibraryW()要注入哪个DLL, 在这个实例中我们给的值是myhack.dll的路径, 所以LoadLibraryW()会注入myhack.dll, 并执行其DllMain(), 剩下的事情就跟消息钩子一样了.
23.4.3 调试方法
首先打开notepad.exe
然后打开OD, 使用附加功能, 将OD附加到notepad上
然后就可以调试notepad.exe了
然后我们设置选项, 使得调试会在DLL注入时在DLL的EP处暂停
AppInit_DLLs注入
该方法使用的是注册表修改, 修改AppInit_DLLs 和 LoadAppInit_DLLs, 使得每个加载了user32.dll的进程都会加载我们添加的DLL, 从而实现DLL注入
源代码分析
1 | // myhack.cpp. 作用是以隐藏模式运行IE,连接到指定网站 |
实际操作
运行 → 注册表编辑器regedit.exe
随后进入如下路径
可以看到我们要找的AppInit_DLLs , 但是我的虚拟机里没有LoadAppInit_DLLs选项, 但是其实就是一个DWORD值, 所以新建一个试试
同时修改其值, 如下:
重启电脑后, 查看ProcessExplorer
发现所有加载了user32.dll的进程都加载了myhack2.dll
但是myhack2.dll只对notepad.exe生效, 所以我们打开notepad.exe查看效果, 由于窗口为隐藏, 只能使用ProcessExplorer查看
下面就是复原重启即可
最后的DLL再上章讲过了, 看21章内容即可