案例目标
网址:第十八题 jsvmp 洞察先机 - 猿人学
本题目标:抓取 5 页数字,计算加和并提交结果
![](https://img-blog.csdnimg.cn/img_convert/d0117f89bdd09f6cda2b50855e9f31e0.png)
常规 JavaScript 逆向思路
一般情况下,JavaScript 逆向分为三步:
-
寻找入口:逆向在大部分情况下就是找一些加密参数到底是怎么来的,关键逻辑可能写在某个关键的方法或者隐藏在某个关键的变量里,一个网站可能加载了很多 JavaScript 文件,如何从这么多的 JavaScript 文件的代码行中找到关键的位置,很重要;
-
调试分析:找到入口后,我们定位到某个参数可能是在某个方法中执行的了,那么里面的逻辑是怎么样的,调用了多少加密算法,经过了多少赋值变换,需要把整体思路整理清楚,以便于断点或反混淆工具等进行调试分析;
-
模拟执行:经过调试分析后,差不多弄清了逻辑,就需要对加密过程进行逻辑复现,以拿到最后我们想要的数据
接下来开始正式进行案例分析:
寻找入口
F12 打开开发者人员工具,刷新网页进行抓包,在 Network 中可以看到数据接口为 18data?page=1,响应预览中可以看到当前页面各数字数据:
![](https://img-blog.csdnimg.cn/img_convert/80a946cb86b5d693c6fbad7a0e0534f2.png)
但是并没有看到类似加密的字段,通过对比,点击第二页的后,数据接口变成了 18data?page=2&t=XXX&v=XXX,请求 url 中多出了两个参数 t 和 v:
![](https://img-blog.csdnimg.cn/img_convert/6e0c62ac096c0c5f6a1acc6d80e8bef5.png)
t 看起来像是时间戳,不过时间戳一般为 13 位,这里只有 10 位,v 是一串加密内容,从 Initiator 处跟栈到 getdata 中,逆向分析以下加密逻辑:
![](https://img-blog.csdnimg.cn/img_convert/712c342d541bd477e23225efb92c0372.png)
调试分析
会跳转到 18 文件的第 764 行,点击左下角的 { } 对其进行格式化操作,在第 1453 行 send 处打下断点调试分析,在第 1423 行创建了一个 XMLHttpRequest 对象,XMLHttpRequest 用于在后台与服务器交换数据,后面通过 xml.open( ) 建立链接,一个 HTTP 请求,xml.send( ) 发送请求,连接对象为当前接口的 url:
![](https://img-blog.csdnimg.cn/img_convert/269b6b1c9c7ad702d4876267f2d4701a.png)
在第 1427 行定义了个 data 参数,在第 1429 行打断点调试,data 传入了当前页面所有数字:
![](https://img-blog.csdnimg.cn/img_convert/2bdc0af9b6cbc09c2de19d2b51b5d76f.png)
在第 1452 行建立连接处打断点,鼠标选中 xml.open 进入到 y__ 中:
![](https://img-blog.csdnimg.cn/img_convert/e88eba54bdd1c57e1275952e943b8067.png)
在第 1365 行,这块就是 jsvmpzl 框架混淆过的代码,全是大小写的 v、u、y 和下划线,非常不便于调试分析,第 1415 行能看到该框架的版本,在第 1385 行打下断点:
![](https://img-blog.csdnimg.cn/img_convert/95e39252265ca57a472acf2f9f93235f.png)
先分析一下,_U__ 函数有四个参数,后三个定义在第 771 行:
![](https://img-blog.csdnimg.cn/img_convert/7c8c60a02286fef8bf94a2f47c5a5bdb.png)
![](https://img-blog.csdnimg.cn/img_convert/7a8e945dad12b1d688644e883a68dea3.png)
_ 参数在这一块并没有定义,鼠标选中后查看,展开后发现了特别的字段 AES、mode、pad,这里就看起来是数组中的对象经过了 AES 加密,并且加密模块为 CBC,填充方式为 Pkcs7:
![](https://img-blog.csdnimg.cn/img_convert/46183777e7472ea8db40c7092ea51c77.png)
并且这里类似于鼠标滑点坐标,断点调试时也发现只要鼠标在页面晃动断点就能断住:
![](https://img-blog.csdnimg.cn/img_convert/41a8edf548d23b048b8d23ac74297cc8.png)
在第 1385 行打断点,向上跟栈,跟到 _y__ 处,这里将 __ 数组作为参数传递给了 yU[_v] 函数:
![](https://img-blog.csdnimg.cn/img_convert/1c6fe6220688d5961cdab0a34963aa24.png)
return yU_[_v].apply(yU_, __)
在第 1338 行,在控制台打印输出一下,_v 为 createEncryptor,是个加密方法,yU_ 有个 encryptBlock,跟进去发现又跳转回了 y__ 函数位置:
![](https://img-blog.csdnimg.cn/img_convert/ab1f7876cc641034e3f6970d71f6f15f.png)
__ 中看到了关键字 iv、mode、padding,更确定了这里有过 AES 加密处理:
![](https://img-blog.csdnimg.cn/img_convert/50b73bbd3e3b316a1b618357b44d1b1d.png)
在该行插桩打下日志断点,鼠标在网页中滑动控制台会打印出如下内容,与 _[1][0]['mouse'] 中的鼠标坐标一致:
![](https://img-blog.csdnimg.cn/img_convert/6f1a21402bd1b069882e2dd9c004caac.png)
这部分和 _U__ 函数都囊括在第 979 行的 __V 函数体中,往后跟栈,所有的堆栈都走到其中,可以尝试 hook 该函数中的 _ 参数的加密内容,找找突破口,__V 函数有五个参数,hook 内容如下:
encrypt = _[1][0]['CryptoJS']['AES']['encrypt']
_[1][0]['CryptoJS']['AES']['encrypt']=function(a,b,c,d,e){
var result = encrypt(a,b,c,d,e);
console.log(result.toString())
debugger;
return a;
}
先在第 1385 行打断点断住后,将以上内容输入到控制台中进行 hook,不然会报错显示 _ 未定义,双击打印出的结果,即可进入到虚拟机中:
![](https://img-blog.csdnimg.cn/img_convert/af8796bb40434c1612a45b4bbe28ef6f.png)
取消其他断点,点击第二页,即会在虚拟机中 hook 代码的 debugger;处断住,并会在控制台打印出如下内容:
![](https://img-blog.csdnimg.cn/img_convert/2fc5f8308df9cc31dd4fafb746e3fd08.png)
通过与 v 参数对比,内容相似,长度匹配,即 __V 函数完成了对 v 参数的加密,且使用了 AES 加密,向上跟栈到 _y 中,又会跳转到第 1338 行,此处为加密位置:
![](https://img-blog.csdnimg.cn/img_convert/2c426ad59c59b5a3608579e77d41a21d.png)
上文讲过 __ 数组作为参数传递给了 yU[_v] 函数,__ 数组中的三个对象分别对应 hook 函数中的 a、b、c:
![](https://img-blog.csdnimg.cn/img_convert/5b35b1bed7dc70eb4b7f5bc55b273199.png)
一般的 AES 加密方式如下,需要传入 text、key、iv,即对应 a、b、c.iv:
function aesEncrypt() {
var key = CryptoJS.enc.Utf8.parse(aesKey),
iv = CryptoJS.enc.Utf8.parse(aesIv),
srcs = CryptoJS.enc.Utf8.parse(text),
encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Iso10126
});
return encrypted.toString();
}
var aesKey = 'YyRose',
aesIv = 'YyRose',
text = 'YyRose';
console.log(aesEncrypt())
打印一下这三部分内容:
![](https://img-blog.csdnimg.cn/img_convert/f1a33891d9a6fe69e2337f3432611896.png)
可以看出来 a 是页码加上 | 符再加上鼠标坐标组成的,那 b 和 c.iv 偏移量呢,在第 1338 行插桩打印下日志:
![](https://img-blog.csdnimg.cn/a0d8fa17679d45f28f992562a0a92dd2.png)
取消其他断点,点击第二页,打印出来的内容中有如下部分,第四行样式与 b 和 c.iv 一致,第四行是由第二行和第三行两个一样的内容组合而成的,第二行是由十三位时间戳去掉后三位转换为十六进制后,去掉前两位得到的结果,至此分析完成:
![](https://img-blog.csdnimg.cn/730279e4e8dd40b4b45e2d1f8c5945f7.png)
完整代码
JavaScript 代码
// 引用 crypto-js 加密模块
var CryptoJS = require('crypto-js')
function vEncrypt(text_num) {
var text = text_num + "|67m508,66m509,66d509,66m509,66u509"
var timestamp = Math.round(Date.parse(new Date())/1000);
var aesIv = timestamp.toString(16) + timestamp.toString(16);
var key = CryptoJS.enc.Utf8.parse(aesIv),
iv = CryptoJS.enc.Utf8.parse(aesIv),
srcs = CryptoJS.enc.Utf8.parse(text),
encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
return encrypted.toString();
}
// console.log(vEncrypt(2));
Python 代码
sessionid 要改为自己的:
import time
import execjs
import requests
import re
def yrx18_demo():
num_sum = 0
for page_num in range(1, 6):
with open('yrx18.js', 'r', encoding='utf-8') as f:
encrypt = f.read()
v = execjs.compile(encrypt).call('vEncrypt', page_num)
headers = {
"user-agent": "yuanrenxue.project",
}
cookies = {
"sessionid": " your sessionid ",
}
params = {
"t": str(int(time.time() * 1000))[:-3],
"v": v
}
url = "https://match.yuanrenxue.com/match/18data?page=%s" % page_num
response = requests.get(url, headers=headers, cookies=cookies, params=params)
for i in range(10):
value = response.json()['data'][i]
num = re.findall(r"'value': (.*?)}", str(value))[0]
num_sum += int(num)
print(num_sum)
if __name__ == '__main__':
yrx18_demo()