前言:
最近学习js逆向案例,本文仅以初学者思路记录学习,内容比较繁多~
目标声明:仅仅记录一下自己的学习方法,不作为学习参考、更不作为商业用途。如有侵犯请联系本人删除
1.分析需要逆向参数。
xm-sign值为
5bc0af13a02e5d1da409d0d8acb77c32(44)1703550877500(54)1703550877738
初步分析可能是md5+随机数字+时间戳+随机数字+时间戳构成。下面我们进一步分析xmsign值
2.定位、分析xm-sign
2.1定位
该案例直接全局搜索xm-sign即可。
var d = new i.default;
a.interceptors.request.use((function(e) {
return e.url.indexOf("/revision") > -1 && (e.headers = f(f({}, e.headers), {}, {
"xm-sign": d.getSign()
})),
e
}
2.2分析xm-sign构建
在此处打断点,进一步跟踪。
跳转到该代码块。
t.prototype[c("0x27")] = function() {
var t, e, r, n = 0;
return n = s() ? Date.now() : window.XM_SERVER_CLOCK || 0,
t = this[c("0x13")],
e = n,
r = Date[c("0x25")](),
("{" + t + e + "}(" + u(100) + ")" + e + "(" + u(100) + ")" + r)[c("0xa")](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
}
//上述代码经过还原后如下:
t.prototype["getSign"] = function() {
var t, e, r, n = 0;
return n = s() ? Date.now() : window.XM_SERVER_CLOCK || 0,
t = this["secretKey"],
e = n,
r = Date["now"](),
("{" + t + e + "}(" + u(100) + ")" + e + "(" + u(100) + ")" + r)["replace"](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
}
这里我们分析下这个函数。
很明显n是一个三元运算符。此处加断点后,分析s(),发现结果固定为false。
所以此处可以简化为 return n= window.XM_SERVER_CLOCK
window.XM_SERVER_CLOCK此处已经有值,这里需要注意,最后需要返回此处分析window.XM_SERVER_CLOCK的构建方式。此处暂时不做分析。
继续加断点分析:("{" + t + e + "}(" + u(100) + ")" + e + "(" + u(100) + ")" + r)[c("0xa")](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
此处返回的是xm-sign密文,可以知道密文一定是这一串代码构建的,只要分析这段代码即可。
t和e参数在函数中都已经赋值。
由此可知
t="himalaya-" //固定值
e=window.XM_SERVER_CLOCK
r=Date["now"]() //时间戳
//最后该函数优化为
("{" + t + e + "}(" + u(100) + ")" + e + "(" + u(100) + ")" + r)['replace'](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
其中/{([\w-]+)}/为正则表达式,/是转义符,简化后为{([\w-]+)}
即将{" + t + e + "}的值替换为a(e)
通过第一步生成的xm-sign加密参数,我们可以知道
5bc0af13a02e5d1da409d0d8acb77c32(44)1703550877500(54)1703550877738
可能是md5+随机数字+时间戳+随机数字+时间戳构成
即5bc0af13a02e5d1da409d0d8acb77c32猜测可能是md5加密。
通过控制台执行可以知道
e='himalaya-1703556671595'
a(e)='43aab441946e1db6f4656d1a0db29020'
我们找一个md5在线加密网站加密e值测试一下
OK!结果完全一致,可以确定a函数就是进行md5加密。
2.3进一步分析u(100)
通过random我们可以知道这里是返回0~100之间随机值。
在pycharm中创建js文件,并创建一个get_xmsign函数,并进行打印输出:
const crypto = require('crypto');
window=global;
function a(e){
const md5 = crypto.createHash('md5');
return md5.update(e).digest('hex')
}
function get_xmsign() {
var t, e, r, n = 0;
return n =window.XM_SERVER_CLOCK ,
t = this["secretKey"],
e = n,
r = Date["now"](),
("{" + t + e + "}(" + 33 + ")" + e + "(" + 22 + ")" + r)["replace"](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
}
console.log(get_xmsign())
//其中a函数里面进行md5加密,网上搜一下crypto的md5加密就有代码。
执行结果为
e21cc36bf0d65014c717a481a3f8a468(74)undefined(20)1703558161144
其中undefined是n值,即window.XM_SERVER_CLOCK。
2.4分析window.XM_SERVER_CLOCK
从头开始分析"xm-sign": d.getSign()
刷新页面,断到此处后,在控制台执行,有返回结果。说明window.XM_SERVER_CLOCK这个值在调用d.getSign()这一步之前就已经生成了。
在d生成的地方加断点试一下,
这时候需要刷新页面,window.XM_SERVER_CLOCK值是在加载页面的时候生成的。
发现this[]中没有我们要的XM_SERVER_CLOCK值, 进一步分析。
this[c("0x36")]()这里没进行赋值,只调用了函数。断点到此处,鼠标放在这里分析一下
return t[c("0x9")][c("0x36")] = function(t) {
var e = this;
void 0 === t && (t = !1),
s() || window.XM_SERVER_CLOCK && !t || this[c("0x44")]()[c("0x45")]((function(t) {
e[c("0x12")] = t,
window[c("0x20")] = t,
e[c("0x48")]()
}
))
}
window.XM_SERVER_CLOCK = t ,t就是这个时间戳。需要继续分析t是从哪里构建的。
this[c("0x44")]()[c("0x45")]((function(t) {
e[c("0x12")] = t,
window[c("0x20")] = t,
e[c("0x48")]()
}
))
//解混淆后::
this["getServerDate"]()["then"]((function(t) {
e[c("0x12")] = t,
window[c("0x20")] = t,
e[c("0x48")]()
}
))
//分析得知
异步调用this.getServerDate(),并将返回值作为参数t,调用后面的函数。
所以t=this.getServerDate()
分析this.getServerDate后发现是通过调用网址
获取时间戳,作为参数t传入。
到这里,大功告成!
js代码参考如下:
const crypto = require('crypto');
function a(e){
const md5 = crypto.createHash('md5');
return md5.update(e).digest('hex')
}
u = function(t) {
return ~~(Math["random"]() * t)
};
function get_xmsign(timestamp) {
var t, e, r, n = 0;
return n =timestamp ,
t = this["secretKey"],
e = n,
r = Date["now"](),
("{" + t + e + "}(" + u(100) + ")" + e + "(" + u(100) + ")" + r)["replace"](/{([\w-]+)}/, (function(t, e) {
return a(e)
}
))
}
console.log(get_xmsign())
Python代码如下:
import execjs
import requests
def get_timestamp():
##获取时间戳
res = requests.get('https://www.ximalaya.com/revision/time',headers={
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
}).text
return res
def get_xm_sign(timestamp):
js_Code=open('ximalaya.js').read()
js_compile = execjs.compile(js_Code)
xm_sign = js_compile.call('get_xmsign',timestamp)
return xm_sign
def get_info(bookName):
timestamp = get_timestamp()
xm_sign=get_xm_sign(timestamp)
cookies = {
'_xmLog': 'h5&35b9cf13-8e2c-48eb-9dd4-a6cbe84a8919&process.env.sdkVersion',
'xm-page-viewid': 'ximalaya-web',
'impl': 'www.ximalaya.com.login',
'x_xmly_traffic': 'utm_source%253A%2526utm_medium%253A%2526utm_campaign%253A%2526utm_content%253A%2526utm_term%253A%2526utm_from%253A',
'wfp': 'ACM4MzFmMjY1NTM5NGY3ZGQ0POuVQ3wgf4B4bXdlYl93d3c',
'Hm_lvt_4a7d8ec50cfd6af753c4f8aee3425070': '1703550655',
'Hm_lpvt_4a7d8ec50cfd6af753c4f8aee3425070': '1703567472',
}
headers = {
'Accept': '*/*',
'Accept-Language': 'zh-CN,zh;q=0.9',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
# 'Cookie': '_xmLog=h5&35b9cf13-8e2c-48eb-9dd4-a6cbe84a8919&process.env.sdkVersion; xm-page-viewid=ximalaya-web; impl=www.ximalaya.com.login; x_xmly_traffic=utm_source%253A%2526utm_medium%253A%2526utm_campaign%253A%2526utm_content%253A%2526utm_term%253A%2526utm_from%253A; wfp=ACM4MzFmMjY1NTM5NGY3ZGQ0POuVQ3wgf4B4bXdlYl93d3c; Hm_lvt_4a7d8ec50cfd6af753c4f8aee3425070=1703550655; Hm_lpvt_4a7d8ec50cfd6af753c4f8aee3425070=1703567472',
'Pragma': 'no-cache',
'Referer': 'https://www.ximalaya.com/so/%E6%88%91%E7%9A%84%E8%80%81%E5%8D%83%E7%94%9F%E6%B6%AF',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'same-origin',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'sec-ch-ua': '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'xm-sign': xm_sign,
}
params = {
'core': 'all',
'kw': bookName,
'spellchecker': 'true',
'device': 'iPhone',
'live': 'true',
}
response = requests.get('https://www.ximalaya.com/revision/search/main', params=params, cookies=cookies,
headers=headers)
return response.json()
def download():
pass
if __name__ == '__main__':
bookName = input('请输入小说名称:::')
res = get_info(bookName)
books=[]
for i in res['data']['album']['docs']:
dic={}
if i['vipType']!=0 or len(i['priceTypes'])>0:#收费和会员不要 #此处写的比较随意,可以不参考
pass
else:
dic['title']=i['title']
dic['url'] = 'https://www.ximalaya.com'+i['url']
dic['priceTypes']=i['priceTypes']
books.append(dic)
print(books)
# url = books[0]['url']
# title=books[0]['title']
# albumId = books[0]['url'].split('/')[-1]
#后面可以继续扩展,调用download函数,通过名称搜索所有书籍,并进入到书籍明细中,获取每一章节的mp3地址,进行下载。