长亭百川云 - 文章详情

在内存的目标文件上执行shellcode

木星安全实验室

55

2024-07-13

在后渗透利用开发中,反射型DLL和shellcode注入仍然是最大的威胁,因为其执行仅在内存中进行,且不会把任何内容拖放到磁盘上。但是,大多数offsec工具只有在初始访问或利用易受攻击的服务和进程时,才会注入shellcode。后渗透利用的攻击者通常会选择可以直接加载到内存的反射性DLL和C#可执行文件。但如果我们可以用更高级的语言(例如C语言)编写shellcode呢?本文将深入探讨链接器和编译器的滥用,用C语言编写shellcode,然后进行提取。

本文提到的所有代码都已上传到我的github(https://github.com/paranoidninja/PIC-Get-Privileges)上。

Windows编译的可执行文件有不同的头文件和段。它们通常以DOS标头、PE标头、可选标头开头,然后段主要时.text,.bss,.idata,.edata,.rdata等。用C语言编写shellcode,覆盖反射型DLL或C#可执行文件,向远程进程注入shellcode时,就算内存里有PE标头、DOS标头或其他明显的字符串,PE也不会被检测到。AMSI也不会检测shellcode,且shellcode的大小比DLL或C#可执行文件小得多。

用C语言编写shellcode时,我们必须注意编译后的可执行文件只有一个可执行文件段(.text段)。PE的全局变量储存在.bss段,.把DLL导入到.idata,导出到.edata。由于我们只要.text段,所以我们的代码中不能包含全局变量,导入的符号或静态字符串。我们的函数中不能包含基于函数字符串的char或wchar数组,因为char数组储存在.rdata段。而我们的目的是把所有内容都写入.text段,从.text段提取操作码,然后在内存中执行它们。也就是说,我们必须在运行时解决导入问题,且不能用静态库,把字节数组转换成字符串。默认情况下,链接器把PE的入口点链到mainCRTStartup,加载dll,解析命令行参数等。如果不想把msvcrt.dll链到可执行文件,那就要把这个入口点更改为其中一个函数。如果只编写x64 shellcode,那就必须确保shellcode以16字节的堆栈对齐方式对齐,。我们要编写独立于Microsoft的cl.exe编译器,且可以用MingW GCC交叉编译器进行编译的代码。

因此,要实现以上所有内容,必须满足以下条件:

  • 16字节堆栈对齐

  • 编译的可执行文件只有.text段

  • 没有独立的char 数组或wchar数组字符串

  • 在运行时解决所有导入

  • 用自定义入口点替换mainCRTStartup函数的链接器脚本

为了确保shellcode始终对齐堆栈,需要编写一个小的程序集存根,它会让堆栈对齐,调用C语言函数作为入口点。把这个汇编代码转换为目标文件,然后将它链到C语言源代码。接着,编写一个函数,获取当前用户的特权信息,并把它输出到屏幕上,给这个C语言函数命名为getprivs。在汇编代码中,把这个函数添加为外部函数,因为我们不会把getprivs函数写入程序集。然后,把程序集文件转换为目标文件,并用C语言编写所有代码来完成所要执行的操作。

extern getprivs

把上面的asm文件命名为Adjuststack.asm,用mingw对其进行编译:

nasm -f win64 adjuststack.asm -o objects/adjuststack.o

既然要获取当前用户的特权信息,那么我们需要导出符号或WinAPI,即LoadLibraryA,CloseHandle,kernel32.dll的GetCurrentProcess,OpenProcessToken,GetTokenInformation,advapi32.dll的LookupPrivilegeNameW以及msvcrt.dll的calloc和wprintf。同时还要更改入口点,为了在创建目标文件时不让其被静态链到。在运行时,计算kernel32.dll的ror13哈希,找到LoadLibraryA的地址,然后用LoadLibraryA加载所需要的库,获取上述每个Windows导出符号的函数指针来解决这些导出问题。GetProcAddress是Windows API中最容易被AV和EDR钩住的函数,所以我们不用它。我们用C语言编写自己的GetProcAddress函数,该函数能解析在LoadLibraryA之后加载的DLL,提取符号的指针。把这些64位指针地址中的每个地址类型转换为我们的typedef。以下是所需符号的类型转换。

#include "addresshunter.h"

为了获取DLL的符号地址,需要用到下面的C语言函数,把该函数添加到addresshunter.h中:

#include <windows.h>

既然要更改入口点,那么链接器也要知道这一点。把入口点重定向到程序集标签alignstack,对齐16字节的堆栈,然后调用getprivs()函数。我们不会编写任何int main()或void main(),所以要确保链接器对齐函数执行的顺序。下面是将执行该操作的链接器脚本。

ENTRY(alignstack)

一切都设置好后,就可以编写入口点函数了。如果检查以上所有C语言代码,你会发现我们没有用任何全局变量或静态char数组字符串。但是大多数时候,我们必须用char数组字符串来格式化或打印shellcode的输出。在这种情况下,我们可以用char或wchar字节数组执行此操作,如下所示。在字节数组中编写char字符串,确保我们的字符串不在.bss段,编译器会强制把它包括在PE的.text段里:

CHAR *loadlibrarya_c = "LoadLibraryA"; // will become ->

获取当前用户特权的最终代码如下所示:

#include "addresshunter.h"

为了编译以上所有内容,我们可以用makefile,如下所示:

make:

上面的makefile执行以下操作:

* 为adjuststack创建一个目标文件

* 为getprivs创建一个64位目标文件。

  • 为源文件中的每个函数生成单独段,并在链接期间删除未使用的函数。

  • 禁用可执行文件的静态链接

  • 优化PE的大小

  • 禁用SEH

  • 用链接器脚本提供的参数对齐函数可执行文件顺序

* 编译上面的目标文件,剥离所有调试符号和注释

运行脚本并使用objdump,可以从PE获得实际的shellcode。

(用objdump从可执行文件中获取shellcode。)

可以用objdump来检查文件是否有.text以外的任何标头。

最后,可以把shellcode复制到bin文件中,然后根据需要执行bin文件。

(用objdump查看从二进制文件获取的shellcode。)

在本文中,我会用ASM的inc-bin技术执行shellcode。

; compile with

最终的可执行文件大小不可以超过5 KB,在我们的示例中,该文件的大小为4.9kb。

(用objdump查看从二进制文件获取的shellcode。)

最后,在Windows上执行此操作:

(用objdump查看从二进制文件获取的shellcode。)

用有特权的cmd提示符和无特权的cmd提示符,输出结果会有所不同。本文已经进入尾声了。我们将在Brute Ratel的下一个版本中添加功能更新,其中将包括在自身进程和远程进程的目标文件中执行Shellcode。

木星安全实验室(MxLab),由中国网安·广州三零卫士成立,汇聚国内多名安全专家和反间谍专家组建而成,深耕工控安全、IoT安全、红队评估、反间谍、数据保护、APT分析等高级安全领域,木星安全实验室坚持在反间谍和业务安全的领域进行探索和研究。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2