反射式dll注入「建议收藏」

反射式dll注入「建议收藏」前不久实现了经典dll注入exe,实现注入到用户层面进程,比如notepad.exe和wps.exe。(像金山毒霸kxetray.exe有自我保护机制也注入不进去)。由于对会话隔离注入不太了解,怕搞坏主机,没有再去实现注入到系统进程中。最近在网上看到dll反射注入,想把学习总结的笔记给大家分享一下。首先什么是反射式注入?它和传统经典注入有什么区别呢?我这里作个比喻。经典式:在别人的内存里调用…

大家好,又见面了,我是你们的朋友全栈君。

  前不久实现了经典dll注入exe,实现注入到用户层面进程,比如notepad.exe和wps.exe。(像金山毒霸kxetray.exe有自我保护机制也注入不进去)。特此记录一下。
  首先什么是反射式注入?它和传统经典注入有什么区别呢?我这里作个比喻。

经典式
  在目标进程申请一段空间并放入恶意dll,创建远线程让目标进程调用LoadLiabrary函数加载这段内存
  先把病毒扔进邻居家,告诉邻居这是个炮仗(病毒dll),邻居误以为真,点燃导火索(创建线程执行),显然邻居被骗了
反射式:
   在别人的内存里调用自己编写的dll导出函数 ,自己dll导出函数里实现自我加载(加载PE的整个过程)
   先把病毒扔进邻居家,然后自己把它直接点燃(调用dll导出函数),病毒自己爆炸(dll内部实现自己从底层dll遍历加载函数),邻居浑然不知(没有通过系统LoadLibrary调用dll,没有记录)

区别是啥
  少了使用LoadLibrary的过程。

反射式注入dll的优点

   反射式注入方式并没有通过LoadLibrary等API来完成DLL的装载,DLL并没有在操作系统中”注册”自己的存在,因此ProcessExplorer等软件也无法检测出进程加载了该DLL。利用解密磁盘上加密的文件、网络传输等方式避免文件落地,DLL文件可以不一定是本地文件,可来自网络等,总之将数据写到缓冲区即可。
   由于它没有通过系统API对DLL进行装载,操作系统无从得知被注入进程装载了该DLL,所以检测软件也无法检测它。同时,由于操作流程和一般的注入方式不同,反射式DLL注入被安全软件拦截的概率也会比一般的注入方式低。

实现需求

一、注射器
  (将这个DLL文件写入目标进程的虚拟空间中)实现大部分和传统注入一样。但它要做一件很重要的事情,就是获得病毒dll里的一个最重要的导出函数,也即是我们要编写的反射加载自己的函数ReflectiveLoader的文件偏移。(如果你知道自己dll这个导出函数文件偏移多少其实也没必要再用程序读取)

二、编写一个邪恶的dll以及它的核心函数ReflectiveLoader
  要知道ReflectiveLoader函数运行时所在的DLL还没有被装载,它在运行时会受到诸多的限制,例如无法正常使用全局变量等。而且,由于我们无法确认我们究竟将DLL文件写到目标进程哪一处虚拟空间上,所以我们编写的ReflectiveLoader必须是地址无关的。

注射器实现

  1. 将待注入DLL读入自身内存(可以在磁盘上存放一份DLL的加密后的版本,然后将其解密之后储存在内存里)
  2. 利用VirtualAlloc和WriteProcessMemory在目标进程中写入待注入的DLL文件
  3.利用CreateRemoteThread等函数启动位于目标进程中的ReflectiveLoader(要首先获得ReflectiveLoader的文件偏移地址),获取的是DLL中反射加载函数在文件中的偏移,由于这种注入没有使用LoadLibraryA函数,所以DLL在内存中的状态和在磁盘中的状态是相同的,要想调用函数,我们就需要找到文件偏移。
  线程函数的地址=缓冲区的基地址+文件偏移

在这里插入图片描述

在这里插入图片描述
图中1264十六进制就是4F0

  4.通过DLL的导出表找到这个ReflectiveLoader并调用它
   将自身合适地展开到虚拟空间中。我们都知道在PE文件包含了许多节,而为了节省存储空间,这些节在PE文件中比较紧密地凑在一起的。而在广阔虚拟空间中,这些节就可以映射到更大的空间中去。(更不用说还存在着.bss这样的在PE文件中不占空间,而要在虚拟空间中占据位置的节)

ReflectiveLoader函数实现

实现中的问题:

  DLL中可能会用到其他DLL的函数,装载一个DLL还需要将这个DLL依赖的其他动态库装入内存,并修改DLL的IAT指向到合适的位置,这样对其他DLL函数的引用才能正确运作ReflectiveLoader的代码是地址无关的,但是该DLL的其他部分的代码却并不是这样的。在一份源代码编译、链接成为DLL时,编译器都是假设该DLL会加载到一个固定的位置,生成的代码也是基于这一个假设。在反射式注入DLL的时候,我们不太可能申请到这个预先设定好的地址,所以我们需要面对一个重定位(Rebasing)的问题。

解决方案

  1.ReflectiveLoader做的第一件事就是查找自身所在的DLL具体被写入了哪个位置
ULONG_PTR caller( VOID ) { return(ULONG_PTR)_ReturnAddress(); }
借助上文找到的地址,我们逐字节的向上遍历,当查找到符合PE格式的文件头之后,就可以认为找到了DLL文件在内存中的地址了。
Caller()函数:获得当前指令的下条指令的地址。

  2.我们需要的函数是kernel32.dll中的LoadLibraryA(), GetProcAddress(),VirtualAlloc()以及ntdll.dll中的NtFlushInstructionCache()函数。我们需要遍历已经加载的模块,从中找到我们需要的模块,获得以上几个函数的地址。
#define KERNEL32DLL_HASH 0x6A4ABC5B
#define NTDLLDLL_HASH 0x3CFA685D
#define LOADLIBRARYA_HASH 0xEC0E4E8E
#define GETPROCADDRESS_HASH 0x7C0DFCAA
#define VIRTUALALLOC_HASH 0x91AFCA54
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
// compute the hash values for this function name
dwHashValue = hash((char *)(uiBaseAddress + DEREF_32(uiNameArray)));
每一个线程都具有一个TEB结构,记录了相关线程的一些基本信息。线程运行时,其FS段寄存器记录了其TEB的位置。
获取PEB的方法:FS:[0x30]和GS:[0x60],前者为32位系统,后者为64位系统。
这里需要用到之前TEB定位与导出函数定位的知识。

  3. 分配一片用来装载DLL的空间。
虽然在ReflectiveLoader运行时,DLL文件已经在进程内存中了,但是要装载这个DLL,我们还需要更大的空间。借助在第2)步得到的函数VirtualAlloc(),我们可以分配一片更大的内存空间用于加载DLL。在PE头中的IMAGE_OPTIONAL_HEADER结构体中的SizeOfImage成员记载DLL被装载后的大小,我们按照这个大小分配内存即可。
uiBaseAddress=(ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage,
MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE );
uiBaseAddress记录了VirtualAlloc的返回值,也就是分配内存空间的起始地址。于是uiBaseAddress就成为了DLL被装载后的基地址。

  4.复制PE文件头和各个节
分配了用于装载的空间后,ReflectiveLoader将DLL文件的头部(也就是DOS文件头、DOS插桩代码和PE文件头)复制到新的空间的首部。再根据PE文件的节表将各个节复制到相应的位置中。

  5.根据节表加载节

  6. 处理DLL的导入表
被注入的DLL可能还依赖于其他的DLL,因此我们还需要装载这些被依赖的DLL,并修改本DLL的引入表,使这些被引入的函数能正常运行。
无论是以什么方式导入,我们都要需要找到对应的函数,然后将其地址填入FirstThunk指向的IMAGE_THUNK_DATA数组中。这个跑起来是IAT

  7.对DLL进行重定位
  由于基址改变,所以程序中的一些直接寻址等会出问题,所以要更改重定向表。
  被注入的DLL只有其ReflectiveLoader中的代码是故意写成地址无关、不需要重定位的,其他部分的代码则需要经过重定位才能正确运行。
  我们首先计算得到基地址的偏移量,也就是实际的DLL加载地址减去DLL的推荐加载地址。DLL推荐加载地址保存在NT可选印象头中的ImageBase成员中,而实际DLL加载地址则是我们在第3)步中函数VirtualAlloc()的返回值。然后我们将VirtualAddress和Typeoffset合力组成的地址所指向的双字加上这个偏移量,重定位就完成了。
(DWORD)(VirtualAddress + Typeoffset的低12位) += (实际DLL加载地址 – 推荐DLL加载地址)
  在完成所有的重定位后,我们最后调用第2)步得到的NtFlushInstructionCache()清除指令缓存以避免问题。
8. 调用DLL入口点
  至此,ReflectiveLoader的任务全部完成,最后它将控制权转交给DLL文件的入口点,这个入口点可以通过NT可选印象头中的AddressOfEntryPoint找到。一般地,它会完成C运行库的初始化,执行一系列安全检查并调用dllmain。

用到的底层C++语言编程

  dll遍历kernel32与ntdll的导出函数部分用汇编语言更加直接简短,这部分代码同样是大部分加壳程序的壳内核心代码,有时间一定会细心研究一下。
  _rotr函数,功能是value右循环移动shift位( unsigned int value, int shift );
  register 关键字请求“编译器”将局部变量存储于寄存器中——最快的关键字
  register 这个关键字请求编译器尽可能的将变量存在CPU内部寄存器,而不是通过内存寻址访问,以提高效率。注意是尽可能,不是绝对。
  数据从内存里拿出来先放到寄存器,然后CPU 再从寄存器里读取数据来处理,处理完后同样把数据通过寄存器存放到内存里,CPU 不直接和内存打交道。
内存 寄存器 CPU
  寄存器其实就是一块一块小的存储空间,只不过其存取速度要比内存快得多。CPU从寄存器里拿数据比在大内存里去寻找某个地址上的数据快多了。
  __readgsqword(0x60) 从GS寄存器里读取数值

查杀建议

  回想我们注射器实现的过程中所调用的函数,与正常的注入似乎没有太大的区别,像CreateRemoteProcess这种危险函数杀软抓的很严,可以被替换掉。整个过程没有发现LoadLibraryA函数。但这个样本有明显的特征:解析PE结构,所以当我们遇到这种样本的时候,可以考虑为反射式DLL注入。
  用IDA逆向程序时,解析PE结构的过程,编程中会出现很多+0xnumber 这里number代表十六进制数字。
  在磁盘发现某dll很可疑,如果该dll被注入到进程中已经在运行,删除或移动dll时会弹窗正在某某程序中使用,可以看到它是不是注入的dll。
  另外注入到进程的恶意Dll程序执行错误时崩溃并不会导致被注入程序实际的崩溃。

参考

来源https://bbs.pediy.com/thread-224241.htm

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/145399.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • vue怎么和后端对接_vue后端框架推荐

    vue怎么和后端对接_vue后端框架推荐简单分享一下jeeplus框架部署liunx服务器跨域问题 ,因为我这个是前后端服务器分离所以配置了俩份java后端配置1.跨域后端配置nginx(图-1)上配置 server { listen 80; server_name xxx.xx.xxx; #后端服务域名 #charset koi8-r; #access_log logs/host.access.log main; locatio

    2022年8月19日
    33
  • 上下文无关文法产生的语言都可以用正则文法来描述_c语言结构体默认值

    上下文无关文法产生的语言都可以用正则文法来描述_c语言结构体默认值对于文法G=(V,T,S,P),如果产生式的形式如下:A->xBA->x其中A,B属于V,x属于T*,则称为右线性文法;相似的,如果产生式的形式如下:A->BxA->x则称为左线性文法。右线性文法和左线性文法统称为正则文法。正则表达式的表达能力等价于正则文法,正则表达式的定义如下:字母表中的任意字母是正则表达式,空串和空集也是正则表达式;如果r,s…

    2022年10月24日
    0
  • oracle的union和union all_oracle count函数

    oracle的union和union all_oracle count函数定义了unionvar{uchartempa[4];ulongtemp3;};unionvarlongdat小编们利用C语言定义一个简单的Union共用体结构。你曾经喜欢小编,现在不喜欢了,小编不怪你,那是小编没本事。在这个结构中包含若干个属性,其中有Int、Char和Double型。谁是谁生命中的过客,谁是谁生命的转轮,前世的尘,今世的风,无穷无尽的哀伤的精魂。此时小编们…

    2022年10月26日
    0
  • MapReduce编程案例系列篇(01-15)

    MapReduce编程案例系列篇(01-15)由于本人最开始接触大数据工作,主要以写MapReduce程序为主,虽然现在有流行的言论称MapReduce这种运行很慢的分布式计算编程框架将要被各种内存计算框架取代。但是MapRedcue也会吸收很多流行的内存计算的各种优点,我相信,将来,MapReduce绝对不会沦落到要淘汰的地步。甚至会后来居上。在此,本人总结一篇关于MapReduce编程的各种典型应用场景编程案例,便于大家查阅学习…

    2022年6月17日
    33
  • 7-20 表达式转换(栈)[通俗易懂]

    7-20 表达式转换(栈)[通俗易懂]原题链接算术表达式有前缀表示法、中缀表示法和后缀表示法等形式。日常使用的算术表达式是采用中缀表示法,即二元运算符位于两个运算数中间。请设计程序将中缀表达式转换为后缀表达式。输入格式:输入在一行中给出不含空格的中缀表达式,可包含+、-、*、\以及左右括号(),表达式不超过20个字符。输出格式:在一行中输出转换后的后缀表达式,要求不同对象(运算数、运算符号)之间以空格分隔,但结尾不得有多余空格。输入样例:2+3*(7-4)+8/4输出样例:2 3 7 4 – * + 8 4 / +注意

    2022年8月9日
    2
  • svn提交或更新代码选择性设置后缀类型的文件或文件夹不需要资源同步更新

    svn提交或更新代码选择性设置后缀类型的文件或文件夹不需要资源同步更新

    2021年7月15日
    121

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号