获取 什么值得买 跳转链接的方法
in php后端 with 0 comment

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

in php后端 with 0 comment

跳转方式

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

1.后端执行302跳转。
2.输出js脚本,由前端进行跳转(location.href之类的形式做跳转)。

后端跳转

如果是后端执行302跳转的短连接,可以使用php的get_headers(URL,true)的方法获取header头中的location参数

$url = 'http://t.cn/h5mwx';
$headers = get_headers($url, TRUE);
print_r($headers);

//输出跳转到的网址
echo $headers['Location'];

经过测试,什么值得买并不是采用这种方式进行跳转的。

前端跳转

使用js脚本进行跳转的话,php就无法获取到最终跳转到的链接了吗?

一开始我尝试使用curl进行抓取,发现抓取的数据长度始终为0。

于是我尝试通过谷歌浏览器的 view-source:页面URL 确认该页面是否存在数据。

微信截图_20181110160328.png

在图上的代码里发现了这行代码,它的意思大致是 当访问者的浏览器无法执行script时,立即跳转到一个空的页面。

<noscript><meta http-equiv="refresh" content="0; url=/"></noscript>

经过测试,对于这个url使用curl获取时数据为空,而file_get_contents则能正常获取到数据。

$url = 'https://go.smzdm.com/c637e4991bd40490/ca_aa_yh_163_11379359_10054_257_179_0';
$htmlCode = file_get_contents($url);

接下来通过正则表达式获取eval()的值

$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()中的闭包查看下结果。

微信截图_20181114110833.png

可以正常解密,那么现在的问题就是 如何使用PHP去解密这段代码?

一开始走了不少弯路,安装了phantomjs拓展之后发现这个拓展没办法执行js,导致获取到的页面内容也是空(不过这拓展可以进行网页截图还是很厉害的哈哈哈)。

然后看到一个php-v8js的拓展,可以使用php调用v8js引擎执行js代码。但是需要重新编译PHP而且win和Linux下不同的编译流程也太过繁琐了。(万一以后挂了,也很难去找原因)

当拓展没办法实现的时候,就剩下一条路子了 —— 用PHP重写这个js匿名函数,然后传入同样的值进行解密。

ok,先格式下话这个js函数方便我们观看

微信截图_20181114113049.png

首先我们先看传入的参数 p、a、c、k、e、d。

p : 一个乱码字符串
a : 一个数值
c : 一个数值
k : 一个字符串,使用 | 分割成数组
e : 一个数值
d : 一个空对象

看到这里我还是云里雾里,继续看匿名函数

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']的赋值。
// 建议使用二维数组的形式存储
$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源代码 上提供的帮助。

Comments are closed.