开启辅助访问 切换到宽版

精易论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

用微信号发送消息登录论坛

新人指南 邀请好友注册 - 我关注人的新帖 教你赚取精币 - 每日签到


求职/招聘- 论坛接单- 开发者大厅

论坛版规 总版规 - 建议/投诉 - 应聘版主 - 精华帖总集 积分说明 - 禁言标准 - 有奖举报

查看: 26992|回复: 11
收起左侧

[Android逆向] 某直播APP逆向TCP协议分析

[复制链接]
结帖率:0% (0/1)
发表于 2021-5-6 15:04:44 | 显示全部楼层 |阅读模式   湖北省天门市
一、概述
        拖延症晚期,一枚小菜鸟终于完成对APP的分析。该直播APP采用TCP协议,TCP连接建立之后,首先进行基础连接认证,认证通过之后,进行帐号认证,完成即可进行获取角色信息、进入房间等各类操作。发送数据先进行ProtoBuf序列化,接着采用CRC32循环加密,添加包头(包括命令号以及长度、校验位等)之后,发送,接受到的数据反之。本文主要阐述逆向中遇到的难点及思路、基础连接认证过程、数据包的序列化过程、CRC32循环加密过程以及重要数据包的分析过程等,旨在提供APP逆向中,TCP通信协议的一般性分析研究方法,如果有更好的思路或者经验交流,欢迎联系。

        本文使用到的工具有:JEB2.3.13、GDA3.53、IDA7.0、雷电模拟器、工厂APP。
二、 逆向难点及思路
        1.寻找切入点
        一般来说,一个好的切入点可以让我们事半功倍。接触到APP之后,首先进行了流程分析。开启android monitor,进行登录操作。通过分析日志以及调用方法堆栈,发现一条日志信息很可疑:


  1. I/SystemLog(20023): [CallCenter[Net]J](handleCEventVideoInitConnectionResponse) : initConnectionRes ok... is_force_ver_up:true tunnelId:0 roomId:0
复制代码
其中,is_force_ver_up表示是否强制更新,正好对应了手机上面要求强制更新。通过JEB逆向app之后,搜索相关信息:handleCEventVideoInitConnectionResponse,发现如下:
  1. <blockquote>if(v0 == 0) {
复制代码
有理由相信,z即为所需的日志类。有这么一个入手点,就好办很多。一般app都会对日志类进行重写,并且在发布版本时,关闭部分信息的输出。采用xposed对z类所有方法进行hook,打印出日志信息,可以发现,日志输出了很多有用的信息。
通过对日志信息的分析,搜索日志标签:NetConnection,分析发现com.h3d.qqx5.framework.d.g即为app的网络操作类,在该类中,进行了连接之后的基础连接认证,以及后续的数据包的发送接收等操作。也发现了具体的发送数据包的函数
  1.    private void b(h arg5) {
  2.         arg5.a();
  3.         OutputStream v0 = this.c.getOutputStream();
  4.         v0.write(arg5.d().k().array());
  5.         v0.write(arg5.c().k().array(), 0, arg5.a);
  6.     }
复制代码
与此同时,日志也明确告诉TCP连接的IP和端口,方便抓包分析。
  1. I/SystemLog(20023): [CallCenter[Net]J](connectImpl) : try to connect ip:rp.mgc.qq.com port:31000
复制代码
找到发包函数,即有了突破点。一层一层向上追溯即可找到调用层次。

       2.代码混淆
        该APP代码做了混淆,对于分析协议的过程中十分不利。我的思路为跟踪方法过程,并及时进行重命名,重命名的过程中,我的命名规则是:原方法名_现方法名,之所以保留原方法名,是方便后续的hook操作。比如上述发包函数,在我对此方法详细分析之后,其效果如下:
  1. private void b_sendMessage(h arg5) {
  2.         arg5.a_addHeader();
  3.         OutputStream m_outputStream = this.c_socket.getOutputStream();
  4.         m_outputStream.write(arg5.d().k_buffer().array());
  5.         m_outputStream.write(arg5.c().k_buffer().array(), 0, arg5.a);
  6.     }
复制代码
可读性增强很多,慢慢由点及面,从日志入手,各处分析,最后零零散散,即可大致分析出整体app通信流程。

三、基础连接认证
通过抓包分析以及对反编译之后的代码分析,关键认证代码在com.h3d.qqx5.framework.d.g.b.a:
  1.         void a() {
  2.             int v3 = 0x20;
  3.             h v0 = new h();
  4.             f_NetBuffer netBuffer = v0.c();
  5.             netBuffer.b_putInt(9);
  6.             netBuffer.a_putLong(0);
  7.             netBuffer.a_putLong(0);//以上完成组包
  8.             g.a_send(this.a, v0);//发送数据包
  9.             int v0_1 = this.a.c_recv().c().e_getInt();
  10.             if(v0_1 != 10) {//看第一个int是否是10
  11.                 throw new IOException("protocol error:" + v0_1);
  12.             }

  13.             if(this.f) {
  14.                 v0 = this.a.c_recv();//接收数据包
  15.                 int v1_1 = v0.c().e_getInt();//看第一个int是否是1
  16.                 if(v1_1 != 1) {
  17.                     throw new IOException("protocol error:" + v1_1);
  18.                 }
  19.                 else {
  20.                     byte[] v6 = new byte[v3];
  21.                     v0.c().k_buffer().get(v6, 0, v3);
  22.                     long v0_2 = this.a_SolvePuzzle(v0.c().f_getLong(), v0.c().f_getLong(), v6);//完成计算
  23.                     h v2 = new h();
  24.                     f_NetBuffer v3_1 = v2.c();
  25.                     v3_1.b_putInt(2);
  26.                     v3_1.a_putLong(v0_2);
  27.                     g.a_send(this.a, v2);
  28.                     v0_1 = this.a.c_recv().c().e_getInt();
  29.                     if(v0_1 != 3) {
  30.                         throw new IOException("puzzel error:" + v0_1);
  31.                     }
  32.                     else {
  33.                         v0 = new h();
  34.                         netBuffer = v0.c();
  35.                         netBuffer.b_putInt(5);
  36.                         netBuffer.a(Long.toString(this.d));
  37.                         g.a_send(this.a, v0);
  38.                         v0 = new h();
  39.                         netBuffer = v0.c();
  40.                         netBuffer.b_putInt(6);
  41.                         netBuffer.b_putInt(200);
  42.                         k v2_1 = new k();
  43.                         v2_1.b = ((int)this.d);
  44.                         System.arraycopy(this.e, 0, v2_1.d, 0, this.e.length);
  45.                         v2_1.c = 0;
  46.                         v2_1.a(netBuffer);
  47.                         g.a_send(this.a, v0);
  48.                         v0_1 = this.a.c_recv().c().e_getInt();
  49.                         if(v0_1 != 7) {
  50.                             throw new IOException("verify error :" + v0_1);
  51.                         }
  52.                     }
  53.                 }
  54.             }
  55.         }
复制代码
得知建立连接之后基础连接认证流程,对比数据包进行分析:

protocal:
  1. send:09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
复制代码
  1. send:09 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00        
  2. recv:1C 00 00 00 38 F3 01 64 0A 00 00 00 A8 86 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 00 00 00 79 F3 56 71 01 00 00 00 57 F4 7C 1A 35 90 CD DA C7 F3 2A BF 06 6F EF 91 5C BC 6D A8 75 F8 02 81 01 C2 8B E6 2C 45 FE 12 D8 F4 F7 15 1A 7C 35 06 DA F4 F7 15 1A 7C 35 06
复制代码
接收到的数据包第一个四字节为长度,所有整数均为大端存储,第二个四字节无意义,下文的send以及recv同理。之后列出来的数据包都是从第九个字节开始。
  1. 1C 00 00 00 //非此数据则出错,protocol error
  2. 38 F3 01 64 0A 00 00 00 A8 86 0A 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 00 00 00 79 F3 56 71 //无意义
  3. 01 00 00 00 //非此数据则出错,protocol error
  4. 57 F4 7C 1A 35 90 CD DA C7 F3 2A BF 06 6F EF 91 5C BC 6D A8 75 F8 02 81 01 C2 8B E6 2C 45 FE 12 //long_key_sha-256
  5. D8 F4 F7 15 1A 7C 35 06 //long_1
  6. DA F4 F7 15 1A 7C 35 06 //long_1
复制代码
从long_1到long_2依次计算其sha-256值,直至结果与数据包返回的long_key_sha-256值相同,此处将符合条件的值命名为long_key

puzzle:
  1. send:02 00 00 00 D8 F4 F7 15 1A 7C 35 06
  2. 其中,02 00 00 00 //固定
  3. D8 F4 F7 15 1A 7C 35 06 //上文计算得到的long_key
复制代码
  1. recv: 03 00 00 00 EC AA 33 74 42 FA CD D5
  2. 其中,03 00 00 00 //表示成功
  3. EC AA 33 74 42 FA CD D5 //无意义
复制代码
verify:
  1. send:05 00 00 00 09 00 00 00 39 30 39 37 38 32 37 32 32 00 2C 03 00
  2. 其中,05 00 00 00 //固定
  3. 09 00 00 00 //帐号长度
  4. 39 30 39 37 38 32 37 32 32 //string_帐号
  5. 00 //固定
复制代码
  1. 两个数据包其实在一起,这儿讲解方便,因此分开
  2. send:06 00 00 00 C8 00 00 00 00 00 00 00 C2 2E 3A 36 ...省略796个0字节
  3. 其中,06 00 00 00 //固定
  4. C8 00 00 00 00 00 00 00 //固定
  5. C2 2E 3A 36 //int_帐号
  6. 其实后面的数据,从代码上看,是有含义的,但是实际抓包发现,始终全部为0
复制代码
  1. recv:07 00 00 00 //返回该值,说明成功
复制代码
至此,tcp基础连接认证成功!可以进行后续操作。

三、数据序列化过程
这部分是最让人难受的一部分,因为一直以为是谷歌的protobuf,尝试套上去解数据包,结果死活不行。app解数据包这儿流程又比较复杂,加上混淆,理解了好久才理解明白流程。

以CEventQueryVideoAccountInfo该事件的发送以及接受处理为例说明
首先,java有一个类,即为CEventQueryVideoAccountInfo类,
  1. public class d extends d_INetMessage {
  2.     @n(a=1) public int a;
  3.     @n(a=2) public int b;
  4.     @n(a=3) public int c;
  5.     @n(a=4) public String d;
  6.     @n(a=5) public String e;

  7.     public d() {
  8.         super();
  9.     }

  10.     public int h() {
  11.         return 60001; //即为61 EA 00 00
  12.     }

  13.     public String toString() {
  14.         return "CEventQueryVideoAccountInfo [m_time_stamp=" + this.a + ", m_device_type=" + this.b + ", m_appid=" + this.c + ", m_skey=" + this.d + ", m_open_id=" + this.e + "]";
  15.     }
  16. }
复制代码
通过代码,可以得知该类的功能为CEventQueryVideoAccountInfo,该类的属性虽然被混淆,但是根据日志,我们可以还原各个属性的真实含义。
首先,先设置各个属性的值,接着进行序列化。序列化之后的数据如下:
  1. 63 00 00 00 09 00 00 11 00 02 19 00 D6 F6 AE 9E 08 20 00 25 00 00 00 20 00 00 00 34 44 34 30 43 33 43 45 35 36 45 36 42 32 35 36 37 42 45 32 43 42 31 43 45 32 33 45 37 31 37 37 00 28 00 25 00 00 00 20 00 00 00 36 30 35 31 30 33 46 33 46 33 33 34 36 42 46 39 31 35 43 45 43 33 31 45 33 34 43 44 42 35 30 44 00
复制代码
  1. 解析如下:
  2. 63 00 00 00 //数据包长度
  3. 09 00 //代表了index和type,即索引号和类型,下面同理。这儿对比谷歌的protobuf即可理解
  4.     00 //timestamp:0 //凡是值为整数形式的,均采用了压缩整数,但是和谷歌的protobuf的压缩算法不太一样。
  5. 11 00
  6.     02 //device_type:1
  7. 19 00
  8.     D6 F6 AE 9E 08 //appid:1105583531
  9. 20 00
  10.     25 00 00 00 //长度
  11.         20 00 00 00 //长度
  12.             34 44 34 30 43 33 43 45 35 36 45 36 42 32 35 36 37 42 45 32 43 42 31 43 45 32 33 45 37 31 37 36
  13.             //skey:4D40C3CE56E6B2567BE2CB1CE23E7176
  14.         00
  15. 28 00
  16.     25 00 00 00
  17.         20 00 00 00
  18.             35 30 35 31 30 33 46 33 46 33 33 34 36 42 46 39 31 35 43 45 43 33 31 45 33 34 43 44 42 35 30 44
  19.             //openid:505103F3F3346BF915CEC31E34CDB50D
  20.     00
复制代码
序列化完成之后,进行加密,加密之后添加包头
  1. 61 EA 00 00 //clsid,命令号
  2. AF DE AB AC //security_flag,固定
  3. 00 00 00 00 //checksum,固定
  4. 67 00 00 00 //密文长度
  5. 2D 61 BC 00 55 E8 79 63 C9 51 39 50 8F 10 3E BC D3 1E 4A 2E 51 ED 5D F5 A2 D1 0D 05 13 EF 99 CC 0B 4C 42 C6 8D 23 BB 7D 83 06 CF 1A C2 CD 17 28 AC E1 02 11 74 B2 1A E1 AD DD D4 64 D5 1D BA B4 33 97 36 F6 7A E0 72 D6 E4 12 AD CD 82 31 C1 AA C1 B6 69 E5 82 2B 08 7F 4B AB 2C 69 C2 45 10 81 1E 53 6E 6E 1F 21 0C //密文
复制代码
整数型压缩与解压算法和加密与解密算法随后给出

接下来先讨论返回的数据

java同样定义了返回的数据类
  1. public class e extends d_INetMessage {
  2.     @n(a=1) public int a;
  3.     @n(a=2) public boolean b;
  4.     @n(a=3) public ArrayList c;
  5.     @n(a=4) public ArrayList d;
  6.     @n(a=5) public int e;
  7.     @n(a=6) public ag f;
  8.     @n(a=7) public long g;
  9.     @n(a=8) public boolean h;

  10.     public e() {
  11.         super();
  12.     }

  13.     public int h() {
  14.         return 60002; //即为62 EA 00 00
  15.     }

  16.     public String toString() {
  17.         return "CEventQueryVideoAccountInfoRes [m_time_stamp=" + this.a + ", m_succ=" + this.b + ", m_room_proxy_infos=" + this.c + ", m_account_infos=" + this.d + ", m_err_code=" + this.e + ", m_last_login_acc=" + this.f + ", m_qq=" + this.g + ", m_is_also_anchor=" + this.h + "]";
  18.     }
  19. }
复制代码
收到数据:
  1. 62 EA 00 00 AF DE AB AC 00 00 00 00 E4 0C 00 00 AE 6D BC 00 E4 08 8F 95 84 B9 2C 4A 23 F1 C8 26 B7 1E EB F6 4B 67 0D 5E 3B D7 B4 14 C8 3C DB 1C 9A 45 01 72 0C 74 37 ......数据太长,后面不放出来了。
复制代码
与发送数据正好相反
  1. 62 EA 00 00 //clsid
  2. AF DE AB AC //security_flag
  3. 00 00 00 00 //checksum
  4. E4 0C 00 00 //密文长度
  5. AE 6D BC 00 E4 08 8F 95 84 B9 2C 4A 23 F1 C8 26 B7 1E EB F6 4B 67 0D 5E 3B D7 B4 14 C8 3C DB 1C 9A 45 01 72 0C 74 37 //密文
复制代码
客户端拿到数据之后,首先获取clsid号,接着在一个hashmap中寻找对应的类,找到之后,通过反射获取类的所有属性,从而完成解析。
解析之后数据如下:

  1. CEventQueryVideoAccountInfoRes [m_time_stamp=0, m_succ=true, m_room_proxy_infos=[RoomProxyInfo [name=华南一区, provider=, group=, recommend=false, zoneid=110, addresses=[], channel=0], RoomProxyInfo [name=华南二区......数据太长,截取部分
复制代码
需要注意的是,并不是所有的数据结构类都会重写toString()方法帮助我们理解各个属性的真实含义,同样有很多类是没有重写该方法的。因此,我们需要尽可能重命名我们知道真实含义的每个值,方便以后遇到没有重写toString()方法时,对其属性的含义完成猜测和验证。

四、压缩和解压算法,加密与解密算法

1. 压缩解压算法如下:

  1.     //long转为var_int
  2.     private static void a_to_var_int(long arg6, f_NetBuffer arg8) {
  3.         byte v2;
  4.         long v0 = arg6 << 1 ^ arg6 >> 0x3F;
  5.         while(true) {
  6.             v2 = ((byte)(((int)(0x7F & v0))));
  7.             v0 >>>= 7;
  8.             if(v0 == 0) {
  9.                 break;
  10.             }

  11.             arg8.a_putByte(((byte)(v2 | 0x80)));
  12.         }

  13.         arg8.a_putByte(v2);
  14.     }
复制代码
  1.     //var_int转为long
  2.     private static long a_from_var_int(f_NetBuffer arg8) {
  3.         long v2 = 0;
  4.         int v0 = 0;
  5.         do {
  6.             int v1 = arg8.g_getByte();
  7.             v2 += ((((long)v1)) & 0x7F) << v0;
  8.             v0 += 7;
  9.         }
  10.         while((v1 & 0x80) != 0);

  11.         return v2 >>> 1 ^ -(v2 & 1);
  12.     }
复制代码
2. 加密解密算法:
  1. //加密代码片段,arg4为初始密钥,12345678,arg5为明文,arg6为长度
  2.     static void a(int arg4, byte[] arg5, int arg6) {
  3.         int v0;
  4.         for(v0 = 0; v0 < arg6; v0 += 4) {
  5.             int v1 = v0;
  6.             arg5[v1] = ((byte)(arg5[v1] ^ (((byte)arg4))));
  7.             v1 = v0 + 1;
  8.             arg5[v1] = ((byte)(arg5[v1] ^ (((byte)(arg4 >> 8)))));
  9.             v1 = v0 + 2;
  10.             arg5[v1] = ((byte)(arg5[v1] ^ (((byte)(arg4 >> 16)))));
  11.             v1 = v0 + 3;
  12.             arg5[v1] = ((byte)(arg5[v1] ^ (((byte)(arg4 >> 24)))));
  13.             arg4 = l.a_GetCRC32(arg5, v0, 4);
  14.         }
  15.     }
复制代码
  1. //解密代码片段,arg5为初始密钥,12345678,arg6为密文,arg7为长度
  2.     static void b(int arg5, byte[] arg6, int arg7) {
  3.         int v0 = 0;
  4.         while(v0 < arg7) {
  5.             int v1 = l.a_GetCRC32(arg6, v0, 4);
  6.             int v2 = v0;
  7.             arg6[v2] = ((byte)(arg6[v2] ^ (((byte)arg5))));
  8.             v2 = v0 + 1;
  9.             arg6[v2] = ((byte)(arg6[v2] ^ (((byte)(arg5 >> 8)))));
  10.             v2 = v0 + 2;
  11.             arg6[v2] = ((byte)(arg6[v2] ^ (((byte)(arg5 >> 16)))));
  12.             v2 = v0 + 3;
  13.             arg6[v2] = ((byte)(arg6[v2] ^ (((byte)(arg5 >> 24)))));
  14.             v0 += 4;
  15.             arg5 = v1;
  16.         }
  17.     }
复制代码
五、重要数据包分析

        在数据序列化一节已经以QueryVideoAccountInfo为例进行了阐述。在抓包之后,想要完成对数据包的详细分析,我采用xposed和jeb分析相结合的方法。我们已经知道,数据包开头即为clsid号,对应了一个java类,我们可以在JEB的disassembly页面直接搜索相关的十六进制或者十进制的值来定位关键类,同时,我们也可以通过xposed,在其序列化和反序列化的入口,拦截这个类,打印其方法名,从而得到其具体位置。

假设我们现在有一个数据包,其clsid为D1 A0 00 00。我们想要找到其实现类。

通过xposed日志,我们搜索数据包发送的clsid: D1 A0 00 00

接着找到SaveStream_param_0: com.h3d.qqx5.model.s.a.c

进入 com.h3d.qqx5.model.s.a.c类,我们发现并没有重写toString()方法,无法准确得知各个属性的含义:

  1. public class c extends d_INetMessage {
  2.     @n(a=1) public String a;
  3.     @n(a=2) public String b;
  4.     @n(a=3) public String c;
  5.     @n(a=4) public String d;
  6.     @n(a=5) public String e;
  7.     @n(a=6) public int f;
  8.     @n(a=7) public int g;
  9.     @n(a=8) public boolean h;

  10.     public c() {
  11.         super();
  12.         this.h = false;
  13.     }

  14.     public int h() {
  15.         return 0xA0D1;
  16.     }
  17. }
复制代码
利用jeb交叉引用功能,以及之前对其他类的各种注释以及猜测,我们尽可能的完成了对属性的含义的解析
  1. public class c extends d_INetMessage {
  2.     @n(a=1) public String a_open_id;
  3.     @n(a=2) public String b_open_key;
  4.     @n(a=3) public String c_pay_token;
  5.     @n(a=4) public String d_pf;
  6.     @n(a=5) public String e_pf_key;
  7.     @n(a=6) public int f_save_num;
  8.     @n(a=7) public int g_device_type;
  9.     @n(a=8) public boolean h_0; //其含义并没有分析出来,但是分析发现其固定为false,也就是0

  10.     public c() {
  11.         super();
  12.         this.h_0 = false;
  13.     }

  14.     public int h() {
  15.         return 0xA0D1;
  16.     }
  17. }
复制代码
分析出其含义,我们对于数据包的组包和解包也就有了很大的信息。

下面给出xposed代码:

  1. public class qqx5 {
  2.     public static void qqx5Hook(final XC_LoadPackage.LoadPackageParam loadPackageParam) throws ClassNotFoundException{
  3.         if (!loadPackageParam.packageName.contains("com.tencent.tmgp.MGCForAndroid")) return;
  4.         XposedBridge.log("Loaded app: " + loadPackageParam.packageName);
  5.          
  6.         //该函数应该是一个日志类,此处hook该类可以查看关键信息
  7.         XposedHelpers.findAndHookMethod("com.tencent.open.a.f", loadPackageParam.classLoader, "c", String.class, String.class, new XC_MethodHook(){
  8.             @Override
  9.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  10.                 //XposedBridge.log("日志类*****");
  11.                 String str_1 = param.args[0].toString();
  12.                 if((!str_1.contains("HotUpdate")) && (!str_1.contains("GiftConfig"))){
  13.                     XposedBridge.log("日志类:" + str_1 + ":" + param.args[1].toString());
  14.                 }
  15.             }
  16.         });
  17.          
  18.         //另一个日志类
  19.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.utils.z", loadPackageParam.classLoader, "b", String.class, String.class, new XC_MethodHook(){
  20.             @Override
  21.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  22.                 XposedBridge.log("日志类*****");
  23.                 String str_1 = param.args[0].toString();
  24.                 if((!str_1.contains("HotUpdate")) && (!str_1.contains("GiftConfig"))){
  25.                     XposedBridge.log("日志类:" + str_1 + ":" + param.args[1].toString());
  26.                 }
  27.             }
  28.         });
  29.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.utils.RSAUtil", loadPackageParam.classLoader, "encrypt", int.class, String.class, int.class, int.class, new XC_MethodHook(){
  30.             @Override
  31.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  32.                 XposedBridge.log("RSAUtilencrypt*****");
  33.                 XposedBridge.log("RSAUtilencrypt_param_0:" + param.args[0].toString());
  34.                 XposedBridge.log("RSAUtilencrypt_param_1:" + param.args[1].toString());
  35.                 XposedBridge.log("RSAUtilencrypt_param_2:" + param.args[2].toString());
  36.                 XposedBridge.log("RSAUtilencrypt_param_3:" + param.args[3].toString());
  37.             }
  38.             @Override
  39.             protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  40.                 XposedBridge.log("RSAUtilencrypt_return:" + param.getResult().toString());
  41.             }
  42.         });
  43.       //发送数据包加密与解密
  44.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.j", loadPackageParam.classLoader, "b", int.class, byte[].class,int.class, new XC_MethodHook(){
  45.             @Override
  46.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  47.                 XposedBridge.log("decrypt*****");
  48.                 XposedBridge.log("decrypt_param_0:" + param.args[0].toString());
  49.                 XposedBridge.log("decrypt_param_1:" + utils.bytesToHexFun1((byte[])param.args[1]));
  50.                 XposedBridge.log("decrypt_param_2:" + param.args[2].toString());
  51.             }
  52.             @Override
  53.             protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  54.                 XposedBridge.log("decrypt_return:" + utils.bytesToHexFun1((byte[])param.args[1]));
  55.             }
  56.         });
  57.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.j", loadPackageParam.classLoader, "a", int.class, byte[].class,int.class, new XC_MethodHook(){
  58.             @Override
  59.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  60.                 XposedBridge.log("encrypt*****");
  61.                 XposedBridge.log("encrypt_param_0:" + param.args[0].toString());
  62.                 XposedBridge.log("encrypt_param_1:" + utils.bytesToHexFun1((byte[])param.args[1]));
  63.                 XposedBridge.log("encrypt_param_2:" + param.args[2].toString());
  64.             }
  65.             @Override
  66.             protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  67.                 XposedBridge.log("encrypt_return:" + utils.bytesToHexFun1((byte[])param.args[1]));
  68.             }
  69.         });
  70.          
  71.         Class<?> NetBufferClass = loadPackageParam.classLoader.loadClass("com.h3d.qqx5.framework.d.f");
  72.         
  73.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.j", loadPackageParam.classLoader, "a", Object.class, NetBufferClass, new XC_MethodHook(){
  74.             @Override
  75.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  76.                 String str = param.args[0].getClass().getName();
  77.                 XposedBridge.log("SaveStream_param_0:" + str);
  78.             }
  79.         });
  80.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.j", loadPackageParam.classLoader, "b", Object.class, NetBufferClass, new XC_MethodHook(){
  81.             @Override
  82.             protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
  83.                 String str = param.args[0].getClass().getName();
  84.                 XposedBridge.log("LoadStream_param_0:" + str);
  85.             }
  86.         });
  87.          
  88.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.h", loadPackageParam.classLoader, "c", new XC_MethodHook(){
  89.             @Override
  90.             protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  91.                 Method rpcMethod = param.getResult().getClass().getMethod("k");
  92.                 ByteBuffer response = (ByteBuffer) rpcMethod.invoke(param.getResult());
  93.                 byte[] bytes = response.array();
  94.                 XposedBridge.log("m_body_buffer_return:" + utils.bytesToHexFun1(bytes));
  95.             }
  96.         });
  97.          
  98.         XposedHelpers.findAndHookMethod("com.h3d.qqx5.framework.d.h", loadPackageParam.classLoader, "d", new XC_MethodHook(){
  99.             @Override
  100.             protected void afterHookedMethod(MethodHookParam param) throws Throwable {
  101.                 Method rpcMethod = param.getResult().getClass().getMethod("k");
  102.                 ByteBuffer response = (ByteBuffer) rpcMethod.invoke(param.getResult());
  103.                 byte[] bytes = response.array();
  104.                 XposedBridge.log("m_header_buffer_return:" + utils.bytesToHexFun1(bytes));
  105.             }
  106.         });
  107.     }

  108. }
复制代码
全部完毕。

这次App逆向中,学习到了很多知识,该App采用了Java完成了序列化和反序列化,让我知道反射还可以这样玩,深入理解了基础连接认证,实际上安全隐患很大,因为其数据包并没有采用安全的密钥体系,中间人随意窃听。深入理解了其加密解密流程,整数的压缩与解压流程,感觉自己的功力又深了一层等等等等。。收货蛮大。

彩蛋环节:

在分析app的过程中,发现在帐号的认证过程中,也就是CEventQueryVideoAccountInfo数据包,account,skey,openid三者应该对应才可以成功登陆帐号,进而对帐号进行后续操作。实际发现,服务器对于skey与openid并没有校验,也就是说任意帐号即可登录炫舞梦工厂App。

已经和腾讯安全应急中心报告了该漏洞,并且已经完成了修复。

发表于 2023-6-13 20:15:31 | 显示全部楼层   四川省成都市
你傻啊,为啥要上报,没得玩了
回复 支持 反对

使用道具 举报

签到天数: 11 天

发表于 2023-6-5 16:36:07 | 显示全部楼层   湖北省武汉市
谢谢分享
回复 支持 反对

使用道具 举报

结帖率:75% (9/12)

签到天数: 3 天

发表于 2023-6-2 18:26:39 | 显示全部楼层   安徽省宿州市
大佬加个星球啊--给个暗号  喜欢这方面的 求个组织
回复 支持 反对

使用道具 举报

发表于 2022-11-15 14:11:12 | 显示全部楼层   江苏省扬州市
厉害,谢谢分享指导
回复 支持 反对

使用道具 举报

结帖率:90% (66/73)

签到天数: 7 天

发表于 2021-7-3 14:00:00 高大上手机用户 | 显示全部楼层   福建省漳州市
原作者是你吗? 如果是转载请写清楚 看雪和吾爱上面去年就发布了
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)

签到天数: 1 天

发表于 2021-5-17 17:56:26 | 显示全部楼层   四川省泸州市
看到TX的我就知道事情没那么简单,果然在结局看到了已经和TX安全应急中心报告了该漏洞,并且已经完成了修复。
回复 支持 反对

使用道具 举报

结帖率:0% (0/1)
 楼主| 发表于 2021-5-7 14:47:05 | 显示全部楼层   湖北省天门市
欢迎各位大佬来交流
回复 支持 反对

使用道具 举报

发表于 2021-5-6 21:07:18 | 显示全部楼层   福建省福州市
厉害了..大婶
回复 支持 反对

使用道具 举报

结帖率:100% (4/4)

签到天数: 1 天

发表于 2021-5-6 16:19:48 | 显示全部楼层   浙江省衢州市
看到TX的我就知道事情没那么简单,果然在结局看到了已经和TX安全应急中心报告了该漏洞,并且已经完成了修复。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 致发广告者

发布主题 收藏帖子 返回列表

sitemap| 易语言源码| 易语言教程| 易语言论坛| 易语言模块| 手机版| 广告投放| 精易论坛
拒绝任何人以任何形式在本论坛发表与中华人民共和国法律相抵触的言论,本站内容均为会员发表,并不代表精易立场!
论坛帖子内容仅用于技术交流学习和研究的目的,严禁用于非法目的,否则造成一切后果自负!如帖子内容侵害到你的权益,请联系我们!
防范网络诈骗,远离网络犯罪 违法和不良信息举报电话0663-3422125,QQ: 793400750,邮箱:wp@125.la
Powered by Discuz! X3.4 揭阳市揭东区精易科技有限公司 ( 粤ICP备12094385号-1) 粤公网安备 44522102000125 增值电信业务经营许可证 粤B2-20192173

快速回复 返回顶部 返回列表