跳转方式
先介绍一下短连接的实现方式:
1.后端执行302跳转。
2.输出js脚本,由前端进行跳转(location.href之类的形式做跳转)。
后端跳转
如果是后端执行302跳转的短连接,可以使用php的get_headers(URL,true)的方法获取header头中的location参数
1 2 3 4 5 6
| $url = 'http://t.cn/h5mwx'; $headers = get_headers($url, TRUE); print_r($headers);
echo $headers['Location'];
|
经过测试,什么值得买并不是采用这种方式进行跳转的。
前端跳转
使用js脚本进行跳转的话,php就无法获取到最终跳转到的链接了吗?
一开始我尝试使用curl进行抓取,发现抓取的数据长度始终为0。
于是我尝试通过谷歌浏览器的 view-source:页面URL 确认该页面是否存在数据。
![]()
在图上的代码里发现了这行代码,它的意思大致是 当访问者的浏览器无法执行script时,立即跳转到一个空的页面。
1
| <noscript><meta http-equiv="refresh" content="0; url=/"></noscript>
|
经过测试,对于这个url使用curl获取时数据为空,而file_get_contents则能正常获取到数据。
1 2
| $url = 'https://go.smzdm.com/c637e4991bd40490/ca_aa_yh_163_11379359_10054_257_179_0'; $htmlCode = file_get_contents($url);
|
接下来通过正则表达式获取eval()的值
1 2 3 4 5 6 7 8
| $scriptExp = "/eval\(function([\w\W]*)<\/script>/"; preg_match($scriptExp,$htmlCode,$match); if(isset($match[0])){ $match[0] = str_replace('</script>','',$match[0]); echo $match[0]; }
|
这样能够正常取到eval(…)的值,接下来就是对eval进行解密了。
eval解密
之前看过一篇csdn的博文,大致讲解了js pack中使用eval()进行代码混淆加密的方式。eval内部是一个用于unpack匿名函数,通过传入的参数解密之后获取原始代码的字符串,然后通过eval()执行这串代码。
所以我们可以在console控制台执行这个eval()中的闭包查看下结果。
![]()
可以正常解密,那么现在的问题就是 如何使用PHP去解密这段代码?
一开始走了不少弯路,安装了phantomjs拓展之后发现这个拓展没办法执行js,导致获取到的页面内容也是空(不过这拓展可以进行网页截图还是很厉害的哈哈哈)。
然后看到一个php-v8js的拓展,可以使用php调用v8js引擎执行js代码。但是需要重新编译PHP而且win和Linux下不同的编译流程也太过繁琐了。(万一以后挂了,也很难去找原因)
当拓展没办法实现的时候,就剩下一条路子了 —— 用PHP重写这个js匿名函数,然后传入同样的值进行解密。
ok,先格式下话这个js函数方便我们观看
![]()
首先我们先看传入的参数 p、a、c、k、e、d。
p : 一个乱码字符串
a : 一个数值
c : 一个数值
k : 一个字符串,使用 | 分割成数组
e : 一个数值
d : 一个空对象
看到这里我还是云里雾里,继续看匿名函数
1 2 3 4 5 6 7 8 9 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 41 42 43 44 45 46 47 48 49
| function (p, a, c, k, e, d) { e = function (c) { return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36)) }; if (!''.replace(/^/, String)) { while (c--) { d[e(c)] = k[c] || e(c) } k = [function (e) { return d[e] }]; e = function () { return '\\w+' }; c = 1 } while (c--) { if (k[c]) { p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c]) } } return p }
|
看完整个函数后,大致已经清楚了解密的方法,其中字符串p是整个混淆之后的结果,c是k切割之后的长度,而k则是整个字符表的内容。
php重写难点
- php的键名不区分大小写,所以$d[‘A’]的赋值会覆盖掉d[‘a’]的赋值。
1 2 3
| $d[] = ['name'=>'A','value'=>'xxxx'] $d[] = ['name'=>'a','value'=>'xxxx']
|
之前也考虑过逆向 闭包函数e 的方式获取原始键名,但是太过复杂 最后还是采用了二维数组的形式。
preg_replace和js的replace有区别。php的preg_replace默认是全局替换,需要填写第4个参数为1。
36进制转换时,字母a-z需要是小写的,因为程序会把小写字母通过chr($c+29)转换成大写。
截取传入参数时,可以先逆向参数字符串,再以 , 为分隔符截取6次。
php版本的解密源代码暂不放出,喜欢钻研的小伙伴们可以通过上面我对js函数的代码注释自行提炼解密函数 23333…
感谢
最后感谢 veris 和 峰丨逸 在 php解密方式 、阅读js源代码 上提供的帮助。