今日有网友抛出一个小说网站,说是爬虫遇到了点障碍,兴趣使然我也去凑了个热闹,结果发现这个网站的反爬也是我平生第一次所见,在此记录下详细的破解过程。
问题
站点链接:传送门
- 我们发现最终呈现在浏览器上的页面内容是有些差异,比如页面上的“地皇元年”,而我们在html返回的数据中只能看到“皇元年”,说到这里我们也很容易想到这可能是一个
CSS反爬
的页面。 - 打开Elements页签查看源代码,果不其然,事实上确实是通过
::before
伪元素来实现的CSS渲染,这也是一种常见的反爬手段。
- 但接下来就发现了事情没有那么简单,那就是竟然无法找到这个伪元素样式定义的源头,通常情况下,我们可以从控制面板看出这些样式是哪里定义的,可能是来自外联CSS,也可能是内联style。然而此处我们却碰壁了,控制面板上只有
<style>
寥寥数字,点击style跳转到了一个空的style链接,同时我们发现页面返回的源码并不包含此style节点。
无法找到样式源头
空的style节点
思路分析
- 猜想查找
在这种情况下我虽然不太清楚怎么实现的,但是第一感觉告诉我这和JS有关,我试着全局搜索如context_kw
(class属性前缀),地
,::
(伪元素前缀),最后终于在页面内的一处JS代码出发现了伪元素相关的字符。
猜想查找的过程 - JS解析
-
JS格式化
为了更好地对代码进行分析,网上找个在线的JS格式化工具进行格式化。
格式化后的代码一览 -
粗略解读JS代码
粗略看过代码之后,我们看到了几个关键词:crypto
,decrypted
,secWords
,words
,createElement
,text/css
。
很快,我们可以猜想这段JS代码必定是与解密相关的,并且操纵了DOM树(这边猜想就是上面那个空的style节点)。
我们发现,这段代码没有闭包,这意味这段代码中的变量都是全局变量,我们直接在console
查看变量的数据,并且可以将其中一些关键代码在console
中重新执查看。
从下图中我们就可看出words就是伪元素中的content
。
-
细读JS代码
1.第一次解密
从_0xa12e
数组下标为0xc(即12)的元素,用AES
解密成字符串之后,用,
分割成数组,最终得到secWords
:
-
["65291", " 30339", " 12289", " 21015", " 20153", " 19967", " 20181", " 26160", " 19982", " 22311", " 26378", " 20101", " 30527", " 8222", " 8219", " 31167", " 22824", " 19977", " 36948", " 27461", " 20009", " 21518", " 19980", " 22319"]
2.第二次解密
以下代码就是将secWords解密为最终的文字并保存到words数组中。
总结起来就是对于secWords的每个元素进行以下操作:
假设输入n,则输出
伪代码
var t = (n+3) % 2 === 0 ? (n+3)-2 : (n+3)-4 ==> n % 2 === 0 ? n - 1 : n + 1
String.fromCharCode(t)
输出示例
secWords[0] => 65291 => 65292(65291+1) => ','(String.fromCharCode('65292'));
secWords[1] => 30339 => 30340(30339+1) => '的'(String.fromCharCode('30340'));
源代码
for (var i = 0x0; i < secWords[_0xea12('0x18')]; i++) {
var _0x5420ee = '3|5|2|4|0|1' [_0xea12('0x17')]('|'),
_0x9ff9d9 = 0x0;
while ( !! []) {
switch (_0x5420ee[_0x9ff9d9++]) {
case '0':
_0x423190 = _0x5796d9(_0x423190);
continue;
case '1':
words[i] = String[_0xea12('0x25')](_0x423190);
continue;
case '2':
var _0x5796d9 = function(_0x490c80) {
var _0x1532b6 = {
'ifLSL': function _0x256992(_0x118bb, _0x36aa09) {
return _0x118bb + _0x36aa09;
}
};
return _0x1532b6[_0xea12('0x26')](_0x490c80, 0x3 * +!(typeof document === _0xea12('0x27')));
};
continue;
case '3':
var _0x423190 = secWords[i];
continue;
case '4':
_0x423190 = _0x3e8e1e(_0x423190);
continue;
case '5':
var _0x3e8e1e = function(_0xd024e1) {
var _0x3e40d1 = {
'mPDrG': function _0x411e6f(_0xa8939, _0x278c20) {
return _0xa8939 % _0x278c20;
},
'DWwdv': function _0x1e0293(_0x5b15eb, _0x443876) {
return _0x5b15eb - _0x443876;
}
};
return _0x3e40d1[_0xea12('0x28')](_0xd024e1, 0x2) ? _0x3e40d1[_0xea12('0x29')](_0xd024e1, 0x2) : _0xd024e1 - 0x4;
};
continue;
}
break;
}
}
3.设置style
这一步就是样式设置的关键所在了,将根据上一步得到的words
数组添加到styleSheets
中(就是这一步导致了控制板面板上无法看到样式定义的源头)
源代码
for (var i = 0x0; i < words[_0xea12('0x18')]; i++) {
try {
//document.styleSheets[0].addRule('.context_kw'+i + '::before', 'content:\x20\x22' + words[i] + '\x22')
document[_0xea12('0x2a')][0x0][_0xea12('0x2b')]('.context_kw' + i + _0xea12('0x2c'), 'content:\x20\x22' + words[i] + '\x22');
} catch(_0x527f83) {
document['styleSheets'][0x0]['insertRule'](_0xea12('0x2d') + i + _0xea12('0x2e') + words[i] + '\x22}', document[_0xea12('0x2a')][0x0][_0xea12('0x2f')][_0xea12('0x18')]);
}
}
爬虫思路
换了几个页面之后,我发现这个words
列表并不是固定不变的,因此我们得寻找到一种方法得到words
数组。
- 遍历
<Script>
节点,获取其中包含CryptoJS
文本的节点 - 截取
var words =
之前的代码,用JS引擎执行得到secWords
- 用相应的爬虫语言实现
secWords
到words
的转变(上述思路分析已详细讲解) - 之后就变成了普通的
css伪元素反爬
的处理(这一部分不是这篇文章的重点,不再展开)
PC站点会更加复杂点,需要先通过ajax加载文章内容,然后解密文章文本,余下步骤与手机版一致。
网友评论