实战dll注入(原理, 踩坑及排雷)
摘要
- 使用
vs2019
编写注入器程序, 在生成的注入器可用前, 踩了不少坑, 因此记录一下. - 本文涉及三种恶意代码注入方法: 直接dll注入, 反射式dll注入, 镂空注入. 之所以选这三种注入方法, 是因最近在做一个检测进程内存空间以期发现代码注入的程序, 而实验发现这三种方法对目标进程的改变各有特点:
- 直接dll注入: 还有APC注入, 本质都是在目标进程中执行
LoadLibrary
函数, 因而在枚举进程的模块列表时可看到注入的dll. - 反射式dll注入: 这种方法也会在目标进程中开辟新的内存空间并写入代码. 但因为没有调用
LoadLibrary
, 所以枚举目标进程的模块列表并不能看到注入的dll. (意味着目标进程的PEB没有变化) - 镂空注入: 直接改进程中某一模块的内存空间, 或者先注入一个合法模块, 再镂空该模块.
- 直接dll注入: 还有APC注入, 本质都是在目标进程中执行
- 标了
注:
的地方基本都是踩的坑.
直接注入
- 示例: LibSpy项目的
OnMouseMove
和InjectDll
- 流程:
OpenProcess
打开目标进程(一参为PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE| PROCESS_VM_READ
, 后面CreateRemoteThread
才能执行)- 需要给进程提权, 得到SeDebug权限.
0. 注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用. 成功使用下面这些函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限. 而如果进程没有该权限, 则使用下面的函数后再调用GetLastError
会返回ERROR_NOT_ALL_ASSIGN
.- 先用
LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)
得到用户的debug权限. - 然后用
OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)
获取进程的令牌句柄. - 最后用
AdjustTokenPrivileges
启用特权.
- 先用
- 若要打开关键进程(
csrss.exe
等), 需在驱动中打开, 去掉关键进程的EPROCESS
中的PsIsProtectProcess
标志位, 并关闭dll签名策略
. (参考开源项目blackbone
)
- 需要给进程提权, 得到SeDebug权限.
- 获取待注入的dll路径, 在目标进程内分配一块内存(
VirtualAllocateEx
), 将路径拷贝到该内存中(WriteProcessMemory
) - 获取kernel32中的
LoadLibraryA
地址(如果dll路径是宽字符串, 则用LoadLibraryW
) - 调用
CreateRemoteThread
, 在目标进程中执行LoadLibrary
, 进而执行DllMain
函数中的目标代码- 注意, 因为
VirtualAllocEx
返回的是虚拟地址, 默认情况下CreateRemoteThread
函数的lpStartAddress
参数使用该地址是没问题的. 但是若注入器是32位而被注入程序是64位, 则可能导致CreateRemoteThread
失败而返回NULL. 参考: https://stackoverflow.com/questions/60687458/createremotethread-returns-null-while-trying-to-use-it-to-inject-dll
- 注意, 因为
反射式注入(reflective injection)
- 概要
- 没有用到
LoadLibrary
函数注入dll - 需由注入器自行解析PE文件:
- 将dll头部(包括
DOS头
,PE头
,区块表
)逐字节写入新开辟的内存. - 按重定位表的信息手动重定位
- 修复导入函数表: 使用
LdrLoadDll
得到shellcode需要的库的内存地址,LdrGetProcedureAddress
得到要导入的函数的内存地址, 然后将这些地址填入导入表.
- 将dll头部(包括
- 没有用到
- 流程
- 将自己实现的LoadLibrary功能函数保存为shellcode.
- 注: 在shellcode中使用的系统api都要事先通过
GetProcAddress
获取(使用GetModuleHandleA
获取模块句柄, 传入的模块名不用后缀), 并作为参数传给shellcode. - 注: 需要审查注入器保存的shellcode是否是真实的函数体. 调试发现在vs2019中, 按默认选项编译得到的函数地址处是一条跳转到实际函数体的jmp指令. 因此
需要使用jmp指令的操作数计算实际函数地址
. 如下获取一个shellcode函数的代码:// shellcode函数 void shellcodeFunc(PMY_PARAMS pParams) {// pParams保存LdrLoadDll等系统api的内存地址// 用NtAllocateVirtualMemory在目标进程中开辟一块内存(需指定PAGE_EXECUTE_READWRITE权限)// 将dll的文件内容写入开辟的内存// 修复导入表; 重定位// 执行dll的入口函数DLLMain }DWORD size = 0, ssss=0;// 获取jmp指令后的双字操作数(即jmp的目的地址偏移) DWORD* jmpAddr = (DWORD*) ((BYTE*) shellcodeFunc + 1); // 加5得到jmp指令的下一条指令地址, 然后加上jmp的目的地址偏移, 得到函数体的实际起始地址 WORD* Memx0 = (WORD*) ((BYTE*) shellcodeFunc + 5 + *jmpAddr); LONG_PTR* Memx = (LONG_PTR*) Memx0;// 用0xCCCCCCCCCCCCCCCC作为函数体结束识别标识. while (*Memx != 0xCCCCCCCCCCCCCCCC) { Memx++;size += 8; }// 将shellcode写入文件 HANDLE hFile = CreateFile(LOADECODE, GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, NULL, NULL); if (hFile) {WriteFile(hFile, Memx0, size, &ssss, NULL);CloseHandle(hFile); }
- 注: 用windbg调试远程线程, 发现远程线程中
出现地址访问冲突
:- 原因1: 出问题的地方试图读取
__security_cookie
- 解决: 编译时关闭
/GS
选项, 禁用栈保护.
- 解决: 编译时关闭
- 原因2: 远程线程中试图调用一些不可用的函数, 包括
__CheckForDebuggerJustMyCode
,_RTC_CheckStackVars
- 解决: 禁用
/JMC
选项,/RTC
选项, 其他的如果是必要使用的动态库函数, 则需要用LoadLibrary
和GetProcAddress
获取, 且要确保目标进程已载入相应dll.
- 解决: 禁用
- 原因1: 出问题的地方试图读取
- 注: shellcode函数中不要用字符串常量, 因为在vs2019中调试发现这些字符串总是存在注入器进程的数据区而非栈上, 这样一来shellcode在运行时无法获取字符串(毕竟注入并运行的shellcode在目标进程而非注入器进程). Shellcode中需要用到的字符串常量最好还是通过传参的方式获取.
- 注: 在shellcode中使用的系统api都要事先通过
- 在目标进程中开辟新内存, 依次写入:
- 要注入的dll的文件内容.
- shellcode.
- 传给shellcode的参数, 主要是shellcode需要的如下系统api的函数地址.
LdrLoadDll
: 获取注入的dll依赖的dll的内存地址.LdrGetProcedureAddress
: 获取注入的dll需导入的函数的内存地址.RtlInitAnsiString
RtlAnsiStringToUnicodeString
RtlFreeUnicodeString
: 用以配合上述两个函数, 得到导入函数的内存地址.NtAllocateVirtualMemory
: 分配内存空间, 以写入要注入的dll的文件内容.
- 创建远程线程, 执行shellcode, 由shellcode载入dll.
- 将自己实现的LoadLibrary功能函数保存为shellcode.
- 优点
- 仅枚举进程模块列表并不能发现注入的dll.
- 仅枚举进程模块列表并不能发现注入的dll.
镂空(hollowing):
- 概要:
- 两种方式:
- 镂空已有进程模块: 直接修改进程中已有模块的代码节, 注入恶意代码.
- 先注入后镂空: 注入一个合法dll(拥有合法签名), 然后修改dll入口点处代码为自己想执行的代码.
- 下面只讲先注入dll后镂空的方法.
- 两种方式:
- 流程
- 首先, 如普通的dll注入,
CreateRemoteThread
创建远程线程, 执行LoadLibrary
注入一个dll, 不同的是注入到进程的是一个合法dll(比如system32目录下的dll). EnumProcessModules
枚举进程模块,GetModuleBaseNameA
得到每个模块的名称, 从而找到注入的dll.- 注入器进程中分配0x1000的内存空间(可用
malloc
或HeapAlloc
), 然后将找到的dll的PE头部内容读进来. - 由PE头中的optional头得到目标dll的入口地址, 加上枚举模块时得到的dll的基址, 得到实际dll入口地址, 并用
WriteProcessMemory
向该地址写入shellcode.- 注: 通过windbg调试发现, 注入入口地址不一定能成功执行shellcode, 因为DllMain函数可能多次执行. 如果只想执行一次shellcode, 可把shellcode及其写在dll的末尾对齐空间.
- 如同反射式dll注入, 生成shellcode的方法也是在注入器中定义shellcode函数并获取其机器码.
- 创建远程线程, 并以写入shellcode的地址为线程执行地址.
- 首先, 如普通的dll注入,
- 优点
- 无需将恶意dll保存在磁盘中, 可躲避静态查杀.
实战dll注入(原理, 踩坑及排雷)相关推荐
- AB测试实战案例讲解及踩坑事项
Hey,我是小z 今天我们来结合流程,讲讲具体的AB测试案例,以及AB测试中需要注意的问题,还有面试中可能会踩的坑. AB测试案例串讲 大体背景如下: 某社交APP增加了"看一看" ...
- 【网络杂烩 ---> 网络安全】DLL 注入 --- c/c++ 代码实现(超 · 详细)
麻了,之前写的博客全是收藏,没人点赞,来点赞行不行! GitHub同步更新(已分类):DLL_Injection Gitee同步更新(已分类)DLL_Injection 公众号:URLeisure 的 ...
- cxfreeze打包python项目踩坑笔记
先说结论 推荐使用cxfreeze-quickstart命令来手动配置打包参数. 若项目包含有ctypes加载的dll文件,需要在setup.py的buildOptions内传入,include_fi ...
- concat拼接的坑 mysql_DNSlog注入踩坑记录:
我遇到了两个巨坑,加上http://ceye.io/,经常无法访问,记录一下心酸的踩坑历史. 直接将两个巨坑放到最前面,提醒后人!! 1. sql盲注,后端数据库用的mysql数据库,说一下用dnsl ...
- sonar覆盖率怎么统计的_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(上)...
本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 一.概述 测试覆盖率是老生常谈的话题.因为我测试理论基础不是很好,这里就不提需求.覆盖率等内容,直奔主 ...
- python array赋值_从踩坑学Python内部原理(5):执行时机的差异
(给Python开发者加星标,提升Python技能) 英文:Satwik Kansal,翻译:暮晨 Python开发者整理自 GitHub [导读]:Python 是一个设计优美的解释型高级语言,它提 ...
- Python打包工具Pyintealler打包py文件为windows exe文件过程及踩坑记录+实战例子
Python打包工具Pyintealler打包py文件为windows exe文件过程及踩坑记录+实战例子 目录 Python打包工具Pyintealler打包py文件为windows exe文件过程 ...
- HBase实战:记一次Safepoint导致长时间STW的踩坑之旅
本文记录了HBase中Safepoint导致长时间STW此问题的解决思路及办法. 上篇文章回顾:HBase Replication详解 过 程 记 录 现象:小米有一个比较大的公共离线HBase集群 ...
- mvn exec: java_实战|Java 测试覆盖率 Jacoco插桩的不同形式总结和踩坑记录(下)
本文为霍格沃兹测试学院优秀学员关于 Jacoco 的小结和踩坑记录.测试开发进阶学习,文末加群. 六.注意事项汇总 修改 JAVA_OPTS 参数时,如果位置不对,可能造成代理无法启动. java - ...
最新文章
- 10624 - Super Number
- swift混编oc碰到的问题
- matlab 查找字符串中第一个不为空格的_替换空格(剑指offer第三题)
- android 计算圆周率方法,android Math的使用
- Coloring Flame Graphs: Code Hues
- bypassuac提权
- 极域电子教室破解控制---万能密码、查找密码
- 入侵检测系统原理和实现
- 【MySQL学习笔记】电子杂志订阅表的操作
- win10下ipv6安装与设置
- C凛冬将至(LCA最短路+分情况5次LCA)
- 计算机开机按f1,电脑开机要按f1怎么解决 开机按F1的各种解决方法整理
- RS232、RS485、RS422、RJ45接口有什么区别
- CSK6开发分享1-视觉开发套件初体验篇
- 数学:矩估计和最大似然估计
- Docer镜像的导入导出
- C语言实验室内部培训讲义
- java socket同步_Java socket客户端与服务端同步通信实例
- 大学的计算机专业英文,计算机专业大学生英文简历模板
- iOS逆向学习笔记(一)
热门文章
- httpd: Could not reliably determine the server's fully qualified domain name
- Educoder离散数学实训【自然推理系统】答案
- PyQt5:简易视频播放器
- 用c++写一个地铁售票系统
- windows快捷键自定义_在Windows中创建自定义Windows键盘快捷键
- HCL配置二层静态链路聚合实验
- wav、mp3和ogg格式浏览器的支持情况
- exadata cell节点的模拟
- 可解释性与积分梯度 Integrated Gradients
- Python Web开发-django搭建环境