记一次anti anti debug

去年在微博上看到某前端大佬提供的一种检测用户是否打开控制台的方式,后面自己也去探索了一种方式,同时也发现在StackOverflow上有关于这个话题的讨论。不过这些方式后面都失效了。

今天偶然打开了一个视频网站,好奇按了下F12,发现浏览器彻底卡死了,Chrome自带的控制台都无法被打开了,心中瞬间冒出两个疑点:
1. 他是通过什么新的方式知道我打开了控制台
2. 他是通过什么方式让我的浏览器挂掉的,chrome不是多进程的吗?我们代码里面出现死循环都只是这个标签死掉,完全可以打开Chrome的进程工具结束掉这个标签的进程

在不停重启浏览器,再打开这个网站开启控制台的过程中发现了问题出在地址栏,一旦我打开控制台,地址栏便由a.com变成了a.com/0123456789101112....然后浏览器就挂掉了。

那第二个疑惑解除了:原来Chrome的网址不停增长会导致浏览器主进程挂掉。

不刷新而改变了地址栏,那应该是用了history.pushState

先实验一把,打开浏览器输入网址,不着急打开控制台,直接在地址栏输入:javascript:history.pushState = function(){}回车,以覆盖pushState实现,然后打开控制台,果然没有再卡死。

然后在其引入的js代码中搜pushState关键字,找到关键代码:

eval(function(e, t, n, r, o, i) {
        if (o = function(e) {
            return e.toString(20)
        }
        ,
        !"".replace(/^/, String)) {
            for (; n--; )
                i[o(n)] = r[n] || o(n);
            r = [function(e) {
                return i[e]
            }
            ],
            o = function() {
                return "\\w+"
            }
            ,
            n = 1
        }
        for (; n--; )
            r[n] && (e = e.replace(new RegExp("\\b" + o(n) + "\\b","g"), r[n]));
        return e
    }("1 2=c.3('8');4.b(2,'5',{6:7(){1 a=\"\";9(1 i=0;i<d;i++){a=a+i.e();f.g(0,0,a)}}});h.j(2);", 0, 20, " var x createElement Object id get function div for  defineProperty document 1000000 toString history pushState console  log".split(" "), 0, {}))

改eval为console.log,我们即可得到源码:

var x = document.createElement('div');
Object.defineProperty(x, 'id', {
  get: function() {
    var a = "";
    for (var i = 0; i < 1000000; i++) {
      a = a + i.toString();
      history.pushState(0, 0, a)
    }
  }
});
console.log(x);

看来检查控制台打开的原理还和原来失效的new Image差不多,控制台打开后,会自动触发一些通过console输出的标签的getter,然后在getter中就可以为所欲为了。

至此两点疑虑解开。

不过,他都这么做了,完全可以防得更彻底:

// 页面一加载就先备份
var p = history.pushState.bind(history)
// 检测到控制台被打开再做地址栏填充
 var a = "";
for (var i = 0; i < 1000000; i++) {
    a = a + i.toString();
    p(0, 0, a)
}

这样,我最开始通过地址栏直接覆盖原生API验证想法的路子就行不通了。

不过,这还是太小儿科了,太简单粗暴的告知调试者:老子知道你在调试我的网页了,我现在要原地爆炸。

而猥琐流的做法应该是:发现后偷偷打下标记,然后埋雷,在一些本来应该走a分支的地方走到b分支,一些关键的中间数据也故意做处理,时不时来个大的循环卡几秒钟….

当然,这些方式都不是我创的,参考EtherDream大佬的前端加密与混淆 ,当时看完一阵感慨:原来反调试的套路原来这么多。