无文件后门之内存注入 Fay·D·Flourite

无文件后门–内存注入

shellcode简介

shellcode是十六进制的机器码,由于我不会手动编写(没汇编功底),所以偷懒用msf

msf生成一段打开计算器的shellcode msfvenom -p windows/exec CMD=calc.exe EXITFUNC=thread -f c

生成的shellcode

自己也尝试用c#写了个helloworld窗口,下断点反汇编后提取十六进制码。和生成的shellcode对比,可以看到,不变的是一些机器码的十六进制数。

无文件后门

我们常见的后门,比如msf生成的后门,是能在windows运行的pe文件,以exe文件形式存在,自然容易被发现,所以可以选择将exe文件转为shellcode,达到无实际后门文件的效果。

执行方法

VirtualAlloc

用VirtualAlloc申请一个内存空间,然后直接将shellcode写入内存空间中,即可执行shellcode。

这里并不一定指的只能是VirtualAlloc,像是HeapCreate/HeapAlloc创建的rwe堆也能放shellcode。

这里再提一下,一些脚本如powershell的Invoke-ReflectivePEInjection.ps1,能直接加载转成二进制的PEbyte,究其根本也是用powershell加载了kernel32.dll中的VirtualAlloc,然后写入内存。

是个通用的思想,不管任何语言应该都能参考。

嵌入式汇编

shellcode既然是机器码,当然也可以由汇编语言来执行,在cpp文件中用__asm关键字可以调用内联汇编程序,将shellcode的指针移入寄存器中(mov,lea),再jmp过去,或是call,就可以执行shellcode了。

强制类型转换为函数指针

还是比较好理解吧,强制类型转换一个函数指针,把shellcode当函数执行,也是一种方法

检测

三种方式有各自的特点,VirtualAlloc的特点是开辟了一块保护属性是读,写或执行的内存空间,后两种的特点是能在程序的text段找到

另外就是通过pe文件的导入表查看调用函数中是否有VirtualAlloc之类函数(导入表会受到加壳影响)(如果判断调用函数的话还能靠Windows api hook?这方面不是很懂)

virtualalloc

virtualalloc为特征,则通过读取进程内存,判断内存是否是虚拟内存,再判断内存的保护属性,如果能定位到注入的shellcode的地址,还可以判断一下shellcode的头部是否有dos头(pe文件),亦或是是否有metasploit或是cs生成的payload的特征(比如fce88200…?)。(本人逆向学艺不精不会定位)

写了个简单的脚本遍历当前用户进程以及获取进程的基地址。

可以看到我们的shellcode.exe的基地址是008e13de,以及找到的写入内存的shellcode

这里我们先记一下MEMORY_BASIC_INFORMATION这个结构体的几个重要属性 官方文档

  • BaseAddress –内存的基地址
  • AllocationBase –VirtualAlloc分配的虚拟内存的基地址
  • AllocationProtect –分配内存时设置的内存保护属性,也就是后面判断内存可否执行shellcode的关键,有以下属性

    PAGE_EXECUTE –0x10 PAGE_EXECUTE_READ –0x20 PAGE_EXECUTE_READWRITE –0x40 PAGE_EXECUTE_WRITECOPY –0x80 PAGE_NOACCESS –0x01 PAGE_READONLY –0x02 PAGE_READWRITE –0x04 PAGE_WRITECOPY –0x08 PAGE_TARGETS_INVALID –0x40000000 PAGE_TARGETS_NO_UPDATE –0x40000000 PAGE_GUARD –0x100 PAGE_NOCACHE –0x200 PAGE_WRITECOMBINE –0x400

    主要是前8个

  • PartitionId 区域id
  • RegionSize 区域大小
  • State 初始时设置的状态

    MEM_COMMIT –0x1000 MEM_FREE –0x10000 MEM_RESERVE –0x2000

  • Protect 也是说明保护属性的,是AllocationProtect的一个子项
  • Type 类型

    MEM_IMAGE –0x1000000 MEM_MAPPED –0x40000 MEM_PRIVATE –0x20000

ce看了看各种进程的内存,外加使用virtualalloc注入的shellcode进程,一个很明显的特征就是有一块不是映像(MEM_IMAGE)的内存区域,且保护属性是可执行的,即前四个(PAGE_EXECUTE,PAGE_EXECUTE_READ,PAGE_EXECUTE_READWRITE,PAGE_EXECUTE_WRITECOPY),如图所示区域0x00a50000即是开辟的内存空间,由于要执行shellcode所以需要有execute。

如果特征不对欢迎指正…这是自己总结出来的

有dalao的思路是用c++的栈回溯拿进程每个功能点的栈帧,再通过栈帧去判断每个功能点的内存特征。c#自带一个StackTrack(但是只支持自己进程栈回溯…)没其他办法(不想dllimport),选择的办法是进程一个分区一个分区判断是否有这个特征,有就报没有就下一个。

效果如图:

只能说误报率确实蛮高的…(我疯起来连自己都报

可能是VirtualAlloc特征抓的不够准确,其次shellcode特征也不准确,这点应该找找各大av库。不过一个不是映像的可执行内存空间确实需要警惕

踩坑汇总

  • IntPtr的位数是根据系统决定的,32位系统就是32位,64位系统就是64位,无脑进制转换会导致溢出报错(
  • 有些系统进程即使给了管理员权限给了特权也打不开(实名批评csrss.exe
  • 搞清楚返回的位数,再选用int32或者int64来接收MEMORY_BASIC_INFORMATION返回的结构体,否则会导致错位而有些是0
  • 有几个进程分配到的内存加起来不够4g???横向对比了一下,似乎不够的都是最后一个大的剩余空间(留给内核的2g)没分配够?

嵌入式汇编以及函数指针

汇编后生成的程序有四个大的分段:数据段、代码段、栈区、堆区。

而我们可以在内存中的.data段找到我们存储的shellcode代码(变量

(当然最简单异或一下就没特征了,再根据异或自行调整汇编执行代码。算是静态查杀,上面根据特征抓VirtualAlloc也算静态),这一块也没有virtualalloc的特征,是映像。所以估计得动态查杀或者挂钩子才能治了。

参考了dalao文章的思路