提前说一句: 所有版本均可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中运行,
- 再次请求,返回 200 页面
代码结构
Js代码大概可以分为这几个部分:
从上到下依次是:
- 自己补的环境 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 = re.findall('<meta content="(.*?)"', response.text)[0] 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] 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);
|
缺啥补啥,先去浏览器看看这个东西是什么然后直接补,如果浏览器也显示找不到,那就不用管。
我们注意到document缺了createElement
先补一个这样的
1 2 3 4
| createElement = function () { return {}; }; document.createElement = createElement;
|
然后它报错
我们直接调试
这里改为上一个出现的属性即可
比如createElement之后报错了,我们就改为createElement,便于快速定位
进入debugger后一路单步往下走
吐槽一下pycharm: 进入到虚拟机之后就无法定位到代码在哪了,还得自己找,现在临时换到vscode进行调试
这里也是成功进入,这里看到_$jb是一个{}, {} 当然没有getElementsByTagName
属性了,所以我猜测是我们createElement之后的对象,缺少了这个属性
所以我们这里改一下hook代码
1 2 3 4
| createElement = function () { console.log('创建对象:',arguments[0]); return {}; };
|
再次执行
w
所以这里是要创建一个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'];
|
果然是这样!那我就需要针对div补一个getElementsByTagName属性即可
1 2 3 4 5
| getElementsByTagName = function(){ console.log('获取元素:',arguments[0]); return {} } div.getElementsByTagName = getElementsByTagName;
|
这里我们调试出了获取了元素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 []; } }
|
然后也补一下addEventListener
、attachEvent
这里我们一律补成null_func,假如有传参的话,它会自动输出,我们再针对性的补(参考刚才的获取i元素)
1 2
| window.addEventListener = null_func window.attachEvent = null_func
|
继续执行,再次报错
这里我们先补一下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
我们吸取上次记得经验,先不急着补Meta,看看它是不是对meta也做了什么手脚,所以开始调试
意思就是,它会读取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 []; } }
|
继续执行
直接补null_func即可
1 2
| document.addEventListener = null_func; document.attachEvent = null_func;
|
继续执行
getElementsByTagName获取了script,我们再按照刚才的流程,补一个script然后把它加上代理
针对性的补一个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 []; } }
|
继续执行
发现一直输出这些,陷入了死循环
我们直接置空这俩函数
1 2
| setTimeout = function () {}; setInterval = function () {};
|
再次执行
终于看到了想要的结果!
把抠出来的环境放到我们一开始说的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 = 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())
|
至此,Rs4补环境结束