写在前面
最近接了个需求,某个老哥想让我帮忙恢复被删的聊天记录(虽然最后没有成功),获取手机端wx数据库密码时遇到了点挫折,有时候imei+uin进行MD5加密后再取前7位的逻辑不对,别人提供的终极解决方案是使用frida进行Hook,直接把密码打印出来。
虽然最后我没有采用那个方法,但是试了下用frida获取微信小程序code,还是比较方便的,搜了别人的代码,并修改了一些因微信版本更新产生的错误。
怎么安装frida就自行百度吧。
代码
以下代码在微信电脑版3.7.0.30测试通过。
import frida
import sys
session = frida.attach('WeChat.exe')
script_js_demo = """
/*
* @name 微信小程序PC版 wxapkg提取
* @author ljc545w 基于`代码果`版修改
*/
var WeChatWinAddr = Module.findBaseAddress('WeChatWin.dll');
console.log('WeChatWin.dll baseAddr: ' + WeChatWinAddr);
// 现在不再使用WeChatAppHost.dll了
var HostAddr = Module.findBaseAddress('libruntime_host_export.dll');
console.log('libruntime_host_export.dll baseAddr: ' + HostAddr);
if (HostAddr) {
var EncryptBufToFile = Module.findExportByName('libruntime_host_export.dll','EncryptBufToFile');
console.log('EncryptBufToFile 函数地址: ' + EncryptBufToFile);
// HOOK函数, 监听参数
Interceptor.attach(EncryptBufToFile, {
onEnter: function (args) {
// 微信小程序AppId
this.appId = ptr(args[0]).readAnsiString();
console.log('Appid: ', this.appId);
// 微信小程序本地缓存文件路径
this.apkgFilePath = ptr(args[1]).readAnsiString();
// 小程序代码原始内容(未加密)
this.originalData = Memory.readByteArray(args[2], args[3].toInt32());
},
onLeave: function (retval) {
console.log('文件解密成功: ', this.apkgFilePath);
// 将文件替换为未加密的wxapkg包
var f = new File(this.apkgFilePath + '.bak', 'wb');
f.write(this.originalData);
f.flush();
f.close();
console.log('文件已保存至: ', this.apkgFilePath + '.bak');
// 释放内存
delete this.appId;
delete this.apkgFilePath;
delete this.originalData;
}
});
} else {
console.log('libruntime_host_export.dll 模块未加载, 请先打开界面中的小程序面板');
}
"""
script = session.create_script(script_js_demo)
def on_message(message,data):
print(message)
script.on('message',on_message)
# 开始hook
script.load()
sys.stdin.read()
# 结束hook
# script.unload()
解密
上面保存的就是未加密的小程序代码包,使用相应的解包工具进行下一步工作就行了。不过有时候不想hook,也可以直接通过代码解密:
import os
from Crypto.Cipher import AES
from Crypto.Protocol.KDF import PBKDF2
from Crypto.Hash import SHA1
V1MMWX = 'V1MMWX'
V1MMWX_LEN = len(V1MMWX)
# 通过pbkdf2和AES生成密钥
def get_cipher(appid,salt = 'saltiest',iv = 'the iv: 16 bytes'):
# 创建key,迭代1000次取32位
key = PBKDF2(appid.encode(),salt.encode(),32,1000,hmac_hash_module = SHA1)
cipher = AES.new(key,AES.MODE_CBC,iv.encode())
return cipher
# appid可以在加密程序包存储路径中找到,一般是以wx开头
def decrypt(appid,apkg_path,save_path = None):
if not os.path.exists(apkg_path):
print("文件不存在",apkg_path)
return False
with open(apkg_path,"rb") as f:
dataByte = f.read()
if dataByte[0:V1MMWX_LEN].decode() != V1MMWX:
print("文件无需解密或不是wxapkg文件")
return False
cipher = get_cipher(appid)
originData = cipher.decrypt(dataByte[V1MMWX_LEN: 1024 + V1MMWX_LEN])
xor_key = ord(appid[-2]) if len(appid) >= 2 else 0x66
afData = dataByte[1024 + V1MMWX_LEN:]
out = bytearray()
for i in range(len(afData)):
out.append(afData[i] ^ xor_key)
originData = originData + out
with open(save_path or apkg_path + '.bak', mode='wb') as f:
f.write(originData)
print('解密成功', save_path)
return True