目标链接:https://www.qimao.com/shuku/a-a-a-a-a-a-a-click-1/

直接请求的话,没有返回网站内容,而是一大串混淆过后的js代码

重新启动一个无痕浏览器,观察一下请求时候链接的跳转、cookies的变化等

自动跳转的第二次请求就正常返回内容了,而且cookies中多了三个参数,其中acw_tc aliyungf_tc 是第一次请求返回的,这里忽略,只有 acw_sc__v2是从其他地方生成的,跳转过程中没有引入其他的js文件,所以可以肯定就是第一次请求返回的js代码生成的

话不多说,直接来上手分析代码

起手就是一个大数组,这里凭经验判断,代码是经过了 ob混淆,即 JavaScript Obfuscator

de4js网站做一个简单的还原

这里就是入口了,是调用 reload函数 进行赋值的,把代码放入vscode调试一下,首先找到reload调用的地方

自执行setInterval的无效代码去掉

直接运行代码,把我的系统卡死了 😇🐶 应该是那里进行了死循环

还是现在浏览器调试一下吧,先打开开发者模式,然后输入网址,自动就断住了,源码里面应该是有做检测

发现很多常量都是用 _0x55f3 函数做一个混淆,先把部分还原了

从上面两张图可以看到,两个函数其实是一样的,只不过经过 _0x55f3的混淆,不还原的话看不出来,两个比较重要的函数如下:

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
var get_acw_sc = function (arg1) {
String['prototype']['hexXor'] = function (_0x4e08d8) {
var _0x5a5d3b = '';
for (var _0xe89588 = 0x0; _0xe89588 < this['length'] && _0xe89588 < _0x4e08d8[
'length']; _0xe89588 += 0x2) {
var _0x401af1 = parseInt(this['slice'](_0xe89588, _0xe89588 + 0x2), 0x10);
var _0x105f59 = parseInt(_0x4e08d8['slice'](_0xe89588, _0xe89588 + 0x2),
0x10);
var _0x189e2c = (_0x401af1 ^ _0x105f59)['toString'](0x10);
if (_0x189e2c['length'] == 0x1) {
_0x189e2c = '\x30' + _0x189e2c;
}
_0x5a5d3b += _0x189e2c;
}
return _0x5a5d3b;
};

String['prototype']['unsbox'] = function () {
var _0x4b082b = [0xf, 0x23, 0x1d, 0x18, 0x21, 0x10, 0x1, 0x26, 0xa, 0x9, 0x13, 0x1f, 0x28, 0x1b, 0x16,
0x17, 0x19, 0xd, 0x6, 0xb, 0x27, 0x12, 0x14, 0x8, 0xe, 0x15, 0x20, 0x1a, 0x2, 0x1e, 0x7, 0x4,
0x11, 0x5, 0x3, 0x1c, 0x22, 0x25, 0xc, 0x24];
var _0x4da0dc = [];
var _0x12605e = '';
for (var _0x20a7bf = 0x0; _0x20a7bf < this['\x6c\x65\x6e\x67\x74\x68']; _0x20a7bf++) {
var _0x385ee3 = this[_0x20a7bf];
for (var _0x217721 = 0x0; _0x217721 < _0x4b082b['length']; _0x217721++) {
if (_0x4b082b[_0x217721] == _0x20a7bf + 0x1) {
_0x4da0dc[_0x217721] = _0x385ee3;
}
}
}
_0x12605e = _0x4da0dc['\x6a\x6f\x69\x6e']('');
return _0x12605e;
};
var _0x5e8b26 = '3000176000856006061501533003690027800375';
var tmp = arg1['unsbox']();
result = tmp['hexXor'](_0x5e8b26);
return result;
}

get_acw_sc('F8AD2846B6C7DF57413E6306A26A83820526280E');

那么接下来就很简单了,我们只要把那两个函数里面的逻辑弄清楚就可以了,把代码抠出来拷贝到 chrome 的 snippets 进行调试

0x 开头的是16进制数字,0x23 表示 2*16 + 3 = 35

\x 开头的是 16进制的unicode编码

这里有一个 parseInt 函数,python没有对应的内置函数,我自己封装了一个函数,来实现相同的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'''
下面的函数是 parseInt(s, 10) 的实现,处理的是有 字母 + 数字混合的情况
如果是纯数字的话,直接int(s)jiu'xing
如果是 parseInt(s, 16),需要配合int(s, 16)使用
'''

def parseInt(s):
l = []
for w in s:
# 十进制的只能处理数字
if s.isdigit():
l.append(w)
else:
break
return int("".join(l))

还有一个 ['toString'](0x10) 的操作,其实就是 number.toString(16) 的魔改,这里有介绍,用python实现就是:

1
n = hex(n).lstrip('0x')

代码整理

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
49
50
51
import requests
import re


def unsbox(arg1: str):
"""
用来打乱原arg1字符的顺序
"""
l1 = [15, 35, 29, 24, 33, 16, 1, 38, 10, 9, 19, 31, 40, 27, 22, 23, 25, 13, 6,
11, 39, 18, 20, 8, 14, 21, 32, 26, 2, 30, 7, 4, 17, 5, 3, 28, 34, 37, 12, 36]
l2 = l1.copy()
for i in range(len(arg1)):
k = arg1[i]
for j in range(len(l1)):
k2 = l1[j]
if k2 == i + 1:
l2[j] = str(k)
return "".join(l2)


def hexXor(tmp):
seed = "3000176000856006061501533003690027800375"
s = ""
min_length = min(len(tmp), len(seed))
for i in range(0, min_length, 2):
p1 = int(tmp[i:i+2], 16)
p2 = int(seed[i:i+2], 16)
q = hex((p1 ^ p2)).lstrip("0x")
if len(q) == 1:
q = '\x30' + q
s += q
return s


def main():
session = requests.session()
url = 'https://www.qimao.com/shuku/a-a-a-a-a-a-a-click-1/'
session.headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36"}

resp1 = session.get(url)
arg1 = re.search(r"arg1='(\w+?)'", resp1.text).group(1)
arg2 = hexXor(unsbox(arg1))
session.cookies.update({"acw_sc__v2": arg2})
resp2 = session.get(url)
print(resp2.content.decode())


if __name__ == "__main__":
main()

运行结果