AST反混淆实战:猿人学爬虫比赛第二题详细题解

缘起

应星友要求,写下此文,哎,有钱能使鬼推磨。

实战地址:

http://match.yuanrenxue.com/match/2

  

抓包分析

由于谷歌浏览器某些请求不会显示,建议使用火狐浏览器来抓包分析。

这是一个典型的cookie反爬,最后一个红框处是数据接口。如果不看题目,有经验的人一眼就能看出来。

第一次请求,返回的是一段js代码:

第二次请求的是同一地址,这时候带上了cookie:

根据经验,第二次请求的cookie,肯定是第一次请求计算的,因为第一次请求结果中并没有直接设置cookie。

注意记录此时的 cookie 值:

m : "9c8b2ba4362ff5a7023d8db7041dcd04|1603517539000"

  

|后面明显是个时间戳

AST化简代码,静态分析

将第一次请求的js代码保存为encode.js,去掉 script标签。

保存后,使用一键解ob混淆工具还原代码,命令:

node decode_obfuscator.js encode.js

  

打开保存后的结果 decode_result.js,代码很短,一下就看到了 cookie设置的地方:

是在 _0x721154 这个函数里设置的,后面直接就调用了,实参是 函数 _0x5015e3 ,它的返回值是一个 时间戳,也就是 |后面的值

把 _0x721154 这个函数改造一下,去掉DOM相关的操作,让它直接返回结果:


   
  1. function _0x721154(_0x1243f6, _0x167ea5) {
  2. return "m" + _0x3445fe() + "=" + _0x531a93(_0x1243f6) + "|" + _0x1243f6 + "; path=/";
  3. }

现在实参明确了,返回结果也明确了,先来看 _0x3445fe 这个函数

去除垃圾代码

_0x3445fe 这个函数 定义如下:


   
  1. function _0x3445fe(_0x22e65, _0x533ac1) {
  2. var _0x537cb8 = 0;
  3. var _0x385b2c = _0x2d77f8(this, function () {
  4. var _0x245ea0 = function () {
  5. var _0x56862a = _0x245ea0["constructor"]("return /\" + this + \"/")()["compile"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
  6. return !_0x56862a["test"](_0x385b2c);
  7. };
  8. return _0x245ea0();
  9. });
  10. _0x385b2c();
  11. _0x449be1();
  12. qz = [10, 99, 111, 110, 115, 111, 108, 101, 32, 61, 32, 110, 101, 119, 32, 79, 98, 106, 101, 99, 116, 40, 41, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 115, 41, 32, 123, 10, 32, 32, 32, 32, 119, 104, 105, 108, 101, 32, 40, 49, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 40, 105, 61, 48, 59, 105, 60, 49, 49, 48, 48, 48, 48, 48, 59, 105, 43, 43, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 104, 105, 115, 116, 111, 114, 121, 46, 112, 117, 115, 104, 83, 116, 97, 116, 101, 40, 48, 44, 48, 44, 105, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 10, 10, 125, 10, 99, 111, 110, 115, 111, 108, 101, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93, 39, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 402, 32, 116, 111, 83, 116, 114, 105, 110, 103, 40, 41, 32, 123, 32, 91, 110, 97, 116, 105, 118, 101, 32, 99, 111, 100, 101, 93, 32, 125, 39, 10];
  13. eval(_0x19be63(qz));
  14. try {
  15. if (global) {
  16. console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
  17. } else {
  18. while (1) {
  19. console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
  20. debugger;
  21. }
  22. }
  23. } catch (_0x1d08b1) {
  24. return navigator["vendorSub"];
  25. }
  26. }

来看 _0x385b2c 这个函数:


   
  1. var _0x385b2c = _0x2d77f8(this, function () {
  2. var _0x245ea0 = function () {
  3. var _0x56862a = _0x245ea0["constructor"]("return /\" + this + \"/")()["compile"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
  4. return !_0x56862a["test"](_0x385b2c);
  5. };
  6. return _0x245ea0();
  7. });
  8. _0x385b2c();

这个函数在声明之后马上就执行了,没有实参,虽然有返回值,但是并没有赋值给其他变量。 再看函数体,没有对任何全局变量进行操作,因此,可以直接进行删除!

按照这个思路,后面的 _0x449be1(); 调用同样可以删除,没啥用。

再看 try语句:


   
  1. try {
  2. if (global) {
  3. console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
  4. } else {
  5. while (1) {
  6. console["log"]("\u4EBA\u751F\u82E6\u77ED\uFF0C\u4F55\u5FC5python\uFF1F");
  7. debugger;
  8. }
  9. }
  10. } catch (_0x1d08b1) {
  11. return navigator["vendorSub"];
  12. }

浏览器环境没有 global 这个对象,那就会执行 catch语句,返回了:

navigator["vendorSub"]

  

这个值,直接在控制台运行得出它是一个空值。因此,直接替换成这样就好:

return "";

  

这样就清爽多了:


   
  1. function _0x3445fe(_0x22e65, _0x533ac1) {
  2.     var _0x537cb8 = 0;
  3. qz = [10, 99, 111, 110, 115, 111, 108, 101, 32, 61, 32, 110, 101, 119, 32, 79, 98, 106, 101, 99, 116, 40, 41, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 115, 41, 32, 123, 10, 32, 32, 32, 32, 119, 104, 105, 108, 101, 32, 40, 49, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 40, 105, 61, 48, 59, 105, 60, 49, 49, 48, 48, 48, 48, 48, 59, 105, 43, 43, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 104, 105, 115, 116, 111, 114, 121, 46, 112, 117, 115, 104, 83, 116, 97, 116, 101, 40, 48, 44, 48, 44, 105, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 10, 10, 125, 10, 99, 111, 110, 115, 111, 108, 101, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93, 39, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 402, 32, 116, 111, 83, 116, 114, 105, 110, 103, 40, 41, 32, 123, 32, 91, 110, 97, 116, 105, 118, 101, 32, 99, 111, 100, 101, 93, 32, 125, 39, 10];
  4. eval(_0x19be63(qz));
  5. return "";
  6. }

这个函数后面是一个 定时器语句,因为后面有调用 _0x3445fe 这个函数,因此这行调用也可以删除:

setInterval(_0x3445fe(), 500);

  

定你妹呀!

运行得到结果

好了,经过上面的操作,似乎可以运行了。注意加打印并传入实参,方便比对结果:

console.log(_0x721154("1603517539000"));

  

将上面的代码添加到自执行函数的最好一行,运行报错:

报错位置在 _0x3445fe 函数的 下面代码处:

eval(_0x19be63(qz));

  

有个history,估计也是dom相关的,先屏蔽掉,再运行:

不报错了,比对之前的cookie值,一模一样。

这就出结果了,简直不要太简单。完!

文章来源: blog.csdn.net,作者:悦来客栈的老板,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/qq523176585/article/details/109507934

(完)