手撕某解析网站,获取真实播放地址(保姆级讲解)(转)
原贴地址:手撕某解析网站,获取真实播放地址(保姆级讲解) - 『脱壳破解区』 - 吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn
文章的网址做了脱敏处理,笔者用了BASE64编码,请自行解码。
文章里面的Fiddler使用了编程猫专用插件,请自行百度下载。
一、声明
本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除!
二、前言
最近追上了电视剧,为了方便自己,准备去抓几个接口,于是就度娘一下,除了广告还有这个么多,很好,那我们就拿排名第一的分析分析,文才不佳,还请各位将就着
<ignore_js_op>
三、目标
主页:dHYuaHp3ZGQuY24=
接口:aHR0cHM6Ly9qeC5wYXJ3aXguY29tOjQ0MzMvcGxheWVyL2FuYWx5c2lzLnBocD92PWh0dHBzOi8vdi5xcS5jb20veC9jb3Zlci96cjVhNjdsMzMzZWh6dTkuaHRtbA==
四、无限debugger
F12打开控制台后,会进入一个无限debugger,为了我们以后的调试,我们不得不将它干掉
<ignore_js_op>
我们回溯到这里,发现调用 setInterval 方法,该方法可按照指定的周期(以毫秒计)来调用函数或计算表达式,
很明显,它调用了check,注意check函数,是将debugger 传递给了构造方法constructor,所以这里hook掉constructor
<ignore_js_op>
我们打开fiddler,开启hook代码如下
1
2
3
4
5
6
7
|
Function.prototype.constructor_ = Function.prototype.constructor; Function.prototype.constructor = function (a) { if (a == "debugger" ) { return function (){}; } return Function.prototype.constructor_(a); }; |
<ignore_js_op>
五、抓包分析
ok,我们干掉了烦人的无限debugger,我们抽取接口地址:
aHR0cHM6Ly9qeC5wYXJ3aXguY29tOjQ0MzMvcGxheWVyL2FuYWx5c2lzLnBocD92PWh0dHBzOi8vdi5xcS5jb20veC9jb3Zlci96cjVhNjdsMzMzZWh6dTkuaHRtbA==
重新加载一篇,很明显视频可以正常播放,而且我们的目标播放地址也能看到了
<ignore_js_op>
接下来我们分析播放地址是怎么来的,我们用接口地址访问,整个页面非常简单,我们看看接口地址返回了什么
<ignore_js_op>
一个加密的视频地址,一个加密混淆的abc函数代码
ok,我们来看看abc函数
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
function abc(){ [ "sojson.v4" ][ "\x66\x69\x6c\x74\x65\x72" ][ "\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72" ]((([ "sojson.v4" ]+[])[ "\x63\x6f\x6e\x73\x74\x72\x75\x63\x74\x6f\x72" ] [ "\x66\x72\x6f\x6d\x43\x68\x61\x72\x43\x6f\x64\x65" ][ "\x61\x70\x70\x6c\x79" ] ( null , "118M97i114L32C95D112e114M32G61l32C36A40O39B109e101w116m97N91Y110y97I109I101J61q34E118i105N101h119R112I111G114T116z34Z93o39m41o46S97o116b116T114C40Q39r105I100m39O41f46U1 14P101B112t108d97g99M101X40Z39P118u111s100A95O39A44e39g39J41Y59l10b32j32e32e32l32Q32n32f32t32a118D97w114r32U95V112F117z32K61r32u36G40g39d109N101d116S97V91d99M104m97q114M115y10 1f116h61K34w85c84X70z45j56J34p93O39e41V46G97P116a116d114P40O39i105B100S39x41s46l114Y101I112E108w97m99h101q40y39Q118Y111A100D95p39V44X39z39a41k59d10l32M32v32d32n32C32G32G32y32Y 118K97O114u32v95f112e117z65W114w114y32i32f61T32Y91i93E59x10x32v32T32e32F32b32U32r32a32i118t97z114A32W95U110r101v119r65O114i114Q32O61b32E91L93N59u10R32u32A32c32y32R32I32o32Y32W 118A97L114D32l95E99E111W100P101N32D32N32o61n32d39r39Q59g10e32K32g32w32X32k32b32j32p32A10r32X32X32r32U32D32j32q32F32v102U111D114X40g118e97v114V32B105N61c48U59v105z60L32m95z112Z 117S46y108a101b110K103a116h104H59O32A105Z43o43m41Y123Q10e32c32v32B32G32E32J32a32W32t32O32J32s32j10C32T32K32A32I32A32h32Z32V32J32T32b32C32A95X112e117z65C114t114M46Z112J117P115l 104d40w123z32e39j105C100S39Q58a95S112I117K91j105n93t44k32P39w116Q101p120K116x39p58p32R95z112D114A91S105Z93U32x125Q41J59i10H32c32l32G32D32r32A32R32L32G125F10Q32h32M32W32j32E32Y 32m32J32P10L32v32y32A32C32t32U32B32F32B47D47G23545R23494p38053e37325J26032k36827g34892u25490S24207C10x32l32G32B32d32m32E32i32u32i95d110c101L119a65o114y114k32C61p32k95Y112l117v 65w114h114i46w115f111r114A116X40T80Z65Q82U46v99c111s109f112g97f114p101g40q34x105x100U34i41e41i59J10a32U32m32y32y32X32s32K32W32P10J32V32H32w32E32x32U32Q32s32M102B111V114Y40j118 e97h114K32v105L61J48h59d105G60A32g95B110Z101F119J65v114x114v46B108c101j110g103W116R104F59f32a105p43b43f41b123o10Q32Z32I32J32e32A32m32k32r32p32b32Q32W32G10P32J32H32T32F32V32U32 V32g32W32p32A32n32W95q99w111Y100k101e43b61C95A110p101v119I65k114F114V91d105G93N91D39y116d101X120u116H39t93a59b10h32Z32D32E32F32S32K32D32a32l125J10O32P32n32b32c32K32u32S32b32u9 9t111x110K102Q105h103D46T117p114L108l32q61u32t32H80X65I82r46Q115H101x99D114Y101a116V40C99c111I110h102K105f103X46Q117N114w108E44f32f95U99b111W100R101a44L32u116U114P117q101I41w5 9" [ "\x73\x70\x6c\x69\x74" ](/[a-zA-Z]{1,}/))))( "sojson.v4" ); |
我们先把字符串编码解决一下,大概就是这个样子
1
|
[ 'sojson.v4' ]+[])[ "constructor" ][ 'fromCharCode' ][ 'apply' ]( null , "123456..." [ 'split' ](/[a-zA-Z]{1,}/))))[ 'sojson.v4' ] |
根据个人理解,上面几乎可以等于以下代码
1
|
'' .constructor.fromCharCode.apply( null , "123456..." .split(/[a-zA-Z]{1,}/)) |
大概意思就是把"123456..."转换成字符数组“1”“2”...,然后用fromCharCode方法转换成字符串
1
|
String.fromCharCode(“1”“2”...) |
我们在控制台输出一下
<ignore_js_op>
ok,我们得到如下字符串代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
( function anonymous( ) { var _pr = $( 'meta[name="viewport"]' ).attr( 'id' ).replace( 'vod_' , '' ); var _pu = $( 'meta[charset="UTF-8"]' ).attr( 'id' ).replace( 'vod_' , '' ); var _puArr = []; var _newArr = []; var _code = '' ; for ( var i=0;i< _pu.length; i++){ _puArr.push({ 'id' :_pu[i], 'text' : _pr[i] }); } //对密钥重新进行排序 _newArr = _puArr.sort(PAR.compare( "id" )); for ( var i=0;i< _newArr.length; i++){ _code+=_newArr[i][ 'text' ]; } config.url = PAR.secret(config.url, _code, true ); }) |
我们分析分析,获取接口地址返回pr和pu,然后组装成puArr数组,对puArr通过PAR.compare方法按照id大小重新排序,
然后得到_code,然后通过PAR.secret方法,传入页面加密的url和_code和true
得到一个新的url,这里,我们大胆一点,新的url应该就是真实播放地址
<ignore_js_op>
ok,流程理清楚了,现在关键的就是PAR.compare和PAR.secret这2个函数了
我们全文模糊搜索PAR PAR:PAR= var PAR等关键字,很快就定位到尾号为676f.js里
<ignore_js_op>
五、OB混淆
我们不难发现,整个尾号为676f.js是一个混淆代码,我们拷贝下来,分析分析
<ignore_js_op>
开头定义了一个大数组,然后对这个大数组里的内容进行位移,再定义一个解密函数。后面大部分的值都调用了这个解密函数,以达到混淆的效果。
这个就是传说中的OB混淆我们先看看PAR.compare和PAR.secret
01
02
03
04
05
06
07
08
09
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
|
'compare' : function (_0x209a13) { var _0x2b0a5b = { 'hupwx' : function (_0x28592c, _0x5c0aae) { return _0x28592c - _0x5c0aae; } }; return function (_0x4490c6, _0x47bde8) { var _0x11d139 = _0x4490c6[_0x209a13]; var _0x2b383f = _0x47bde8[_0x209a13]; return _0x2b0a5b[_0x15b4( '6e' , 'yYg5' )](_0x11d139, _0x2b383f); } 'secret' : function (_0x141a71, _0x433196, _0x45cf51) { var _0x20b4ff = { 'bCQqZ' : _0x15b4( '6f' , 'xfLW' ), 'hAcOs' : _0x15b4( '70' , 'wHsA' ) }; _0x433196 = CryptoJS[_0x15b4( '71' , 'p5&!' )](_0x433196)[ 'toString' ](); var _0x4f0cf6 = CryptoJS[ 'enc' ][_0x15b4( '72' , 'a]Oa' )][_0x15b4( '73' , 'Knq)' )](_0x433196[ 'substring' ](0x0, 0x10)); var _0x1030c4 = CryptoJS[ 'enc' ][_0x15b4( '74' , 'eKXt' )][ 'parse' ](_0x433196[ 'substring' ](0x10)); if (_0x45cf51) { if (_0x20b4ff[_0x15b4( '75' , 'eKXt' )] !== 'vLPSI' ) { return CryptoJS[_0x15b4( '76' , 'AIBX' )][_0x15b4( '77' , 'tzTY' )](_0x141a71, _0x1030c4, { 'iv' : _0x4f0cf6, 'padding' : CryptoJS[_0x15b4( '78' , 'I0@(' )][ 'Pkcs7' ] })[ 'toString' ](CryptoJS[ 'enc' ][_0x15b4( '79' , 'vNOA' )]); } else { PAR[_0x15b4( '7a' , 'wHsA' )][ 'post_r' ](a, b, c, d, _0x20b4ff[_0x15b4( '7b' , 'xB(L' )]); } } return CryptoJS[_0x15b4( '7c' , 'bVZ)' )][_0x15b4( '7d' , 'Bb3L' )](_0x141a71, _0x1030c4, { 'iv' : _0x4f0cf6, 'mode' : CryptoJS[_0x15b4( '7e' , 'vzyU' )][_0x15b4( '7f' , 'Ya)8' )], 'padding' : CryptoJS[_0x15b4( '80' , 'jX$g' )][_0x15b4( '81' , 'xB(L' )] })[_0x15b4( '82' , 'OZnY' )](); } |
看样子混淆程度不高,一些关键字眼我们还是可以认出来,比如说CryptoJS,我们可以选择动态调试硬钢
不过,我还是选择把它还原一下,本地慢慢分析。
我们刚讲了,尾号为676f.js为标准的OB混淆样式,解密函数很明显就是_0x15b4,更何况PAR.compare和
PAR.secret多处调用,我们只需要把尾号为676f.js的前三段提取出来运行一遍,就能得到类似于_0x15b4('6e', 'yYg5')的值
这里可以选择硬钢替换,也可以选着用AST还原
<ignore_js_op>
不过需要注意的是这里有个暗坑,此段代码会检测你是否格式化,如果格式化了,就会内存爆破,进去无限循环,直到浏览器崩溃,有兴趣的同学可以试试
<ignore_js_op>
<ignore_js_op>
简单还原一下,然后PAR.compare和PAR.secret就成了这个样子
01
02
03
04
05
06
07
08
09
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
|
'compare' : function (_0x209a13) { var _0x2b0a5b = { 'hupwx' : function (_0x28592c, _0x5c0aae) { return _0x28592c - _0x5c0aae; } }; return function (_0x4490c6, _0x47bde8) { var _0x11d139 = _0x4490c6[_0x209a13]; var _0x2b383f = _0x47bde8[_0x209a13]; return _0x2b0a5b[ "hupwx" ](_0x11d139, _0x2b383f); }; }, 'secret' : function (_0x141a71, _0x433196, _0x45cf51) { var _0x20b4ff = { 'bCQqZ' : "\u8D4C\u535A\u8BC8\u9A97" , 'hAcOs' : "LSEHx" }; _0x433196 = CryptoJS[ "MD5" ](_0x433196)[ 'toString' ](); var _0x4f0cf6 = CryptoJS[ 'enc' ][ "Utf8" ][ "parse" ](_0x433196[ 'substring' ](0x0, 0x10)); var _0x1030c4 = CryptoJS[ 'enc' ][ "Utf8" ][ 'parse' ](_0x433196[ 'substring' ](0x10)); if (_0x45cf51) { if ( "LSEHx" !== 'vLPSI' ) { return CryptoJS[ "AES" ][ "decrypt" ](_0x141a71, _0x1030c4, { 'iv' : _0x4f0cf6, 'padding' : CryptoJS[ "pad" ][ 'Pkcs7' ] })[ 'toString' ](CryptoJS[ 'enc' ][ "Utf8" ]); } else { PAR[ "danmu" ][ 'post_r' ](a, b, c, d, "\u8D4C\u535A\u8BC8\u9A97" ); } } return CryptoJS[ "AES" ][ "encrypt" ](_0x141a71, _0x1030c4, { 'iv' : _0x4f0cf6, 'mode' : CryptoJS[ "mode" ][ "CBC" ], 'padding' : CryptoJS[ "pad" ][ "Pkcs7" ] })[ "toString" ](); }, |
先看PAR.compare,我们发现就是sort的排列方式函数,我们改写一下
01
02
03
04
05
06
07
08
09
10
11
|
function compare(d){ function hupwx(a, b) { return a - b; } return function (a1, b1) { var a = a1[d]; var b = b1[d]; return hupwx(a, b); }; } |
至于PAR.secret,细心的童鞋已经发现他是调用加解密库(crypto-js)进行的一个AES解密,我们也改写一下
01
02
03
04
05
06
07
08
09
10
11
12
13
|
function secret (url, _bcode) { code = CryptoJS[ "MD5" ](_bcode)[ 'toString' ](); var _0x4f0cf6 = CryptoJS[ 'enc' ][ "Utf8" ][ "parse" ](code[ 'substring' ](0x0, 0x10)); var _0x1030c4 = CryptoJS[ 'enc' ][ "Utf8" ][ 'parse' ](code[ 'substring' ](0x10)); return CryptoJS[ "AES" ][ "decrypt" ](url, _0x1030c4, { 'iv' : _0x4f0cf6, 'padding' : CryptoJS[ "pad" ][ 'Pkcs7' ] })[ 'toString' ](CryptoJS[ 'enc' ][ "Utf8" ]); } |
几个重要的参数'iv','padding',key清新可见
我们来梳理一下,前面我们说了对puArr通过PAR.compare方法重新排序,然后得到_code,然后传入加密url和_code,
通过md5加密得到code,取code前16位得到IV,取后16位得到key,最后decrypt解密成明文url
因此我们改写一下abc函数,全局搜索CryptoJS,拿到尾号为cd51.js,稍微改写一下来验证一下(当然这里直接调用crypto-js加解密库也是一样)
<ignore_js_op>
需要注意,每次请求接口地址url和pr/pu都要变化,为了方便我们暂时先固定一下
01
02
03
04
05
06
07
08
09
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
|
function abc(){ //var _pr = $('meta[name="viewport"]').attr('id').replace('vod_',''); //var _pu = $('meta[charset="UTF-8"]').attr('id').replace('vod_',''); var _pr = "vod_dQb7shnQWf" .replace( 'vod_' , '' ); var _pu = "vod_5318769240" .replace( 'vod_' , '' ); var _puArr = []; var _newArr = []; var _code = '' ; var url= "a7GlmXWmVruOTAcF8VaB3idxA924haXMwKMPzu7dl0NDlQQVrqVTZwUvzFj8KRYjkthOjRXHwPtT86kzfj46WVnIA3DbSmBfQEE2ICd3OI2FBhOfcRauLNQ2kSxcrRlDOl8R6wY3q7tG6xBA0k +Drs/iZFbc/llpLEyk06vOMkdt/3qxWxazor5E6mIvJ0PoieCvj9FJoooWECzXOiWgMBIX0EmKw5o4zWWRipLZTXdo69bbSFUJ1PphkS50snOkvdtzh1uKd2xTKqERLC +iOTqRvBz8YtqjashI5MW0yhhkboK8StIK2QUIa0AACQBDJfhk3bxkmLyW8zdFBZa5iRfW5BNcq3DulkpKK8wY/+c40wesKygPXgBpgultR82yZmPCMHXrxDpxJDDywKXhYauH/iYyGO05V +2pLMx6i9Biaq6XUPluh2VsMUe7GjWE1cKneEkmyJw4bMB23YY9Rr0RrV4gzoQ08xT+n2LeLsOKU4APkXuVEIKLXx+Wgo5SzpWqJL5N4MahNRpcetHavjoynwvCX1Mh59U4U67x5TKan8a9hO4209Qvc+0n +fWxWjYZ0erlgTrxvlwU1EVQeRincA0jI0DF5tVtErYxs90=" ; for ( var i=0;i< _pu.length; i++){ _puArr.push({ 'id' :_pu[i], 'text' : _pr[i] }); } //对密钥重新进行排序 _newArr = _puArr.sort(compare( "id" )); console.log(_newArr); for ( var i=0;i< _newArr.length; i++){ _code+=_newArr[i][ 'text' ]; } url1 = secret(url, _code); return url1; } |
测试OK,至此播放地址拿到手了
<ignore_js_op>
以下附上js源码和简单的AST源码,如果对AST感兴趣,我们下次再聊
链接:https://pan.baidu.com/s/16ThFDjy9acw5eOH8LMaM5Q
提取码:52pj