JS逆向系列14-Bypass Debugger

admin 2024年12月9日13:24:43评论13 views字数 5852阅读19分30秒阅读模式

0x00 前言

本文将让读者摆脱无限debugger的困扰。

注:

1.本文将涉及反hook与反反hook。2.本文掠过的地方都是前面文章已讲过的,读者可自行查阅。3.在文中如果提到的代码有涉及到正则匹配的地方,笔者不会去细讲,望谅解。4.本文将放出三个我个人写的hook脚本,分别是:Hook_eval、Hook_Function、Bypass_Debugger,这三个脚本都是为了bypass掉debugger而所写的,具体细节看下文。

0x01 Fix bug

关于我之前写的bypass_debugger hook脚本,我在近期多次测试中发现了一个诡异的bug,也不能说是bug,是我当时写这个hook脚本时没考虑到网站会这样获取原型,虽然我仅用了一行代码就解决了,但我还是想和读者分享一下,直接上图:

JS逆向系列14-Bypass Debugger

这是我用bypass_debugger02脚本hook某站时出现的报错,直接跟一下报错位置:

JS逆向系列14-Bypass Debugger

图示标明处即为报错行,很明显代码混淆了,并且可以清楚地看到访问了_0x4fbc14的constructor属性,我已经大概能猜到_0x4fbc14就是某个实例Funcition了,复制到console里输出一下看看:

JS逆向系列14-Bypass Debugger

确实是个函数,所以_0x4fbc14['constructo' + 'r']就是获取了下Function构造函数:

JS逆向系列14-Bypass Debugger

注意此时我还是处在hook后的环境下,所以打印的是我重写后的内容。继续看一下它后面的[_0x2f807b(0x94f, 0x6f5, 0x78a, 'IS@C')]是什么:

JS逆向系列14-Bypass Debugger
JS逆向系列14-Bypass Debugger

也就是说代码又获取了Function构造函数的原型,再看一下它后面是什么:

JS逆向系列14-Bypass Debugger
JS逆向系列14-Bypass Debugger

至此已经很明显了,代码获取了在Function原型上的构造函数的原型上的bind方法,有点拗口,方便读者理解我将这段混淆代码转换成人话:

newFunction().constructor.prototype.bind()

再简单一些:

Function.constructor.prototype.bind()

而报错原因极有可能和我重写了Function原型上的constructor属性有关:

JS逆向系列14-Bypass Debugger

我重写了constructor属性就导致其又有了一个新的原型:

JS逆向系列14-Bypass Debugger

正常来说Function的constructor属性的原型就是Function的原型:

Function.prototype == Function.prototype.constructor.prototype
JS逆向系列14-Bypass Debugger

所以我们就需要给这个重写后的constructor属性赋上Function的原型:

Function.prototype.constructor.prototype = Function.prototype;

这样就解决了问题,再hook一下并断点看看能不能获取到:

JS逆向系列14-Bypass Debugger

成功获取到,断点取消后发现console里也没有报错,成功解决,但是上文也仅仅只是本文的一个开始。

0x02 Hook eval

不知道有没有读者还记得我在反调试与反反调试一文的最后提到过一个案例:

JS逆向系列14-Bypass Debugger
JS逆向系列14-Bypass Debugger

这段js是通过eval执行的,这也是很多网站反调试的一种方法,我先给大家看一下eval的用法:

JS逆向系列14-Bypass Debugger
JS逆向系列14-Bypass Debugger

用法十分简单,调用时填上js代码即可,例如:

eval("console.log(1)")
JS逆向系列14-Bypass Debugger

这就很好玩了,我直接上代码:

JS逆向系列14-Bypass Debugger

我认为这段代码的每一处都值得讲,所以接下来我一步一步来解释这串代码都干了些什么。

开头我先将eval和toString方法备份到了两个临时变量:

var temp_eval = eval;var temp_toString = Function.prototype.toString;

我这样做的意图很明显,如果有不懂的朋友可以看一下我前面写的Js Hook一文。toString方法我打算留到最后再讲,因为这涉及到了反hook的内容。代码中最重要的部分就是重写eval的那段代码:

window.eval = function () {if (typeofarguments[0] == "string") {var temp_length = arguments[0].match(/debugger/g);if (temp_length != null) {                temp_length = temp_length.length;var reg = /debugger/;while (temp_length) {arguments[0] = arguments[0].replace(reg, "");                    temp_length--;                }            }        }returntemp_eval(...arguments);    }

首先我判断了一下传参的值类型是否为字符串(这里大家不用关心如果调用没有传值的情况,因为调用eval时如果没有传值,typeof arguments[0]获取的值就是undefined),我这里判断传参的值必须得是字符串类型的原因是下面的代码要用到字符串的match方法,下文会讲到这个方法。接下来如果是字符串类型就会进入if语句,然后执行这段代码:

var temp_length = arguments[0].match(/debugger/g);

我对传进来的字符串调用了它的match方法:

JS逆向系列14-Bypass Debugger

简单来说就是这个match方法可以用来做正则匹配,arguments[0].match(/debugger/g)这段代码实际上就是用来匹配字符串中存在几个debugger,后续便于我们去将这些debugger替换成空字符串,先给大家看一下其效果:

temp_str = "debugger1231fasdf311413412312sfdebugger/";console.log(temp_str.match(/debugger/g))
JS逆向系列14-Bypass Debugger

通过调用match(/debugger/g)方法成功匹配到两个debugger,也就是数组里的内容。字符串中存在几个debugger字符串就会放进数组里,这就代表返回的数组里有多少元素就代表字符串中存在多少个指定字符,比如我的字符串中就存在两个debugger字符串,如果字符串中没有debugger返回的就是null:

JS逆向系列14-Bypass Debugger

所以接下来我做了一个判断:

if (temp_length != null) {        temp_length = temp_length.length;var reg = /debugger/;while (temp_length) {arguments[0] = arguments[0].replace(reg, "");            temp_length--;        }    }

如果正则匹配后返回的是null,则代表代码给eval传的字符串中不存在debugger,如果存在则反之。进入if语句后,我获取了数组的长度,也就是获取了字符串中的debugger数量,随后就是这段代码:

var reg = /debugger/;while (temp_length) {arguments[0] = arguments[0].replace(reg, "");        temp_length--;    }

我写成这样是由于arguments[0] = arguments[0].replace(reg, "");只能替换一个目标字符,这就是我获取字符串中存在的debugger数量的原因,譬如:

JS逆向系列14-Bypass Debugger

我将循环去掉后,可以清楚的看到只清除了字符串中第一个debugger,最后一个就没有被替换掉,所以我写成了循环去替换掉debugger,这就是我重写eval的所有代码,但是还有一处我还没有讲,那就是上文提到的toString:

var temp_toString = Function.prototype.toString;Function.prototype.toString = function () {if (this === eval) {return'function eval() { [native code] }';        }return temp_toString.apply(this, arguments);    }

其实除这段代码之外已经完成了我们想要的功能,但是这段代码也是十分重要,我先给大家看一个案例,大家就能明白我为什么要重写toString方法了:

JS逆向系列14-Bypass Debugger

我将重写toString的那段代码删掉了,hook一下有反调试的那个网站:

JS逆向系列14-Bypass Debugger

很明显报了很多异常,我就挑一个进去看看:

JS逆向系列14-Bypass Debugger

又是一个混淆的,我们打个断点看一下这段代码:

JS逆向系列14-Bypass Debugger

第一个是location,我们继续看:

JS逆向系列14-Bypass Debugger

现在我脸上已经有了一个大大的问号了,我们继续看:

JS逆向系列14-Bypass Debugger
JS逆向系列14-Bypass Debugger

也就是说代码想让eval执行这段反调试代码,这段反混淆翻译成人话就是这样的:

location['eval']('(function() {var a = new Date(); debugger; return new Date() - a > 100;}())')

这段代码没人能看懂它在干什么,因为这段代码什么都不是!eval是全局对象的一个函数属性,而代码却获取location对象的eval属性,这算什么?这什么都不是,因为location对象上压根就没有eval这个属性,就算是它原型链上也没有,那为什么代码要这么做?显然是故意的,这就不得不提到一个概念:暗桩,我也是看了其他师傅的文章才了解到的这个概念,也就是说如果网站发现用户进行了hook或调试后,就会给出错误逻辑,这就是反hook的一种套路,我在查阅相关资料后,了解到了上文中提到的toString方法:

JS逆向系列14-Bypass Debugger

这个方法在Function的原型上,所有函数都能用到这个方法,在Object原型上也有一个toString方法,大家可自行查阅,本人这里只重点介绍Function原型上的toString方法。这个方法使用很简单,不需要传什么参数,我这里写了个demo给大家看一下其效果:

functiontest(){console.log(1);}console.log(test.toString());
JS逆向系列14-Bypass Debugger

也就是说这个方法会返回函数源代码的字符串,那么用eval调用toString方法会返回什么呢?我这里给大家演示一下:

JS逆向系列14-Bypass Debugger

但是如果我重写了效果就不一样了:

JS逆向系列14-Bypass Debugger

显然重写后效果大变,这就是网站反hook的一种方法,如果发现执行eval.toString()后的结果不是function eval() { [native code] },那么就会给出错误逻辑,所以我在代码中重写了toString方法,如果发现调用的对象是eval,那么就返回function eval() { [native code] },从而防止网站给出错误逻辑,以上就是我这段hook eval的全部代码逻辑。

0x03 Update Script

在写完hook eval的脚本后,有一刹那我突然想我为什么我不顺手优化一下hook Function的那个脚本,因为我那个通过hook Function来bypass debugger的脚本只能防止诸如以下这种情况:

JS逆向系列14-Bypass Debugger

JS逆向系列14-Bypass Debugger也就是说这段hook脚本只能bypass实例Function只传了一个debugger的情况,但是如果是以下这种情况就不行了:

JS逆向系列14-Bypass Debugger

因为我代码是这么写的:

JS逆向系列14-Bypass Debugger

所以如果我把重写eval的那段代码思路也放到重写Function的这段代码来,那么效果将大大增强,直接上优化后的代码:

JS逆向系列14-Bypass Debugger

这段代码能直接bypass掉所有通过实例Function的debugger,效果我暂时先不演示,因为我在改善完我这段代码后就在想,我为什么不把这两段代码整合起来,一般网站要么就是通过eval来进行反调试的,要么就是通过实例Function来进行反调试的(注:这里一般会用到定时器,在之前的反调试与反反调试一文中也给出过相关示例代码),这两种方法已经用烂了,其他的方法我就不知道了,所以接下来我将整合这两段代码,而这段代码将会作为我正式的Bypass_Debugger的脚本,直接上代码:

JS逆向系列14-Bypass Debugger

经本人多次测试一些存在无限debugger的网站,这段代码完美绕过,并且没有任何报错。

最后我想说一下,如果各位要用我的这些脚本,我推荐只使用Bypass_Debugger这个脚本,因为另外两个Hook_Function和Hook_eval这两个脚本的toString方法我是各自只针对它俩的,比如我在Hook_Function脚本里重写的toString方法就只写了Function:

JS逆向系列14-Bypass Debugger

Hook_eval亦是如此,所以要是两个一块用的话可能会出一些问题,有经验的师傅应该已经猜出来了,因为我这里直接重写的是Function原型上的toString方法,两个一块用的话后执行的就必定会覆盖掉先执行的,所以我还是建议读者只使用Bypass_Debugger这个脚本。

0x04 注意事项

1.如果发现hook后打开F12还是存在debugger,建议开着F12再刷新一次。

2.我的这段Bypass_Debugger脚本,目前已知的只有以下这两种情况不能被bypass掉:

var dbg = function (){debugger;}setInterval(dbg,3000);
JS逆向系列14-Bypass Debugger

以及直接将debugger写在script当中的:

JS逆向系列14-Bypass Debugger

说实在的,一般情况下现在主流的无限debugger都是上文那种,而我给的这两段示例已经很少了,以上这两种设条件断点就能过(如果有读者不知道怎么设置条件断点的可以去看一下我之前的反调试与反反调试一文),或者替换也行,不需要hook的,所以我就暂时没去管这个。

3.hook Function和hook eval的脚本我均已上传到github上,地址:https://github.com/0xsdeo/Hook_JS,那段整合后的正式bypass debugger版本我也上传到了github上,不过我又新开了一个库专门放这个脚本,方便大家去取,地址:https://github.com/0xsdeo/Bypass_Debugger,如果大家觉得有用,可以给仓库点点star,鄙人感激不尽。

结语

这是我第一次给我的文章写结语,选这篇文章是因为这篇文章对我的意义很大。

依稀记得我刚开始学js的时候,当时我就下了决心一定要写出Bypass debugger的脚本,时至今日,过了大概半年的时间我终于写出来了,说实在的,我自己也很有成就感,如果说今年我做的有意义的事都有哪些,写出这个脚本就算一件。

这两天成都降温了,其实我不太喜欢冬天,因为冷,不过一想到雪,反倒也没那么讨厌了。

读者朋友们也记得保暖,没有你们的鼓励和关注,公众号也不会做到这种程度,感谢各位。

原文始发于微信公众号(Spade sec):JS逆向系列14-Bypass Debugger

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年12月9日13:24:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   JS逆向系列14-Bypass Debuggerhttps://cn-sec.com/archives/3484944.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息