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

瑞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的流程
-
瑞shu的网站会请求两次page_url(文档请求),第1次page_url(文档请求)会返回一个cookie1,和一个响应体(HTML源码),以及请求响应码是202(瑞数3、4代)或者412(瑞数5代、6代)

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

看一下第一次的html页面代码

需要借助debug的脚本debug

清除cookie 刷新




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}, # 注意等号两边的括号







上图的VM虚拟环境是我们在补环境的时候需要用到的
1.5 深圳大学总医院
通过上面的学习,对于案例来说,我们来一个总结,需要我们干的事
1. 无限debugger,绕过干扰
2. 瑞数流程发起请求获取第一次的cookie和index.html
2. index.html-获取三方面(meta,外链js,自执行函数)
3. 进入VM,获取需要补环境需要的东西
4. python代码获取页码
1.5.1 环境
每次在刷新调试的时候,第一次的index.html中的meat和自执行函数发生变化,我们需要使用其中一次的,用来进行调试--使用文件替换知识点



这个替换文件之后每次就不会走动态的逻辑,就集中这一版的代码进行调试
1.5.2 解决报错和补环境
补环境

放好代码之后运行报错
报错一:没有window,和解决办法

报错二:没有document,1是报错2是补环境

报错三:没有报错
在没有报错的前提下,我们需要使用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

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

在图中可以看到是_$lh[_$H7[20]]的位置报的一个错误,那么我们去源码中找到这个位置--一定要知道这个是在VM中(图中是一个在index.html页面,一个在VM页面,在VM页面才是正确的)

在VM中全部打断点,分析在哪个代码处停下来,然后读代码的逻辑

分析代码

[上图解释]建立了一个div标签

我们自己补环境
// 新建一个div的标签,有一个getElementsByTagName,返回空
document = {
createElement: function(tag){
if(tag === "div"){
return {
getElementsByTagName:function(tag){
return []
}
}
}
}};
结果

报错五:报错document下的getElementsByTagName的属性

接下来在VM中搜索_$lh[_$H7[639]]

注意

分析代码

第一行代码

第二行代码

第三行代码

第四行代码

我们自己补环境再运行
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

我们可以看到是一个location的问题,去控制台,把它拿过来

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

还是上面的办法 搜索_$lh[_$H7[89]]

属性

我们补环境
document = {
createElement: function(tag){
.......
},
getElementsByTagName : function(tag){
.......
},
//这里
getElementById(){
return []
}
};
报错八

第一次

搜索,运行,定位,分析

这个报错 其实就是前端有if中的环境,但是我们的node中没有,但是走到了else
分析代码
1: window 下的 addEventListener

补代码
window.addEventListener=function(){
}
运行 还是有错-说明运行的这一行 没有结束

继续点-是一个document

补环境
document = {
createElement: function(tag){},
getElementsByTagName : function(tag){},
getElementById(){},
// 这里
addEventListener(){
},
};
运行 还是有错-说明运行的这一行 还没有结束
继续点点点---直到出现下一个内容

看报错-补环境

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


是在找一个base的标签
补环境
document = {
getElementsByTagName : function(tag){
console.log("document getElementsByTagName",tag)
if(tag === "meta"){
return {...........}
}else if(tag === "base"){
return []
}
}
再继续

补一个script标签
// 省略写了
else if(tag === "script"){
return []
}

结果

这里有一个定时任务,我们置空
setTimeout=function(){
}
setInterval=function(){
}
我们再次运行

得倒结果了
1.5.3 代码
js代码优化


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

如何看?
举例1:

在终端-也是一个undedined这种就不需要修改

举例2
XMLHttpRequest是一个undedined,但是window.XMLHttpRequest是一个函数,我们就需要补充

终端

补环境
window.XMLHttpRequest = function () {
}
注意:一般不用全部补齐,可以根据经验,优先补齐location,navigator等
补齐之后再运行获取验证码 200 此次课程结束

浙公网安备 33010602011771号