爬虫中,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 早有代码,如果有知道的话,可以分享下。或者可以用十几行代码解决的,也请指教下。
这玩意是这样来的: https://jscrew.it
推荐还是弄个专业的 parser 来做这活(各种 chromeDriver 之类的)