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

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

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

引言

"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 脚本主要包含以下功能:

  • 导入必要的库(osbrotli
  • 定义 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. 点击菜单栏的 FileLoad 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日

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