一直有听说jsvmp
的大名,但这玩意具体是什么呢?
首先是vmp
,即 Virtual Machine Protection 虚拟机保护技术 原理这里有篇介绍:
VMP加壳(二):VMP的虚拟化原理 - 第七子007 - 博客园
jsvmp,顾名思义,就是用来保护js代码的,通过自定义一个js虚拟机,将原始的指令转化为自定义的指令,运行时将该函数放到虚拟机中运行,从而整体逻辑无法被跟踪与逆向
这样说你可能还是一头雾水,还是边实战边体验虚拟机的奇妙之处吧
题目地址:https://match.yuanrenxue.com/match/18
这里只有v参数是不确定的,追栈追到cback函数,很明显这里是处理拿到后的数据,生成一些html节点
我们直接在 xml.open 函数这里打上断点,然后F11进入函数内部看一下
不出意外的话,就是这里面生成了v和t两个参数,F10一步一步向下执行,直到生成完整的链接
调试到这里的时候,v和t就已经生成完毕了,所以可以判断生成点在这里
注意到函数的末尾,有一大串乱码的字符串,那其实就是 jsvmp,很多重要的逻辑都在那一大串里面,想调试几乎不可能
进入函数内部调试的时候,发现一个可疑的地方,就是AES加密算法
大胆猜测题目是使用了AES对v参数进行了加密,这里就需要hook一下AES函数了,直接在控制台输入下面的代码:
1 2 3 4 5 6 7 8 9 10
| aes_encrypt_old = _[1][0]['CryptoJS']['AES']['encrypt'] _[1][0]['CryptoJS']['AES']['encrypt'] = function (a, b, c, d, e) { debugger; var result = aes_encrypt_old(a, b, c, d, e); console.log(result.toString()) debugger; return result; }
|
a, b, c, d, e五个参数中d和e其实用不到,传入的时候是undefined,只是为了囊获尽可能多的参数
然后F8跳到下一个断点,就自动hook住了
通过对比,发现result就是v参数
到这里有两个地方是确定的,AES采用了 CBC模式,PKCS7填充,还有三个地方不确定:
还记得我们hook的时候传入的a b c三个参数吗?其实答案就在里面
乍一看三个参数可能有点懵,不是字符串而是数组的形式,这里其实是经过了 enc.Utf8.parse
函数,把原文转换成了十六进制数,才能够用于 AES加密,不信做一个对比:
注意全部都有 sigBytes
属性
还原的话,就需要用到 enc.Utf8.stringify
函数了,鼠标右键,然后clear清空一下控制台
一般来说 key和iv都是16位,所以可以根据长度来判断各自的类型
通过测试发现:
F11 step out function
看一下源码里面是哪里调用了 encrypt
鼠标停留在这里,加上一个 log breakpoint 打印所有经过的参数和结果
然后随便点击一页数据,控制台输出日志
这里已经看到最终的生成结果了
1
| ['3|323d323,323d323,323u323,323u323', '62c014b562c014b5'] 'Yh8TT+T4fF6EaJBp3/JH4lAYtdwYDj76HX92lwwYPsM2XxdErZLVdruPowLe2S3s'
|
分别对应 原文、密钥、密文,观察日志看看它们都是怎么生成的
首先是密钥:
大致过程就是生成10位的时间戳,然后两次转换成十六进制,把两次的结果做一个拼接
用python代码验证一下:
1 2 3 4
| In [3]: a = 1656755381
In [4]: format(a, "x") Out[4]: '62c014b5'
|
再来看一下原文,注意到控制台在鼠标移动的时候,一直都有输出,怀疑是检测鼠标的轨迹
而原文又正好跟轨迹的最后一行是一致的,如图:
因此可以推测,原文就是 page|鼠标轨迹
的形式,看日志轨迹是随机变动的,没什么规律可循,所以这里我们直接写死就行
代码整理
之前有封装过AES的工具类,直接拿来用就行:
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| import base64
from Crypto.Cipher import AES
class AESTool: def __init__(self, iv, key): self.key = key.encode("utf-8") self.iv = iv.encode("utf-8")
def pkcs7padding(self, text): """ 明文使用PKCS7填充 """ bs = 16 length = len(text) bytes_length = len(text.encode("utf-8")) padding_size = length if (bytes_length == length) else bytes_length padding = bs - padding_size % bs padding_text = chr(padding) * padding self.coding = chr(padding) return text + padding_text
def encrypt(self, content: str): """ AES加密 """ cipher = AES.new(self.key, AES.MODE_CBC, self.iv) content_padding = self.pkcs7padding(content) encrypt_bytes = cipher.encrypt(content_padding.encode("utf-8")) result = str(base64.b64encode(encrypt_bytes), encoding="utf-8") return result
def decrypt(self, content: str): """ AES解密 """ cipher = AES.new(self.key, AES.MODE_CBC, self.iv) content = base64.b64decode(content) text = cipher.decrypt(content).decode("utf-8") return self.pkcs7padding(text)
key = iv = '62c014b562c014b5' text = '3|323d323,323d323,323u323,323u323'
aes_tool = AESTool(iv, key) print(aes_tool.encrypt(text))
|
很好,输出结果和网站的一致,证明没问题,爬虫代码如下:
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 26 27 28 29 30 31 32 33 34 35
| import requests import time import base64
from Crypto.Cipher import AES
yuanrenxue_headers = {'User-Agent': 'yuanrenxue.project', 'Cookie': 'sessionid=s4v1l75430r6xvg0ra6011hcdhnp381w'}
def get_v(t, page): x = format(t, 'x') key = iv = 2 * x text = str(page) + '|323d323,323d323,323u323,323u323' aes_tool = AESTool(iv, key) return aes_tool.encrypt(text)
def main(): total = 0 for page in range(1, 6): t = int(time.time()) url = 'https://match.yuanrenxue.com/match/18data' params = {'page': page, 't': t, 'v': get_v(t, page)} res = requests.get(url, params=params, headers=yuanrenxue_headers) data = res.json()['data']
for d in data: total += d['value']
print ('总和为:', total)
main()
|
拿到需要的数据:
通关: