对某旅行APP的逆向分析
本文分析了一种技术架构的实现细节,包括基于开源库的改写、自定义SSL处理、Protobuf解析、Zstd或GZip压缩方案、XOR或AES-CBC加密方式以及随机数与魔改MD5生成Token的方法。同时探讨了TCP分析流程、加密过程及Protobuf反序列化的实现细节。 2025-6-30 02:5:51 Author: www.freebuf.com(查看原文) 阅读量:39 收藏

freeBuf

主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

技术架构

  • 采用多种开源库进行改写
  • 怀疑下一步将使用开源ssl.so在so里进行自写TCP发包
  • protobuffer解析使用io.protobuf自写魔改(字段处理)
  • 压缩采用两套方案:Zstd或GZip
    • Zstd是Facebook开源的压缩库,采用固定等级三
  • token生成:随机数+魔改MD5签名
  • 加解密:XOR或AEScbc两套方案

TCP分析流程

当抓包抓不到包甚至失败的链接都没有时,直接怀疑TCP

Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function(fd, bytearry, offset, byteCount, timeout) {
    var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
    let base64_str = base64_enc(bytearry);
    if(base64_str.length < 1000 || base64_str.indexOf("AAAAAAAAAAA") > -1) {
        return result;
    }
    showStacks();
    console.log("get data: "+ base64_str)
    return result;
}

Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function(fd, bytearry, offset, byteCount) {
    var result = this.socketWrite0(fd, bytearry, offset, byteCount);
    showStacks();
    console.log("\nsend data: "+ base64_enc(bytearry))
    return result;
}

通过hook SocketInputStream、SocketOutputStream方法打印堆栈可以定位到目标方法

TCP分析截图1

TCP分析截图2

加密过程分析

buileRequest有三种情况的返回,基本走的都是getRequestDataBeanV6

加密分析截图1

V6中可以看到返回值,以及发送的实际是返回对象的totelData属性

加密分析截图2

加密流程中ListUtil.combineByteArr作用是连接合并两个数组

buileRequestHeadOfPrefixV6是头信息,传入的是encode长度+6和加密方式代表的数字

加密分析截图3

encode的产生方式有三种,通过hook确定加密方式用的encodeByXor,压缩用的getCompressProvider().compress

加密分析截图4

encodeByXor实现:

public static byte[] encodeByXor(byte[] bArr) {
    if(bArr == null || bArr.length < 1) {
        return bArr;
    }
    byte[] bArr2 = new byte[bArr.length];
    for(int i12 = 0; i12 < bArr.length; i12++) {
        bArr2[i12] = (byte) (bArr[i12] ^ -1);
    }
    return bArr2;
}

压缩接口定义:

public interface SOTPCompressProvider {
    byte[] compress(byte[] bArr) throws Exception;
    byte[] uncompress(byte[] bArr) throws Exception;
}

实际调用了CTZ.a方法,使用的是Facebook开源的zstd压缩库,压缩等级固定为3

protobuff反序列化分析

buileRequestHeadV6方法分析:

protobuff分析截图1

clientToken是前面提到的魔改MD5对随机数进行签名

Serialize.writeMessage将对象序列化成字节数组,基于io.protostuff库但做了魔改

RequestHead类定义示例:

public class RequestHead extends BusinessBean {
    @ProtoBufferField(label = ProtoBufferField.Label.OPTIONAL, tag = 14, type = ProtoBufferField.Datatype.STRING)
    public String appId;
    // 其他字段...
    public RequestHead() {
        AppMethodBeat.i(62638);
        this.extentionList = new ArrayList<>();
        AppMethodBeat.o(62638);
    }
}

BusinessBean基类:

public class BusinessBean implements Serializable, Cloneable {
    public static ChangeQuickRedirect changeQuickRedirect = null;
    @NO_PERSISTENCE
    private static final long serialVersionUID = 1;
    private String cacheKey;
    @NO_PERSISTENCE
    protected byte[] dataBody;
    // 其他字段...
}

与标准io.protostuff库的区别在于对方法标签做了更多判断,根据不同的判断结果调用不同的方法

protobuff分析截图2

代码中存在大量性能监控的冗余代码

冗余代码截图1

冗余代码截图2

本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)


文章来源: https://www.freebuf.com/articles/437113.html
如有侵权请联系:admin#unsafe.sh