欢迎光临!这里是页首

zqdlly的博客(博客标题)

zqdlly的博客(博客子标题)

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

警告:该随笔内容仅用于合法范围下的学习,不得用于任何商业和非法用途,不得未经授权转载,否则后果自负。

如果您是我的老读者,我相信您一定知道我在之前的博客里提到过一个顽固的反调试网页:北京空气质量指数月统计历史数据。很高兴的是,今天经过了一番艰苦奋斗,我终于成功突开了敌人的反调试,基于强大的Tampermonkey扩展(太强大了,比如控制我的代码注入时机,注入晚了就没戏了),火狐浏览器,和无数的先辈的辛勤劳动成果,下面我就稍微渲染点东西出来给你看。

先说战果:今夜,我可以正常的随意打开关上控制台,正常的随意使用右键功能,此外也不会因为被监控到打开控制台造成网页重写。其实最重要的还是因为我把反调试代码提前干掉了,没让他执行成功。

首先说说这网页的加密机制。主要是由两个部分组成,一是某混淆的外联js(外联、外链在我这而同义),由script标签的src引入;另一是某混淆的内联js,直接放在网页的html的script标签里。此外还有一些其他script标签,但不是重点(我把全部的script标签按照他们的载入顺序放在下面,您可以看下),就先忽略不计了。

<script type="text/javascript" src="resource/js/jquery.min.js?v=1.11"></script>
<script type="text/javascript" src="resource/js/derwRJj1CS3u9.min.js?t=1631800501"></script>
<script type="text/javascript" src="resource/highcharts/highcharts.js"></script>
<script type="text/javascript" src="resource/highcharts/highcharts-more.js"></script>
<script type="text/javascript">//这就是内联js
eval(dqc5knBaEu(密文1此处略去一堆字));
eval(dEJbA3QSYFg(密文2此处略去一堆字));
</script>

网页如何加密?它先执行外链引入的混淆js文件(也有eval),然后得到混淆后的工具函数名(见到eval括号里面的乱码函数名字了吗?)。然后再执行内联js,此时内联js里分为密文1和密文2,密文1负责反调试(就是你在控制台看到的那个,有["constructor"]("debugger")这些东西的那段无头代码),密文2则是负责传递需要的爬虫目标信息。此外,这个网页的强大之处是,每隔10分钟就更新一次密文,而且我已经观测到了不同的加密强度(本质是一样,只是代码长短不同,我已经看到外链js有4kb的,10kb的和13kb这三种大小的)。我也不懂它到底是图什么,有必要整天开着服务器,反复的加密那段固定的信息吗?好无聊啊。我更不能理解的是,虽然网站反调试,但连按两下f12不就打开控制台了吗,这到底是图啥啊,又不能阻止我打开控制台。

先介绍一下阶段一的方法。阶段一的特点是根基不稳,或者说这个方法是靠侥幸取胜的。后面分享一下我的部分js代码

function 提取eval内容数组(str){//把多个eval语句的参数提取,存入数组
    let eval_array = str.split("eval(");
    let result_array = [];
    eval_array.forEach(v=>{
        result_array.push(v.replace(");",""));
    });
    //result_array = result_array.filter(v=>{return v})
    return result_array
}

以上是我用到了的1个api,其作用是把原网页的内联script函数(含有eval)分解,然后分别存入数组以供我亲自调用。此外我用到的还有我的历史悠久的函数包(我一直在亲自使用的库)

window.oldEval = window.eval;//先保存老eval,我不能让eval为敌人所用
window.domObserver = new MutationObserver((mutations) => {
    mutations.forEach(function(mutation){//对于监听到的所有类型的改变
        if (mutation.addedNodes[0]){
            const lister=mutation.addedNodes[0];
            if(lister.tagName==("SCRIPT")&&lister.src.indexOf('de')!=-1){//如果监听到引入含有密钥的外链js的dom节点的出现
                window.de=lister;//方便后面使用这个外链节点
            }
            //脆弱点1:如何控制时机?这也是我为什么说阶段一根基不稳的的原因所在。
            if(lister.tagName==("SCRIPT")&&lister.src.indexOf('highcharts-more')!=-1){//监听到最后一个外链js载入(不是执行),此时尚未载入后面的内联js。我这么做的前提是,假设在此时前面的密文外链js已经执行。但是我现在所做的一切并不能保证是这样的。
                domObserver.disconnect();//监听器失联
                const dom=document.createElement('script');
                dom.text=
                    `//alert(1);//这一行是调试用的
                        window.eval = function() {};//**核心代码!**使eval无效化,这一步必须在内联js执行前,必须阻止它调用eval。要先去除其中有害成分,才能为我所用
                        `;
                紧跟其后追加元素(dom,lister)//调用了我的宝库的函数
                window.onload=function(){//等页面所有script标签都有了
                    //window.tmp=lister;
                    window.str=document.scripts[document.getElementsByTagName('script').length-1].innerHTML;
                    window.result_array = 提取eval内容数组(str);
                    //cl(result_array[result_array.length-1])
                    oldEval(oldEval(result_array[result_array.length-1]))//执行内联eval,这时我把反调试的鱼刺剔除了,只留下了鱼肉。然后调用老eval执行,就能解析出爬虫目标信息了!
                }
                cl(seconds2date(de.src.match(/\=[\d]+/).toString().replace(/\=/,'')))//这里,是为了方便调试,我让它在控制台输出每次外链js重新加密的时刻。是不是很变态?
            }
        }
    })
});
window.domObserver.observe(document.body, {//启动监听器
    childList: true
})

我和你说一下我的大体思路吧。我希望置空eval,从而阻断反调试。但也不能赶尽杀绝,有的代码只能靠eval执行。于是我就先让外链js执行了eval,释放出工具函数(用以解密内联js),然后迅速使eval无效化以阻断内联js的执行。然后我剔除内联js里的反调试鱼刺,再用我留下的备份eval执行鱼肉部分的密文,然后载入爬虫目标信息。

阶段一的脆弱性我已经说了,下面我说下阶段二,的重大改进地方。

if(lister.tagName==("SCRIPT")&&lister.src.indexOf('de')!=-1){
    domObserver.disconnect();
    window.de=lister;
    lister.onload=function(){
        window.eval = function() {};
        window.onload=function(){
            window.str=document.scripts[document.getElementsByTagName('script').length-1].innerHTML;
            window.result_array = 提取eval内容数组(str);
            oldEval(oldEval(result_array[result_array.length-1]))//执行内联eval
        }
    }
}

我精简了监听器,只监听外链js节点。监听到它时,我赋予它一个onload事件,该事件足以确保它已执行(没错,我就是希望除了它有权使用eval,其他的都一边玩去),然后释放出必须的解密工具函数(看来监听到节点载入是一回事,onload和执行又是另一回事,我现在已知的是,给一个script标签设置onload,就相当于等它执行后再触发该onload事件)。然后我又设置了window的onload事件为,执行eval剔除鱼刺的密文,最后成功解密。

补充说明:对于<script type="text/javascript"></script>,如果没有提到defer,async,默认是同步sync的。

同步意味着解析遇到script标签时,优先加载它的内容,然后执行它(执行时停止解析html),然后再继续解析后面的html,这也是我的阶段二站得稳的依据;
defer(仅适用于外部脚本)意味着解析遇到script标签时,加载它的内容,同时继续解析后面的html,解析完html后,再执行该js;
async(仅适用于外部脚本)和同步唯一的区别是,前者在加载script时,并不停止解析后面的html。但当此script加载完成时就立即执行(执行时停止解析html),然后再继续解析后面的html。

此外,还有一个常识,一般执行script标签的顺序,都是先执行外链js,然后执行内联js。比如大名鼎鼎的jQuery,你见过有人把jQuery放在script里面内联执行吗?拿来直接用的好吗,谁等你的

posted on 2021-09-16 23:02  zqdlly  阅读(8)  评论(0编辑  收藏  举报

欢迎光临!这里是页脚

联系方式:zqdlly@163.com 非诚勿扰