职业经验 奇奇怪怪的需求之微信小程序逆向工程完整指南:从获取到解密再到反编译

编程梦想佳 · 2026年01月06日 · 11544 次阅读

奇奇怪怪的需求之微信小程序逆向工程完整指南:从获取到解密再到反编译

引言

"xxx 啊,听说你是咱们部门最会 ‘搞事情’?老板让咱们研究一下小程序,看看人家是怎么实现那个酷炫功能的...,然后看看怎么防护?"

周一早上,我正对着电脑屏幕上的猫咪视频傻笑,突然收到了产品经理发来的消息。作为一个资深摸鱼专家,我的第一反应是:这活能不能推给别人?但看到消息末尾的"老板说"三个字,我瞬间像被浇了一盆冷水——摸鱼计划泡汤了。

"可是老板,咱们没有人家的源代码啊..."我试图垂死挣扎。
"不会逆向工程吗?年轻人要多学习嘛!"产品经理的回复让我彻底放弃了抵抗。

于是,我被迫踏上了微信小程序逆向工程的不归路。经过一番摸爬滚打,我终于从一个逆向小白成长为"半吊子专家"。今天,我就把这段"血泪史"整理成一份完整指南,希望能帮助那些和我一样"被迫营业"的摸鱼人们少走弯路...

微信小程序凭借其无需安装、即开即用的特性,已经成为移动互联网生态中不可或缺的一部分。作为开发者,了解微信小程序的内部结构和实现原理,不仅可以帮助我们更好地开发自己的小程序,还能从优秀的小程序中学习到先进的设计理念和技术实现。

本文将为大家提供一份完整的微信小程序逆向工程指南,涵盖从获取小程序包(.wxapkg)、解密到最终反编译的全过程,结合 PC 端和移动端两种获取方式,帮助大家轻松掌握微信小程序逆向。

一、获取微信小程序包(.wxapkg)

1.1 安卓手机 Root 方法

如果您的安卓手机已经获取了 Root 权限,可以直接从手机中提取小程序包:

  1. 使用 RE 管理器或其他具有 Root 权限的文件管理器
  2. 导航到目录:/data/data/com.tencent.mm/MicroMsg//appbrand/pkg/
  3. 在此目录下,您会发现一些以 .wxapkg 为后缀的文件,这些就是微信小程序的安装包
  4. 可以根据文件修改时间判断哪个是您需要的小程序包

1.2 安卓模拟器方法(无需 Root)

如果您不想 Root 手机,可以使用安卓模拟器来获取小程序包:

  1. 安装安卓模拟器(推荐夜神模拟器)
  2. 在模拟器中安装 QQ、微信和 RE 管理器
  3. 设置模拟器的超级用户权限,确保 RE 管理器能获取到 Root 权限
  4. 在模拟器中打开微信,运行您想要获取的小程序(这会让微信将小程序包下载到本地)
  5. 切换到 RE 浏览器,导航到目录:/data/data/com.tencent.mm/MicroMsg//appbrand/pkg/
  6. 找到对应的 .wxapkg 文件,长按压缩后通过 QQ 发送到电脑

1.3 Windows PC 端方法(无需手机)

除了移动端,我们也可以从 Windows PC 端的微信中获取小程序包。不过需要注意的是,PC 端的 wxapkg 包是被加密存储的:

  1. 打开 PC 端微信,运行您想要获取的小程序
  2. 小程序包通常存储在以下路径:C:\Users\[用户名]\Documents\WeChat Files\Applet\[小程序ID]\_APP_.wxapkg
  3. 获取小程序 ID:路径中的文件夹名称就是小程序 ID(如:wx2xxx84w9w7a3xxxx)

二、PC 端 wxapkg 包解密

2.1 加密原理

PC 端微信对 wxapkg 包进行了加密处理,加密后的文件以 V1MMWX 开头。加密过程分为两步:

  1. AES 加密:

    • 使用 PBKDF2 算法生成 AES 密钥
    • 密码:微信小程序 ID 字符串
    • Salt:saltiest
    • 迭代次数:1000
    • 生成一个 32 位的密钥
    • 使用此密钥和 IV(the iv: 16 bytes)对原始 wxapkg 包的前 1023 个字节进行 AES 加密
  2. XOR 加密:

    • 使用微信小程序 ID 字符串的倒数第 2 个字符作为 XOR 密钥
    • 对 1023 字节后的所有数据依次进行 XOR 运算
    • 如果小程序 ID 长度小于 2 位,则 XOR 密钥为 0x66
  3. 文件组装:

    • 在文件头部添加 V1MMWX 标识
    • 将 AES 加密后的数据(1024 字节)和 XOR 后的数组合并写入文件

2.2 解密工具使用

使用专门的解密工具 pc_wxapkg_decrypt.exe(360 报毒,我也就不提供下载了,有需要的可以自己搜索下载) 可以轻松解密 PC 端的 wxapkg 包:

命令行参数

pc_wxapkg_decrypt.exe -wxid 微信小程序id -in 要解密的wxapkg路径 -out 解密后的路径

详细参数说明:

  • -in:需要解密的 wxapkg 文件路径(默认:__APP__.wxapkg)
  • -out:解密后的 wxapkg 文件路径(默认:dec.wxapkg)
  • -wxid:小程序的 ID(必需参数)
  • -iv:AES 加密的 IV,默认不需要设置(默认:the iv: 16 bytes)
  • -salt:PBKDF2 用到的 salt,默认不需要设置(默认:saltiest)

使用示例

pc_wxapkg_decrypt.exe -wxid wx2xxx84w9w7a3xxxx -in "C:\Users\xxx\Documents\WeChat Files\Applet\wx2xxx84w9w7a3xxxx\_APP_.wxapkg" -out "D:\decrypted.wxapkg"

三、微信小程序反编译

获取并解密 wxapkg 包后,我们需要使用反编译工具将其转换为可读的源代码。这里推荐使用 CrackMinApp 工具。(360 报毒,我也就不提供下载了,有需要的可以自己搜索下载)

3.1 CrackMinApp 工具介绍

CrackMinApp 是一款图形化的微信小程序反编译工具,使用 C# 和 Node.js 开发,主要特点包括:

  • 一键获取微信小程序源码
  • 图形化操作界面,易于使用
  • 已配置好所需依赖,无需额外安装
  • 修复了原反编译脚本的部分问题

3.2 使用步骤

  1. 准备工作:

    • 将工具解压到 D 盘根目录
    • 将解密后的 wxapkg 文件放到 wxapkg 目录下
  2. 运行反编译:

    • 打开 CrackMinApp.exe
    • 按照界面提示选择要反编译的 wxapkg 文件
    • 点击开始按钮,等待反编译完成
  3. 查看结果:

    • 反编译完成后,会在指定目录生成小程序的源代码
    • 包含 WXML、WXSS、JS 等文件,可以直接查看和编辑

3.3 注意事项

  • 为避免文件名称过长导致错误,建议将文件复制到根目录下的新建文件夹中
  • 工具可能存在版本兼容性问题,如果遇到问题可以查看更新日志或尝试其他反编译工具
  • 反编译后的代码可能与原始代码存在差异,需要进行适当的调整和修复

四、总结与注意事项

本文为大家提供了一份完整的微信小程序逆向工程指南,涵盖了从获取小程序包、解密到反编译的全过程。无论是移动端还是 PC 端,我们都可以通过相应的方法获取到小程序包,并使用专门的工具进行解密和反编译。

4.1 技术要点回顾

  1. 获取方式多样化:支持安卓手机 Root、安卓模拟器和 Windows PC 端三种获取方式
  2. PC 端加密机制:采用 AES+XOR 的双重加密方式,需要使用小程序 ID 进行解密
  3. 工具选择:
    • 解密工具:pc_wxapkg_decrypt.exe(PC 端)
    • 反编译工具:CrackMinApp

参考资料:

  • PC 端 wxapkg 解密原理与工具
  • CrackMinApp 微信小程序反编译工具
  • 微信小程序开发文档

五、COCOS 引擎反编译

COCOS 小程序目录主要结构:
- assets/:存放自定的资源文件(图片、音频、视频等)
- jsb-default/:存放默认的 JSB 文件(包含小程序的主要逻辑)
- project.json:项目配置文件,包含小程序的元数据和设置
- src/:存放源代码文件(主要是 JS 文件)
- wxss/:存放 WXSS 样式文件(与 WXML 文件对应)
- wxml/:存放 WXML 视图文件(与 JS 文件对应)

src 的分析就不累述了,主要是 JS 文件,懂的都懂,如果是图片资源, 主要是在 assets 目录下。我这主要做了一个脚本,识别看看有没特别大的图,如果超过阀值,就记录下来,方便后续分析。

六、UNITY 引擎反编译

在微信小程序逆向工程中,我们经常会遇到使用 Brotli 压缩算法压缩的 .br 文件。这些文件通常包含了小程序的关键资源,如代码、图片和数据。默认情况下,我们可能需要手动一个个处理这些文件,这在文件数量较多时会变得非常繁琐且低效。

我在处理一个微信小程序逆向项目时,发现目录下有多个 .br 文件需要解密。原始的 decrypt_brotli.py 脚本只能处理单个文件,这显然无法满足批量处理的需求。因此,我决定对脚本进行改造,使其能够自动搜索并批量处理所有 .br 文件。

1. 脚本分析

原始的 decrypt_brotli.py 脚本主要包含以下功能:

  • 导入必要的库(os 和 brotli)
  • 定义 decrypt_brotli_file 函数用于解密单个 .br 文件
  • 在 main 函数中指定单个文件路径并解密

2. 改造思路

为了实现批量处理,我需要:

  1. 添加一个函数来递归搜索目录中的所有 .br 文件
  2. 修改 main 函数以支持批量处理
  3. 添加进度更新和统计功能,提高用户体验
  4. 增强错误处理,确保脚本在遇到问题时不会崩溃

3. 具体实现

3.1 递归搜索 .br 文件

首先,我添加了一个 find_br_files 函数,用于在指定目录中递归查找所有 .br 文件:

def find_br_files(directory):
    """在指定目录中递归查找所有.br文件"""
    br_files = []
    print(f"正在搜索 {directory} 目录中的.br文件...")
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith('.br'):
                br_files.append(os.path.join(root, file))
    print(f"找到 {len(br_files)} 个.br文件")
    return br_files

这个函数使用 os.walk 遍历目录树,检查每个文件是否以 .br 结尾,并将符合条件的文件路径添加到列表中。

3.2 批量处理与统计

接下来,我修改了 main 函数,使其能够:

  • 调用 find_br_files 函数查找所有 .br 文件
  • 遍历所有找到的文件并逐个解密
  • 记录成功和失败的数量
  • 提供清晰的进度更新和最终统计
def main():
    """主函数"""
    # 定义搜索目录
    search_dir = r'目录'

    # 查找所有.br文件
    br_files = find_br_files(search_dir)

    if not br_files:
        print("没有找到.br文件,程序结束")
        return

    # 统计结果
    total_files = len(br_files)
    success_count = 0
    fail_count = 0

    print(f"\n开始解密 {total_files} 个.br文件...")
    print("=" * 60)

    # 逐个解密文件
    for i, br_file in enumerate(br_files, 1):
        print(f"\n[{i}/{total_files}] 处理文件:")
        if decrypt_brotli_file(br_file):
            success_count += 1
        else:
            fail_count += 1

    # 输出结果统计
    print("\n" + "=" * 60)
    print("解密结果统计:")
    print(f"总文件数:{total_files}")
    print(f"成功:{success_count}")
    print(f"失败:{fail_count}")
    print(f"成功率:{success_count/total_files*100:.2f}%")
    print("\n所有操作完成!")

3.3 错误处理增强

为了确保脚本的稳定性,我还增强了 decrypt_brotli_file 函数的错误处理能力:

def decrypt_brotli_file(br_file):
    """解密单个.br文件"""
    try:
        # 构建输出文件路径
        output_file = br_file[:-3]  # 移除.br扩展名

        print(f"  输入文件: {br_file}")
        print(f"  输出文件: {output_file}")

        # 读取压缩文件
        with open(br_file, 'rb') as f:
            compressed_data = f.read()

        # 获取压缩前的文件大小
        compressed_size = len(compressed_data)
        print(f"  压缩大小: {compressed_size / 1024 / 1024:.2f} MB")

        # 解压缩
        decompressed_data = brotli.decompress(compressed_data)

        # 获取解压缩后的文件大小
        decompressed_size = len(decompressed_data)
        print(f"  解压大小: {decompressed_size / 1024 / 1024:.2f} MB")
        print(f"  压缩率: {compressed_size / decompressed_size * 100:.2f}%")

        # 写入解压缩文件
        with open(output_file, 'wb') as f:
            f.write(decompressed_data)

        print("  解密成功!")
        return True

    except Exception as e:
        print(f"  解密失败: {str(e)}")
        return False

结果展示

修改后的脚本运行结果如下:

正在搜索 G:\localgit\wechatMiniAppReverse-main\out 目录中的.br文件...
找到 3 个.br文件

开始解密 3 个.br文件...
============================================================

[1/3] 处理文件:
  输入文件: 小程序目录\80ce864cae15e82f.webgl.data.unityweb.bin.br
  输出文件: 小程序目录\80ce864cae15e82f.webgl.data.unityweb.bin
  压缩大小: 3.90 MB
  解压大小: 12.50 MB
  压缩率: 31.20%
  解密成功!

[2/3] 处理文件:
  输入文件: 小程序目录\35cd412081ec83c5.webgl.wasm.code.unityweb.wasm.br
  输出文件: 小程序目录\35cd412081ec83c5.webgl.wasm.code.unityweb.wasm
  压缩大小: 5.60 MB
  解压大小: 40.50 MB
  压缩率: 13.83%
  解密成功!

[3/3] 处理文件:
  输入文件: 小程序目录\35cd412081ec83c5.webgl.wasm.code.unityweb.wasm.br
  输出文件: 小程序目录\35cd412081ec83c5.webgl.wasm.code.unityweb.wasm
  压缩大小: 3.90 MB
  解压大小: 18.60 MB
  压缩率: 20.97%
  解密成功!

============================================================
解密结果统计:
总文件数:3
成功:3
失败:0
成功率:100.00%

所有操作完成!

完整代码

最后,附上完整的 decrypt_brotli.py 脚本代码:

import os
import brotli

def decrypt_brotli_file(br_file):
    """解密单个.br文件"""
    try:
        # 构建输出文件路径
        output_file = br_file[:-3]  # 移除.br扩展名

        print(f"  输入文件: {br_file}")
        print(f"  输出文件: {output_file}")

        # 读取压缩文件
        with open(br_file, 'rb') as f:
            compressed_data = f.read()

        # 获取压缩前的文件大小
        compressed_size = len(compressed_data)
        print(f"  压缩大小: {compressed_size / 1024 / 1024:.2f} MB")

        # 解压缩
        decompressed_data = brotli.decompress(compressed_data)

        # 获取解压缩后的文件大小
        decompressed_size = len(decompressed_data)
        print(f"  解压大小: {decompressed_size / 1024 / 1024:.2f} MB")
        print(f"  压缩率: {compressed_size / decompressed_size * 100:.2f}%")

        # 写入解压缩文件
        with open(output_file, 'wb') as f:
            f.write(decompressed_data)

        print("  解密成功!")
        return True

    except Exception as e:
        print(f"  解密失败: {str(e)}")
        return False

def find_br_files(directory):
    """在指定目录中递归查找所有.br文件"""
    br_files = []
    print(f"正在搜索 {directory} 目录中的.br文件...")
    for root, dirs, files in os.walk(directory):
        for file in files:
            if file.endswith('.br'):
                br_files.append(os.path.join(root, file))
    print(f"找到 {len(br_files)} 个.br文件")
    return br_files

def main():
    """主函数"""
    # 定义搜索目录
    search_dir = r'小程序目录'

    # 查找所有.br文件
    br_files = find_br_files(search_dir)

    if not br_files:
        print("没有找到.br文件,程序结束")
        return

    # 统计结果
    total_files = len(br_files)
    success_count = 0
    fail_count = 0

    print(f"\n开始解密 {total_files} 个.br文件...")
    print("=" * 60)

    # 逐个解密文件
    for i, br_file in enumerate(br_files, 1):
        print(f"\n[{i}/{total_files}] 处理文件:")
        if decrypt_brotli_file(br_file):
            success_count += 1
        else:
            fail_count += 1

    # 输出结果统计
    print("\n" + "=" * 60)
    print("解密结果统计:")
    print(f"总文件数:{total_files}")
    print(f"成功:{success_count}")
    print(f"失败:{fail_count}")
    print(f"成功率:{success_count/total_files*100:.2f}%")
    print("\n所有操作完成!")

if __name__ == "__main__":
    main()

分析内容

src 源码部分就不展示了,目前提出的需求主要是分析图片资源,src 暂时还没提出需求,留到另一篇 blog 吧。

这里主要先说说图片资源

1. 准备工作

我们已经完成了前期处理,得到了 bin 文件, AssetStudio 这款工具下载地址:AssetStudio

2. 打开 AssetStudio

  1. 双击运行 AssetStudioGUI.exe
  2. 你将看到 AssetStudio 的主界面

3. 加载 Unity 资源包

  1. 点击菜单栏的 File → Load File
  2. 在文件选择对话框中,导航到 小程序目录
  3. 选择 xxxx.webgl.data.unityweb.bin 文件,点击 打开

4. 等待资源加载

AssetStudio 会自动解析资源包,并在左侧面板显示资源类型:

  • Assets:所有资源
  • Animations:动画资源
  • AudioClips:音频资源
  • Fonts:字体资源
  • Materials:材质资源
  • Meshes:模型资源
  • Shader:着色器资源
  • TextAssets:文本资源
  • Textures:图片资源(我们主要关注这个)

5. 选择要提取的图片

  1. 点击左侧的 Textures 选项卡
  2. 你将看到资源包中所有的图片资源
  3. 选择你想要提取的图片(可以按 Ctrl+A 全选)

6. 提取图片

  1. 右键点击选中的图片,选择 Export selected assets
  2. 在弹出的对话框中,选择保存路径
  3. 点击 确定,等待提取完成

7. 查看提取的图片

  1. 打开你选择的保存路径
  2. 你将看到提取出的图片文件,格式通常为 PNG 或 JPEG
  3. 这些图片可以直接使用图片查看器打开

更新作者:zasdsd
更新日期:2026年1月6日

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册。