一款用于分析恶意 JavaScript 的工具。
只需通过 npm 安装 box-js:
npm install box-js --global
box-js 也可作为:
- Cuckoo 模块使用(参见
integrations目录和 Nwinternights/Cuckoo_Boxjs);- Dockerfile 使用(参见
integrations/README.md);- 安全专业人员发行版中的软件包(REMnux,BlackArch);
- 开源应用的一部分(Intel Owl);
- 商业第三方服务的一部分(any.run)。
假设你有一个名为 sample.js 的样本:要分析它,只需运行
box-js sample.js
通常你可能还想下载任何载荷;使用 --download 标志启用下载。否则,引擎将模拟 404 错误,从而使脚本以为分发站点已下线并联系任何备用站点。
Box.js 将模拟 Windows JScript 环境,在控制台打印仿真摘要,并创建一个名为 sample.js.results 的文件夹(如果已存在,则会创建 sample.js.1.results 等)。该文件夹将包含:
analysis.log,屏幕上显示的分析日志;snippets.json,样本执行的代码片段列表(JavaScript、shell 命令等);urls.json,访问的 URL 列表;active_urls.json,似乎会投放活跃恶意软件的 URL 列表;resources.json,ADODB 流(即脚本写入磁盘的文件),包含文件类型和哈希值;IOC.json,被识别为入侵指标(IOC)的行为列表。这些包括注册表访问、写入的文件、HTTP 请求等。你可以自行分析这些内容,也可以自动将它们提交给 Malwr、VirusTotal 或 Cuckoo 沙箱:更多信息请运行 box-export --help。
为进一步隔离,建议在临时 Docker 容器中运行分析。更多信息请查阅
integrations/README.md。
如果你希望自动化分析,可以使用返回码——在
integrations/README.md中有记录——来区分不同类型的错误。
来自 git 的 box-js 仓库包含一个 boilerplate.js 文件。该文件定义了一些常见浏览器对象(如 document)的存根版本。尝试使用 --prepended-code=DIR/boilerplate.js 选项重新运行分析,其中 DIR 是克隆的 box-js 仓库目录,或者使用 --prepended-code=default。--prepended-code 选项告诉 box-js 将给定文件中的 JavaScript 前置到正在分析的样本之前。
请注意,你可以复制 boilerplate.js 并根据需要添加自己的存根类、对象等。使用 --prepended-code=show-default 命令行选项打印默认 box-js boilerplate.js 文件的完整路径。
虽然 box.js 通常用于单个文件,但它也可以运行批量分析。你可以简单地传递一个要分析的文件或文件夹列表:
box-js sample1.js sample2.js /var/data/mySamples ...
默认情况下,box.js 将并行处理样本,每个核心运行一个分析。你可以通过为 --threads 指定值来使用不同的设置:特别地,0 将移除限制,使 box-js 尽可能多地生成分析线程,从而实现非常快速的分析,但可能会导致系统过载(请注意,分析通常是 CPU 密集型,而非内存密集型)。
你可以使用 --loglevel=warn 来静默分析相关消息,仅显示进度信息。
分析完成后,你可以这样提取活跃的 URL:
cat ./*.results/active_urls.json | sort | uniq
NAME 描述
-h, --help 显示帮助文本并退出
-v, --version 显示软件包版本并退出
--license 显示许可证并退出
--debug 当模拟错误发生时终止,即使在“批量模式”下,并传递退出
代码。
--loglevel 日志级别(debug、verbose、info、warning、error - 默认 "info")
--threads 在批量模式下运行时,同时进行多少个分析(0 =
无限制,默认:等于 CPU 核心数)
--download 实际下载载荷
--encoding 输入样本的编码(默认会自动检测)
--timeout 脚本在此秒数后超时(默认 10)
--output-dir 将结果文件和文件夹写入磁盘的位置(默认为
当前目录)
--preprocess 预处理原始源代码(使逆向工程更简单,但需要
几秒钟)
--unsafe-preprocess 更激进的预处理。通常会产生更好的代码,但可能在
某些边缘情况下失败(例如重新定义原型)
--prepended-code 输入文件或目录,其中包含应前置到 JS 的代码
文件。如果指定目录,则前置该目录中所有文件的内容。
如果指定 'default',则使用 box-js 附带的默认 boilerplate.js。
如果指定 'show-default',则仅打印 boilerplate.js 的路径
并退出(如果你想要复制和修改默认 boilerplate 代码,这很有用)。
--fake-script-engine WScript.FullName 和 WScript.Name 中报告的脚本引擎(例如
'cscript.exe'、'wscript.exe' 或 'node')。默认是 wscript.exe。
--fake-cl-args 伪造的脚本命令行参数。在字符串中,这些参数应用逗号
分隔。
--fake-sample-name 用于正在分析的样本的伪造文件名。可以是完整路径或仅
文件名。如果路径中包含 '\',请在此命令行参数值中将其转义为 '\\'
(例如 --fake-sample-name=C:\\foo\\bar.js)。
--fake-language 指定 Win32_OperatingSystem.OSLanguage 返回的语言代码。
支持的值有 'spanish'、'english' 和 'portuguese'。
--fake-domain 指定 WScript.Network.UserDomain 返回的用户域。
--fake-download 假装 HTTP 请求成功并返回假载荷
--no-kill 当运行时错误发生时不要终止应用程序
--no-echo 脚本打印数据时,不要将其打印到控制台
--no-rewrite 根本不要重写源代码,除了 `@cc_on` 支持
--no-catch-rewrite 不要重写 try..catch 子句以使异常全局化
--no-cc_on-rewrite 不要将 `/*@cc_on <...>@*/` 重写为 `<...>`
--no-eval-rewrite 不要重写 `eval` 以使其参数被重写
--no-file-exists 为 Scripting.FileSystemObject.FileExists(x) 返回 `false`
--limit-file-checks 如果执行了大量检查,则为文件夹/文件存在性检查切换默认值
(尝试打破无限文件检查循环)。
--no-folder-exists 为 Scripting.FileSystemObject.FileExists(x) 返回 `false`
--function-rewrite 重写函数调用以便捕获 eval 调用
--no-rewrite-prototype 不要将类似 `function A.prototype.B()` 的表达式重写为 `A.prototype.B =
function()`
--no-hoist-prototype 不要提升类似 `function A.prototype.B()` 的表达式(由
no-rewrite-prototype 隐含)
--no-shell-error 在执行 `WScriptShell.Run` 时不要抛出假错误(默认会抛出假
错误以假装分发站点已关闭,从而使脚本将尝试轮询所有站点)
--no-typeof-rewrite 不要重写 `typeof`(例如 `typeof ActiveXObject`,根据
JScript 标准必须返回 'unknown' 而不是 'object')
--proxy [实验性] 为下载使用指定的代理。如果未提供
--download 标志,则与此无关。
--windows-xp 模拟 Windows XP(影响环境变量的值)
--dangerous-vm 使用 `vm` 模块,而不是 `vm2`。此沙箱可以被突破,因此**不要
使用它**,除非你 100% 确定自己在做什么。有助于调试,因为可以给出正确的堆栈跟踪。
--rewrite-loops 重写某些类型的循环以加速分析
--throttle-writes 限制写入大量数据的文件写入的报告和数据跟踪
--throttle-commands 如果运行了大量相同的命令,停止分析
--extract-conditional-code 从 JScript 条件注释中提取实际要分析的代码 (/*@if(...)。
--loose-script-name 重写 == 检查,以使当前脚本名称与硬编码脚本名称的比较
始终返回 true。
--real-script-name 返回当前正在分析的脚本的真实文件名,而非假
名称。
--activex-as-ioc 将所有 ActiveX 调用记录为 IOC,并尝试判断该调用是否在 JS 源中被混淆。
--ignore-wscript-quit 忽略对 WSCript.Quit() 的调用并继续执行。
--ignore-rewrite-errors 如果任何样本重写失败,则分析原始样本。
第一个信息来源是控制台输出。在成功分析后,它通常会打印如下内容:
Using a 10 seconds timeout, pass --timeout to specify another timeout in seconds
Analyzing sample.js
Header set for http://foo.bar/baz: User-Agent Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Emulating a GET request to http://foo.bar/baz
Downloaded 301054 bytes.
Saved sample.js.results/a0af1253-597c-4eed-9e8f-5b633ff5f66a (301054 bytes)
sample.js.results/a0af1253-597c-4eed-9e8f-5b633ff5f66a has been detected as data.
Saved sample.js.results/f8df7228-7e0a-4241-9dae-c4e1664dc5d8 (303128 bytes)
sample.js.results/f8df7228-7e0a-4241-9dae-c4e1664dc5d8 has been detected as PE32 executable (GUI) Intel 80386, for MS Windows.
http://foo.bar/baz is an active URL.
Executing sample.js.results/d241e130-346f-4c0c-a698-f925dbd68f0c in the WScript shell
Header set for http://somethingelse.com/: User-Agent Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
Emulating a GET request to http://somethingelse.com/
...
在这个例子中,我们看到一个下载器,它从 http://foo.bar/baz 下载一个文件,设置 HTTP 头 User-Agent 为 Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)。然后,它继续解码该文件,并将结果写入磁盘(一个 PE32 可执行文件)。最后,它在 Windows shell 中运行一些命令。
sample.js.results/a0af1253-597c-4eed-9e8f-5b633ff5f66a 将包含从 http://foo.bar/baz 下载的载荷;sample.js.results/f8df7228-7e0a-4241-9dae-c4e1664dc5d8 将包含实际载荷(PE 可执行文件);sample.js.results/d241e130-346f-4c0c-a698-f925dbd68f0c 将包含在 Windows shell 中运行的命令。每个 HTTP 请求都会打印在终端上并记录在 urls.json 中。不会插入重复的 URL(即两次请求同一个 URL 只会导致 urls.json 中有一行记录)。
active_urls.json 包含最终导致可执行载荷的 URL 列表。如果你想下架分发站点,这个文件是最有趣的。
snippets.json 包含 box-js 遇到的每一段代码,无论是 JavaScript、cmd.exe 命令还是 PowerShell 脚本。
resources.json 包含样本写入磁盘的每个文件。例如,如果应用程序尝试将 Hello world! 保存到 $PATH/foo.txt,则 resources.json 的内容将是:
{
"9a24...": {
"path": "(path)\\foo.txt",
"type": "ASCII text, with no line terminators",
"md5": "86fb269d190d2c85f6e0468ceca42a20",
"sha1": "d3486ae9136e7856bc42212385ea797094475802",
"sha256": "c0535e4be2b79ffd93291305436bf889314e4a3faec05ecffcbb7df31ad9e51a"
}
}
resources.json 文件也很重要:要留意任何可执行资源(例如带有 "type": "PE32 executable (GUI) Intel 80386, for MS Windows" 的资源)。
一些在野脚本已被观察到使用 new Date().getYear() 而 new Date().getFullYear()。如果样本没有表现出任何可疑行为,请留意 Date 检查。
如果你遇到 .JSE 文件,请编译解码器并像这样运行:
cc decoder.c -o decoder
./decoder foo.jse bar.js
node run bar.js
你偶尔可能会遇到不支持的组件。在这种情况下,你可以在 GitHub 上提交 issue,或者如果你熟悉 JavaScript,也可以自己模拟该组件。
错误通常看起来像这样(行号可能不同):
1 Jan 00:00:00 - Unknown ActiveXObject WinHttp.WinHttpRequest.5.1
Trace
at kill (/home/CapacitorSet/box-js/run.js:24:10)
at Proxy.ActiveXObject (/home/CapacitorSet/box-js/run.js:75:4)
at evalmachine.<anonymous>:1:6471
at ContextifyScript.Script.runInNewContext (vm.js:18:15)
at ...
你可以看到异常是在 Proxy.ActiveXObject 中引发的,它看起来像这样:
function ActiveXObject(name) {
name = name.toLowerCase();
/* ... */
switch (name) {
case "wscript.shell":
return require("./emulator/WScriptShell");
/* ... */
default:
kill(`Unknown ActiveXObject ${name}`);
break;
}
}
添加一个新的 case "winhttp.winhttprequest.5.1"(注意小写!),并让它返回一个 ES6 Proxy 对象(例如 ProxiedWinHttpRequest)。这用于在恶意样本请求尚未实现的功能时立即捕获它们:
/* emulator/WinHttpRequest.exe */
const lib = require("../lib");
module.exports = function ProxiedWinHttpRequest() {
return new Proxy(new WinHttpRequest(), {
get: function(target, name, receiver) {
switch (name) {
/* 在此处使用 case 语句添加“特殊”陷阱 */
default:
if (name in target) return target[name];
else lib.kill(`WinHttpRequest.${name} not implemented!`)
}
}
})
}
function WinHttpRequest() {
}
重新运行分析:它会再次失败,告诉你具体哪个功能没有实现。
1 Jan 00:00:00 - WinHttpRequest.open not implemented!
Trace
at kill (/home/CapacitorSet/box-js/run.js:24:10)
at Object.ProxiedWinHttpRequest.Proxy.get (/home/CapacitorSet/box-js/run.js:89:7)
根据需要模拟 WinHttpRequest.open:
function WinHttpRequest() {
this.open = function(method, url) {
URLLogger(method, url);
this.url = url;
}
}
并重复迭代,直到代码成功模拟无错误。
@CapacitorSet:原始开发者
@kirk-sayre-work:维护者
--output-directory