瑞数安全产品

学习目标:

  1. 熟悉瑞数处理方式
  2. 熟悉瑞数补环境规则
  3. 熟悉调试瑞数方法

一.简介

1. 瑞数

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

2. 瑞数执行流程分析

我们在做逆向的时候,首先得分析出哪些加密参数是需要逆向的,然后再是去逆向这些参数。当然瑞数也是一样。
所以我们第一步就是明确逆向的目标

  • 现象:上了rs的网站会请求两次page_url,第二次请求page_url时才能得到正确的页面内容;
  • 分析:分析其请求体,发现第二次请求page_url时带上了cookie_s和cookie_t, 而cookies_s是来自第一次请求page_url时其响应头set的;

3.执行流程

这里我们需要关注eval调用的位置(也就是VM的入口),cookie生成的位置

注:浏览器v8调用eval执行代码时会开启一个虚拟机(VM+数字)去执行JavaScript代码。

二.瑞数4

1. 逆向目标

2. 瑞数4特点

  • debugger: 两次
  • key名字:FSSBBIl1UgzbN7N80T是js代码生成, FSSBBIl1UgzbN7N80S是服务器返回

3. 逆向分析

  • 过无限debugger
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
var _constructor = constructor;
Function.prototype.constructor = function(s) {
if (s == "debugger") {
console.log(s);
return null;
}
return _constructor(s);
}

//去除无限debugger
Function.prototype.__constructor_back = Function.prototype.constructor ;
Function.prototype.constructor = function() {
if(arguments && typeof arguments[0]==='string'){
//alert("new function: "+ arguments[0]);
if( "debugger" === arguments[0]){
// arguments[0]="consoLe.Log(\"anti debugger\");";
//arguments[0]=";";
return
}
}
return Function.prototype.__constructor_back.apply(this,arguments);
};

var _Function = Function;
Function = function(s) {
if (s == "debugger") {
console.log(s);
return null;
}
return _Function(s);
}
  • hook对应cookie生成的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
(function () {
// 严谨模式 检查所有错误
'use strict';
// document 为要hook的对象 这里是hook的cookie
var cookieTemp = "";
Object.defineProperty(document, 'cookie', {
// hook set方法也就是赋值的方法
set: function (val) {
// 这样就可以快速给下面这个代码行下断点
// 从而快速定位设置cookie的代码
if (val.indexOf('ihkYnttrQXfVP') != -1) {
debugger;
}
console.log('Hook捕获到cookie设置->', val);
cookieTemp = val;
return val;
}, // hook get 方法也就是取值的方法
get: function () {
return cookieTemp;
}
});
})();
  • 可以看到cookie的生成位置,
  • 是有$FG函数调用生成的,执行的代码是在vm文件当中
  • vm文件在瑞数当中都是通过eval生成的,可以网上找到他执行eval的位置
  • 往下面的栈进行查找可以找到入口文件
  • 这个文件就是瑞数第一次请求返回412状态码的页面
  • 那我们就需要把这个页面的代码拿下来我们自己执行,获取的方法是一个自执行方法
  • 需要把这个文件的外链js代码也拿过来,一般是一个ts数据的js文件
  • 要进外链js文件的话,需要通过抓包工具的脚本功能
  • 那下来之后我们就可以补环境了

4. node联调devtools

  • 把node代码放在开发者工具执行代码
  • 执行命令 node --inspect-brk js文件
  • 浏览器执行命令 chrome://inspect/#devices
  • 检测到文件之后会有个点击选项,点击进入就能调试自己的代码
  • 有了开发者工具调试之后我们可以给自己的吐环境脚本在完善一下,当获取的参数是undefined就下一个断点,我们就能直接定位到空环境的位置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// 代理器封装
function get_enviroment(proxy_array) {
for(var i=0; i<proxy_array.length; i++){
handler = '{\n' +
' get: function(target, property, receiver) {\n' +
' console.log("方法:", "get ", "对象:", ' +
'"' + proxy_array[i] + '" ,' +
'" 属性:", property, ' +
'" 属性类型:", ' + 'typeof property, ' +
// '" 属性值:", ' + 'target[property], ' +
'" 属性值类型:", typeof target[property]);\n' +
'if (typeof target[property] == "undefined"){debugger}' +
' return target[property];\n' +
' },\n' +
' set: function(target, property, value, receiver) {\n' +
' console.log("方法:", "set ", "对象:", ' +
'"' + proxy_array[i] + '" ,' +
'" 属性:", property, ' +
'" 属性类型:", ' + 'typeof property, ' +
// '" 属性值:", ' + 'target[property], ' +
'" 属性值类型:", typeof target[property]);\n' +
' return Reflect.set(...arguments);\n' +
' }\n' +
'}'
eval('try{\n' + proxy_array[i] + ';\n'
+ proxy_array[i] + '=new Proxy(' + proxy_array[i] + ', ' + handler + ')}catch (e) {\n' + proxy_array[i] + '={};\n'
+ proxy_array[i] + '=new Proxy(' + proxy_array[i] + ', ' + handler + ')}')
}
}
proxy_array = ['window', 'document', 'location', 'navigator', 'history','screen']


get_enviroment(proxy_array)

4.逆向结果

  • JavaScript代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
//  检测当前执行的文件是哪一个
delete __dirname
delete __filename
content = "{qqqm26649r0qq{FkKEdKk2UJaS9mv9xISgODoGFRs2ycv9xMYa6UkSY3Sm4DDLs8pSdKbamI1rLrUgp3fwz1kwHi0RWcD9U3OTgybB9Iar4qqDdfe167l3650qqc80r0qq 0wR7HvJ6IsUC410DntKRngA;QyqA82EGtIB6ePNEeYo9NG;iEm6gdSTTpYiqU10OlvsnG;yMG8gk5okQ97gP4eb.IadA;T8F36FaS9AtR4sXBkRr0iG;RTlM3IYjAzboXbIiNSIFRA;t7_svh3Kc3.VU9jOjAJgdq;.8D9Zx78FrKF.Zn4xbfmIG;IMhCM7gXESIqShs5TNMo9A;pvBPF7OtrK6trS5vZYizwa;9qxqLXuEeDQeAlNfAL_l.A;VNeyFcNDtQZhV2sfCxyHqA;kT4JL2WRSOhvUIEcOjSrva;LpFhLGWYI8eFx_X999MLEq;NqssQaVItFB0TevtNxJrkG;AI3RN3R7lP0BBnYsoCO5KG;xrYRhwM6FYW7zCsPL.iecq;0kOXzZzt1eXLrlPo.QQ4xG;ApKNqLIRoybF5rIxSnabBG;hfgZrtz_KscdFC6a3f1wKA;VUUm7pbShRVJBACTrF9y2qqQwSzrE9l9AP2cx9V7Af2rlVWVprYDr1qqqqqq|[q0S_8ClI3pggJOAnVsJuFKEwibwtqkGMMfx0mVwP3DzEMvQhWnLTqsGZWP7CmUp1wmYQU0LqQqNjUDLJQqYjVVw1VuN6t9eYYGGCc9gxirm_ml2H1s2_HY9MYreFl6WXFkNaiUwlsSrcpDOu3Xz2y6s7FXxN4osOiWmAC0oh1X3n6bsC3QW0akP4VBxYyVhK3ITHa9.CiJeDgDd.FBR0LKcTp8pxy01.We9zSDUlWNA02P6iQxyvPlUI1wxlvkH7hQe2nstMJx7zSUchF83aPCMx38JsSuPcQITm6sUssI3CPUioYWYKCsMmwgpXjkG.TISDKHY9Tb9Xrk7z8nEZ9HGt9hOCpIiUy1NIFPvEXZAqq|gwSNkQAmpo6GiUSm7JDGJlqx1oT7YmSJxlCqDVrxiJUzmU0eQibJcY9R6KYfR81TkrmZPrGy7kKWz3ky6lCQHsO0EoKLRMuJipGgVFnpRqDgJrPG0J1mHxuaNx17XlrqIik0KoPm_Df7Yr9JdWSQw3SZAq1GA3u0UEsq9qn3JqSEKtngXpSaTMqY.xsaGhGZKxSWKWfWbKsqZWOyYkA0diaAaJqlrDpeFJqLoh1yIkSZMmr3YWuE1p9erhSAficEWrp3_krqiEkZrJPRHk1Z17P4kxZzkzuh8WXeDYOZxald8X_LaH_5IMltMEhqqqh9WFTWrwxmlLqqlpLNNr0qqKPOLAb4uMpKGrAL2o3InVXAhJsiGKSR3h93}!qeAdBKC2wjEOL6jLRXAf7bjdR42zdni6R5EfBTniM3y7Gb84UWQOPDttRzVf9C5FF3SbzUMFJLw4Os82pFwUn0K0FxVC2ktcMhe4uDj4Q32pucC8RRJUbCDuAmJP3ll_IbpkWlYdR9zURUJ5JCzXKorE1O9S82wWWYNKwkrwwC76wVWCxDJ28vmnisNPQ9eUQPLSMDwWUvwNp07pVbSVMVY1xfLq1Hf0pwmr_KCQA1lNWVGt1074790432!x7z,aac,amr,asm,avi,bak,bat,bmp,bin,c,cab,css,csv,com,cpp,dat,dll,doc,dot,docx,exe,eot,fla,flc,fon,fot,font,gdb,gif,gz,gho,hlp,hpp,htc,ico,ini,inf,ins,iso,js,jar,jpg,jpeg,json,java,lib,log,mid,mp4,mpa,m4a,mp3,mpg,mkv,mod,mov,mim,mpp,msi,mpeg,obj,ocx,ogg,olb,ole,otf,py,pyc,pas,pgm,ppm,pps,ppt,pdf,pptx,png,pic,pli,psd,qif,qtx,ra,rm,ram,rmvb,reg,res,rtf,rar,so,sbl,sfx,swa,swf,svg,sys,tar,taz,tif,tiff,torrent,txt,ttf,vsd,vss,vsw,vxd,woff,woff2,wmv,wma,wav,wps,xbm,xpm,xls,xlsx,xsl,xml,z,zip,apk,plist,ipaqiiw3E4aoPqiqWgqk162qqkswlhp1pITe7_slYqqql4096"

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

proxy_array = ['window', 'document', 'location', 'navigator', 'history', 'screen', 'aaa', 'target']
window = global;
window.top = window;
window.fetch = function (res) {
console.log('window中的fetch接受的值:', res)
}
window.sessionStorage = function (res) {
console.log('window中的fetch接受的值:', res)
}

window.addEventListener = function (res) {
console.log('window中的addEventListener接受的值:', res)
}
window.DOMParser = function (res) {
console.log('window中的DOMParser接受的值:', res)
}
window.localStorage = {}
window.name = '$_YWTU=J5Kqj1vMVZt0p3jcKdQszRtzTokjPjsSc6r8XesJXBg&$_cDro=49&vdFm='
window.self = window
window.XMLHttpRequest = function () {

}

// document 环境
div = {
getElementsByTagName: function (res) {
console.log('div中的getElementsByTagName接受的值:', res)
if (res == 'i') {
return {length: 0}
}
}
}

meta = {
0: {},
1: {
content: content,
parentNode: {
removeChild: function (res) {
console.log('meta中的removeChild接受的值:', res)
}
}
},
length: 2
}
script = {
0: {
getAttribute: function (res) {
console.log('script中的getAttribute接受的值:', res)
if (res == 'r') {
return 'm'
}
},
parentElement: {
removeChild: function (res) {
console.log('script中的removeChild接受的值:', res)
}
}
},
1: {
getAttribute: function (res) {
console.log('script中的getAttribute接受的值:', res)
if (res == 'r') {
return 'm'
}
},
parentElement: {
removeChild: function (res) {
console.log('script中的removeChild接受的值:', res)
}
}
},
length: 2
}
document = {
characterSet: 'UTF-8',
charset: 'UTF-8',
createElement: function (res) {
console.log('document中的createElement接受的值:', res)
if (res == 'div') {
return div
} else {
return {}
}

},
getElementsByTagName: function (res) {
console.log('document中的getElementsByTagName接受的值:', res)
if (res == 'meta') {
return meta
}
if (res == 'script') {
return script
}
if (res == 'i') {
return {length: 0}
}
},
addEventListener: function (res) {
console.log('document中的addEventListener接受的值:', res)
},
exitFullscreen: function (res) {
console.log('document中的exitFullscreen接受的值:', res)
},
documentElement: {
addEventListener: function () {
},
style: {}
},

}

location = {
"ancestorOrigins": {},
"href": "http://www.fangdi.com.cn/service/service_law_detail_img2.html",
"origin": "http://www.fangdi.com.cn",
"protocol": "http:",
"host": "www.fangdi.com.cn",
"hostname": "www.fangdi.com.cn",
"port": "",
"pathname": "/service/service_law_detail_img2.html",
"search": "",
"hash": ""
};
navigator = {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
languages: ["zh-CN", "zh"],
appVersion: "5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36",
webdriver: false,
appName: "Netscape",
vendor: "Google Inc.",
connection: {
downlink: 10,
effectiveType: "4g",
rtt: 200,
saveData: false,
}
};


get_enviroment(proxy_array)
setTimeout = function () {
}
setInterval = function () {
}
// get_enviroment(proxy_array)
setTimeout = function () {
}
setInterval = function () {
}

'ts_code'

'func_code'

function get_cookie() {
return document.cookie
}

console.log(get_cookie());


  • python代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
import re
import execjs
from lxml import etree

requests = requests.session()
headers = {
'Host': 'www.fangdi.com.cn',
'Referer': 'http://www.fangdi.com.cn/service/service_law_detail_img2.html',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36',
}
url = 'http://www.fangdi.com.cn/index.html'


def first_request():
response = requests.get(url, headers=headers)
obj_html = etree.HTML(response.text)
content_data = obj_html.xpath('//meta[2]/@content')[0]
func_code = obj_html.xpath('//script[2]/text()')[0]
return content_data, func_code


def second_request():
content_data, func_code = first_request()
# print(content_data, func_code)
with open('1111.js', encoding='utf-8') as f:
js_code = f.read().replace('content_data', content_data).replace("'func_code'", func_code)
# print(js_code)
js = execjs.compile(js_code)
cookies = {'FSSBBIl1UgzbN7N80T': js.call('get_cookie').split(';')[0].split('=')[-1]}
# print(cookies)
res = requests.get(url, headers=headers, cookies=cookies)
print(res.request.headers)
print(res)
# second_request()


second_request()

三.瑞数5

1.瑞数5特点

  • debugger2层
  • 状态码第一次412
  • 补的方式和瑞数4一致

2.逆向目标

3.逆向结果

  • JavaScript代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
// https://sugh.szu.edu.cn
delete __dirname
delete __filename
// 代理器封装
// 这里写补充的环境
window = global;
window.top = window;

location = {
"ancestorOrigins": {},
"href": "https://sugh.szu.edu.cn/Html/News/Columns/7/2.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/2.html",
"search": "",
"hash": ""
}
navigator = {
connection: {
downlink: 10,
effectiveType: "4g",
onchange: null,
rtt: 200,
saveData: false
},
getBattery: res => {
console.log('navigator.getBattery 获取的对象是:', res)
return undefined
},
languages: ["zh-CN", "en", "en-GB", "en-US"],
platform: "Win32",
webdriver: false,
webkitPersistentStorage: {},
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0",
}
window.addEventListener = function (res) {
console.log('window.addEventListener 获取的对象是:', res)
}
window.HTMLFormElement = function (res) {
console.log('window.HTMLFormElement 获取的对象是:', res)
};


window.self = window
// -----------------------------------------------------------
document = {
cookie: {},
characterSet: "UTF-8",
charset: "UTF-8",
createElement: (res) => {
console.log('document.createElement 获取的对象是:', res)
if (res === 'div') {
div = {
getElementsByTagName: res => {
console.log('div.getElementsByTagName 获取的对象是:', res)
if (res === 'i') {
return []
}
}
}
return div
} else {
console.log('返回的res应该是:', res)
return {length: 0}
}
},
getElementsByTagName: res => {
console.log('document.getElementsByTagName 获取的对象是:', res)
if (res === 'meta') {
meta = [
{}, {}, {}, {}, {}, {}, {
getAttribute: function (res) {
console.log('meta中的getAttribute:', res)
if (res === 'r') {
return 'm'
}
},
parentNode: {
removeChild: res => {
return []
}
},
content: 'meta-text',
}
]
return meta
}
if (res === 'base') {
return []
}
if (res === 'script') {
return {}
}
},
getElementById: (res) => {
console.log('document.getElementById 获取的对象是:', res)
// if (res === 'root-hammerhead-shadow-ui') {
// return null
// } else {
// console.log('document.getElementById 获取的对象应该是:', res)
// }
},
addEventListener: (res) => {
console.log('document.addEventListener 获取的对象是:', res)
if (res === 'load') {
return []
} else {
return []
}
},
documentElement: {
addEventListener: (res) => {
console.log('document.documentElement.addEventListener 获取的对象是:', res)
}
}
}

//-------------取消定时器----------------
setInterval = function () {
}
setTimeout = function () {
}
clearInterval = function () {
}
//-------------------------------------

'ts-code'
'html-code'

function RS_5() {
const tm = document.cookie.toString().split(';')[0].split('=')
return {name: tm[0], value: tm[1]}
}
console.log(RS_5())
  • python代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

import execjs
import requests
from lxml import etree

requests = requests.session()
headers={
'host': 'sugh.szu.edu.cn',
'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0"
}

req_url = 'https://sugh.szu.edu.cn/Html/News/Columns/6/Index.html'
req = requests.get(req_url)
print('第一次请求状态码:', req.status_code)
# print(req.text)
etree = etree.HTML(req.content)
meta_text = etree.xpath('string(//meta[2]/@content)')
ts_url = etree.xpath('string(//script[1]/@src)')
html_code = etree.xpath('string(//script[2]/text())')
# 拿到ts源文件
ts_code = requests.get('https://sugh.szu.edu.cn' + ts_url).text
# 读取js文件,并替换相应部分内容,生成完整的js
with open('demo.js', 'r', encoding='utf-8') as js_file:
js_content = js_file.read().replace("'html-code'",
html_code).replace("meta-text",
meta_text).replace("'ts-code'",
ts_code)
cookies_s = execjs.compile(js_content).call('RS_5')
# 发送请求
cookies = {
cookies_s['name']: cookies_s['value'],
}

req_2 = requests.get(req_url, cookies=cookies, headers=headers)
print('第二次请求状态码:', req_2.status_code)
req_2.encoding = 'utf-8'
print(req_2.text)

四.瑞数变异

1.变异特点

  • 和瑞数6是一致的
  • 在html文件加载的是ts代码,不是一个自执行方法了
  • 通过vmp的方式在加载的代码,外链的js可以直接写死
  • 瑞数6有做更新