爬虫中,PHP 运算 JavaScript 混淆表达式的结果

问题

最近写爬虫遇到下面这样的一个东西

<script type="text/javascript">
$(function(){       
    $(document).ready(function(){       
        var fid = $.sha1(String(((!![]+!![])*!![]))+((!![]+!![]+!![]-![]-![]+![]+[])*(!![]-![]-![]+!![]+!![])));
        $.ajax({
            // 获取完整数据
        });
    }); 
}); 
</script>

因为原站页面正文的数据是被裁剪过的,访问完网页后,它通过 JavaScript 获取完整数据后替换掉不完整的内容。

那串代码是个啥子

String(((!![]+!![])*!![]))+((!![]+!![]+!![]-![]-![]+![]+[])*(!![]-![]-![]+!![]+!![])) 放到 Chrome 的 Console 上执行,结果是 29

看了几个页面,都是 String(...)+(...) 的结构,这样运算的结果都是字符串。其中,它的右边有时是可以用 eval 运算的,也时是 JavaScript 拼接,eval 运算不了,这种情况需要进行替换。

如何解决

本来想用 V8js,但是安装一遍没弄好。Chrome 无头浏览器对这个也不行,想着这个东西蛮好玩的,就自己写代码去运算,主要是用到了迭代。

弄了四个示例,结果分别为

29
35
09
79

以下代码复制直接运行

<?php
    $test = [
        'var fid = $.sha1(String(((!![]+!![])*!![]))+((!![]+!![]+!![]-![]-![]+![]+[])*(!![]-![]-![]+!![]+!![])));',
        'var fid = $.sha1(String(((!![]+!![]+!![]+![]+![])+(!![]+!![]-!![]-!![])))+(((![]+!![])+(!![]-!+[]+[]))/(!+[]-![]+!![]-!+[]+!![]-[]-![]-[])));',
        'var fid = $.sha1(String(((!+[]+!![]-![])-(![]-![]+!![]+!![])))+((!![]+!![]+!![]-![]-![]+![]+[])*(!![]-![]-![]+!![]+!![])));',
        'var fid = $.sha1(String((((!![]+!![]-!![]+![]+[])+(+!![]+!![]+!![]+!![]))/(!+[]+!![]+![])))+((!![]+!![]+!![]-![]-![]+![]+[])*(!![]-![]-![]+!![]+!![])));'
    ];

    foreach($test as $v){
        echo test($v) . PHP_EOL;
    }

    function test($string){
        // HTML 源码里面的 JavaScript 代码片段
        if (preg_match('/String(.*)\)\;/', $string, $match)) {
            $string = $match[1]; // 去掉 String
        }

        // 拆分 「String(...)+(...)」
        $pos = getPos($string, 0); // String( 的 ( 位置
        $a = substr($string, 0, $pos + 1); // (...)
        $b = substr($string, $pos + 2); // (...)

        // 删掉「String()」的「()」
        $a = substr($a, 1, -1);

        // String(...) 的结果
        resolve($a); // 解析器
        cal($a); // 解析器迭代完后,计算被拆分表达式左右两边的结果
        $a = eval("return $a[0]$a[2]$a[1];"); // 两边表达式运算

        // (...) 的结果
        resolve($b);
        cal($b);
        $b = eval("return $b[0]$b[2]$b[1];");

        return $number = $a . $b;
    }

    /**
     * 转换为字符串并运算结果
     * 「!![]」 转换为 「1」等等,然后加减乘除
     *
     * @param string $string JavaScript 表达式
     * @return int
     */
    function toNumber($string)
    {
        $string = unserialize($string);
        $string = str_replace('!![]', 1, $string);
        $string = str_replace('!-[]', 1, $string);
        $string = str_replace('!+[]', 1, $string);
        $string = str_replace('![]', 0, $string);
        $string = str_replace('+[]', null, $string);
        $string = str_replace('-[]', '-0', $string);

        return eval("return {$string};");
    }

    /**
     * 获取「()」的最后位置
     *
     * @param string $string JavaScript 表达式
     * @param int $sub 拆分表达式的话,需要减 1;不拆分不用。
     * @return int
     */
    function getPos($string, $sub = 1)
    {
        $pos = null;

        $array = str_split($string);
        if ($array[1] !== '(') { // 拆分到开头只剩下一个 「(」 不再拆分
            return null;
        }

        // 开头连续几个「(」
        $total = charContinuousCount($array, '(');

        // 几个「(」需要几个「)」来匹配,如果是拆分表达式,最外层右边的「)」的不用匹配,所以减 1
        $remaining = $total - $sub;
        foreach ($array as $key => $v) {
            // 从不是「(」的开始算
            if ($key < $total) {
                continue;
            }

            if ($v === ')') {
                $remaining--;
                if ($remaining === 0) {
                    $pos = $key;
                    break;
                }
            } elseif ($v === '(') {
                $remaining++;
            }
        }

        return $pos;
    }

    /**
     * 字符连续几个
     *
     * @param $array
     * @param string $char 需要判定的字符
     * @return int
     */
    function charContinuousCount($array, $char)
    {
        $total = 0;
        foreach ($array as $v) {
            if ($v === $char) {
                $total++;
            } else {
                break;
            }
        }

        return $total;
    }

    /**
     * 表达式解析
     * 遇到字符串拼接的才会拆分,不是的话,直接 eval() 计算结果
     * 拆分的话,替换字符串为数组存储起来
     *
     * @param string $array 表达式,比如「(!![]+![])」
     */
    function resolve(&$array)
    {
        // 第一次
        if (!is_array($array)) {
            $pos = getPos($array);
            $array = array(
                substr($array, 1, $pos),
                substr($array, $pos + 2, -1),
                $array[$pos + 1]
            );
            resolve($array);
        } else {
            if (strpos($array[0], ']+[]') !== false) {
                $pos = getPos($array[0]);
                if ($pos) {
                    $array[0] = array(
                        substr($array[0], 1, $pos),
                        substr($array[0], $pos + 2, -1),
                        $array[0][$pos + 1]
                    );
                    resolve($array[0]); // 迭代
                }
            }

            if (strpos($array[1], ']+[]') !== false) {
                $pos = getPos($array[1]);
                if ($pos) {
                    $array[1] = array(
                        substr($array[1], 1, $pos),
                        substr($array[1], $pos + 2, -1),
                        $array[1][$pos + 1]
                    );
                    resolve($array[1]); // 迭代
                }
            }
        }

    }

    /**
     * 加减乘除、拼接
     * 计算完,替换数组为字符串
     *
     * @param $array array 表达式解析完后的数组
     */
    function cal(&$array)
    {
        if (is_array($array)) {
            foreach ($array as $key => &$v) {
                cal($v);
                if (is_array($v)) {
                    $a = toNumber(serialize($v[0]));
                    $b = toNumber(serialize($v[1]));
                    $array[0] = $a . $b;
                }
            }
        } else {
            if (!in_array($array, ['/', '*', '-', '+'])) {
                $array = toNumber(serialize($array));
            }
        }
    }
?>

一个生活中实际小应用代码,可能 GitHub 早有代码,如果有知道的话,可以分享下。或者可以用十几行代码解决的,也请指教下。

无论在现实或是网络中,我都是孤独的。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 1

这玩意是这样来的: https://jscrew.it

推荐还是弄个专业的 parser 来做这活(各种 chromeDriver 之类的)

4年前 评论
小李世界 (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!