文章总结: 本文分享了针对微信小程序的jsrpc注入技巧,提供了适配小程序WebSocketAPI的完整代码实现,涵盖连接建立、消息处理及客户端注册流程。文章重点指出小程序注入需在appcontext下进行,并解决了反编译目录与运行时打包文件路径不一致导致的hook失效问题,对小程序安全测试具有较高的参考价值。 综合评分: 72 文章分类: 安全工具,渗透测试,逆向分析
凌曦安全:jsrpc小程序注入tips
原创
Syst1m Syst1m
凌曦安全
2026年2月26日 10:02 广东
本推文提供的信息、技术和方法仅用于教育目的。文中讨论的所有案例和技术均旨在帮助读者更好地理解相关安全问题,并采取适当的防护措施来保护自身系统免受攻击。
严禁将本文中的任何信息用于非法目的或对任何未经许可的系统进行测试。未经授权尝试访问计算机系统或数据是违法行为,可能会导致法律后果。
作者不对因阅读本文后采取的任何行动所造成的任何形式的损害负责,包括但不限于直接、间接、特殊、附带或后果性的损害。用户应自行承担使用这些信息的风险。我们鼓励所有读者遵守法律法规,负责任地使用技术知识,共同维护网络空间的安全与和谐。
2026新版体系课程直达链接:凌曦安全:2026新版课程正式上线(早鸟价时刻)
课程介绍:https://www.yuque.com/syst1m-/blog/lc3k6elv0zqhdal3
原版会有一些问题,结合这位师傅的代码,做了一些修改和完善
https://mp.weixin.qq.com/s/SyQXNkUDqaRnyH6gwmNJfQ
// 1. 微信小程序环境适配const isWechatMiniProgram = typeof wx !== 'undefined' && wx.connectSocket;
// 全局变量直接挂载到globalThisglobalThis.rpc_client_id = wx && wx.getStorageSync ? wx.getStorageSync('rpc_client_id') || '' : '';
// 2. 构造函数 - 适配小程序APIglobalThis.Hlclient = function (wsURL) { if (!wsURL) throw new Error('wsURL can not be empty!!');
this.wsURL = wsURL; this.handlers = { _execjs: function (resolve, param) { try { let res = eval(param); resolve(res || "没有返回值"); } catch (e) { resolve(`执行错误: ${e.message}`); } } }; this.socket = null; this.isWechat = isWechatMiniProgram; this.connected = false;
// 小程序环境初始化 if (this.isWechat) { this.initWechatEvents(); }
this.connect();};
// 3. 小程序专用事件初始化globalThis.Hlclient.prototype.initWechatEvents = function() { let _this = this;
// 监听WebSocket打开 wx.onSocketOpen(function(res) { console.log('微信WebSocket连接已打开'); _this.connected = true; // 发送注册消息 if (globalThis.rpc_client_id) { _this.send(JSON.stringify({ action: 'register', clientId: globalThis.rpc_client_id })); } });
// 监听消息 wx.onSocketMessage(function(res) { console.log('收到微信WebSocket消息:', res.data); _this.handlerRequest(res.data); });
// 监听错误 wx.onSocketError(function(err) { console.error('微信WebSocket错误:', err); _this.connected = false; _this.reconnect(); });
// 监听关闭 wx.onSocketClose(function(res) { console.log('微信WebSocket连接关闭'); _this.connected = false; _this.reconnect(); });};
// 4. 连接方法 - 小程序适配globalThis.Hlclient.prototype.connect = function () { let _this = this;
// 处理URL,添加clientId let connectURL = this.wsURL; if (connectURL.indexOf("clientId=") === -1 && globalThis.rpc_client_id) { connectURL += (connectURL.indexOf('?') === -1 ? '?' : '&') + "clientId=" + encodeURIComponent(globalThis.rpc_client_id); }
console.log('开始连接到:', connectURL);
if (this.isWechat) { // 微信小程序环境 if (this.socket) { try { wx.closeSocket(); } catch (e) {} }
wx.connectSocket({ url: connectURL, success: function() { console.log('微信WebSocket连接请求已发送'); }, fail: function(err) { console.error('微信WebSocket连接失败:', err); setTimeout(function() { _this.reconnect(); }, 3000); } });
this.socket = true; // 微信环境下,socket是一个状态标识
} else { // 浏览器环境 try { this.socket = new WebSocket(connectURL); this.socket.onopen = function() { console.log("WebSocket连接成功"); _this.connected = true; }; this.socket.onmessage = function(e) { _this.handlerRequest(e.data); }; this.socket.onclose = function() { console.log('WebSocket连接关闭'); _this.connected = false; _this.reconnect(); }; this.socket.onerror = function(err) { console.error('WebSocket错误:', err); _this.connected = false; }; } catch (e) { console.error("连接失败:", e); this.reconnect(); } }};
// 5. 重连方法globalThis.Hlclient.prototype.reconnect = function () { let _this = this; console.log("5秒后尝试重连..."); setTimeout(function() { _this.connect(); }, 5000);};
// 6. 发送消息方法globalThis.Hlclient.prototype.send = function (msg) { if (this.isWechat) { if (this.connected) { wx.sendSocketMessage({ data: msg, success: function() { console.log('消息发送成功'); }, fail: function(err) { console.error('消息发送失败:', err); } }); } else { console.warn('连接未就绪,无法发送消息'); setTimeout(() => { this.send(msg); }, 1000); } } else { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(msg); } }};
// 7. 其他方法保持不变,但需要适配globalThis.rpc_client_idglobalThis.Hlclient.prototype.regAction = function (func_name, func) { if (typeof func_name !== 'string') throw new Error("func_name must be string"); if (typeof func !== 'function') throw new Error("must be function"); console.log("注册函数:", func_name); this.handlers[func_name] = func; return true;};
globalThis.Hlclient.prototype.handlerRequest = function (requestJson) { let _this = this; let result = null; try { console.log('处理请求:', requestJson); result = JSON.parse(requestJson);
// 处理注册ID if (result["registerId"] || result["clientId"]) { globalThis.rpc_client_id = result["registerId"] || result["clientId"]; console.log('收到clientId:', globalThis.rpc_client_id);
// 小程序环境保存到storage if (this.isWechat && wx.setStorageSync) { wx.setStorageSync('rpc_client_id', globalThis.rpc_client_id); }
// 发送确认消息 _this.send(JSON.stringify({ action: 'register_ack', clientId: globalThis.rpc_client_id, status: 'success' }));
return; }
// 处理常规请求 if (!result['action'] || !result["message_id"]) { console.warn('无效的请求:', result); return; }
let action = result["action"], message_id = result["message_id"], param = result["param"];
try { if (typeof param === 'string') { param = JSON.parse(param); } } catch (e) { }
let handler = this.handlers[action]; if (!handler) { console.warn('未找到处理函数:', action); return this.sendResult(action, message_id, 'Action not found'); }
// 执行处理函数 try { handler(function (response) { _this.sendResult(action, message_id, response); }, param); } catch (e) { console.error('执行处理函数出错:', e); _this.sendResult(action, message_id, `执行错误: ${e.message}`); }
} catch (error) { console.error("处理请求出错:", error); if (result && result.message_id) { this.sendResult(result.action || '', result.message_id, error.message); } }};
globalThis.Hlclient.prototype.sendResult = function (action, message_id, data) { let response; if (typeof data === 'object') { try { response = JSON.stringify(data); } catch (e) { response = String(data); } } else { response = String(data); }
let resultMsg = JSON.stringify({ action: action, message_id: message_id, response_data: response });
console.log('发送响应:', resultMsg); this.send(resultMsg);};globalThis.socket = new Hlclient("ws://127.0.0.1:12080/ws?group=two");
globalThis.one = this.sm2Encode; // globalThis.【第一次无需变化】 = 要注入的方法名globalThis.socket.regAction("two", function (resolve, param) {try {let result = globalThis.one(param['data']); // param 部分 填参数 resolve(result); } catch (e) { resolve(`调用失败: ${e.message}`); }});
// 第一次注册可以使用上述代码直接复制粘贴, 第二次往后需要单独注册globalThis.two = this.tempKey; // globalThis.【需要变化】 = 要注入的方法名globalThis.socket.regAction("one", function (resolve, param) { // 注册的第一个参数名称不能与之前相同.try {let result = globalThis.two(); // globalThis.two 与之前注册的一样, param 部分 填参数 resolve(result); } catch (e) { resolve(`调用失败: ${e.message}`); }});
小程序需要在 appcontext 下注入
注意事项:
使用wedecode反编译出来是完整的js文件以及目录
如图:
但是强开f12调试的目录,则会变为,全部打包进appContext下的app-server.js文件中,所以在进行hook需要加载外部模块的时候,需要使用app-server.js文件中的路径,反编译的目录是不生效的。
一些细节
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:凌曦安全 Syst1m Syst1m《凌曦安全:jsrpc小程序注入tips》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。









评论