推广 热搜: csgo  vue  angelababy  2023  gps  新车  htc  落地  app  p2p 

逆向硬扣代码,尽可能多的介绍各种细节!

   2023-06-03 网络整理佚名1090
核心提示:对于绝大多数使用了瑞数的网站来说,有以下几点特征(可能有特殊版本不一样,先仅看主流的):就是5代瑞数:值第一个数字为瑞数的版本(为什么可以通过第一个数字来判断版本?在后续逆向分析中再详细介绍),示例如下:还原瑞数代码的实战,本文咱们选择硬刚!号控制流扣出来的代码大致如下:位是瑞数核心,其中的映射关系搞错了请求是通不过的,在五代中这部分的处理逻辑会更加复杂。

前言

睿数动态安全(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态token等技术,不断动态改变服务器网页底层代码,增加“服务器行为的“不可预测性”,实现从客户端到服务端的全方位“主动防护”,为各类Web和HTML5提供强大的安全防护。

睿数多用于政府、企业、金融、运营商等行业。 一度被视为防攀爬天花板。 随着近年来逆向高手越来越多,相关的逆向文章也层出不穷。 真正到了人均睿数时代。 在此也感谢难陀、懒神等逆向高手揭开了瑞书的神秘面纱,总结出来的经验为后人少走了很多弯路。

传递瑞士号码的方法基本有以下几种:自动化工具(隐藏特征值)、RPC远程调用、JS逆向(硬编码和补充环境),本文介绍JS逆向硬编码,尽可能介绍各种细节。

睿数的特点及不同版本的区别

对于绝大多数使用睿数的网站来说,有以下特点(可能有特殊版本不一样,先只看主流的):

1、打开开发者工具(F12),会依次出现两个典型的无穷大:

2、睿数的JS混淆代码中,变量名和方法名大多类似_$xx,还有很多if-else控制流。 新版睿数可能还有jsvmp和很多三元表达式:

3、看请求,会出现三个典型的请求,第一个请求的响应码是202(锐数34代)或者412(锐数5代),然后单独请求一个JS文件,然后重新请求该页面,随后的其他 XHR 请求中,有一个后缀。 这个后缀的值是JS生成的,每次都会变化。 后缀值的第一个数字是瑞士数字的版本。 比如=是4代瑞数,=是5代瑞数:

4.看,睿数3和4有两个T和S结尾,其中S开头的是第一个201请求返回的,T开头的是JS生成的,动态变化的。 T和S前面一般都是数字80或者443,取值为第一个数字是锐号的版本(为什么可以用第一个数字来判断版本?同一个版本的第一个数字不会变吗?这些问题我们可以在分析JS的时候找到答案),比如:

5代睿号中也有两个以T和S结尾的,但是一些特殊的5代锐号也是以O和P结尾的。同理,以O开头的是第一个412请求返回的,以P结尾的版本开头是js生成的,value的第一个数字也是睿数版本。 与3、4代不同,5代没有端口号,例如:

5.看条目。 锐书有一个在虚拟机VM中加载1w+行代码的过程。 不同版本加载这段代码的入口不同(这个入口在哪里?如何定位?在后续的逆向分析介绍中会详细介绍),示例如下:

当然,如果要准确区分不同的版本,就得综合所有的条件。 最重要的是看页面的内部实现逻辑和代码结构。 比如4代有一个生成假货的步骤,而5代没有。 一些特别版虽然好像是5代,但是增加了jsvmp和三目表情,和传统的5代不一样。 偶尔愚人节突然来个新版本,就会不一样。 对每个版本进行分析后,就很容易区分了。

入口定位

本文以锐数4代网站为例:=

首先,通过(不过没关系,后面的分析基本不受影响),直接右键Never pause here,永不在这里停止:

对于定位,Hook是首选,通过抓包工具、油猴脚本、浏览器插件等方式注入如下Hook代码:

(function() {
    // 严谨模式 检查所有错误
    'use strict';
    // document 为要hook的对象 这里是hook的cookie
    var cookieTemp = "";
    Object.defineProperty(document, 'cookie', {
        // hook set方法也就是赋值的方法 
        set: function(val) {
                // 这样就可以快速给下面这个代码行下断点
                // 从而快速定位设置cookie的代码
                console.log('Hook捕获到cookie设置->', val);
                debugger;
                cookieTemp = val;
                return val;
        },
        // hook get 方法也就是取值的方法 
        get: function()
        {
            return cookieTemp;
        }
    });
})();

Hook发现会生成两次。 打散后,上栈,可以看到组装后的代码,类似如下结构:

仔细观察两个生成的地方,分别顺着栈,你会发现这两个是通过两种不同的方式得到的,如下图:

这里的代码存在于VM虚拟机中,是IIFE自执行代码。 我们必须沿着堆栈查看这些 VM 代码是从哪里加载的,并通过调用沿着堆栈到主页(第 202 页):

我们在文章开头介绍的位置就是这样分析的。 这个位置通常作为分析锐数的切入点。 图中_$te其实就是eval方法,传入的第一个参数_$fY就是对象,第二个对象_$F8就是我们之前看到的VM虚拟机中的IIFE自执行代码。

知道瑞书的大概入口后,我们也可以在事件监听中使用断点,进入202页面直到下一个断点(F8),然后搜索call关键字快速定位入口,断点有两种选择其中,第一种表示在运行JS脚本第一条语句时停止,第二种表示由于内容安全策略导致JS被阻塞时停止。 一般可以选择第一个,如下图所示:

文档结构和逻辑

要生成后续分析,我们必须观察202页面的代码。 meta标签有一段内容引用了一个类似c...js的JS文件,后面是一个自执行的JS,如下图所示:

第一部分中元标记的内容每次都会更改。 第二部分引用的外部JS在不同的页面也是不同的,但是同一个网站的同一个页面的JS中的内容一般是固定的,不会发生变化。 3部分自执行代码每次只改变变量名,整体逻辑不变。 我们后面推导代码的时候也会用到这里的一些方法。 自执行代码中也有很多if-else控制流程。 开头的数组,如上图中的_$Dk,用于控制后续的控制流程。

引用的c...js直接打开是乱码,自执行js的主要作用是将js乱码在VM中恢复为1w+行正常代码,并定义了一个全局变量。 $_ts并赋了很多Value,这个变量在后续的VM中很重要,meta标签的内容在VM中也会用到。

由于很多值和变量是动态变化的,肯定不利于我们的分析,所以我们需要在本地固定一套代码。 打断跟栈会更方便。 把202页的代码和页面对应的外链js文件,比如c...js保存一份到本地,使用浏览器自带的重写功能,或者浏览器插件ReRes,或者抓包工具(如 )的替换功能进行替换。

VM内部的代码是生成的主要代码,其中包含很多if-else控制流,这无疑增加了我们代码分析的成本。 这里我们可以使用AST技术来做一些去混淆。 比如Nanda将if-else控制流转化为For-case,将同一个控制流下的代码放在同一个case下,然后在调用入口处,本地替换VM代码。 详细可以参考Nanda的文章:《一个数的四代逻辑分析》,有兴趣的可以试试。 不了解AST的可以看之前的文章《逆向进阶,利用AST技术还原混淆代码》。 后面K哥会写AST实战还原睿书代码。 在本文中,我们选择hard Just!

VM 代码和 $_ts 变量访问

之前我们了解了 VM 代码和 $_ts 的重要性,所以我们的第一步是找到一种获取它们的方法。 至于什么时候有用,文章后面再说,将外链JS的代码,也就是c...js和202页面的自执行代码复制到文件中,你可以直接在本地运行,需要稍微补一下环境,缺的补一下,大致补一下即可,具体内容直接在浏览器控制台使用即可。 copy( ) 命令,然后我们可以直接通过 Hook eval 获取 VM 代码。 大概的补充环境代码如下:

var eval_js = ""
window = {
    $_ts:{},
    eval:function (data) {
        eval_js = data
    }
}
location = {
    "ancestorOrigins": {},
    "href": "http://www.脱敏处理.com.cn/new_house/new_house_detail.html",
    "origin": "http://www.脱敏处理.com.cn",
    "protocol": "http:",
    "host": "www.脱敏处理.com.cn",
    "hostname": "www.脱敏处理.com.cn",
    "port": "",
    "pathname": "/new_house/new_house_detail.html",
    "search": "",
    "hash": ""
}
document = {
    "scripts": ["script", "script"]
}

观察$_ts的key和value,和浏览器中获取的是一样的:

注意:c...js 外链 JS 如果直接下载,用编辑器打开,可能会自动编码,可能与原始数据不同,导致运行时出错。 这里建议直接在浏览器在线访问这个文件,手动复制。 ,或者复制抓包软件中的响应内容,观察以下两种情况。 第一种情况可能会导致运行出错,第二种情况是正常的:

扣除码

前面说了这么多,现在终于可以进入正题了,那就是扣代码,找张好椅子,准备坐屁股。 此时,您的键盘仅对 F11 有用。 连续单步调试只需要几个细节。 结束了!

扣代码步骤太多,不可能每一步都写截图,只写比较重要的,如果有遗漏也没办法,首先在我们替换的202页面,手动从代码开始的地方加一条,一进入页面就断掉,方便后续分析:

通过我们前面的分析,我们已经知道入口就是调用的地方,赶紧搜索下断点:

通过我们前面的分析,我们也知道生成的地方有两个,快速搜索(5),第二个搜索结果就是入口:

假生成逻辑

首先,循序渐进。 虽然是false,但是在后续生成true的时候会用到。 跟随时,你会得出这样的逻辑:

一步会调用_$8e()方法,_$8e = _$Q9,_$Q9嵌套在_$d0中。 搜索调用_$d0的地方,发现是代码的开头:

那么传入的参数_$Wn是什么? 一步跟进是一个方法,作用是获取202页面的内容,那么我们直接在本地删除这个_$Wn方法,直接传入值即可,如下图:

另外,我们发现代码中有很多情况是在数组中通过索引获取值,比如上图中_$PV[68]的值,其实就是一个字符串。 显然,我们需要找到这个数组的来源,直接搜索_$PV=,就可以找到疑似定义和赋值:

所以我们要看看这个_$iL方法。 传入一个很长的字符串,闯入查看。 果然生成了_$PV,这是一个725位的数组:

接下来,在推导代码的过程中,经常会遇到一个变量,在本文中就是_$sX:

你熟悉吗? 这个值就是我们之前得到的 $_ts 变量。 可以看到开头把.$_ts赋值给了_$sX:

继续下去,你会得出以下逻辑:

这里我们会遇到六个数组,它们都已经有值了,所以我们要找出它们是从哪里来的,随机搜索其中一个数组名,就会找到定义和赋值:

赋值明显是调用了_$rv方法,然后搜索_$rv方法,发现是开头调用的:

后续没什么特别的,一直单步执行,最后有个join('')操作,生成false:

接下来是生成的名字,然后给.赋值,再给里面的$_ck赋值,里面的内容可以直接复制,影响不大。

真正的生成逻辑

单步跟进,本文中是_$ZN(768, 1);,可以看到进入了一个无穷无尽的if-else控制流程:

当地应该如何处理? 我的方法是用 _$Hn 及其值来命名函数。 _$Hn768(){}表示所有使用768号控制流的方法。继续关注。 真正的方法基本在747号控制流中,后续我们主要看747号控制流的各个步骤,从747号控制流中推导出来的代码大致如下:

伪造的

如果按照747号控制流一步步走,会有一个步骤进入709号控制流,会把之前生成的false取进去,经过一系列操作后返回一个数组:

至此,我们已经在本地同步了扣费代码。 如果正常的话,返回的数组应该是一样的(后面的数据会不一样,会涉及到时间戳等一些参数的计算):

自动化工具检测

继续走747号控制流,会进入268号控制流,再进入154号控制流,这里会有一些自动化工具的测试,如下图:

这里定义了一个变量_$iL。 如果测试失败则为1,后面这个变量赋值给_$aW,所以我们本地保持不变,设置为false(其实如果不使用自动化工具,这一段测试就是直接返回false即可):

20位核心阵列

继续走268号控制流,会进入668号控制流。668号控制流有两个操作,一个是生成一个16位的数组,一个是在$_ts中取4个变量,加上它们到前 16 位。 组成一个20位的数组,20位数组的最后4位为睿数核心。 如果映射关系错误,则请求无法通过。 到了第五代,这部分的处理逻辑会更加复杂。

这不是简单的取$_ts中的键值对。 扣代码的时候可能会发现这里的值是怎么取的,不是数字,而是字符串? 像这种情况:

其实我们一开始得到的$_ts值是经过了二次处理的。 我们以第一个_$sX._$Xb为例,直接搜索_$sX._$Xb,可以找到这样一个地方:

显然,_$sX._$Xb 在这里被重新赋值了。 我们可以看到在等号右边,_$sX._$Xb取了一次,它的值为_$Rm,对应我们初始的$_ts的值是一样的,然后我们要看看 _$sX["_$Rm"] 在哪里。 直接搜索发现一开始赋值了一个方法,调用这个方法会产生一个新的值:

其他三个值也是同样的套路,赋值代码分别是:

_$sX._$Xb = _$sX[_$sX._$Xb](_$BH, _$DP);
_$sX._$oI = _$sX[_$sX._$oI](_$ZJ, _$DS)
_$sX._$EN = _$sX[_$sX._$EN]();
_$sX._$D9 = _$sX[_$sX._$D9](_$iL);

实际上应该是:

_$sX._$Xb = _$sX["_$Rm"](_$BH, _$DP);
_$sX._$oI = _$sX["_$Nw"](_$ZJ, _$DS)
_$sX._$EN = _$sX["_$Uh"]();
_$sX._$D9 = _$sX["_$ci"](_$iL);

更进一步,它实际上是:

_$sX._$Xb = _$1k(_$BH, _$DP);
_$sX._$oI = _$jH(_$ZJ, _$DS)
_$sX._$EN = _$9M();
_$sX._$D9 = _$oL(_$iL);

静态分析是没有问题的,我们可以先修复它,但是这些值在实际应用中是动态的,那我们应该怎么处理呢? 我们先多看几个对比,找出其中的规律:

可以发现每次对应的位置都不一样,但其实同一个位置的方法在点击的时候是一样的,也就是说只是方法名和变量名变了,实现的逻辑是不变的,所以我们只需要知道这四个值对应的位置后,就可以得到正确的值了。 在本地,我们可以这样做:

1、先用正则模式匹配这四个值,如:[_$sX._$Xb, _$sX._$oI, _$sX._$EN, _$sX._$D9];

2、再匹配VM代码开头的20条赋值语句,如:_$sX._$RH = _$wI; _$sX._$i5 = _$n5; ETC。;

3.然后用$_ts得到这四个值对应的值,相当于:_$sX._$Xb = _$ts._$Xb = _$Rm; 然后找到这四个值定义的方法在20条赋值语句中的位置相当于:find _$sX._$Rm = _$1k; 20条赋值语句中的位置为7(索引从0开始)

4、我们知道这4个方法在20条赋值语句中的位置,那么我们就可以直接匹配对应的局部位置的名字,进行动态替换。 当然,前提是我们在本地扣了一套代码:

经过这样的处理,可以保证这四个值的准确性。

其他使用$_ts值的地方

除了上面提到的20位数组中用到的$_ts的4个值外,还有其他地方用到的7个值 ,直接搜索即可定位。 这7个值比较简单,每次都是固定的。 $_ts中第2、3、4、15、16、17、19位的值都是一样的,找到对应的位置进行动态替换即可:

防范措施

特别注意VM代码的开头,会直接调用执行一些方法。 一些变量的值就是通过这些方法生成的。 当你按部就班,发现有些参数不对或不对时,那么一开始就得注意这些方法了。 它可能是首先生成的。

后缀生成逻辑

随后的其他 XHR 请求都有一个后缀。 这个后缀的值也是JS生成的,每次都会变。 当然,不同网站的后缀名不一定相同。 本例中,下一个是第一个XHR断点,当XHR请求包含=时断点,然后刷新页面:

可以看出传递给l.open()的URL还是正常的。 破解后l.send()有后缀。 看l.open()其实就是xhr.open(),很明显和有区别,而且VM代码中也有这个方法。 应该是重写的方法,可以和普通的比较一下:

跟着VM代码看,在_$sd([1])方法之后,就变成了带后缀的完整链接:

跟进_$sd方法,前面有对url的一些处理,后面有一个进入779号控制流的过程,其实就是我们生成的原始步骤,照着做就行了。

善用手表追踪功能

开发者工具的Watch功能可以持续跟踪某个变量的值。 对于控制流比较多的情况,设置相应的变量跟踪可以让你知道自己在哪个控制流中,以及生成数组的变化情况。 至于跟随,我不知道该去哪里。

结果验证

如果整个过程没问题,代码扣对了,加上正确无误的后缀,就可以成功访问:

 
反对 0举报 0 收藏 0 打赏 0评论 0
 
更多>同类资讯
推荐图文
推荐资讯
点击排行
网站首页  |  关于我们  |  联系方式  |  使用协议  |  版权隐私  |  网站地图  |  排名推广  |  广告服务  |  积分换礼  |  网站留言  |  RSS订阅  |  违规举报
Powered By DESTOON