前言
分析网址:aHR0cHM6Ly93d3cuaXRkb2cuY24v
(数据已脱敏)
本次分析的是“网站测速”模块,其他模块的逆向思路是一样的,所以都可仿照本文章的思路进行逆向。
本文章的技术内容其实并不多,主要需要了解JS混淆的几个特征点,知道如何排查JS是用的什么技术进行混淆,再在网上找找脱壳工具,进行解混淆。
快速开始
WS分析
首先,逆向的第一步都是正着走一遍,看看是如何执行的。
通过F12
,我们也可以看见,测速的底层传输逻辑是WS协议。
所以我们看一下WS的消息,如何进行的消息传递:
通过分析,发现第一次与服务器进行WS连接的时候,向服务器发送了一个数据包;之后,服务端给客户端发送了测速结果:
{"task_id":"202503211422122aa3vh0z2kxxowz4vd","task_token":"9789e2515c589a70"}
那么, 这两个参数是如何生成的?最简单的方法就是跟栈分析,看看创建WS连接的代码是怎么写的:
点进去后,发现JS被混淆了,怎么办?此时,就需要解混淆。
看到"0x"开头的,就应该是 通过变量混淆替换、增加冗余无效代码、增加无限debugger反调试功能、增加多层的套娃式if~else代码、以及埋陷阱等混淆成几千行的js代码。很明显,这里的JS是经过变量名混淆替换的ob混淆。
这里有详细讲解ob高级混淆的:
https://blog.csdn.net/weixin_43411585/article/details/123437953
所以,我们这里就直接使用v_jstools
进行解混淆。
把上述的JS代码,Ctrl + A
,ctrl + C
,到上述工具中:
此时,出结果了,我们把code放到VS Code里面方便查看和分析:
我们搜索"new"就直接定位到关键连接WS的相关代码。
我们来依次看看代码做了什么:
if (error_num < 1) {
return error_num++, setTimeout(() => {
create_websocket(_0x5d9212, _0x261ac6, _0x347e74);
}, 2000), ![];
} else return $.get("/public/ajax.php?type=error_feedback&task_id=" + _0x261ac6), err_tip("WebSocket服务器连接失败,请重试 !"), ![];
};
这个代码是日志记录,如果是WS服务器连接失败,此时会GET一个请求,应该是反馈给开发者的日志。
ws.onopen = function () {
ws.send("{\"task_id\":\"" + _0x261ac6 + "\",\"task_token\":\"" + md5(_0x261ac6 + "token_20230313000136kwyktxb0tgspm00yo5", 16) + "\"}");
};
很明显,这个代码就是刚才我提到的两个值,此时我们可以看到task_token
是MD5加密算法,task_token
是两个字符串(_0x261ac6
与token_20230313000136kwyktxb0tgspm00yo5
)拼接后,进行MD5加密而来。
请注意,MD5加密之后md5(..., 16)
的 16
表示返回16位的十六进制字符串。
那么,_0x261ac6
这个变量的值是多少呢?很明显,看这个JSON格式,这个变量的值就是task_id
。
此时,我们搜索下这个_0x261ac6
变量看看有没有什么对我们有用的信息,这里通过搜索看到了这个变量是这个函数"create_websocket
"的一个传参变量;我们需要用到浏览器的断点大法看这个变量的值是是什么,我们在这个函数的起始位置打个断点:
通过看断点打出来的信息,也证实了这个变量的值是task_id
。
那么,知道这个task_id
是create_websocket
函数的传参参数,我们全局搜索调用create_websocket
函数的相关代码。
发现了,在这里,点进去看看。
真相大白了,原来这个函数传递的三个参数就是这三个。
那么,建立WS的三个参数我们都知道了,但是逆向到这里就结束了吗?还没有。
Cookie保护
在发送WS请求和获取task_id
的时候,如果大量请求接口,则会触发保护。
触发保护后,需要进行Cookie鉴权,即保护器会生成一个值放入Cookie,接口会校验这个Cookie是否正确,如果不正确会导致type_id无法获取和WS无法连接。
至于解决办法嘛,下期再说~
但如果没有大量请求(一般情况下),是不会触发这个保护器的。
自动化
现在我们做一个自动化Python程序,看看我们的推断是否正确。
写自动化的程序和用到的技术这里就不多赘述了,详情可以看看代码:
import hashlib
import json
import requests
import re
import websockets
import asyncio
http_url = "https://www.52pojie.cn"
gbl_taskId = None
gbl_taskToken = None
def get_task_id():
url = "https://www.itdog.cn/http/"
data = f"line=&host={http_url}&host_s={http_url}&check_mode=fast&ipv4=&method=get&referer=&ua=&cookies=&redirect_num=5&dns_server_type=isp&dns_server="
headers = {
'upgrade-insecure-requests': '1',
'origin': 'https://www.itdog.cn',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'priority': 'u=0, i',
'referer': 'https://www.itdog.cn/http/',
'sec-ch-ua': '',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'sec-ch-ua-mobile': '?0',
'sec-fetch-site': 'same-origin',
'cache-control': 'no-cache',
'sec-ch-ua-platform': '',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36',
'sec-fetch-user': '?1',
'content-type': 'application/x-www-form-urlencoded',
'pragma': 'no-cache',
}
response = requests.post(url, data=data, headers=headers)
print(response.text,response.status_code)
match = re.search(r"var task_id='(.*?)';", response.text)
if match:
task_id = match.group(1)
return True, task_id
else:
return False,""
async def connect_and_listen(uri, message_handler=None, extra_headers=None):
"""
连接到指定的WebSocket URI并监听消息。
:param uri: WebSocket服务器的URI(例如wss://www.example.com/websockets)
:param message_handler: 一个可选的回调函数,用于处理接收到的消息
:param extra_headers: 额外的HTTP头信息,如包含Cookie等
"""
try:
async with websockets.connect(uri, extra_headers=extra_headers) as websocket:
print(f"Connected to {uri}")
message = {
"task_id": gbl_taskId,
"task_token": gbl_taskToken
}
await websocket.send(json.dumps(message))
print(json.dumps(message))
while True:
message = await websocket.recv()
if message_handler:
message_handler(message)
except websockets.ConnectionClosed:
print("Connection closed unexpectedly")
def start_websocket_listener(uri, message_handler=None):
"""
启动WebSocket监听器。
:param uri: WebSocket服务器的URI
:param message_handler: 处理接收到消息的回调函数
"""
extra_headers = {
}
asyncio.get_event_loop().run_until_complete(connect_and_listen(uri, message_handler, extra_headers))
def websocket_return_message(message):
json_obj = json.loads(message)
print(json_obj)
if __name__ == '__main__':
task_flag,task_id = get_task_id()
if task_flag == False:
print("获取task_id失败,请检查函数内部调用!")
exit()
task_token_str = "token_20230313000136kwyktxb0tgspm00yo5"
task_token = hashlib.md5((task_id + task_token_str).encode('utf-8')).hexdigest()[8:24]
gbl_taskId = task_id
gbl_taskToken = task_token
print(f'{{"task_id":"{gbl_taskId}","task_token":"{gbl_taskToken}"}}')
start_websocket_listener("wss://www.itdog.cn/websockets",websocket_return_message)