0x00 前言
事件背景
某银行微信小程序测试渗透项目中,流量抓包发现请求和响应包均被加密,渗透测试无法实施,因此开启了加密算法破解之旅。该加密流程的极具典型性,a) 加密算法涉及RSA
非对称加密、AES
对称加密。b) AES Key IV
绑定session
,需拿wx.login
生成的code
置换。c)wx.login code
,需要应用Frida hook
微信客户端关键函数。d) 微信小程序参数加密,仅允许微信登录场景非常常见。
涉及到技术栈
-
• 加密算法基础知识:RSA加密,AES加密
-
• 微信小程序逆向
-
• 微信小程序登录认证流程
-
• Android APK frida hook
0x01 微信小程序逆向
请求响应包的数据均被加密,想要解密数据需要反编译微信小程序wxapkg,只能从代码层面梳理加密算法原理。流程如下:
-
1. 在微信客户端找到待分析
wxapkg
文件 -
2. 使用PC微信小程序一键解密工具解密
wxapkg
文件。 -
3. 使用
wxappUnpacker
脚本进行分包。./bingo.sh testpkg/.wxapkg -s=../master-xxx
-
4. 利用微信开发者工具查看调试明文代码。
0x02 加密认证流程分析
拿到代码后开始静态代码分析来定位加密函数,全局搜索字符串,如: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
。
加密流程如下图所示:
0x03 Frida hook wx.login认证流程
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;
}
})
0x04 代码实现
Javascript
代码,即可获取AES_Key
和 AES_IV
。加密算法破解成功。0x05 安全建议
原文始发于微信公众号(Numen Cyber Labs):某微信小程序参数加密算法分析