发布于 

Python调用JS代码实现恶意样本分析

客户安全设备发现恶意HTML样本,代码内容进行了混淆,让我分析一下。正好面向需求学习,学习一下Python调用JS代码的方法,掌握了这个方法再分析恶意样本简直手拿把掐

常用代码库

Python 调用 JS 代码通常是使用第三方库,常用的库主要有以下这些:

  • PyV8
  • Js2Py
  • PyExecJS
  • PyminiRacer
  • Selenium
  • Pyppeteer

这些库各有各的优劣,很多文章都没有详细介绍,下面我会简单说明一下其中几个第三方库的特点,然后着重介绍使用 PyExecJS 调用 JS 的方法

PyV8

  • V8 是谷歌开源的 JavaScript 引擎,被使用在了 Chrome 中
  • PyV8 是 V8 引擎的一个 Python 层的包装,可以用来调用 V8 引擎执行 JS 代码
  • 年久失修,最新版本是 2013 年更新的(https://pypi.org/project/PyV8/#history)
  • 具有较高的执行速度和良好的兼容性
  • 存在内存泄漏问题,不建议使用

Js2Py

  • Js2Py 是一个纯 Python 实现的 JavaScript 解释器和翻译器,会将 JavaScript 代码转换为等效的 Python 代码
  • 有较好的兼容性,并且可以用于在 Python中 执行复杂的 JavaScript 代码
  • 性能比直接使用原生 JavaScript 引擎慢一些,存在一些 BUG
  • 对于高度混淆的大型 JS 可能会转换失败

PyMinRacer

  • 同样是 V8 引擎的包装,和 PyV8 的效果一样
  • 一个继任 PyExecJS 和 PyramidV8 的库
  • 具有较小的内存占用和较快的执行速度,适用于嵌入式环境和资源受限的场景
  • 缺点是不支持所有的JavaScript特性

Selenium

  • 用于自动化浏览器操作,可以模拟用户在浏览器中的行为
  • 支持多种浏览器,如 Chrome、Firefox、Safari 等,并提供了丰富的 API 来控制浏览器的行为
  • 需要安装和配置浏览器驱动程序
  • 执行速度上可能比其他纯JavaScript执行库慢一些

Pyppeteer

  • 基于 Chrome 浏览器的无头浏览器控制库,用于模拟和控制浏览器的行为
  • 和 Selenium 类似,都是模拟浏览器行为
  • 需要安装和配置Chrome浏览器,某些场景下使用复杂

PyExecJS

  • 一个最开始诞生于 Ruby 中的库,后来被移植到了 Python 上
  • 较新的文章一般都会说用它来执行 JS 代码
  • 有多个引擎可选,但一般我们会选择使用 NodeJS 作为引擎来执行代码
  • 具有高度灵活性和通用性,使用简单

PyExecJS的使用

首先安装需要使用的库

1
pip install pyexecjs

打开 python 终端,验证当前的 js 环境,能正常返回 Node.js 就说明环境没问题

1
2
3
4
In [1]: import execjs

In [2]: execjs.get().name
Out[2]: 'Node.js (V8)'

使用 PyExecJS 库非常简单,只需要简单两步就行:

  1. 编译 JS 代码,通过execjs.compile()函数实现
  2. 调用并运行 JS 代码中的方法获取返回值,通过 call() 函数实现,传入方法名和方法参数做为函数参数值

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
import execjs

js = """
function test(a, b) {
return a + b;
}
"""
# 编译js文件
ctx = execjs.compile(js)
# 执行js中的代码
result = ctx.call("test", 1, 2)
print(result)

也可以封装成方法,在需要时调用即可

1
2
3
4
5
6
7
8
9
10
11
12
13
import execjs

def get_js_function(js_path, func_name, func_args=""):
with open(js_path, 'r', encoding="utf-8") as f:
js = f.read()
# 编译js文件
ctx = execjs.compile(js)
# 执行js中的代码
result = ctx.call(func_name, func_args)
return result

result = get_js_function("demo.js", "demo")
print(result)

恶意样本分析

分析的样本就是这次客户发给我的,代码混淆的方式很简单,也很好助于理解,用来练习 python 代码调用 JS 算是个不错的示例样本

快速识别

在进行样本分析时,我们需要样本做了哪些恶意行为,如果代码执行最后一条语句是执行恶意代码,其他都是在对代码进行反混淆。那么我们找到要执行恶意代码的位置,将其修改为输出要执行的代码,便可以快速解析恶意样本行为

重点是找到 js 代码中最后执行恶意代码的那条语句,然后注释这条并打印要执行的代码,便可以很简单的获取到样本真正要执行的恶意行为

一次值守安全设备检测到恶意 HTML 文件访问,文件执行 JS 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var sc = "/xkvod/",
tc = "f1x2",
wc, uc = new Array(),
vc;

function qc(rc) {
for(wc = 0; wc < vc.length; wc++) uc[wc] = vc.charCodeAt(wc);
wc = 4;
do {
if(wc > 51) break;
uc[wc] = (-((((uc[wc] ^ 231) << 2) & 0xff) | ((uc[wc] ^ 231) >> 6))) & 0xff;
wc++;
} while (true);
wc = "wc=48;while(true){if(wc<2)break;uc[wc]=(uc[wc]+uc[wc-1])&0xff;wc--;}";
eval(wc);
wc = "wc=50;do{if(wc<3)break;uc[wc]=(uc[wc]-uc[wc-1])&0xff;uc[wc]=(uc[wc]>>6)|((uc[wc]<<2)&0xff);wc--;}while(true);";
eval(wc);
vc = "";
for(wc = 1; wc < uc.length - 1; wc++)
if(wc % 7) vc += String.fromCharCode(uc[wc] ^ rc);
eval("wc=eval");
wc(vc);
}
vc = "\xf6V\xf2)\x88\xf6\xed\xe6\xbe\x8bS\xafW\xd4\x15I\xe9\x1e\xab3\x8f\x87 \x97\x01\x09R\nL\xbb\x1d B\xe47\x04\xc8\x09\xad}\xa2\xc2\x94\x07V\xac\xda\xbc{\xf1\x27H\x88";
qc(33);

文件执行逻辑其实很简单,混淆后的代码存放于变量vc,通过调用函数qc对混淆后的代码进行反解析并执行。执行解析后的恶意代码的语句是 wc(vc);,我们只需要注释这行然后添加一行代码return vc;,再调用这函数便能获取这段 JS 代码真正想要执行的操作

通过下面的 python 代码直接调用 JS 函数,获取执行代码返回值

1
2
3
4
5
6
7
8
9
import execjs

with open("eval2.js", 'r', encoding="gbk") as f:
js = f.read()
# 编译js文件
ctx = execjs.compile(js)
# 执行js中的代码
result = ctx.call("qc", "33")
print(result)

获取返回值,在拼接变量,可以知道 JS 代码执行的是网页跳转操作

1
window.location="/xkvod/39618.html?rodqze=xjf1x2"

本想继续跟进网页跳转,由于客户只给了恶意样本,客户没给明确的 IP 地址,只能单单针对恶意样本进行分析。这段代码这么大费周章的通过混淆实现重定向,行为可疑,已经被我直接打入死刑,上报客户进行封禁和终端查杀

代码分析

首先明确混淆后的代码,以十六进制形式加特殊符号进行保储存,我们可以对函数 qc 进行分段分析来看他做了哪些操作

1
vc = "\xf6V\xf2)\x88\xf6\xed\xe6\xbe\x8bS\xafW\xd4\x15I\xe9\x1e\xab3\x8f\x87 \x97\x01\x09R\nL\xbb\x1d B\xe47\x04\xc8\x09\xad}\xa2\xc2\x94\x07V\xac\xda\xbc{\xf1\x27H\x88";

第一步
函数首先执行下面这段代码,遍历恶意代码中的元素,charCodeAt()函数用于返回指定位置的 unicode 值,\xf6对应的 unicode 值是 246V 对应 unicode 值为 86

1
2
for(wc = 0; wc < vc.length; wc++)
uc[wc] = vc.charCodeAt(wc);

执行后的数组 uc 的结果如下

1
[246, 86, 242, 41, 136, 246, 237, 230, 190, 139, 83, 175, 87, 212, 21, 73, 233, 30, 171, 51, 143, 135, 32, 151, 1, 9, 82, 10, 76, 187, 29, 32, 66, 228, 55, 4, 200, 9, 173, 125, 162, 194, 148, 7, 86, 172, 218, 188, 123, 241, 39, 72, 136]

第二步
然后执行下面这段代码,从第 4 位开始到第 52 位结束,循环执行下面四个操作:

  1. 取两组数组中保存的 unicode 值与 231 进行按位异或运算
  2. 将第一组计算后的值左移 2 位,第二组计算后的值右移 6 位
  3. 再将第一组计算后的值与 0xff 进行按位与运算
  4. 然后将第一组计算后的值与第二组值进行按位或运算
  5. 最后将这个值进行按位取反后,再与 0xff 进行按位与运算
1
2
3
4
5
6
wc = 4;
do {
if(wc > 51) break;
uc[wc] = (-((((uc[wc] ^ 231) << 2) & 0xff) | ((uc[wc] ^ 231) >> 6))) & 0xff;
wc++;
} while (true);

计算过程弯弯绕绕,进行了多次位运算,最后数组 uc 变成了下面这些值

1
[246, 86, 242, 41, 67, 188, 216, 252, 155, 79, 46, 223, 62, 52, 53, 70, 200, 25, 207, 173, 95, 127, 225, 63, 101, 69, 42, 73, 82, 143, 21, 225, 106, 244, 189, 113, 68, 69, 215, 150, 235, 108, 51, 125, 58, 211, 12, 147, 142, 168, 253, 66, 136]

第三步
执行如下代码,分别是从数组第 48 位到数组第 2 位,将当前 unicode 值与数组前一位 unicode 值相加后再与 0xff 进行按位与运算

1
2
3
4
5
6
7
wc=48;
while(true){
if(wc<2)
break;
uc[wc]=(uc[wc]+uc[wc-1])&0xff;
wc--;
}

计算后数组值再次改变

1
[246, 86, 72, 27, 108, 255, 148, 212, 151, 234, 125, 13, 29, 114, 105, 123, 14, 225, 232, 124, 12, 222, 96, 32, 164, 170, 111, 115, 155, 225, 164, 246, 75, 94, 177, 46, 181, 137, 28, 109, 129, 87, 159, 176, 183, 13, 223, 159, 33, 168, 253, 66, 136]

第四步

执行如下代码,从第 50 位开始到第 2 位结束,对数组内的数据再次进行位运算

1
2
3
4
5
6
7
8
wc=50;
do{
if(wc<3)
break;
uc[wc]=(uc[wc]-uc[wc-1])&0xff;
uc[wc]=(uc[wc]>>6)|((uc[wc]<<2)&0xff);
wc--;
}while(true);

计算后的值如下

1
[246, 86, 72, 79, 69, 78, 86, 1, 15, 77, 78, 66, 64, 85, 223, 72, 78, 79, 28, 82, 66, 75, 10, 3, 18, 24, 23, 16, 160, 25, 15, 73, 85, 76, 77, 245, 30, 83, 78, 69, 80, 91, 33, 68, 28, 89, 75, 3, 10, 30, 85, 66, 136]

第五步

最后一段代码,首先从第 1 位开始,遍历 uc 数组,执行如下操作:

  1. 先将数组值与 rc 进行比较,rc 值为调用代码解析函数 qc 时传入的值
  2. 利用String.fromCharCode() 函数将数组 unicode 编码值转换为对应字符
  3. 将数组下标与 7 进行取余计算,如果不为 0 就保存字符,即数组为 7 的倍数时丢弃该值
  4. 使用 eval 函数将字符串 vc 值作为 JS 代码执行
1
2
3
4
5
vc = "";
for(wc = 1; wc < uc.length - 1; wc++)
if(wc % 7) vc += String.fromCharCode(uc[wc] ^ rc);
eval("wc=eval");
wc(vc);

简单捋一遍,逻辑还是挺简单的,就是将字符串转 16 进制然后通过位运算进行代码混淆,简单枯燥。主要是这次借机了解下 python 调用 JS 代码就用现有的案例分析一下,分析到后面我都觉得无趣了

总的来讲,分析安全设备捕获的恶意 HTML 恶意样本,如果代码有混淆,直接看调用样本的 JS 函数,获取函数最后执行的代码内容就完事了

复杂度高一些的使用 python 进行 JS 逆向的方法,在后续项目遇到后再详细讲述




👨‍💻本站由 @鱼龙 使用 Stellar 主题创建

📃本"页面"访问 次 | 👀总访问 次 | 🥷总访客

⏱️本站已运行 小时