RDI 反射dll加载(KaynLdr)
RDI是无文件落地,直接内存加载执行PE的技术,C2中经常使用。
背景
最初的rdi技术在很早就已经出现,地址ReflectiveDLLInjection,里面用到了的API包括HeapAlloc
、VirtualAllocEx
、WriteProcessMemory
等,随着现在杀软技术的提升,这种方式已经不能够满足现在的免杀要求,现在需要通过Syscalls
、函数hash等方式来提升免杀能力。
下面就通过KaynLdr这个项目来简单分析一下目前在代码方面可以优化的地方。
项目介绍
该项目代码使用了C语言和汇编,分配内存时设置内存属性为RW,等到将dll代码写入到分配的内存以后将内存属性改为RW,这可以有效避免杀软对敏感内存的动态扫描。最后在执行dll代码时,又清空了dll的DOS和NT头,让这段内存在杀软看起来不那么可疑。
编译及运行
编译使用的系统为Parrot Linux
,git clone项目后使用make来进行编译,此时会提示x86_64-w64-mingw32-gcc: not found
,此时需要使用命令sudo apt-get install gcc-mingw-w64-x86-64
来安装mingw编译器。目前该项目不支持x86编译,所以需要修改KaynInject
目录下的makefile文件,去掉x86编译的命令即可正常编译。最后的结果如下图
重点代码分析
在dll中获取文件头位置
|
|
可以优化的地方:此时dll已经加载到目标进程的内存中,MZ这个标志对杀软来说比较明显,可以修改为其他字符。
获取函数地址的方式
-
获取函数所在dll的地址
1 2
hKernel32 = KGetModuleByHash(KERNEL32_HASH); hNtDLL = KGetModuleByHash(NTDLL_HASH);
KaynLoader函数运行完以前,输入表还没有初始化,所以此时获取dll地址是通过PEB获取的,通过代码
__readgsqword( 0x60 )
来获取到PEB的地址,然后读取Ldr结构中的InMemoryOrderModuleList
链表,使用dll名字的hash去对比,然后获取到dll地址。(此处使用hash可以有效的免杀) -
获得dll的地址以后,通过读取dll的导出表,同样比对函数名字的hash来查找函数地址,可以获得必要的函数包括
NtAllocateVirtualMemory
、NtProtectVirtualMemory
,此处是从本身的ntdll中获取函数地址。此处写内存时使用的__movsb
函数
可以优化的地方:此处获取函数地址是从本身的ntdll中获取,这篇文章里面说过ntdll里面敏感函数可能被修改,所以尽可能使用其他方法来获取ntdll中干净的函数地址,可以使用文章里面已经讲解过那几种方法。
项目中Syscall的代码
|
|
这个项目中的syscall分为两个部分。
-
SyscallPrepare
部分将函数的调用号传入r11寄存器的低32位,中间还使用了许多的nop混淆了代码,可以干扰杀软的判断 -
SyscallInvoke
部分将上面获取的函数调用号传入eax中,同上面一样中间加了nop来混淆代码
可以改进的地方:此处杀软可能会检测syscall指令来源地址,上面的这种写法syscall的来源是当前进程中的某个地址,不合常理。syscall应该来源于ntdll的内存领域中,关于这一点可以参考这个代码,这份代码的功能是创建挂起的指令然后获取到syscall的调用号,编写汇编指令模拟syscall过程,将dll中原本syscall指令的地址传入R11
寄存器,然后jmp r11
,这种方式在目前有效。