js攻防对抗之回溯 - Touale Cula's Blog

题目内容

任务六:采集全部5页的彩票数据,计算全部中奖的总金额(包含一、二、三等奖)


初步分析

1
2
3
4
5
6
7
8
9
10
11
12
GET https://match.yuanrenxue.com/api/match/6?m=OTfA1luzknp0JvTbZefBFolpU8kdVfMeLq6RaU9C1dOQVziK8tm6EX0hBWXAt954thxjgweOHgz6hpWdBXyynIQdeabEpcCoe7y4ERhA112WBzgniwiXZdRoFLqf%252B%252BhHgOsn%252FXJxPcZHHuNB3XbBUGFvK6uFBOmeVB5OMCCqkoE%253D&q=1-1651903761000%7C HTTP/1.1
Host: match.yuanrenxue.com
Connection: keep-alive
Accept: application/json, text/javascript, */*; q=0.01
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36
X-Requested-With: XMLHttpRequest
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://match.yuanrenxue.com/match/6
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8

返回内容:

1
2
3
4
5
6
7
8
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 07 May 2022 06:09:22 GMT
Content-Type: application/json
Content-Length: 214
Connection: keep-alive

{"status": "1", "state": "success", "data": [{"value": 6562}, {"value": 690}, {"value": 7843}, {"value": 9760}, {"value": 9049}, {"value": 5782}, {"value": 4895}, {"value": 8547}, {"value": 6879}, {"value": 4695}]}

初步结论,应该就是提交参数存在加密,返回时明文


深度分析

捕捉请求,对发起请求的堆栈进行分析

非常明显,m和q的生成位置都在于此,可以打个断点,查看如何生成。


首先对m进行分析,断点后发现t应该是一个时间戳,而o应该是一个常数,具体不清楚,继续查看r函数的定义

从上面的代码来看,o应该是点击上一页和下一页所触发加一的操作,并且大于6时,整个网页会进行重新加载,使其o变成1,而r最终调用的应该是z,z应该是一种加密函数。

上面的代码来看,几乎没有任何混淆,那直接用一种暴力手法,全部复制下载在本地加载,最后附加一个console的输出!

强制运行后会发现有问题,emm…下面进入正题了,对模糊点进行分析!

1.表情加密

在代码最前面出现了一堆颜文字,这种俗称表情加密,正常人成aaencode加密

解决方案很好使,因为最终还是要放入解释器解释,所以要么在控制台二次输出,要么就是随便一个解密网站也行,比如(这个网站)[https://www.qtool.net/decode]

解密后,执行代码很简单如下

1
window.o = 1;

2.window 坑

正常情况下,在node环境运行,没有window环境很正常,补环境也很正常,比如下面几种方法

1
2
3
window = {}
window = this
window = global

但是,补完后出现这种情况,提示xxx不存在

ok,那就找到关于ASN1的地方,搜索大法

ASN1应该是一个全局变量,但是最后丢失了,原因经过一系列断点,原因在此

解决方案,去掉就好了。。。。把window重置了,浏览器环境没有影响,但是对于node环境有影响


3.加密坑

继续运行,输出了该错误

继续断点在问题出现的地方

发现node环境与浏览器环境不同,浏览器环境是128,而node下是0

调用堆栈,e来源于pe,但似乎没什么用,折叠代码,找下关于this.n

发现此处存在jsFuck混淆加密,还原

还原结果

1
false;

应该就是由于本地无法解析该语句


4.生成坑

经过上述运行,已经得到加密结果,比如

1
2
3
4
5
console.log(z(1651903761000,1))

PS C:\Users\lenovo\Desktop\js攻防> node "c:\Users\lenovo\Desktop\js攻防\回溯\2.js"
OTfA1luzknp0JvTbZefBFolpU8kdVfMeLq6RaU9C1dOQVziK8tm6EX0hBWXAt954thxjgweOHgz6hpWdBXyynIQdeabEpcCoe7y4ERhA112WBzgniwiXZdRoFLqf%2B%2BhHgOsn%2FXJxPcZHHuNB3XbBUGFvK6uFBOmeVB5OMCCqkoE%3D

这时候问题又来了,每次运行都是得到上面的结果

浏览器运行,每次结果都不一样

出现这个问题,我陷入了很大的迷茫,,,本质上这是一个rsa加密没错,作者经过了魔改rsa,rsa每次加密结果都不一样确实,然后就是不断地调试调试。。。

后面发现一个事情,我每次都是重新运行,浏览器下是持续keep life状态

那么,尝试一下

发现,每次结果都不一样了,而且和浏览器对应上了,卒。


5.风控坑

经过一些列操作,封装发现风控问题了

这个时候我就很无语了,利用fiddle直接抓包,重放发现浏览器也是风控,这时候怀疑服务端校验了重放请求。

那就直接拦截fd的包,然后复制到requests里面请求,也是风控。

ok,这个时候怀疑时间问题。果然,拦截后时间如果长了点再运行放出来就会风控,而且时间只有2秒。

这个时候陷入了矛盾之中,后面想到上一个坑的状态必须时持续,于是找了个node_vm。。。。

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

from node_vm2 import NodeVM
import requests
import time


with open(r'C:\Users\lenovo\Desktop\js攻防\回溯\1.js', 'r') as f:
jsCode = f.read()

vm = NodeVM.code(jsCode)

headers = {
'User-Agent': 'yuanrenxue.project'
}


count = 0
with requests.Session() as session:
q = ''
for page in range(1, 6):

t = int(time.time()*1000)
q += str(page) + '-' + str(t) + "|"
m = vm.call_member('z', t, page)

res = session.get(url="https://match.yuanrenxue.com/api/match/6",
params={
'page':page,
'm': m,
'q': q
},
headers=headers,
)
print(res.json())
for _ in res.json()['data']:
count += _['value'] * 24


print(count)


果然,通过…

1
2
3
4
5
6
7
                                 > python -u "c:\Users\lenovo\Desktop\js攻防\回溯\temp.py"
{'status': '1', 'state': 'success', 'data': [{'value': 6562}, {'value': 690}, {'value': 7843}, {'value': 9760}, {'value': 9049}, {'value': 5782}, {'value': 4895}, {'value': 8547}, {'value': 6879}, {'value': 4695}]}
{'status': '1', 'state': 'success', 'data': [{'value': 9836}, {'value': 5569}, {'value': 2984}, {'value': 503}, {'value': 2117}, {'value': 502}, {'value': 9796}, {'value': 9133}, {'value': 5486}, {'value': 9961}]}
{'status': '1', 'state': 'success', 'data': [{'value': 2543}, {'value': 8665}, {'value': 8460}, {'value': 8686}, {'value': 4523}, {'value': 7504}, {'value': 8907}, {'value': 2504}, {'value': 7637}, {'value': 3682}]}
{'status': '1', 'state': 'success', 'data': [{'value': 5675}, {'value': 4954}, {'value': 397}, {'value': 4802}, {'value': 2075}, {'value': 3533}, {'value': 8813}, {'value': 4154}, {'value': 5053}, {'value': 8612}]}
{'status': '1', 'state': 'success', 'data': [{'value': 9508}, {'value': 3182}, {'value': 8739}, {'value': 5367}, {'value': 145}, {'value': 3416}, {'value': 7663}, {'value': 2491}, {'value': 4782}, {'value': 9745}]}
6883344

6.结果坑

回过头,发现返回的对应上网页的三等奖,而题目要求的时一等奖、二等奖、三等奖的总和

好家伙,于是又跟了下网页前端代码

关键处

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
success: function(data) {
if (window.page) {} else {
window.page = 1
}
data = data.data;
let html = '';
let arg = 1;
let puq = `<tr data-week="3"><td>date_twice</td><td>2020-10-date_value</td>caipiaohao<td>total_value</td><td>result_value1</td><td>result_value2</td><td>result_value3</td><td>0</td><td></td></tr>`;
let arr_arg = [1, 8, 7, 3, 5, 7, 10, 2, 9, 4];
$.each(data, function(index, val) {
let caipiao = `<td><span class="rq1">arg1</span><span class="rq1">arg2</span><span class="rq1">arg3</span><span class="rq1">arg4</span><span class="rq1">arg5</span><span class="rq1">arg6</span><span class="rq1">arg7</span></td><td><span class="bq1">arg8</span></td>`;
let arr = [5, 1, 7, 6, 3, 2, 4, 7];
for (let c = 1; c <= 8; c++) {
caipiao = caipiao.replace('arg' + c, (Math.floor(((arr_arg[arg] << 2) / 5 + c) * c / 3) + 1) + arr[window.page])
}
html += puq.replace('caipiaohao', caipiao).replace('date_twice', arg * window.page + 2020097).replace('date_value', '0' + window.page).replace('result_value3', val.value).replace('total_value', val.value * 24).replace('result_value2', val.value * 8).replace('result_value1', val.value * 15);
arg += 1
});
$('.resbbp').text('').append(html)
},

很明显,关键处在这

1
html += puq.replace('caipiaohao', caipiao).replace('date_twice', arg * window.page + 2020097).replace('date_value', '0' + window.page).replace('result_value3', val.value).replace('total_value', val.value * 24).replace('result_value2', val.value * 8).replace('result_value1', val.value * 15);

解释一下,当前行的总和时三等奖乘以24(val.value * 24)

于是,顺利通关


最后

说一句,我服了风控这个老六…