使用frida获取小程序code


写在前面

最近接了个需求,某个老哥想让我帮忙恢复被删的聊天记录(虽然最后没有成功),获取手机端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