首页
社区
课程
招聘
某xun视频通信协议逆向分析
发表于: 2023-3-9 10:34 8748

某xun视频通信协议逆向分析

2023-3-9 10:34
8748

在一些app中自主设计的通信架构,就算没有进入so库就让逆向分析人员非常难处理,以某大厂的某某视频介绍下整体的流程。

 

*一.抓包分析
⊙二.找到加密点
⊙三.还原加密流程
⊙四.代码还原
⊙总结

一.抓包分析

如下图所示请求体和响应体都被加密了,如果我们想要获得这个接口的数据那么就找到请求发送的逻辑及其请求被解包的逻辑

 

图片描述

二.找到加密点

1
2
3
frida-trace -U - -j "*!AdFeedImagePoster" com.t***.******
 
frida-trace -U -F -j "AdFeedImagePoster!*" com.t***.******

result:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* TID 0x37fb */
  3192 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
  3194 ms  <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
  3248 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
  3249 ms  <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
  7125 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
  7126 ms  <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18272 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18273 ms     | AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18273 ms     |    | AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18273 ms     |    | <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18273 ms     | <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18274 ms  <= "<instance: java.lang.Object, $className: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18279 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18279 ms     | AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18279 ms     | <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18280 ms  <= "<instance: java.lang.Object, $className: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18287 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18287 ms     | AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18287 ms     | <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18288 ms  <= "<instance: java.lang.Object, $className: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18319 ms  AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18319 ms     | AdFeedImagePoster$ProtoAdapter_AdFeedImagePoster.decode("<instance: com.squareup.wire.ProtoReader>")
 18320 ms     | <= "<instance: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"
 18320 ms  <= "<instance: java.lang.Object, $className: com.tencent.qqlive.protocol.pb.AdFeedImagePoster>"

frida可以显示出来调用流程,但是我们想要的数据为主页广告,现在请求包是加密的,虽然上面的代码被调用,但是我们无法确定想要的数据就在那个请求包内

三.还原加密流程

但是在apk中的源码中 UnifiedProtocolUtils类里面找到了
方法

 

encodeUnifiedRequest
decodeUnifiedResponse

 

经过charles抓包的的数据和这个两个方法的数据来看,是经过这两个方法进行组包后发送到服务器上。

 

那么如何确定广告在哪呢?
首先让腾讯视频停留在将要进视频详情页单还没进去视频详情页的时候,打开charles进行抓包

 

截取距离g广告最近的acc..com发送的请求

 

并将其使用charles保存为二进制文件

 

小插曲:观察视频的整个网络请求都在NetWorkTask类下面 package com.t**.route

 

在其请求完成之后调用了

1
2
3
int[] iArr = new int[1];
#bArr2是acc这个请求返回的二进制数据
 bArr3 = UnifiedProtocolUtils.decodeUnifiedResponse(bArr2, iArr);

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package qqlive;
 
import qqbrower.GzipUtils;
 
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
 
public class main {
 
    private static final int CMD = 65281;
    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        System.out.println(sb.toString());
        return sb.toString();
    }
 
    private static byte[] getJceDataFromBuffer(byte[] bArr, boolean z, int i, int[] iArr) {
        bArr = GZIP.Decode(bArr);
        ByteBuffer wrap = ByteBuffer.wrap(bArr);
        int position = wrap.position() + 16;
        int length = (bArr.length - position) - 1;
        if (length <= 0) {
            iArr[0] = -869;
            return null;
        }
        byte[] bArr2 = new byte[length];
        System.arraycopy(bArr, position, bArr2, 0, length);
        return bArr2;
 
 
    }
 
 
    public static byte[] decodeUnifiedResponse(byte[] bArr, int[] iArr) {
        boolean z;
        if (bArr == null || bArr.length == 0) {
            return null;
        }
        ByteBuffer wrap = ByteBuffer.wrap(bArr);
        if (wrap.get() != 19 || wrap.getInt() != bArr.length) {
            return null;
        }
        wrap.getShort();
        if ((wrap.getShort() & 65535) != CMD) {
            return null;
        }
        wrap.getShort();
        int i = wrap.getShort() & 65535;
        if (i != 0) {
            iArr[0] = i;
            return null;
        }
        wrap.getLong();
        int i2 = wrap.getInt();
        if ((i2 & 2) <= 0) {
            z = false;
        } else if ((i2 & 16) > 0) {
            z = true;
        } else {
            iArr[0] = -867;
            return null;
        }
        if (wrap.getInt() == 5) {
            return null;
        }
        wrap.getLong();
        wrap.position(wrap.position() + 32);
        wrap.get();
        wrap.position(wrap.position() + 10);
        wrap.get();
        wrap.position(wrap.position() + (wrap.getShort() & 65535));
        int i3 = 65535 & wrap.getShort();
        wrap.position(wrap.position() + i3);
        int i4 = 83 + i3 + 2;
        int i5 = wrap.getInt();
        int i6 = i4 + 4;
        if (wrap.get(bArr.length - 1) != 3) {
            iArr[0] = -869;
            return null;
        }
        int length = (bArr.length - i6) - 1;
        if (length <= 0) {
            iArr[0] = -868;
            return null;
        }
        byte[] bArr2 = new byte[length];
        System.arraycopy(bArr, i6, bArr2, 0, length);
        return getJceDataFromBuffer(bArr2, z, i5, iArr);
    }
 
    public static void main(String[] args) throws IOException {
 
 
        File file = new File("C:\\mycode\\javatools\\src\\main\\java\\qqlive\\acc25196");
        FileInputStream inputStream = new FileInputStream(file);
        byte[] bytes = new byte[(int) file.length()];
        inputStream.read(bytes);
 
        main tsmain = new main();
        int[] iArr = new int[1];
        main.decodeUnifiedResponse(bytes, iArr);
        tsmain.bytesToHex(   main.decodeUnifiedResponse(bytes, iArr));
 
 
    }
 
 
}

输出的二进制文件通过肉眼观察不符合jcestruct的格式

 

这时猜想是不是protobuf

 

然后验证了下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[1](i):53
[2](b):com.tencent.qqlive.protocol.pb.adService
[3](b):/com.tencent.qqlive.protocol.pb.adService/getAdDetail
[9](b):
  [9.1](i):217
  [9.3](i):1655792059010
  [9.4](i):1655792059227
[1(1)](b):
     [1(1).1](b):mod_recommend_ad
     [1(1).2](b):
          [1(1).2.2](i):9
          [1(1).2.3](b):
                 [1(1).2.3.1](i):5
                 [1(1).2.3.2](i):1
                 [1(1).2.3.3](b):
                          [1(1).2.3.3.1](b):type.googleapis.com/com.tencent.qqlive.protocol.pb.AdFeedImagePoster
                          [1(1).2.3.3.2](b):
                                     [1(1).2.3.3.2.1](b):
                                                  [1(1).2.3.3.2.1.1](b):http://pgdt.gtimg.cn/gdt/0/EABeJclAPAAIcAAAQuTBiocBWCa2eKvgt.jpg/0?ck=f28abb5b10650481e1a4d76ba72d1a49
                                                  [1(1).2.3.3.2.1.2](b):抢爆了!悄悄上线的300个经典英语短视频,0元抢!趣味学英语
                                                  [1(1).2.3.3.2.1.3](b):英孚教育
                                                  [1(1).2.3.3.2.1.4](b):
                                                                 [1(1).2.3.3.2.1.4.1](i

那么UnifiedProtocolUtils类 不是走的这个地方

 

但是仔细观察 除了 gzip之外,其组包的方式是相同的

 

那么我们可以搜索组包的公共代码 发现下方有四处调用

 

图片描述

 

观察四个之后

 

初步确定 package com.*.route.v3; QmProtocolTools类

 

QmProtocolTools类开始追踪 看到底做了什么

 

经过分析笔者画出来了其通信图

 

图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#发送数据数据组包
 bArr = QmfProtocolTools.packageQmfRequest(this.netContext, mockPBRequestData);
 
    #protobuf组包
    mockPBRequestData =>byte[] pkgProtocolData =pkgProtocolData();
 
        bArr = PBProtocolTools.PbProtocolAsQmfBody.packageRequest(this.netContext);
 
 
                #组装协议头 需要实现
                packagePBFrameHead((short) packageRequestHead.length, pbBusinessReqBytes.length).array();
 
 
               int length = packageRequestHead.length + 16 + pbBusinessReqBytes.length;
 
 
 
                省略--------

因为

 

预先处理
acc25196req是 请求的二进制文件

 

将其转化为hex,中所周知gzip经过压缩后,头文件的标志为1f8b,并把二进制最后一个字节单独提出来

 

1f8b这段就是gzip压缩后的数据 ,我们先分析gzip压缩后的数据是如何产生的 然后再分析第一段1300开头的数据如何构造,其实也就完成了发包的请求

1
2
3
4
5
1300000b910001ff01626c00000000000000000035000002130000271c0000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000025d8
 
1f8bxxxxxxxxxxxx
 
03

四、代码还原

解析gzip压缩的数据

1
2
3
4
5
6
7
import gzip
if __name__ == '__main__':
 
    hex1 = "1f8b0800000000000000ed5ac96f245719ef9e2da6218963401a5922b29c300af1b8fbed55cfc6686a6b4f8f778f673cf6a5525d9bcbeeee6a5757b597241244424a500e11ca29e280222171e64214258ac491235c728620c20189bf00f155b5b799c910100805a95b5eeabdf7bdf7adeffbfd4af6d750093edffdf4322e159f113ef6921bb7aba9df71fd4e5adddf6f457dbfda4de23476e356b5dbac3ade5d3fe947ae3fce6bffaa682df453cd33fdd4895ae4c7e5910f4646fffaecf50f2e4f965e2ecd54dd0011e1313f903250294332902a57b0a2b8cc0b9a8eea790c218411c10ac7581f7508a79431a1aa2af57dc2ef5c596a4ca8abe5ed09411816bed2f43ddfc5d87755e97ac295a41930b5e936c92ba59d5272ed41e4c4ed688657bea1566955a02ac1928bb1abc5afeb97272bf9713789bc092aaf622c309b790aa3fcc36f95f46f8d5c1e2d8f3f4308ad2aa88a19ae6245bd53f9761ebeefe43fd6af8c94464bdb5747fef2d45869177f994db7b0e05c9158128e54768bd3e40fe5caa765693043612a68c78a59975c1a5c63266754a15a1daba6a406a786610a5d97583724e31499c8acd7eb58e375a611cad5bac5b13414a2720b5155d4996ed67552d775811503234674824d8d33d5e496d4f3182ba6c91037a86e69dc8424803e5ed738d14d13e5a64845ea9c5b0637a46a5253a526cc4ac4292748c7481716434ca95b8602c2dc34558b68521a583328a186260c3a5afa5bb9345e3aae559e5a6b2c590d2ac65ea48a4b039535b14b691008465d4f5287b13ce8388018c061c751e5e9344ba24e68a791bbe7a7630fa024b0a680a99a863553e18223c550b055d7552e4d222df08ad6b98918932a31306396243aa39a4615a2883a46d2c44addd490412d9383f30617c4508f472b23aed36a6d1c75fdb12b3dbf151c5faf5cc97a9137367a962c41a4108c559eaf7c3d4208fca78edfdf197bb627d36696e0fd3d7918527c38f6623bf6ecc4879bd2f63b9eed7873f2463e95267017fc249f98c6e3bf302a1f5dabbc02ae6c0977c5378ecc29ddd8bddb8a0da3be6bacedf53a0de37837e0fd9500d5369bc91a6ef4d5e61d16de5fd3eaa1b6a519b073fe4075e6ddeeb671200ee61bda5690c82cabd1d5db0e5e9b5fd3d6b4b94acb5a11bb3b0c59f30bcda9f4200e5d6357434e045a4c1caad9bcb9111d76b756fabbfbc2cdb5ec67cdfe9d96a66d6a7aa83534fd406cf2d8255b2d86ad48bbdda7b57ead26a95a6badac69cdf9c6413759db776e276168e5dac0aaa91523017fccf9a3a8d3f3f62dd054539689a51defd60f1565b527069a7656a77a8761f7dc9fc0f4bbbd5da3dd131b7a23d46b99c053a236d5d94f56b46d4dcfc4427b6f7b31142bfd485b9b9babbc5b76bcbe9fa451cf4fe60855105c1a7c23ea78592f4d8ee61845e40674272f73d339aaaa8a541157941b4ee4cd81a060aa545474e3fc0cbbe3b47d7bc7e9edf89edd775a993f071583a450289ceb26be9342c3b303284910ee4269a67352100c552b115c2e2a89e4a4f266398fc2c1e2e68616e9e9c2aab12c1b6c7969f9b69a6dc5d6ca1209536f77a1bbe5a4e6bd4864d9fc9a5e37945a2de03ace23a11f996ba16eadd620d2533453e7b5e3b07e646aa161adcc73355d8871b7bdb909b9d98ba37de66d62b190ae45dafcd454164cd1fe83a43f85f43dcdd206f9e879c9eaa0be22fd78c1b9505f91d16d3867993f6a044bed70f93c1fee6614668b4677db713758a88be0414d098e93feb6f220cf47b8b5b8d4bb7357135bf3eb793ec6feb7dafe7ea9f262c0a41320c79b0e5444a699a77ad3900939cd5893369b8c7005d1b16b04ba8aa0e3bfbee474bb36247b6e8002b36d27ead8ad388c3a739db8e3cf2e6e1ad0703a734492d9d86ee78504a5d2b967ed04e4bebbb8615877378c3bc7d9ca6abcbab91df5b73363a9b107dbee2f16fb24c230b8533c334ce059bf5b0c54acce2ede6f2c17034a25acacea83812a665b7dbbeda47e12392d3b4ce2ac3b373b380f1a6d6ed3f6c02a2ed5597020cb8bdd4475a1222a04158a25b10962b71707ba18cd8d58ac1703456530581818c1113c9b830508170cee0e6c154899dd3fb6432f9ddbf329758ef643c77102de0a591cc7ceec4489946646738cbbfec377defde5a5891fbdf1ce9b97c67f7fb972cd8bba5d3f192bf078fcb2a8921ce0c97388cc20f4d0d733dfcf3a7b9df8a033d183b6fa03fd0493ef3ca7b1198e66989831614f7d86c9f5c7e07ebb8c9aa5dd5272952142c9f144402815c8038c00d4c034f002a950ea3b8c10ce3d82de289f9cfe937261d84fcb0572a84202b2095a670aa0a962321d00981a16e6ba2298a48253806e43811543a1b8ce54a56e48ac48039010ee37a0a3958336ab5b8a29095320f4ef95092c1a4862ca754b5a8c63bd0e0dc33204174803b580df02613812e056981ab38478bf3c81b0ae49a221956266524d3053609320ceeb5221d8621f964bbf29977e5b9ef069c0c12e0768870afe3619f2034f003d0ab0871ce6ffae5cd13a5e1247de0446ef5fba653aad7eb45723555c45132f2d469dec7076e2deecc4b9d0ec441e13f871af51bb8f4915c16540d5354b33961f2c7defe5b745e58fbc72cdcda0c385639ff25727bba1dd4bbb933318dfcc9fddc89b9c997c04f5266f4e267e60fb2d7f72e65578ccd1d4765a21487286312c07adf8c00e92b80d5314c64ebe6c479edd8a7ae9b9182065d6f201f1d3fca8c93fbffdb3cf3efef9671fbdf7f9271fc36a0f88272c024cc35a2ede728ee22cbd30d14b5c7bcf3fca474025242e34fdf343cfd60bc7e066c0c662fa316d5927171dc825000418294d817d5fe226c38c379b943499ab785c0d80d170d8e1e7e1b2bb60a65f1c3ef0104e383c39d2b71dd7f57b3ddb8d7b2958d82e742968b0f44511f2737201df4e2fee3c9e8817f29dadd8f14eed46275b12a7b3f79012921b13a57efbc41a34f0d9dfef64798ebe8cc5dae72cb6ca5441b8b40bf31cb00cf478851f700730c6059586a53071ba3b763772cf33f1e1fb9fbff52b48c39fdefa044b26f304fa6e1ac59d0b218a7a76d689822370e22c68e751b531824e9d8727dfdcf5ddbc8de64df2c4a5530fa1c7e6078ab30c0c727896ee27d5c0e3f5e49fd5d320c267d230e324f0e253505627ecc19ab723ed4e9cdafda87bd3dba1f0addc3c19171b809e04c00a1fce3e7ad445e022855d6290cb4733c94e9d7a42813fe9c6fac57c174e826841f0d2a396ff50d5f81dc0c6478c13ac48d27f5825cd56ecee5dd0755e1a59d282b99d34edced46add2c8baafb305d753bb5be1b0378db3bc7b95c0dd51e71087ab42214c404bc5b8a9ac251d1937280b38b1a18184cc4798d5db8d9fe40e66486e45b4f9ef3e01539ffa220be5034b6acb773727a11b6c3eea08a340308c3dd698c38c22f18daba39dd5836ad07d32a705181e4c5296365d1bc28b8ba6ead6bcb0b0f4fadd41b8bd6c5a9424465f9bb8818ccdcb5162d636365fda2d4c6d2c9c85a9e6f2c9f1e30d03b787e58dbc38a4e5ed9a68ddb96712a7141787de5de8635bd7eef4cfecc029502bb16fc7ce2828fa7265d8081f3fe96f8fb45b1f552a7dd2d2aeeb48260b1eb244ebb67379db0b8bea72f59adb813be96278cbcd6747a5045c5440139f9d379829f7013c08a27b72d48289033d0939e748713cf06be9c66f3240f1720ec91ee3028f182e00dce29f40eabe5dfa99602fb726a71b12571e5b4cdf7dcb8c0085445c096a99c7c7d404bba61ce4a1e45c5335e83f2e7412f744278018ce12dd0dd713a1dbf0562c769b194b77f34a01ca0da6db97b43b633643b43b633643b43b633c4af21db19b29daf02dbc95b68333e3c87439873a1f10f19d090010d19d090010d19d090010d19d09001fd9f54cb7f89015de4365ef13f4993af575eac3c0da8011dc6745267c13f1afbe660c92ec48ae63887274a2f7f75fe9c4fc62ba38ffe73c9d8b591e7474bd7cbff001a0533a4d8250000"
    deData = gzip.decompress(bytes.fromhex(hex1))
    hexStr3 = deData.hex()
    print(hexStr3)

图片描述

1
2
3
4
5
6
7
8
9
09300000000025d8033100000000000016个字节
 
 
000025d8:第一个protobuf的字节长度+ 16 +第二个字节的protobuf长度
 
0331:第一个字节的protobuf的数据长度
 
 
08351228636f6d2e74656e63656e742e71716c6976652e70726f746f636f6c2e70622e6164536572766963651a352f636f6d2e74656e63656e742e71716c6976652e70726f746f636f6c2e70622e6164536572766963652f676574416444657461696c32850108b80810ed0f18b80322002a003a2e636630323664346566393966383334303966393835373137376334646662613864643430303031303231373531314210613235333334343638383833656532354a044d49203850015a203632343136653762656465

组装协议代码如下图所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
rom appReverse.tencent.txlive.bytebuffer import ByteBuffer
 
import  gzip
class Pbpa:
    def __init__(self):
        pass
    #拼接gzip压缩前的头
    def packagePBFrameHead(self, packageRequestHead_len,pbBusinessReqBytes_len):
        allocate = ByteBuffer.allocate(16)
        allocate.put_UBInt16(0x0930)
        allocate.put_UBInt8(0)
        allocate.put_UBInt8(0)
        allocate.put_UBInt32(packageRequestHead_len +16 +pbBusinessReqBytes_len)
        allocate.put_UBInt16(packageRequestHead_len)
        allocate.put_UBInt16(0)
        allocate.put_UBInt32(0)
        logger.info((allocate._array).hex())
        return allocate._array
 
    def packageRequest(self):
        #protobuf组包的第一个包
        packageRequestHead = "123".encode()
        #protobuf组包的第二个包
        pbBusinessReqBytes  = "456".encode()
        allocate = ByteBuffer.allocate(len(packageRequestHead) + 16 +len(pbBusinessReqBytes))
        headerdata = self.packagePBFrameHead(len(packageRequestHead) ,len(pbBusinessReqBytes))
        allocate.put(headerdata)
        allocate.put_bytes(packageRequestHead)
        allocate.put_bytes(pbBusinessReqBytes)
        logger.info((allocate._array).hex())
        #gzip压缩
        deData = gzip.compress(allocate._array)
        logger.info(deData.hex())
 
        return

组装request 请求的二进制的头

 

上面得到的数据就是 中间的数据 ;
完整的请求数据应该是

 

头数据 + gzip压缩后的数据 + 0x03

 

头数据的组装涉及到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import gzip
 
from loguru import logger
 
from appReverse.tencent.txlive.bytebuffer import ByteBuffer
import hexdump
 
class QmfProtocolTools:
    def __init__(self):
        pass
    @staticmethod
    def packageQmfRequest(gzipdata=b"helloword"):
        CMD = 65281
        QMF_PB_CMD = 0x626c
        allocate = ByteBuffer.allocate(9999)
        allocate.put_UBInt8(19)
        allocate.put_UBInt32(0)
        allocate.put_UBInt16(1)
        CMD = 65281
        allocate.put_UBInt16(CMD)
        allocate.put_UBInt16(QMF_PB_CMD)
        allocate.put_UBInt16(0)
        getRequestId = 0x35
        allocate.put_UBInt64(getRequestId)
        allocate.put_UBInt32(531)
        QmfAppId = 0x271c
        allocate.put_UBInt32(QmfAppId)
        LoginQQUin =0x0
        allocate.put_UBInt64(LoginQQUin)
        allocate.put_UBInt64(0x0)
        allocate.put_UBInt64(0x0)
        allocate.put_UBInt64(0x0)
        allocate.put_UBInt64(0x0)
        allocate.put_UBInt16(0x0100)
        allocate.put_UBInt64(0x0)
        allocate.put_UBInt32(0x0)
        allocate.put_UBInt16(0x0)
        #这个位置压入四字节的长度
        allocate.put_UBInt32(len(gzipdata))
        enData = gzip.compress(gzipdata)
        allocate.put_bytes(enData)
        #压入结尾
        allocate.put_UBInt8(3)
 
        hexdump.hexdump(bytes.fromhex((allocate._array).hex()[:allocate._position *2]))
        logger.info((allocate._array).hex()[:allocate._position *2])
 
if __name__ == '__main__':
    QmfProtocolTools.packageQmfRequest()

解析gzip压缩后的protobuf:

 

第一个protobuf:
package com.tencent.qqlive.protocol.vb.pb; =>RequestHead

 

通过还原的第一部分的protobuf的可以确认第一个为这个

 

那么我们将其还原

 

第二个protobuf

 

根据第一个protobuf

 

可以判断出来走的是这个接口,那么我们直接去寻找

 

图片描述

 

剩下的都是苦力活了 自己拼装probuf结构即可,真是个苦力活。。

 

然后组装完了发包,搞定

 

图片描述

 

总结:

 

加大通信逻辑的复杂度也会增长分析的时间,如果再加上一些vmp就真的没得看了。

 

即日起看雪文章和微信同步更新:

 

图片描述


[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2023-3-9 23:00 被BestToYou编辑 ,原因:
收藏
免费 7
支持
分享
最新回复 (9)
雪    币: 58
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
小飞棍来咯
2023-3-9 10:43
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
学习了
2023-3-9 10:46
0
雪    币: 0
活跃值: (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
向大牛学习
2023-3-9 11:27
0
雪    币: 350
活跃值: (901)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
像大牛学习
2023-3-9 11:48
0
雪    币: 246
活跃值: (4427)
能力值: ( LV4,RANK:45 )
在线值:
发帖
回帖
粉丝
6
厉害
2023-3-9 11:49
0
雪    币: 163
活跃值: (426)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
2023-3-9 16:06
0
雪    币: 197
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8

学习学习,2471910785

最后于 2023-3-9 19:50 被q2471910785编辑 ,原因:
2023-3-9 19:50
0
雪    币: 0
活跃值: (131)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
学习学习
2024-5-11 16:40
0
游客
登录 | 注册 方可回帖
返回
//