web逆向-多层加密
0 声明
本文章仅供技术交流
1 抓包
脱敏网址:aHR0cHM6Ly93d3cuY29pbmdsYXNzLmNvbS96aA==
首先明确下要抓取的数据:加密货币数据分析
打开开发者工具,抓一下包看一下
可以看到数据接口是通过XHR
加载,并且返回结果进行了加密
2 思路
0x01 定位解密函数位置
其实思路是比较多的,比如打XHR断点
或者是使用decrypt(
定位解密函数,亦或者使用我们常用的拦截器
,
这里就以拦截器为例:
在代码里搜索interceptors.response.use
,可以发现只有一处,直接打上断点
经过翻页,断住断点,发现这个函数传入的t
中包含我们需要的加密函数,但是经过Yt
函数后从密文变成了明文,所以判定此函数为解密函数
0x02 阅读并分析核心代码
阅读代码发现主要经过了三步,1. 先通过一系列方式生成一个参数n
,2. 把第一步生成的参数n
和参数t.headers.user
传入解密函数Yt
,得到一个参数n
,3. 把第二步生成的参数n
和t.data.data
再次传入解密函数Yt
得到解密后的结果
通过控制台输出打印t
,可以看到t.data
为接口返回的加密数据,t.headers
为接口的请求headers数据
接下来看一下解密函数Yt
是什么:
var Yt = function(t, e) {
var n = function(t) {
var e, n = Gt.ZP.inflate(new Uint8Array(t.match(/[\da-f]{2}/gi).map((function(t) {
return parseInt(t, 16)
}
)))), r = "", i = 16384;
for (e = 0; e < n.length / i; e++)
r += String.fromCharCode.apply(null, n.slice(e * i, (e + 1) * i));
return r += String.fromCharCode.apply(null, n.slice(e * i)),
decodeURIComponent(escape(r))
}(qt.AES.decrypt(t, qt.enc.Utf8.parse(e), {
mode: qt.mode.ECB,
padding: qt.pad.Pkcs7
}).toString(qt.enc.Hex));
return '"' == n.charAt(0) && (n = n.substring(1, n.length)),
'"' == n.charAt(n.length - 1) && (n = n.substring(0, n.length - 1)),
n
};
第一眼看到AES加密,模式为ECB,填充为Pkcs7,那么分析一下qt
,发现其和node中的crypto-js
完全相同
第二眼看到有个Gt.ZP.inflate,如果要补这个函数的环境,发现要补很久,涉及到很多函数的调用,经过大佬指点得知这是一个解压用的包,其可使用pako
来替代,因为这个包比较陌生,搜了一下这个包
至此,已经大概解决了代码,拿到本地运行,发现能得到正常结果
3 代码
python代码:
import requests, execjs, time
headers = {
"accept": "application/json",
"accept-language": "zh-CN,zh;q=0.9",
"cache-control": "no-cache",
"cache-ts": str(int(time.time() * 1000)),
"encryption": "true",
"language": "zh",
"origin": "https://www.coinglass.com",
"pragma": "no-cache",
"priority": "u=1, i",
"referer": "https://www.coinglass.com/",
"sec-ch-ua": "\"Chromium\";v=\"136\", \"Google Chrome\";v=\"136\", \"Not.A/Brand\";v=\"99\"",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "\"Windows\"",
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-site",
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36"
}
url = "https://capi.coinglass.com/api/home/v2/coinMarkets"
params = {
"sort": "",
"order": "",
"keyword": "",
"pageNum": "1",
"pageSize": "20",
"ex": "all"
}
# 网站需要挂代理才能访问
proxies = {
"http": "http://127.0.0.1:7890",
"https": "http://127.0.0.1:7890",
}
response = requests.get(url, headers=headers, params=params, proxies=proxies)
# print(response)
arg1 = '/api/home/v2/coinMarkets'
arg2 = response.headers.get('user')
arg3 = response.json()['data']
res = execjs.compile(open('./demo.js', 'r', encoding='utf-8').read()).call("decrypt", arg1, arg2, arg3)
print(res)
JS代码:
const qt = require("crypto-js");
const pako = require("pako");
var Yt = function(t, e) {
var n = function(t) {
var e, n = pako.inflate(new Uint8Array(t.match(/[\da-f]{2}/gi).map((function(t) {
return parseInt(t, 16)
}
)))), r = "", i = 16384;
for (e = 0; e < n.length / i; e++)
r += String.fromCharCode.apply(null, n.slice(e * i, (e + 1) * i));
return r += String.fromCharCode.apply(null, n.slice(e * i)),
decodeURIComponent(escape(r))
}(qt.AES.decrypt(t, qt.enc.Utf8.parse(e), {
mode: qt.mode.ECB,
padding: qt.pad.Pkcs7
}).toString(qt.enc.Hex));
return '"' == n.charAt(0) && (n = n.substring(1, n.length)),
'"' == n.charAt(n.length - 1) && (n = n.substring(0, n.length - 1)),
n
};
function decrypt(arg1, arg2, arg3) {
n = btoa("coinglass".concat(arg1, "coinglass"));
n = n.substring(0, 16)
result1 = Yt(arg2, n)
result2 = Yt(arg3, result1)
return result2
}