提前说一句: 所有版本均可rpc通杀,一键注入、高并发采集;但是吃机器性能、不太方便,不过是一个很好的临时方案。

这里主要说明补环境方案

Rs4

网址: aHR0cDovL3d3dy5mYW5nZGkuY29tLmNuL25ld19ob3VzZS9uZXdfaG91c2UuaHRtbA==

Rs4分析

瑞数的两个cookie标志:
FSSBBIl1UgzbN7N80T: 4.W3r2VRGFyg31RdmB8.t.StPEOTYFfahgM9RJdr8ncd_n8I3h0SFVHCTuU8Kjh2bWSRL.kNdSEAfq5Fz4iGP_A.OmJKoAfdtuEH1eWbuvptiv_s9eIgWB7fYUrxOlJHeME4nO8aX3GCXxt8pSV7.hXYWdqB_hstgMtq3YynZSxnK0IWJk9Kh1F.28lFRS3HnscVuJE3Waa48AfP4bhDLblMunctN4n.L_1e33dmux7AVWsjjHEZb7eDVQXSjvEGi8htWecaSdtXaeNW8sDM5WHeF0vVqTX7zyuekRvvF8GHk6WZmcuxM0e4wEevxLjoZYhQomRSkYI.Z6o6PQVJkHQWTD1JL_CsLq8YS7gi2fSeCEE2cJRCfBxPhxVsVJBhm1j3

  • 80T代表 http
    • 值以 4 开头,表示瑞数四代
      • httponly: false
        FSSBBIl1UgzbN7N80S: t41Jq5g8fMaZVHDOe7BnmrInFJhrLmgpnofodLpZqLcPXDRTyd4.GP7OwPFCU012
      • 80s代表 https
      • httponly: true,属于后台返回的

请求分析

  • 初次请求,没有cookie时,会返回一个 202 的页面 (可清除cookie后测试)
    • 我们的cookie就是通过该 202页面js, meta中的content 和 一个ts代码文件 共同生成的
      • ts 代码无法看懂,202页面 自执行js 会解析 ts 为正常js代码
      • 自执行函数有一个call方法,通过eval方法 将ts解析后的 js 代码放到VM中运行,
        • 通过调试可以看到 对应的js代码
  • 再次请求,返回 200 页面

代码结构

Js代码大概可以分为这几个部分:

image-20231024162932444

从上到下依次是:

  • 自己补的环境 windows、documents这类的
  • 初次请求返回的content
  • ts文件内容(初次请求返回的有js文件地址)
  • 初次返回的自执行函数
  • 导出cookie函数

2、3、4部分我们均可以在第一次请求中拿到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
headers = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Cache-Control': 'no-cache',
'Pragma': 'no-cache',
'Proxy-Connection': 'keep-alive',
'Referer': 'http://www.fangdi.com.cn/new_house/new_house.html',
'Upgrade-Insecure-Requests': '1',
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36',
}
response = session.get(
'http://www.fangdi.com.cn/new_house/new_house.html',
cookies=cookies,
headers=headers,
)
with open('固定.html','w') as f:
f.write(response.text)
# 匹配出content
content = re.findall('<meta content="(.*?)"', response.text)[0] # content
auto_js = re.findall('<script type="text/javascript" r="m">(.*?)</script>', response.text,re.S|re.I)[0] # 自执行函数
ts_url = re.findall('src="(.*?js)" r=',response.text,re.S|re.I)[0] # js文件
ts_url = f'http://www.fangdi.com.cn{ts_url}'
logger.debug(f'ts_url:{ts_url}')

补环境

我们先把最基本的一些环境补充好,我们的环境放在最顶部

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
null_func = function () {
console.log("空函数参数:", arguments[0]);
};
window = global;
window.top = window;
window.self = window;
window.TEMPORARY = 0;
location = {
href: 'http://www.fangdi.com.cn/index.html',
protocol: "http:",
origin: 'http://www.fangdi.com.cn',
hostname: 'www.fangdi.com.cn',
port: '',
};
document = {
charset: 'UTF-8',
characterSet: 'UTF-8',
cookie: 'xxxxxx; xxxx'
};
navigator = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
webdriver: false,
languages: ['zh-CN', 'zh', 'en'],
appVersion: '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
webkitPersistentStorage: {},
};
history = {};
null_func = function () {
console.log("空参:", arguments[0]);
};

紧接着,我们把代理写到我们环境的下方

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
window = new Proxy(window, {
set(target, property, value, receiver) {
console.log("设置属性set window", property, typeof value);
return Reflect.set(...arguments);
},
get(target, property, receiver) {
console.log("获取属性get window", property, typeof target[property]);
return target[property]
}
});

function proxy (proxy_array) {
for (let i=0; i<proxy_array.length; i++){
eval(proxy_array[i] + `= new Proxy(` + proxy_array[i] + `, {
get(target, key) {
if(key == '你需要debugger的属性'){
debugger;
}
console.log('----------------------')
// console.log( '【` + proxy_array[i]+ `】取属性 ' + key + ', 详细位置debugger查看');
// target[key] 为某些对象时, 拼接字符串会异常,如 window['document']
console.log( '【` + proxy_array[i]+ `】取属性 ' + key + ' 值: ' + target[key] + ', 详细位置debugger查看');
console.log('----------------------')
return target[key];
}
});`)
}
}
// 数组中放入需要代理的对象
var proxy_array = ['document', 'navigator','location', 'history'];
proxy(proxy_array);

image-20231024164831290

缺啥补啥,先去浏览器看看这个东西是什么然后直接补,如果浏览器也显示找不到,那就不用管。

我们注意到document缺了createElement

先补一个这样的

1
2
3
4
createElement = function () {
return {};
};
document.createElement = createElement;

然后它报错

image-20231024165219319

我们直接调试

image-20231024170010871

这里改为上一个出现的属性即可

比如createElement之后报错了,我们就改为createElement,便于快速定位

image-20231024170151898

进入debugger后一路单步往下走

吐槽一下pycharm: 进入到虚拟机之后就无法定位到代码在哪了,还得自己找,现在临时换到vscode进行调试

image-20231024171046462

这里也是成功进入,这里看到_$jb是一个{}, {} 当然没有getElementsByTagName属性了,所以我猜测是我们createElement之后的对象,缺少了这个属性

image-20231024171121418

所以我们这里改一下hook代码

1
2
3
4
createElement = function () {
console.log('创建对象:',arguments[0]);
return {};
};

再次执行

image-20231024171412775w

所以这里是要创建一个div

我们提前定义div,然后给它加到代理器,验证下我的猜想

1
2
3
4
5
6
7
8
9
10
11
div = {}
createElement = function () {
console.log('创建对象:',arguments[0]);
switch (arguments[0]) {
case 'div':
return div;
default:
return {};
}
};
var proxy_array = ['document', 'navigator','location', 'history','div'];

image-20231024171603306

果然是这样!那我就需要针对div补一个getElementsByTagName属性即可

1
2
3
4
5
getElementsByTagName = function(){
console.log('获取元素:',arguments[0]);
return {}
}
div.getElementsByTagName = getElementsByTagName;

image-20231024171814966

这里我们调试出了获取了元素i,所以我们就直接补i,

1
2
3
4
5
6
7
8
9
getElementsByTagName = function(){
console.log('获取元素:',arguments[0]);
switch (arguments[0]) {
case 'i':
return [];
default:
return [];
}
}

然后也补一下addEventListenerattachEvent

这里我们一律补成null_func,假如有传参的话,它会自动输出,我们再针对性的补(参考刚才的获取i元素)

1
2
window.addEventListener = null_func
window.attachEvent = null_func

继续执行,再次报错

image-20231024172219167

这里我们先补一下document的相关属性

其实一些基本的都可以先提前写入了,类似这样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
location = {
href: 'http://www.fangdi.com.cn/index.html',
protocol: "http:",
origin: 'http://www.fangdi.com.cn',
hostname: 'www.fangdi.com.cn',
port: '',
};
document = {
charset: 'UTF-8',
characterSet: 'UTF-8',
cookie: 'xxxxxx; xxxx'
};
connection = {};
navigator = {
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
webdriver: false,
languages: ['zh-CN', 'zh', 'en'],
appVersion: '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36',
webkitPersistentStorage: {},
};

然后我们看那个报错,再补一下document.getElementsByTagName

image-20231024172457368

我们吸取上次记得经验,先不急着补Meta,看看它是不是对meta也做了什么手脚,所以开始调试

image-20231024172926210

意思就是,它会读取getElementsByTagName返回对象的_$tp[68]属性和_$tp[42]属性,也就是content和removeChild

那这就好办了,这样补就行(注意getElementsByTagName返回的是一个列表,所以我们也要返回列表哦)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
getElementsByTagName = function(){
console.log('获取元素:',arguments[0]);
switch (arguments[0]) {
case 'i':
return [];
case 'meta':
return [
{
content:content,
parentNode:{
removeChild:null_func
}
}
]
default:
return [];
}
}

继续执行

image-20231024173242596

直接补null_func即可

1
2
document.addEventListener = null_func;
document.attachEvent = null_func;

继续执行

image-20231024174827411

getElementsByTagName获取了script,我们再按照刚才的流程,补一个script然后把它加上代理

image-20231024175024233

针对性的补一个getAttribute函数即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
script = {
getAttribute:null_func
}
getElementsByTagName = function(){
console.log('获取元素:',arguments[0]);
switch (arguments[0]) {
case 'i':
return [];
case 'meta':
return [
{
content:content,
parentNode:{
removeChild:null_func
}
}
]
case 'script':
return [script]
default:
return [];
}
}

继续执行

image-20231024173353645

发现一直输出这些,陷入了死循环

我们直接置空这俩函数

1
2
setTimeout = function () {};
setInterval = function () {};

再次执行

终于看到了想要的结果!

image-20231024173527300

把抠出来的环境放到我们一开始说的env即可

测试补环境成果

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
# ---------拿到ts文件内容----------------
ts = requests.get(ts_url).text
with open('./ts.js','w') as f:
f.write(ts)
# --------
js = f"""
{env2}

var content="{content}";

// ts 文件内容
require('./ts');

// 自执行函数
{auto_js}


function get_cookie(){{
cookie = document.cookie
FSSBBIl1UgzbN7N80T = cookie.split(';')[0].split('=')[1]
return FSSBBIl1UgzbN7N80T;
}}
console.log(get_cookie());
"""
with open('End.js','w') as f:
f.write(js)
logger.debug('写入成功')



#第二次请求

file = open('End.js', 'r', encoding='utf-8').read()
_80T = execjs.compile(file).call('get_cookie')

cookies = {
'www.fangdi.com_http_ic': 'www.fangdi.com.cn_80_RS',
'www.fangdi.com.cn': 'www.fangdi.com.cn_rs1',
'FSSBBIl1UgzbN7N80T': _80T
}

print('==cookie入参:', cookies)
resp = session.get('http://www.fangdi.com.cn/new_house/new_house.html', headers=headers, cookies=cookies)
print(resp.status_code)
print(resp.cookies.get_dict())

image-20231024175313053

至此,Rs4补环境结束