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

引言

"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 加密:

  2. XOR 加密:

  3. 文件组装:

2.2 解密工具使用

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

命令行参数

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

详细参数说明:

使用示例

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. 准备工作:

  2. 运行反编译:

  3. 查看结果:

3.3 注意事项

四、总结与注意事项

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

4.1 技术要点回顾

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

参考资料:

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

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 函数,使其能够:

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 会自动解析资源包,并在左侧面板显示资源类型:

5. 选择要提取的图片

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

6. 提取图片

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

7. 查看提取的图片

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

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


↙↙↙阅读原文可查看相关链接,并与作者交流