跳转方式
先介绍一下短连接的实现方式:
1.后端执行302跳转。
2.输出js脚本,由前端进行跳转(location.href之类的形式做跳转)。
后端跳转
如果是后端执行302跳转的短连接,可以使用php的get_headers(URL,true)的方法获取header头中的location参数
| 12
 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则能正常获取到数据。
| 12
 
 | $url = 'https://go.smzdm.com/c637e4991bd40490/ca_aa_yh_163_11379359_10054_257_179_0';$htmlCode = file_get_contents($url);
 
 | 
接下来通过正则表达式获取eval()的值
| 12
 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 : 一个空对象
看到这里我还是云里雾里,继续看匿名函数
| 12
 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’]的赋值。
| 12
 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源代码 上提供的帮助。