实战DLL注入

Posted androidstarjack

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实战DLL注入相关的知识,希望对你有一定的参考价值。


 

点击上方关注 “终端研发部

设为“星标”,和你一起掌握更多数据库知识

来源:https://bbs.pediy.com/user-home-919002.htm

一


摘要


使用vs2019编写注入器程序, 在生成的注入器可用前, 踩了不少坑, 因此记录一下。


本文涉及三种恶意代码注入方法: 直接dll注入, 反射式dll注入, 镂空注入。之所以选这三种注入方法, 是因最近在做一个检测进程内存空间以期发现代码注入的程序, 而实验发现这三种方法对目标进程的改变各有特点:


直接dll注入: 还有APC注入, 本质都是在目标进程中执行LoadLibrary函数, 因而在枚举目标进程的模块列表时可看到注入的dll。


反射式dll注入: 这种方法也会在目标进程中开辟新的内存空间并写入代码。但因为没有调用LoadLibrary, 所以枚举目标进程的模块列表并不能看到注入的dll。(意味着目标进程的PEB没有变化)。


镂空注入: 直接改进程中某一模块的内存空间, 或者先注入一个合法模块, 再镂空该模块。因为该方法没有注入恶意dll, 所以直接枚举目标进程的模块列表也无法发现恶意代码, 需扫描进程的内存空间。


标了注:的地方基本都是踩的坑。


二


直接注入


示例: LibSpy项目的OnMouseMove和InjectDll


流程:


1、OpenProcess打开目标进程(一参为PROCESS_CREATE_THREAD|PROCESS_QUERY_INFORMATION|PROCESS_VM_OPERATION|PROCESS_VM_WRITE| PROCESS_VM_READ, 后面CreateRemoteThread才能执行)。

需要给进程提权, 得到SeDebug权限。

(1)注: 准确地说不是提权, 而是将访问令牌中禁用的权限启用。成功调用下面几个函数的前提是进程具备该权限, 只是访问令牌中没有启用该权限。而如果进程没有该权限, 则使用下面的函数后再调用GetLastError会返回ERROR_NOT_ALL_ASSIGN。


(2)先用LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&luid)得到用户的debug权限。


(3)然后用OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES,&hToken)获取进程的令牌句柄。


(4)最后用AdjustTokenPrivileges启用特权。

若要打开关键进程(csrss.exe等), 需在驱动中打开, 去掉关键进程的EPROCESS中的PsIsProtectProcess标志位, 并关闭dll签名策略。(参考开源项目blackbone)


2、获取待注入的dll路径, 在目标进程内分配一块内存(VirtualAllocateEx), 将路径拷贝到该内存中(WriteProcessMemory)。


3、获取kernel32中的LoadLibraryA地址(如果dll路径是宽字符串, 则用LoadLibraryW)。


4、调用CreateRemoteThread, 在目标进程中执行LoadLibrary, 进而执行DllMain函数中的目标代码。


注意, 因为VirtualAllocEx返回的是虚拟地址, 默认情况下CreateRemoteThread函数的lpStartAddress参数使用该地址是没问题的。但是若注入器是32位而被注入程序是64位, 则可能导致CreateRemoteThread失败而返回NULL。参考: createremotethread-returns-null-while-trying-to-use-it-to-inject-dll(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得到要导入的函数的内存地址, 然后将这些地址填入导入表。

流程


1、将自己实现的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。

注: shellcode函数中不要用字符串常量, 因为在vs2019中调试发现这些字符串总是存在注入器进程的数据区而非栈上, 这样一来shellcode在运行时无法获取字符串(毕竟注入并运行的shellcode在目标进程而非注入器进程)。在shellcode中最好还是通过传参的方式获取需要用到的字符串常量。

2、在目标进程中开辟新内存, 依次写入:

(1)要注入的dll的文件内容。


(2)shellcode。


(3)传给shellcode的参数, 主要是shellcode需要的如下系统api的函数地址:

LdrLoadDll: 获取注入的dll依赖的dll的内存地址。

LdrGetProcedureAddress: 获取注入的dll需导入的函数的内存地址。

RtlInitAnsiString RtlAnsiStringToUnicodeString RtlFreeUnicodeString: 用以配合上述两个函数, 得到导入函数的内存地址。

NtAllocateVirtualMemory: 分配内存空间, 以写入要注入的dll的文件内容。


3、创建远程线程, 执行shellcode, 由shellcode载入dll。


优点

仅枚举进程模块列表并不能发现注入的dll。

四


镂空(hollowing):


概要:


两种方式:

(1)镂空已有进程模块: 直接修改进程中已有模块的代码节, 注入恶意代码.


(2)先注入后镂空: 注入一个合法dll(拥有合法签名), 然后修改dll入口点处代码为自己想执行的代码。

下面只讲先注入dll后镂空的方法。


流程


1、首先, 如普通的dll注入, CreateRemoteThread创建远程线程, 执行LoadLibrary注入一个dll, 不同的是注入到进程的是一个合法dll(比如system32目录下的dll)。


2、EnumProcessModules枚举进程模块, GetModuleBaseNameA得到每个模块的名称, 从而找到注入的dll。


3、注入器进程中分配0x1000的内存空间(可用malloc或HeapAlloc), 然后将找到的dll的PE头部内容读进来。


4、由PE头中的optional头得到目标dll的入口地址, 加上枚举模块时得到的dll的基址, 得到实际dll入口地址, 并用WriteProcessMemory向该地址写入shellcode。


注: 通过windbg调试发现, 注入入口地址不一定能成功执行shellcode, 因为DllMain函数可能多次执行。如果只想执行一次shellcode, 可把shellcode及其写在dll的末尾对齐空间。


如同反射式dll注入, 生成shellcode的方法也是在注入器中定义shellcode函数并获取其机器码。

5、创建远程线程, 并以写入shellcode的地址为线程执行地址。


优点


无需将恶意dll保存在磁盘中, 可躲避静态查杀。

回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群
阅读更多
用 Spring 的 BeanUtils 前,建议你先了解这几个坑!

lazy-mock ,一个生成后端模拟数据的懒人工具

在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!

字节跳动一面:i++ 是线程安全的吗?

一条 SQL 引发的事故,同事直接被开除!!

太扎心!排查阿里云 ECS 的 CPU 居然达100%

一款vue编写的功能强大的swagger-ui,有点秀(附开源地址)


相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!



喜欢就给个“在看”

以上是关于实战DLL注入的主要内容,如果未能解决你的问题,请参考以下文章

windbg查找Kernel32.dll基址

易语言 程序怎样使用dll中的全局变量

远程线程注入代码

CE修改器使用教程 [提高篇]

渗透实战 | MySQL命令执行(UDF命令执行)

如何实现静态dll注入