大家好,今天分享一个通过中间人的方式自动注入 JSRPC 脚本的方法。
JSRPC
JSRPC 的使用方法上篇文章已经讲过了,不知道怎么用的小伙伴可以先看看上篇文章。
上篇文章是使用的油猴拓展注入 JSRPC 脚本,如果需要频繁修改注入代码或者需要浏览器集群来跑自动化的话,使用油猴拓展就比较麻烦了。每个浏览器都要手动添加注入代码,如果有需要修改的地方还要依次修改。
这个时候可以考虑使用抓包软件的脚本功能自动将 JSRPC 注入到浏览器中。
脚本功能
我使用的抓包软件是 proxyman,它有一个脚本功能,可以针对请求的数据和响应的数据进行修改。其他的抓包软件应该也有类似的功能,大家可以自己找一下。
![image-20250325205917036]()
脚本功能在这里,在随意一个请求上面点右键,然后选择工具~脚本即可打开脚本编辑页面。
![image-20250325210015786]()
可以看到脚本里面由两个回调函数,分别是 onRequest
和 onResponse
,分别会在请求到达时和响应到达时调用。脚本中还带了一些简单的注释,可以对请求头、请求参数、请求体做一些新增、删除、修改操作,响应也是一样的。
自动注入 JSRPC
自动注入 JSRPC 代码,原理就是在收到响应的时候,对响应进行修改。正好可以使用脚本对网页源代码进行修改,网页源代码其实就是一些文本,可以使用 replace
函数对它进行替换。我的思路是:每个网页都有 head
标签,head
标签中可以存在 script
标签,可以将 JSRPC 的代码放到 head
标签中,以便于尽可能早的执行 JSRPC 注入代码。
思路说完了,接下来开始写代码:
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
| async function onRequest(context, url, request) { console.log(url);
return request; }
async function onResponse(context, url, request, response) { console.log(url); var text = `<head> <script> var rpc_client_id, Hlclient = function (wsURL) { this.wsURL = wsURL; this.handlers = { _execjs: function (resolve, param) { var res = eval(param) if (!res) { resolve("没有返回值") } else { resolve(res) } } }; this.socket = undefined; if (!wsURL) { throw new Error('wsURL can not be empty!!') } this.connect() } Hlclient.prototype.connect = function () { if (this.wsURL.indexOf("clientId=") === -1 && rpc_client_id) { this.wsURL += "&clientId=" + rpc_client_id } console.log('begin of connect to wsURL: ' + this.wsURL); var _this = this; try { this.socket = new WebSocket(this.wsURL); this.socket.onmessage = function (e) { _this.handlerRequest(e.data) } } catch (e) { console.log("connection failed,reconnect after 10s"); setTimeout(function () { _this.connect() }, 10000) } this.socket.onclose = function () { console.log('rpc已关闭'); setTimeout(function () { _this.connect() }, 10000) } this.socket.addEventListener('open', (event) => { console.log("rpc连接成功"); }); this.socket.addEventListener('error', (event) => { console.error('rpc连接出错,请检查是否打开服务端:', event.error); }) }; Hlclient.prototype.send = function (msg) { this.socket.send(msg) } Hlclient.prototype.regAction = function (func_name, func) { if (typeof func_name !== 'string') { throw new Error("an func_name must be string"); } if (typeof func !== 'function') { throw new Error("must be function"); } console.log("register func_name: " + func_name); this.handlers[func_name] = func; return true } Hlclient.prototype.handlerRequest = function (requestJson) { var _this = this; try { var result = JSON.parse(requestJson) } catch (error) { console.log("请求信息解析错误", requestJson); return } if (result["registerId"]) { rpc_client_id = result['registerId'] return } if (!result['action'] || !result["message_id"]) { console.warn('没有方法或者消息id,不处理'); return } var action = result["action"], message_id = result["message_id"] var theHandler = this.handlers[action]; if (!theHandler) { this.sendResult(action, message_id, 'action没找到'); return } try { if (!result["param"]) { theHandler(function (response) { _this.sendResult(action, message_id, response); }) return } var param = result["param"] try { param = JSON.parse(param) } catch (e) { } theHandler(function (response) { _this.sendResult(action, message_id, response); }, param) } catch (e) { console.log("error: " + e); _this.sendResult(action, message_id, e); } } Hlclient.prototype.sendResult = function (action, message_id, e) { if (typeof e === 'object' && e !== null) { try { e = JSON.stringify(e) } catch (v) { console.log(v)// } } this.send(JSON.stringify({"action": action, "message_id": message_id, "response_data": e})); } window.Hlclient = Hlclient; var demo = new Hlclient("ws://127.0.0.1:12080/ws?group=zzz"); </script>`; console.log(text) response.body=response.body.replace('<head>',text) return response; }
|
![image-20250325213337887]()
将上面的代码粘贴进编辑框内,别忘了给当前的规则起一个有意义的名称,并且设置好匹配规则,以便于只注入需要的网页,而不是对所有的网页都生效。
![image-20250325214257745]()
![image-20250325214151668]()
全部都设置好了以后,刷新网页,即可看到效果。对了,别忘了提前开启服务端哦。
连接成功以后,不用打开浏览器控制台,即可对需要注入的网站生效并且自动连接服务端,此时可以使用 http 请求在指定的网站执行特定的代码了。
更多用法
更多的用法可以参考官方文档,官方文档中给出了很多 proxyman 内置的脚本片段,可以在开发脚本的时候参考使用。
同时 proxyman 也可以用来作为 mock 服务器,可以使用脚本功能动态的返回响应,便于测试自己的 Api 接口。
总结
上面的方法适用于几乎所有的网站,如果有使用浏览器集群的需求,可以直接在抓包时自动注入 JSRPC 代码,搭配一些自动化工具,可以节省很多工作量。
如果嫌在脚本中修改 JSRPC 的代码麻烦的话,也可以将 JSRPC 的代码放到本地,在请求的时候由 proxyman 脚本读取本地文件然后注入到网页中,感兴趣的小伙伴可以自己试一下。
本文章首发于个人博客 LLLibra146’s blog
本文作者:LLLibra146
更多文章请关注公众号 (LLLibra146):![LLLibra146]()
版权声明:本博客所有文章除特别声明外,均采用 © BY-NC-ND 许可协议。非商用转载请注明出处!严禁商业转载!
本文链接:
https://blog.d77.xyz/archives/ff1fc3cc.html