事件背景
某银行微信小程序测试渗透项目中,流量抓包发现请求和响应包均被加密,渗透测试无法实施,因此开启了加密算法破解之旅。该加密流程的极具典型性,a) 加密算法涉及RSA非对称加密、AES对称加密。b) AES Key IV绑定session,需拿wx.login生成的code置换。c)wx.login code,需要应用Frida hook 微信客户端关键函数。d) 微信小程序参数加密,仅允许微信登录场景非常常见。
涉及到技术栈
• 加密算法基础知识:RSA 加密,AES 加密
• 微信小程序逆向
• 微信小程序登录认证流程
• Android APK frida hook
请求响应包的数据均被加密,想要解密数据需要反编译微信小程序 wxapkg,只能从代码层面梳理加密算法原理。流程如下:
1. 在微信客户端找到待分析wxapkg文件
2. 使用 PC 微信小程序一键解密工具解密wxapkg文件。
3. 使用wxappUnpacker脚本进行分包。./bingo.sh testpkg/.wxapkg -s=../master-xxx
4. 利用微信开发者工具查看调试明文代码。
拿到代码后开始静态代码分析来定位加密函数,全局搜索字符串,如:encrypt,decrypt,jiami,jiemi,AES,DES,RSA,AESKey或根据url,请求参数:login等定位加解密业务逻辑,有时候在浏览器 console 可能会产生调试日志等。
这次分析中采用AESKey搜索,找到如下代码:
function l(e) {
console.log("arguments:",arguments)
console.log("AES encrypt 参数:",a.globalData.aesKey_key, a.globalData.aesKey_iv)
......
}根据该代码,可确定加密算法为AES,加密参数存储在全局变量中a.globalData中,全局搜索对a.globalData.aesKey_key和a.globalData.aesKey_iv赋值操作的代码,字符串全局搜索定位到如下代码:
function g() {
return new Promise(function(s, r) {
o.wxLogin()().then(function(e) {
console.log("wxLogin 函数结果:" ,e)
a.globalData.appMyself_key = n.generateKeys();
var o = {
code: e.code,
publicKey: a.globalData.appMyself_key[0]
};
return console.log(o, "login-params"), c(t.login, o);
}).then(function(t) {
console.log(t, "登陆成功");
var o = e.addpasjiemi.jiemi(t.data.content.aesKey, a.globalData.appMyself_key[1]);
a.globalData.addpasjiemiAA = o, a.globalData.getSession = t.data.content.sessionId,
a.globalData.openId = t.data.content.id, a.globalData.aesKey_key = o.substring(0, 16),
a.globalData.aesKey_iv = o.substring(16), s(t);
}).catch(function(e) {
console.log(e, "登陆失败"), r(e);
});
});
}分析代码,梳理出调用流程:aesKey_iv 和 aesKey_key -> o 字符串 -> 函数e.addpasjiemi.jiemi(t.data.content.aesKey, a.globalData.appMyself_key[1])-> 因此需确定 t值和appMyself_key:appMyself_key根据 n.generateKeys()函数生成; t来源于c(t.login, o),跟进 c(t.login, o),其中o.code参数来源于微信登录接口wx.login返回的响应值。function c(a, t)代码如下:
function c(a, t) {
var o = i(t);
console.log(t, "--http-params--login-- 加密前");
var n = e.addpasjiemi.addPass(JSON.stringify(o), "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDFyD4xpy6JJG4bFGt4PdF7eDT4p9pKbDyz7zvyG7RgTKhNVm+mgmG4iuX04GLuBscin9g33LWm3586DimRkMXdMEIAA7lbfh7ADybG+rClhJmztWqIcJFDwCMUX8vaRg4uG5C+Vn6Pp7NeywDN/aDFxS9A/b83eZh2TeNDe/ywWQIDAQAB")
, s = l(wx.request);
return console.log(n, "data_mesage--login-- 加密后")
, s({
url: a,
data: n,
method: "POST",
header: {
"content-type": "application/json"
}
});
}函数 c(a, t)对t参数进行加密签名处理,根据分析可知道,aesKey_iv 和 aesKey_key间接从l(wx.request)的响应中获得。因此最核心的是拦截到wx.login的响应值code。
加密流程如下图所示:

session_key无效。经查阅微信官方文档.appId是本人的测试账号,因此无权限生成小程序appID对应的code. 开发服务器无法根据该code向腾讯接口换取session_key, 故失败。appid生成任意code,联系某论坛,要价 6000 RMB,放弃。一方面钱的问题,另外这是个骗子,腾讯安全团队应该不会发现不出来这个简单的业务逻辑漏洞。Frida主要用于Java层的hook,而小程序是由JS编写的,无法直接进行hook。安卓的WebView组件用于网页的解析和js执行,JSBridge可以支持js代码调用安卓的java代码,微信小程序特有的API则是由WxJsBridge提供的,因此以wx. 开头的API都能在这个框架中找到对应的Java代码,通过hook js api对应的Java代码可实现微信小程序的api hook。package com.tencent.mm.plugin.appbrand.jsapi.auth中的类JsApiLogin的子类LoginTask为wx.login 对应的Java代码。
发现asn()函数实现了登录流程,然而该函数没有参数和返回值,hook没有意义。
public final void asn() {
AppMethodBeat.i(46053);
final a aVar = new a() { // from class: com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin.LoginTask.3
@Override // com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin.LoginTask.a
public final void onSuccess(String str) {
AppMethodBeat.i(326809);
Log.i("MicroMsg.JsApiLogin", "onSuccess !");
LoginTask.this.code = str;
LoginTask.this.rFe = "ok";
LoginTask.a(LoginTask.this);
AppMethodBeat.o(326809);
}
....
}
}onSuccess函数str参数虽然拿到了code,但是内部函数无法直接hook。LoginTask.a(LoginTask.this) 这段引起了我的注意,该函数被调用前,LoginTask已经完成赋值操作,提取LoginTask的code值即可。
static /* synthetic */ boolean a(LoginTask loginTask) {
AppMethodBeat.i(46057);
boolean cpA = loginTask.cpA();
AppMethodBeat.o(46057);
return cpA;
}待hook的函数分析完毕,Frida javascript 测试代码较为简单,如下:
Java.perform(function(){
var InnerClasses = Java.use("com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin$LoginTask");
InnerClasses.a.overload('com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin$LoginTask').implementation = function (str){
var result = this.a(str);
console.log("Enter the LoginTask.a functions");
console.log("The result is",str.code.value)
return result;
}
})执行:frida -UF -l ./hookcode.js 小程序点击微信登录,code hook 成功。结果如下:
Script loaded successfully
Enter the LoginTask.a functions
The result is 051p7A0w3NE9a03EcS0w3etsp13p7A0R由于wx.login code 有效时间 5 分钟,调用一次即实现。this.a(str)验证函数要注释掉。导致的问题,微信小程序一直卡在登录中页面,不过没关系,我们已经拿到code, 自己实现代码换回AES_Key 和 AES_IV.
Java.perform(function(){
var InnerClasses = Java.use("com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin$LoginTask");
InnerClasses.a.overload('com.tencent.mm.plugin.appbrand.jsapi.auth.JsApiLogin$LoginTask').implementation = function (str){
//var result = this.a(str);
console.log("Enter the LoginTask.a functions");
console.log("The result is",str.code.value)
//return result;
}
})Javascript代码,即可获取AES_Key和 AES_IV。加密算法破解成功。
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。
