获取 什么值得买 跳转链接的方法

跳转方式

先介绍一下短连接的实现方式:

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])){
//水平不高,获取的值中会带上最后的</script>,此处手动删除下
//如果有大佬能提供更好的正则表达式请在评论留言哈哈哈
$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被赋值为了一个闭包函数
// 逻辑是这样的:
// 传入一个数值c返回一个字符,它由两部分组成
// (c < a ? '' : e(parseInt(c / a))) 这个部分比较简单就不解释了
// ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))这个是重点
// 如果 c除以a之后的余数大于35,那么返回c+29的ascii码对应的字符,否则把c转换成对应的36进制数
e = function (c) {
return (c < a ? '' : e(parseInt(c / a))) + ((c = c % a) > 35 ? String.fromCharCode(c + 29) : c.toString(36))
};

// 这行 !''.replace(/^/, String) 一开始没看懂,请教了大佬得知是判断String对象是否可用
// 因为js存在一些类型对象,这些对象提供了诸多的内置方法。
// 如果String被修改过,那么上面的String.fromCharCode()方法可能就无法正确运行了。
if (!''.replace(/^/, String)) {
while (c--) {
// 递减 c,从 字符切割的数组k中赋值到空对象d中
// 经过测试我发现c = k.length,当k[c] == ""时,就执行e(c)填充到对象d中。
// d的键也是通过e(c)生成的,有点类似hashMap
d[e(c)] = k[c] || e(c)
}
// 这行到现在都没看懂,它似乎实现了一个匿名闭包数组
// 因为后面用PHP实现的过程不一样,所以这段无关紧要
k = [function (e) {
return d[e]
}];
// e重新赋值了一个闭包函数,返回 '\\w+'
e = function () {
return '\\w+'
};
// c重置为1
c = 1
}
// 此处我们只讨论 String 对象可用的情况
// c=0为false,这种情况下while只执行了一次
while (c--) {
if (k[c]) {
// 对字符串p进行正则替换,此处的正则表达式为/\b\w+\b/g
// 意思是 以元字符(空格、换行等特殊字符)为间隔,匹配数字、字母、下划线和加号等内容
// 因为加上g之后,每次只匹配一个内容,而不是一次性全部替换

// 最开始我没看懂为什么while只执行了一次,但这个替换还能逐个进行下去?
// 后来查阅文档,replace的第二个参数可以是字符串或者函数
// 当第二个参数为函数时,每个匹配都调用该函数,它返回的字符串将作为替换文本使用
p = p.replace(new RegExp('\\b' + e(c) + '\\b', 'g'), k[c])
}
}
return p
}

看完整个函数后,大致已经清楚了解密的方法,其中字符串p是整个混淆之后的结果,c是k切割之后的长度,而k则是整个字符表的内容。

php重写难点

  1. php的键名不区分大小写,所以$d[‘A’]的赋值会覆盖掉d[‘a’]的赋值。
1
2
3
// 建议使用二维数组的形式存储
$d[] = ['name'=>'A','value'=>'xxxx']
$d[] = ['name'=>'a','value'=>'xxxx']

之前也考虑过逆向 闭包函数e 的方式获取原始键名,但是太过复杂 最后还是采用了二维数组的形式。

  1. preg_replace和js的replace有区别。php的preg_replace默认是全局替换,需要填写第4个参数为1。

  2. 36进制转换时,字母a-z需要是小写的,因为程序会把小写字母通过chr($c+29)转换成大写。

  3. 截取传入参数时,可以先逆向参数字符串,再以 , 为分隔符截取6次。

php版本的解密源代码暂不放出,喜欢钻研的小伙伴们可以通过上面我对js函数的代码注释自行提炼解密函数 23333…

感谢

最后感谢 veris峰丨逸 在 php解密方式 、阅读js源代码 上提供的帮助。


评论区