对某旅行APP的逆向分析

admin 2025年4月21日22:15:28评论16 views字数 6080阅读20分16秒阅读模式

目标APP 采用了很多开源库进行改写,怀疑下一步就是拿开源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) {
varresult = this.socketRead0(fd, bytearry, offset, byteCount, timeout);
let base64_str = base64_enc(bytearry);
if(base64_str.length < 1000 || base64_str.indexOf("AAAAAAAAAAA") > -1) {
returnresult;
}
showStacks();
console.log("get data: "+ base64_str)
returnresult;
}
Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor''[B''int''int').implementation = function(fd, bytearry, offset, byteCount) {
varresult = this.socketWrite0(fd, bytearry, offset, byteCount);
showStacks();
console.log("nsend data: "+ base64_enc(bytearry))
returnresult;
}

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

对某旅行APP的逆向分析

对某旅行APP的逆向分析

 搜一下,果然

对某旅行APP的逆向分析

 理论上建议大家先从buileResponse来,因为 buileRequest 有可能会存在随机数等情况造成同一个值加密后的结果不一致,而解密响应体,必然不可能随机,只有解成功和解失败

 这里 buileRequest 的坑比较多,就从这里开始吧

 这里可以看到 buileRequest 有三种情况的返回,挨个hook 就会发现基本走的都是 getRequestDataBeanV6

对某旅行APP的逆向分析

 V6 这里就很明显可以看到返回值,以及 上文写的他发送的实际是 返回对象的totelData属性

对某旅行APP的逆向分析

 这里就到了加密流程,ListUtil.combineByteArr 作用是连接合并两个数组

加密过程分析

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

对某旅行APP的逆向分析

 里面也没做啥特殊的,就是把传进来的两个数字给转换成byte[],SerializeWriter这里先不看,后面会一起解决

 那现在唯一的问题就是 Encode 了

 从上面V6的代码中不难看到encode 的产生方式有三种,我们不好确定用的是哪个,这里上面分析了buileRequestHeadOfPrefixV6 传的第二个参数就是加密方式,所以hook一下这个入参就好了

 发现进来的值是5,int i12 = 5; 而且中间值没有改变

 那就确定加密方式用的 encodeByXor,压缩用的 getCompressProvider().compress

对某旅行APP的逆向分析

 encodeByXor 点过去就能看到

public static byte[] encodeByXor(byte[] bArr) {
if(bArr ==null || bArr.length < 1) {
returnbArr;
}
byte[] bArr2 =new byte[bArr.length];
for(inti12 =0; i12 < bArr.length; i12++) {
bArr2[i12] =(byte) (bArr[i12] ^ -1);
}
returnbArr2;
压缩的这个getCompressProvider().compress

 按照正常流程,肯定要先看一下 getCompressProvider 返回的哪个对象,尝试跟了一下没跟到,所以就直接看compress,点进方法

1

2

3

4

5

public interface SOTPCompressProvider {

byte[] compress(byte[] bArr) throws Exception;

byte[] uncompress(byte[] bArr) throws Exception;

}

 这里可以看到是一个接口,直接复制 byte[] compress(byte[] bArr),去搜索看看谁实现了这个方法

对某旅行APP的逆向分析
 实际上是调用了 CTZ.a 方法

对某旅行APP的逆向分析

 CTZ 搜了半天没有结果,但是我在搜索这个注释的zstdBytes的时候发现了惊喜

对某旅行APP的逆向分析

 这个 zstd 就是facebook开源的一个压缩库,第二个参数3明显就是写死的压缩等级了,而且经过测试压缩没有经过魔改

当然有些请求走的其他加密流程,压缩换成 Gzip 或者加解密换成 AEScbc ,区别不大

protobuff反序列化分析

  (别问为什么不直接用 blacboxprotubuf,因为领导要看每个字段对应的 tag,而且请求和字段多了后,一直用 blackbloxprotubuff 很不方便)

 加解密和压缩的流程基本都没问题了,现在问题就是 buileRequestHeadV6 方法做了什么

对某旅行APP的逆向分析

 clientToken 就是前面两篇文章说过的魔改MD5对随机数进行签名,这里就不进行赘述了

对某旅行APP的逆向分析

这里的Serialize.writeMessage 就是将对象序列化成字节数组

 点进去这个方法之后,就能看到

对某旅行APP的逆向分析

就是这个方法,让我感觉一阵的熟悉,那就是我之前搞过某个航司APP用的开源库 io.protostuff,用法跟这里几乎一模一样

 所以我尝试使用老方法,把 request 对象完整的抠出来,然后传给io.protostuff

public classRequestHead extends BusinessBean {
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =14type=ProtoBufferField.Datatype.STRING)
public String appId;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =12type=ProtoBufferField.Datatype.STRING)
public String authToken;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =5type=ProtoBufferField.Datatype.STRING)
public String clientId;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =6type=ProtoBufferField.Datatype.STRING)
public String clientToken;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =7type=ProtoBufferField.Datatype.STRING)
public String clientVersion;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =9type=ProtoBufferField.Datatype.STRING)
public String exSourceId;
@ProtoBufferField(label =ProtoBufferField.Label.REPEATED, tag =13type=ProtoBufferField.Datatype.MESSAGE)
public ArrayList<Extention> extentionList;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =3type=ProtoBufferField.Datatype.STRING)
public String language;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =11type=ProtoBufferField.Datatype.STRING)
public String messageNumber;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =1type=ProtoBufferField.Datatype.ENUM)
public SerializeCode serializeCode;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =10type=ProtoBufferField.Datatype.STRING)
public String serviceCode;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =8type=ProtoBufferField.Datatype.STRING)
public String sourceId;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =2type=ProtoBufferField.Datatype.STRING)
public String systemCode;
@ProtoBufferField(label =ProtoBufferField.Label.OPTIONAL, tag =4type=ProtoBufferField.Datatype.STRING)
public String userId;
public RequestHead() {
AppMethodBeat.i(62638);
this.extentionList =new ArrayList<>();
AppMethodBeat.o(62638);
}
}

这里的ProtoBufferField 注解中 tag 参数,很明显就能看出来就是 io,protostuff

 但是我把对象构建完传进去后发现,程序报错,对象中存在没有tag的属性存在

public classBusinessBean implements Serializable, Cloneable {
public static ChangeQuickRedirect changeQuickRedirect =null;
@NO_PERSISTENCE
private static final longserialVersionUID =1;
private String cacheKey;
@NO_PERSISTENCE
protected byte[] dataBody;
public intpk;
@NO_PERSISTENCE
private longprocessingDataBodyTime;
@NO_PERSISTENCE
protected String realServiceCode ="";
@NO_PERSISTENCE
protected String charsetName ="";
@NO_PERSISTENCE
protected String jsonBody ="";
public intcachedSerializedSize =-1;
}

哦豁,这些都不是tag,那为啥他没报错呢?

 那就看一下 io,protostuff 的源代码,找到 writeMessage 方法,这里可以发现调用的实际上还是 toByteArray方法,而且看代码,一样的getclass,一样的模板类

对某旅行APP的逆向分析

 然后跟一下会发现调用到了 writeto,一个for循环,遍历所有tag

对某旅行APP的逆向分析

 但是在目标APP源码中再跟一步就会发现 不一样的地方

<M extends CtripBusinessBean> void u(M m12, ProtoBufferOutput protoBufferOutput) {
for(FieldInfo fieldInfo : f()) {
Objecte12 =e(m12, fieldInfo);
if(e12 !=null) {
inti12 =fieldInfo.f58010a;
ProtoBufferField.Datatype datatype =fieldInfo.f58012c;
ProtoBufferField.Label label =fieldInfo.d;
if(!label.isRepeated()) {
z(protoBufferOutput, i12, e12, datatype);
elseif(label.isPacked()) {
x(protoBufferOutput, (List) e12, i12, datatype);
else{
y(protoBufferOutput, (List) e12, i12, datatype);
}
}
}
}

 在标准的 Io.protostuff 中这里就只是一个循环然后调用方法,但是这里对方法标签做了很多判断,根据不同的判断结果调用不同的方法

 所以如果对这个库比较熟悉,直接在这个库的源码中改就行了,然后重新打包jar包调用

 我这里为了防止后面有很多需要扣代码的地方有互相依赖的情况,直接把这个库全部抠出来了

对某旅行APP的逆向分析

代码量也不是很大,有点耐心很快就可以扣完

对某旅行APP的逆向分析

在扣代码的过程中可以发现一个很难受的地方,它的代码复制粘贴出来会有很多以下性能监控的垃圾代码

对某旅行APP的逆向分析
对某旅行APP的逆向分析

流程结束,魔改MD5上两篇已经写过了,接下来就是一个sign和一个AES

         文章来源:看雪论坛 

原文始发于微信公众号(编码安全):对某旅行APP的逆向分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年4月21日22:15:28
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   对某旅行APP的逆向分析http://cn-sec.com/archives/3981000.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息