一、背景
今天看到一篇破解js混淆的文章,里面针对快音视的视频id加密,做了分析破解(链接:https://www.jianshu.com/p/5c945834270d ),刚好闲着无事,也分析一波,记录下自己的采坑经历
二、过程
1、确定加密参数
抓包后发现数据接口:https://kuaiyinshi.com/api/kuai-shou/recommend/?callback=showData&_=1557698894077, 里面的video_url字段,即视频下载链接为:
该链接无法打开,查看页面展示的实际下载链接为:
尝试全局搜索short_id, play_id, data_id, video_id, 在搜索video_id时找到js文件
2、确定加密函数
全局搜索video_id,发现两个js链接
先看下public.js,相关代码如下
大致意思就是根据不同平台的链接,将video_id取出来,无加密函数
再看下main.js函数,该js文件进行了混淆,关键字都用数组__Ox27641通过下标获取,同时将十进制数字转为16进制,如
var sc = getQueryString(__Ox27641[0x9]);
var st = getQueryString(__Ox27641[0xa]);
var video_id = getQueryString(__Ox27641[0xb]);
搜索后发现很多video_id,一开始以为是var video_id = getQueryString(__Ox27641[0xb]);即getQueryString是加密参数,进入publish.js里查看getQueryString函数
打断点调试后,发现请求一次,会调用三次该函数,传入的那么分别为source, st,video_id,但是没有发现加密
重新看main.js,看到ab3d7fc(video_id, ky),代码如下
function ab3d7fc(_0x871ex15, _0x871ex16) {
var _0x871ex17 = [];
var _0x871ex18 = _0x871ex15[__Ox27641[0x1e]](__Ox27641[0x1d]);
delete _0x871ex18[0x0];
var _0x871ex19 = [];
var _0x871ex1a = _0x871ex16[__Ox27641[0x1e]](__Ox27641[0xc]);
_0x871ex1a[__Ox27641[0x17]](function(_0x871ex1b) {
_0x871ex19[__Ox27641[0x20]](_0x871ex1b[__Ox27641[0x1f]]())
});
var _0x871ex1c = _0x871ex19[__Ox27641[0x21]];
_0x871ex18[__Ox27641[0x17]](function(_0x871ex1d, _0x871ex1e) {
_0x871ex17[__Ox27641[0x20]](parseInt(_0x871ex1d) - (0xFF & _0x871ex19[(_0x871ex1e - 1) % _0x871ex1c]))
});
var _0x871ex1f = __Ox27641[0xc];
_0x871ex17[__Ox27641[0x17]](function(_0x871ex13) {
_0x871ex1f += String[__Ox27641[0x16]](_0x871ex13)
});
return _0x871ex1f
}
该函数出现了好几次,然后尝试打断点,发现该函数传入加密后video_id值,返回解密后的video_id,确定该函数为加密函数
3、处理js函数
(1)传入参数确定:
第一个参数为video_id, 第二个参数: ky,在console中输入ky,获得该值的数据:‘A0MjZfMTY0NDA3Mj’,固定值
(2)解析函数
第一步:将所有16进制数字转为10进制:
function ab3d7fc(_0x871ex15, _0x871ex16) {
var _0x871ex17 = [];
var _0x871ex18 = _0x871ex15[__Ox27641[30]](__Ox27641[29]);
delete _0x871ex18[0];
var _0x871ex19 = [];
var _0x871ex1a = _0x871ex16[__Ox27641[30]](__Ox27641[12]);
_0x871ex1a[__Ox27641[23]](function(_0x871ex1b) {
_0x871ex19[__Ox27641[32]](_0x871ex1b[__Ox27641[31]]())
});
var _0x871ex1c = _0x871ex19[__Ox27641[33]];
_0x871ex18[__Ox27641[23]](function(_0x871ex1d, _0x871ex1e) {
_0x871ex17[__Ox27641[32]](parseInt(_0x871ex1d) - (255 & _0x871ex19[(_0x871ex1e - 1) % _0x871ex1c]))
});
var _0x871ex1f = __Ox27641[12];
_0x871ex17[__Ox27641[23]](function(_0x871ex13) {
_0x871ex1f += String[__Ox27641[22]](_0x871ex13)
});
return _0x871ex1f
}
第二步:根据__Ox27641数组的值及下标,替换关键字,同时使用短变量名重新命名变量,方便查看
function ab3d7fc(video_id, ky) {
var aa = [];
var bb = video_id.split(':');
delete bb[0];
var cc = [];
var dd = ky.split("");
dd.forEach(function(ff) {
cc.push(ff.charCodeAt())
});
var xx = cc.length;
bb.forEach(function(val, key) {
aa.push(parseInt(val) - (255 & cc[(key - 1) % xx]))
});
var hh = '';
aa.forEach(function(pp) {
hh += String.fromCharCode(pp)
});
return hh
}
执行到这一步,就面临两个选择,第一个是直接执行该js获取数据,方便简单,但效率慢,第二个就是用python重写该函数
第三步:执行js函数或Python实现该函数
方法一:执行js:选择js2py库,效率相对于PyV8和pyExecJS,效率更高,但要注意js2py执行复杂js代码会出错,所以要先试着能否js2py库正常执行,不行可以换成上面的两个库
import js2py
context = js2py.EvalJs()
context.execute(js)
video_id = context.ab3d7fc(':131:125:183:171:210:181:145:149:141:125:184:153:184:130:145:191:185:125:199:188:192:179:183:157:138:126:162:149:184:128:145:191:118:136:199:205:141:180:161:157:142:127:146:153:115:130:161:180:167:125:163:162:212:197:181:184:140:143:144:169:167:105:178:157:118:100:178:163:142:158:131:184:144:101:134:169:114:153:130:155:167:98:130:206:192:157:175:182:146:98:130', 'A0MjZfMTY0NDA3Mj')
print(video_id)
方法二:Python重写该函数
def get_video_id(video_id, ky='A0MjZfMTY0NDA3Mj'):
aa = []
bb = video_id.split(':')
del bb[0]
print(bb)
cc = []
for d in ky:
cc.append(ord(d))
print(cc)
xx = len(cc)
for key, val in enumerate(bb):
aa.append(int(val)- (255&cc[(key)%xx]))
print(aa)
hh = ''
for a in aa:
hh+= chr(a)
return hh
至此,完成该video_id的破解
三、知识点&踩坑
js混淆:关键字都用数组__Ox27641通过下标获取,同时将十进制数字转为16进制
int(), 第一个参数是字符串 ,第二个参数是说明,这个字符串是几进制的数, 转化的结果是一个十进制数
2进制转10进制:int('10100111110',2) ==> 1342
16进制转10进制: int('0x10', 16) ==> 16
8进制转10进制:int('17',8) ==> 15
oct(), 将 任意进制的数 转换成 8进制的
10进制转8进制: oct(11) ==> '013'
16进制转8进制: oct(0xf) ==> '017'
2进制转8进制: oct(0b1010) == > '012'
hex(), 10进制转16进制
10进制转16进制: hex(16) ==> 0x10
8进制转16进制: hex(int('17',8)) ==> '0xf'
2进制转16进制: hex(int('101010',2)) ==> '0x2a'
bin(), 10进制转2进制
10进制转2进制: bin(10) ==> '0b1010'
16进制转2进制: bin(int('ff',16)) ==> '0b11111111'
8进制转2进制: bin(int('17',8)) == > '0b1111'
2、js的forEach和Python的enumerate