技术架构
- 采用多种开源库进行改写
- 怀疑下一步将使用开源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方法打印堆栈可以定位到目标方法


加密过程分析
buileRequest有三种情况的返回,基本走的都是getRequestDataBeanV6

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

加密流程中ListUtil.combineByteArr作用是连接合并两个数组
buileRequestHeadOfPrefixV6是头信息,传入的是encode长度+6和加密方式代表的数字

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

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方法分析:

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库的区别在于对方法标签做了更多判断,根据不同的判断结果调用不同的方法

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


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



