0
.前言
最近准备找个实习,把之前学的一些DLL注入方案简单整理了一下,供大家交流学习的同时自己也做一个备份。
代码说明:文件名即为该种注入方式相关的代码 main
函数是测试用的
在Vs2017
环境下测试课正常执行(部分需要管理员权限)
没了解过PE结构的可能看不懂,大佬除外
代码中的参数很容易理解,所以没加太多注释 非常抱歉(稍微复杂的PE自加载还是有注释的)
Shellcode
项目是PELoader
的Shellcode
版本,与testReloc
中的代码相同(包含丰富注释,了解PE的都能看懂)
Shellcode
项目是Github
上开源的一个模版,十分感谢hid3rx
大佬做的模板,很好用:https://github.com/hid3rx/Shellcode 项目说明中包含使用方法
为了你的计算机安全,请不要在物理机上运行!!!
源代码地址方在文章末尾
1.DLL注入
1.1 消息钩子注入
1.1.1 原理
OS
的全局消息队列(os message queue
)会首先获取消息,然后OS
进行判断以后,将消息添加到应用的消息队列(app message queue
)
在消息从os message queue
向app message queue
传递的过程中可以通过SetWindowsHookEx
设置钩子实现信息拦截
此时,你在SetWindowsHookEx
设置的消息处理函数所属DLL
会被加载到目标进程当中从而实现注入
目标进程载入DLL时会调用DllMain
同样可以在这里设置一些小玩意然后卸载掉本DLL
1.1.2流程
1.在DLL
中设置对应的消息处理回调函数
2.通过PID/进程
名获取窗口句柄进而获得线程ID
3.本程序LoadLibrary
载入DLL
;GetProcAddress
获取消息处理回调函数指针
4.SetWindowsHookEx
将消息处理回调注入到目标进程所属的线程,从而将整个DLL
注入到目标进程空间
1.2 远程线程注入
1.2.1 原理:
LoadLibrary()
在任何程序中的地址都是相同的且一定会被加载(kernel32.dll
);
LoadLibrary()
只需要一个参数即dll
路径
LoadLibrary()
载入dll
时会触发DLL_PROCESS_ATTACH
CreateRemoteThread()
会指定一块内存地址为起始执行点并带入一个参数
类似函数:CreateRemoteThread()/NtCreateThread()/RtlCreateUserThread()
1.2.2 流程:
1.利用VirtualAllocEx()
在目标进程空间中申请一块内存
2.利用WriteProcessMemory()
项目标进程写入LoadLibrary()
的参数,即dll
路径
3.GetModuleHandleW()
获取kernel32.dll
模块句柄并通过GetProcAddress()
获取LoadLibrary()
的地址
4.通过CreateRemoteThread()
创建远程线程,指针为LoadLibrary
的地址,参数为我们写入的dll
文件路通
5.在我们编写的dll
中的DLL_PROCESS_ATTACH
分支再次创建一个运行线程或者直接在该分支处理完自己的逻辑
1.2.3 利用CreateRemoteThread
实现Shellcode
注入
1.利用VirtualAllocEx()
在目标进程空间中申请一块内存用于存放ShellCode
2.通过CreateRemoteThread()
创建远程线程,指针为ShellCode
的运行起始地址
1.2.4内核中的远程线程注入
在R0
环境下调用CreateRemoteThread
的底层实现RtlCreateUserThread
同样可以实现DLL
注入和ShellCode
注入
1.3导入表注入、DLL劫持、IATHOOK
1.3.1 原理:
PE
文件被加载时,系统会根据导入表信息加载dll
,将自己的dll
添加到exe
的导入表即可实现
DLL劫持与IATHOOK类似 一个是在外部添加文件劫持,一个是修改内部指向劫持;原理都是利用导入表结构
1.3.2 流程:
1.新增节或扩大最后一个节
2.将原始导入表移到新增节中并修复数据字典指针
3.重新构建导入表并添加到末尾
这个方法没实现
1.4 APC注入
1.4.1 原理
稳定触发APC检查的时机:sleep
、waitforsingleobject
,发生系统调用 异常或中断返回用户空间的时候KiServiceExit()
KeInitializeApc
:初始化APC
结构
KeInsertQueueApc
:APC
插入
1.4.2 用户态APC注入
调用QueueUserAPC
函数,其他内容同远程线程注入类似
1.4.3 内核态APC注入
方法1:QueueUserAPC
对应的内核函数为NtQueueApcThread
,可调用此函数在内核实现APC注入
方法2:NtQueueApcThread
通过调用KeInitializeApc
和KeInsertQueueApc
来实现APC
结构的初始化和插入,手动调用这两个函数更安全
1.4.4 注意事项
1.内核APC不需要考虑线程是否为可唤醒
2.APC执行函数后会调用 ZwContinue 往往HOOK这里进行检测
3.32位进程的函数地址需要手动调用PsWrapApcWow64Thread将其转为64为地址
4.想要用户APC快速执行,需要设置线程的Alertable位,并通过KeAlertThread来唤醒进程进入警惕状态
5.挂靠 KeStackAttachProcess
1.5 反射式注入
1.5.1 原理:
在目标进程空间通过掉用dll的导出函数使dll自加载
模拟dll
加载到内存的过程从而实现不落地的dll注入
好处:没有句柄等相关消息实现隐藏
1.5.2 流程
-----------------注入器进程-----------------
1.将dll源文件载入到目标进程中
2.获取dll中导出的反射加载函数偏移
3.远程线程启动反射加载函数(APC加载更隐蔽)
-----------------反射加载函数-----------------
1.获取文件首地址,通过加载函数的调用返回地址获取
2.通过fs+0x30指向的peb获取Ldr,从而获取KERNEL32DLL和NTDLLDLL的基地址
3.通过解析上述两个dll的pe结构获取LoadLibraryA GetProcAddress VirtualAlloc NtFlushInstructionCache的地址
4.VirtualAlloc分配一段ImageSize大小的内存存放拉伸后的dll
5.复制头和各个节区到新地址
6.LoadLibraryA GetProcAddress修复导入表
7.修复重定位表
8.NtFlushInstructionCache刷新缓存
9.通过dllMain函数指针调用OEP执行入口点函数
疑问:为什么不在注入器中就加载好再扔进目标进程
注入器导入的dll以及导入函数地址依赖于原始注入器进程,加载到目标进程后没有效果
1.6 注册表注入
1.6.1 原理
User32.dll
的 DLL_PROCESS_ATTACH
分支中,会读取注册表HKEY_LOCAL_MACHINE\SOFTWARE(\Wow6432Node)\Microsoft\Windows NT\CurrentVersion\Windows
对应的值,并且调用Loadlibrary
以DLl_PROCESSS_ATTACH
为fdwReason
来载入注册表指定的每个DLL
因此,在需要导入User32.dll
可以使用该方法依靠User32.dll
载入自己的DLL
1.6.2 流程
1.将该注册表键值设置为1:
HKEY_LOCAL_MACHINE\SOFTWARE(\Wow6432Node)\Microsoft\WindowsNT\CurrentVersion\Windows\LoadAppInit_DLLs
2.在该注册表键值中添加要载入的dll的路径:
HKEY_LOCAL_MACHINE\SOFTWARE(\Wow6432Node)\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
代码流程
1.RegCreateKeyExW获取注册表路径句柄(通过主键和多级子键)
2.RegSetValueExW设置键对应的值,注意REG_DWORD和REG_SZ两种键值类型(对应整数和字符串)
1.6.3 注意事项
1.因为x64系统包含Wow64
子系统(处理32位R3应用程序),在32位应用程序载入时使用的注册表路径与64位的不同,因此针对32和64位程序要分别编译两个对应位数的dll放到注册表的不同路径中
2.因为该方法依赖于User32.dll
实现,对于不使用该dll
的程序无法注入
3.同样的,注入的dll会影响到每一个导入User32.dll
的进程,可能会造成大范围崩溃
1.7 挂起注入
1.7.1 原理
CreateProcess
通过挂起方式创建进程,在此状态下只会分配目标进程结构不会对目标PE文件进行解析(执行
GetThreadContext
,在挂起时eax(x86)/rcx(x64)
存放进程入口点(ImageBase + OEP
)
ebx(x86)/rdx(x64)
存放进程_PEB
结构的指针,从_PEB
中可以获取进程进程得到ImageBase
因此,利用挂起到恢复的这个时间差,将傀儡进程
的内存替换为Shell进程
并修复他的Context结构
和PEB
结构,然后再恢复进程即可实现进程镂空注入
1.7.2 镂空(傀儡)注入流程
1.读取Shell进程到注入器中
2.以挂起方式创建傀儡进程CreateProcess
3.获取傀儡进程上下文结构GetThreadContext
4.获取傀儡进程的映像基址ebx(x86)/rdx(x64)
5.判断Shell进程的载入内存地址和傀儡进程的ImageBase是否冲突
6.冲突的话Nt/ZwUnmapViewOfSection解除映射
7.向目标内存写入文件头和各个节区
8.向寄存器eax(x86)/rcx(x64)写入入口点并重设目标进程上下文;重写PEB的ImageBase
9.ResumeThread恢复线程
1.7.3 注意事项
1.若申请的Shell进程内存的ImageBase
和傀儡进程的ImageBase
冲突,需要调用 Nt/ZwUnmapViewOfSection
解除目标进程的内存映射,防止冲突
2. 程序的ImageBase
(PEB
中)、入口点(寄存器中eax/rcx)要替换
3.64位进程只能注入到64位进程中,32位进程也只能注入到32位进程中
※4.注入的进程必须是与地址无关的进程,或者使用类似反射注入相同的反射在加载方式加载后才能运行
5.镂空注入exe还存在问题没有修复,注入Shellcode
是正常的
1.7.4 挂起线程、进程注入
明白了镂空注入以后这两种方式与镂空注入大同小异
1.OpenThread打开线程
2.SuspendThread线程挂起线程(区别1)
3.申请内存并写入代码
4.GetThreadContext获取上下文并修改EIP(区别2)
5.SetThreadContext重设上下文
6.恢复线程
注:代码中,以上所有的CreateRemoteThread
都可以通过替换成QueueUserAPC
来避免线程检测
最后再说一遍:不要在物理机上运行!!!
题外话:有没有大佬公司缺人的,求捞(●'◡'●) (研二找实习)
解压密码:52pj