逆向12-瑞数信息-案例深圳大学总医院

瑞shu信息

1.1 瑞shu介绍

image

瑞shu信息是一家专注于提供互联网动态业务应用安全防护解决方案的公司

瑞shu动态安全Botgate(机器人防火墙)以“动态安全”技术为核心,通过动态封装、动态验证、动态混淆、动态令牌等技术对服务器网页底层代码持续动态变换,增加服务器行为的“不可预测性”,实现了从用户端到服务器端的全方位“主动防护”,为各类Web、HTML5提供强大的安全保护。

瑞shu可以理解为是我们进入到JS逆向世界的标志

过瑞shu的方法基本上有以下几种

  • 自动化工具
  • 补环境
  • 纯算

1.2 瑞shu之无限 debugger

我们进入debugger的页面之后,在点击下一步之后,会一直重复进入debugger的页面,使用下面的代码 绕过去


// (1)
let _Function = Function;

Function = function(s){
    if (s == "debugger"){
        console.log(s)
        return null
    }
    return _Function(s)
}



// (2)
let _constructor = constructor
Function.prototype.constructor = function(s){
    if (s == "debugger"){
        console.log(s)
        return null
    }
    return _constructor(s)
}
// (3)
Function.prototype._constructor = Function.prototype.constructor;

Function.prototype._constructor = function(){
    if (arguments && typeof arguments[0] === "string"){
        if ("debugger" === arguments[0]){
            console.log(arguments[0])
            return null
        }
    }
    return Function.prototype._constructor.apply(this, arguments);

};

1.3 瑞shu的流程

  1. 瑞shu的网站会请求两次page_url(文档请求),第1次page_url(文档请求)会返回一个cookie1,和一个响应体(HTML源码),以及请求响应码是202(瑞数3、4代)或者412(瑞数5代、6代)
    image

  2. 响应体(HTML源码)包括3个部分:

  • 一个meta标签,其content内容很长且是动态的(每次请求会变化),会在eval执行第二层JS代码时使用到;
  • 一个外链js文件,一般同一页面中其内容是固定的,下面的自执行函数会解密文件内容生成eval执行时需要的JS源码,也就是第二层vm代码;
  • 一个大自执行函数(每次请求首页都会动态变化),主要是解密外链JS内容,给window添加一些属性如$ts,会在vm中使用;
    这三个要素用于在本地生成一个cookie2,用于第2次请求
  1. 第2次page url(文档请求)携带cookie1和ncookie2,获取真正的页面内容

image

看一下第一次的html页面代码
image
需要借助debug的脚本debug
image

清除cookie 刷新
image

image
image
image

1.4 VM虚拟环境

【1】入口定位
先定位入口,这里涉及一个VM环境

  • VM表示的是Virtual Machine(虚拟机),这些文件通常表示由浏览器生成和执行的虚拟机脚本环境中的临时脚本。这些脚本并不是项目源代码的一部分,也不是实际存在的物理文件。它们在浏览器的内存中创建并执行
  • 比如说,当你在调试一个网页时,如果在某些动态生成并执行的JS代码上设定了断点,Chrome调试器会在一个以VM开头的文件中显示这些代码,例如VM111.js。这个VM文件的存在只是为了调试目的,它并不存在于服务器端,也不会被存储在本地,而是存在于浏览器内存中。一般情况下,这类文件的出现是因为浏览器对JavaScript代码的处理方式,如动态编译或者JavaScript堆栈跟踪。
  • 通过eval函数或者new Function方法,Chrome浏览器会创建一个VM文件来展示这段临时执行的代码

通过脚本断点可以断住ts和自执行函数,因为第一次响应的index.html是动态的,所以为了后面调试,我们先试用文件替换本地化。

接下来在index.html中直接使用正则快速定位入口函数:

两种方法:
 1. 搜索 call
 2. 正则匹配: \S{4} = \S{4}\[\S{4}\[\d{2}\]\]\(\S{4}, # 注意等号两边的括号

image
image
image
image
image
image
image
上图的VM虚拟环境是我们在补环境的时候需要用到的

1.5 深圳大学总医院

通过上面的学习,对于案例来说,我们来一个总结,需要我们干的事

1. 无限debugger,绕过干扰
2. 瑞数流程发起请求获取第一次的cookie和index.html
2. index.html-获取三方面(meta,外链js,自执行函数)
3. 进入VM,获取需要补环境需要的东西
4. python代码获取页码

1.5.1 环境

每次在刷新调试的时候,第一次的index.html中的meat和自执行函数发生变化,我们需要使用其中一次的,用来进行调试--使用文件替换知识点
image
image
image
这个替换文件之后每次就不会走动态的逻辑,就集中这一版的代码进行调试

1.5.2 解决报错和补环境

补环境
image
放好代码之后运行报错
报错一:没有window,和解决办法
image
报错二:没有document,1是报错2是补环境
image
报错三:没有报错
在没有报错的前提下,我们需要使用proxy代理监控器

// 这两个删除-是因为有的地方会检测你是不是nodejs语言
delete __dirname
delete __filename

self = top = window = global
document = {}
locaion = {}
navigator = {}
history = {}
screen = {}

function getEnv(proxy_array) {
    for(let i=0; i<proxy_array.length; i++){
        handler = `{
            get: function(target, property, receiver) {
                   console.log('方法:get','    对象:${proxy_array[i]}','    属性:',property,'    属性类型:',typeof property,'    属性值类型:',typeof target[property]);
                   return target[property];
            },
            set: function(target, property, value, receiver){
                    console.log('方法:set','    对象:${proxy_array[i]}','    属性:',property,'    属性类型:',typeof property,'    属性值类型:',typeof target[property]);
                    return Reflect.set(...arguments);
            }
        }`;
        eval(`
            try{
                ${proxy_array[i]};
                ${proxy_array[i]} = new Proxy(${proxy_array[i]},${handler});
            }catch(e){
                ${proxy_array[i]}={};
                ${proxy_array[i]} = new Proxy(${proxy_array[i]},${handler});
            }
        `);
    }
}

proxy_array = ['window','document','locaion', 'navigator', 'history', 'screen', 'history']
getEnv(proxy_array);

之后再运行szdx.js来看报错再补环境

报错四:报错document
image

找报错的原位置,读代码-【后续可以靠经验来,就不用读代码】,,这里是一个大的模版
image

在图中可以看到是_$lh[_$H7[20]]的位置报的一个错误,那么我们去源码中找到这个位置--一定要知道这个是在VM中(图中是一个在index.html页面,一个在VM页面,在VM页面才是正确的)
image
在VM中全部打断点,分析在哪个代码处停下来,然后读代码的逻辑
image
分析代码
image
[上图解释]建立了一个div标签

image

我们自己补环境

// 新建一个div的标签,有一个getElementsByTagName,返回空
document = {
    createElement: function(tag){
        if(tag === "div"){
            return {
                getElementsByTagName:function(tag){
                    return []

                }
        }
    }
}};

结果
image

报错五:报错document下的getElementsByTagName的属性
image
接下来在VM中搜索_$lh[_$H7[639]]
image
注意
image
分析代码
image
第一行代码
image
第二行代码
image
第三行代码
image
第四行代码
image

我们自己补环境再运行

content="xxxxxyyyyyyyyzzzzzzzzzzz"
document = {
    createElement: function(tag){
        if(tag === "div"){return {getElementsByTagName:function(tag){return [] }}}},
    // 这里:
    //第一行代码
    getElementsByTagName : function(tag){
        if(tag === "meta"){
            return {
              //第二行
                length:2,
                "1":{
                    //第三行
                    "content":content,
                    "parentNode":{
                        //第四行
                        "removeChild":function(){
                            return {}
                        }
                    }
                }
            }
        }
    }

};

再运行
报错六 location
image
我们可以看到是一个location的问题,去控制台,把它拿过来
image

location = {
    "ancestorOrigins": {},
    "href": "https://sugh.szu.edu.cn/Html/News/Columns/7/index.html",
    "origin": "https://sugh.szu.edu.cn",
    "protocol": "https:",
    "host": "sugh.szu.edu.cn",
    "hostname": "sugh.szu.edu.cn",
    "port": "",
    "pathname": "/Html/News/Columns/7/index.html",
    "search": "",
    "hash": ""
}

再运行
报错七 getElementById
image
还是上面的办法 搜索_$lh[_$H7[89]]

image
属性
image
我们补环境

document = {
    createElement: function(tag){
	.......
    },
    getElementsByTagName : function(tag){
	.......
    },
    //这里
    getElementById(){
        return []
    }

};

报错八
image
第一次
image

搜索,运行,定位,分析
image
这个报错 其实就是前端有if中的环境,但是我们的node中没有,但是走到了else

分析代码
1: window 下的 addEventListener
image
补代码

window.addEventListener=function(){
}

运行 还是有错-说明运行的这一行 没有结束
image

继续点-是一个document
image
补环境

document = {
    createElement: function(tag){},
    getElementsByTagName : function(tag){},
    getElementById(){},
    // 这里
    addEventListener(){

    },
};

运行 还是有错-说明运行的这一行 还没有结束
继续点点点---直到出现下一个内容
image
看报错-补环境
image

document = {
    createElement: function(tag){},
    getElementsByTagName : function(tag){},
    getElementById(){},
    addEventListener(){},
    // 这里
    documentElement:{
        addEventListener(){

        }
    }
};

报错九
image
image
是在找一个base的标签
补环境

document = {
    getElementsByTagName : function(tag){
        console.log("document getElementsByTagName",tag)
        if(tag === "meta"){
            return {...........}
        }else if(tag === "base"){
            return []

        }
    }

再继续
image

补一个script标签

// 省略写了
else if(tag === "script"){
            return []

        }

image
结果
image

这里有一个定时任务,我们置空

setTimeout=function(){
}
setInterval=function(){
}

我们再次运行

image
得倒结果了

1.5.3 代码

js代码优化
image
image

再发请求的时候发现还是拿不到数据----原因是我们拿到的第二次的cookie是一个假值
那么回去看看是哪里的错误
这里可能借助监控器
其实就是看这些undefined的值
image

如何看?
举例1:
image
在终端-也是一个undedined这种就不需要修改
image
举例2
XMLHttpRequest是一个undedined,但是window.XMLHttpRequest是一个函数,我们就需要补充
image
终端
image
补环境

window.XMLHttpRequest = function () {
}

注意:一般不用全部补齐,可以根据经验,优先补齐location,navigator等

补齐之后再运行获取验证码 200 此次课程结束

总代码

posted @ 2024-08-02 15:36  流年中渲染了微笑  阅读(199)  评论(2)    收藏  举报