一次逆向网页内容加密

一次逆向网页内容加密

以往爬取网页内容复杂点的,一般就是处理下页面内容动态载入,动态载入的内容可能会要求复杂奇怪的参数,或者找到这个动态载入的HTTP接口在哪里麻烦点。但是这次要爬去的网页不同。

类似如下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<td class="omission" width="20%">
<span name="record:shenqingrxm" title="pos||">
<span id="23c0b75102b6428aa7b476376500874b" class="nlkfqirnlfjerldfgzxcyiuro">江苏</span>
<span id="0e6878027a6a4ccf8d65611a2fb27531" class="nlkfqirnlfjerldfgzxcyiuro">先思</span>
<span id="a3aeb4224cf4427e8bd9835a8b7d97a1" class="nlkfqirnlfjerldfgzxcyiuro">江苏</span>
<span id="1493721d631c4151bc06c6ff22d14cd2" class="nlkfqirnlfjerldfgzxcyiuro">先思</span>
<span id="7281a4fc0de04a41ac0a7aaf99313d88" class="nlkfqirnlfjerldfgzxcyiuro">江苏</span>
<span id="f8e5b5fe489f44cfbcbc8f65537cf43a" class="nlkfqirnlfjerldfgzxcyiuro">先思</span>
<span id="5f330b68a24a4e1282fcde2b78a26df3" class="nlkfqirnlfjerldfgzxcyiuro">达生</span>
<span id="fe91da602e864b7f833e2712db254ee5" class="nlkfqirnlfjerldfgzxcyiuro">物科</span>
<span id="dead9499be73496fa689123b9656422c" class="nlkfqirnlfjerldfgzxcyiuro">技有</span>
<span id="eee677751a2d4613bdb89df9ce9f3e23" class="nlkfqirnlfjerldfgzxcyiuro">限公司</span>
</span>
</td>

最终希望得到的内容其实是江苏先思达生物科技有限公司,但是得到的网页确实乱序后的字符串,并且每次刷新得到的乱序还不一样,试过几次也看不出规律。

按照以往的思路,猜测肯定是某个js文件中包含了还原算法,我的目标就是找出这个算法,在爬虫程序中实现这个算法,以还原出可读的字符串。

js中要完成这样的事,首先得找到网页元素,包括:根据外层span name="record:shenqingrxm";根据再外层的table;根据内层span class='nlkfqirnlfjerldfgzxcyiuro'。以前一直想要个工具,可以在某网页载入的所有js文件中搜索特定字符串,从而帮助逆向,但是一直没有这个工具。所以这次也只有人肉看每个js。根据js的名字猜测这个逻辑会放在哪里。

看了几个可能的js文件,在文件中都没有搜索出认为可能的字符串。于是又人肉搜索其他不太可能的js文件,均未果。此时陷入死胡同。

网页文件末尾会有个超长id的span元素,类似:

1
2
3
4
<span style="display: none" id="65356430316133353f6d6c3c38683b3b717222202422247120782d787a2a292a0c184011154414101c4d4c1e1f191d4c06535152560c57045e015f5d045f0e0f236d2670202123257c7d7b2d757e7a2c6935303560626060396f3c686f38383a02024e5055040402510c5c5b5c095d5b4743404a474746121a1d4c1f1f1f1a4db5e0b7afb3

...... 中间省略 ......
491a1e4c504c1b49b4b0e0b1b2bde5e1ebbdb3e8bdb5eceaa7f2f4aaadf1a6f3aaabf9fdf9b1f8a892c5c390c2c090c4c9c89e9fcac9cfcd85d7d4d78cd3d580de80dc8e8dd992dbf9f8f1fbfcf7f6a6f9fff2ffffa9f7aee7b5e2b7e4b0e3e6b9e0ebbdece8b8f3d4d683d5ddd3d7dedbda8f8ad88bdbdb91c2c2c3c3ccc693cbc99c9f9ec4c7c62c34373b65363e346a6d686969393d6b2673247227272e2e2b7f282f2e797a26180d">XXXXXX</span>

这个字符串不像base64加密,看这个网页带了md5js,怀疑跟md5有关,但md5不应该用来加密字符内容,js文件中也未看到可能的API

后来发现乱序的字符串中有些字符是不显示的,通过这个css控制:

1
2
3
4
nlkfqirnlfjerldfgzxcyiuro {
display: none!important;
visibility: hidden!important;
}

网页载入经过js处理后,显示出来的字符看起来是相同的css class : nlkfqirnlfjer1dfgzxcyiuro,开始觉得奇怪,研究了下这个的差异。折腾了好久发现被人戏弄了:nlkfqirnlfjer1dfgzxcyiuro与nlkfqirnlfjerldfgzxcyiuro,前一个是r1d后一个是rld,分别是数字1和字母L

原始网页中所有字符的css class都是不显示的,所以可以推测js中经过一定算法将需要显示的字符改了css class。但是此刻还是没有思路。

后来尝试了chromeDOM breakpoint,可以在DOM元素被改变时断点,但是用起来不是特别好用,没有带来任何帮助。

绝望之际把整个网页另存下来,另存下来的网页是经过js处理后的,手工将css改回原始内容,本地载入网页发现还是可以正常显示,证明处理逻辑真的还在js文件中。然后我逐个删除每一个js文件,还是想找出具体是哪个js文件包含了这个还原算法。

然后发现竟然是jquery-1.7.2.min.js。但我想这不能说明问题,因为作者肯定是通过jQuery来获取元素的,删除jQuery作者的代码不能工作,当然就显示不出来。这时候开始清理html中的js代码,发现所有js代码都被清除完后,网页内容依然可以还原,所以断定还原算法就在jQuery.js中。然而这个文件是min版本的,用sublime texthtml pretty插件重新格式化方便阅读。

但是此刻发现在这个文件中依然搜索不到可能的字符串,例如前面提到的找元素的一些线索,如span css,如span name等等。此时重新通过chromeDOM断点来获取调用堆栈。这次直接断css class会被改变的span元素,竟然发现可行。此时无非是断点,看效果,继续下更精确的断点,最后发现源头:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
b(function() {
b.mix()
});
...
mix: function() {
var b0 = bF("s" + "p" + "a" + "n");
if (b0 && b0[b0.length - 1]) {
var b5 = b0[b0.length - 1].getAttribute("i" + "d");
if (!b5) {
return
}
var b2 = "";
var b4 = 0;
for (var b3 = 0; b3 < b5.length; b3 += 2) {
if (b4 > 255) {
b4 = 0
}
var b1 = parseInt(parseInt(b5.substring(b3, b3 + 2), 16) ^ b4++);
b2 += String.fromCharCode(b1)
}
if (b2) {
// ... 省略

首先看到的是"s" + "p" + "a" + "n",这不就是span!看前面几行代码很快就明白这是在取网页的最后一个span元素,也就是那个包含超长id属性的span元素。此时需要提下,之前也是对这个页尾span元素做过实验,发现必须是span元素且为最后一个元素才能正确还原网页内容,可以推断这个span是多么关键的一个线索。感兴趣的可以把这个网页的jQuery-1.7.2.min.js还原后查看mix函数实现。

翻译过来还原函数非常简单,写一个java版本:

1
2
3
4
5
6
7
8
9
public static String parseSipoIds(String enStr) {
int b4 = 0;
StringBuilder sb = new StringBuilder();
for (int i = 0; i < enStr.length(); i += 2) {
if (b4 > 255) b4 = 0;
int c = Integer.parseInt(enStr.substring(i, i + 2), 16) ^ b4++;
sb.append((char)c);
}
return sb.toString(); }

即这个span元素就是需要显示出来的span元素id集合,以逗号分隔。

作者把还原算法写到jQuery.js里,也真是够够的了😓。