西枫游戏 发表于 2024-2-15 22:29

简单的分析:某点评 TCP协议 算法-登录(二)

本帖最后由 西枫游戏 于 2024-2-15 22:34 编辑

# 前言
**简单分析{某点评}TCP私信协议,本帖只负责分析交流切勿他用。若侵犯了权益,麻烦管理帮忙删除,谢谢!**
# TCP交互流程
TCP协议交互理解:
1. 请求服务器共享密钥,或本地按规则生成一组key【篇文比较长分段开始】
2. RSA公钥加密并且组包发送到服务器统一加密密钥({某小破站登陆协议一样})【分析初始化并且登陆服务器】
3. 登陆服务器:组包数据包括 1. 登陆的设备id2. 附带APP版本号   3. 附带该账号的uid   4. 附带该账号的令牌{token,CK}之类

# 工具
| Tools | Version
| -------- | --------
| 样本   | 11.12.14   
| Frida   | 16.1.11   
| objection   | 1.11.0   
| jadx-gui   | 1.2.0
| 调试工具   | Redmi Note 7
# 分析流程
1.[前篇文章](https://www.52pojie.cn/thread-1883805-1-1.html)已经分析了统一密钥,接着突破口继续来初始化并且登陆服务器。frida脚本 hookjava 层的系统加解密并且打印堆栈。观察到统一密钥之后的一个初始化token令牌堆栈。
```
AES/CTR/NoPadding: AAABwQADAHEAFQAA9Lz1gwAAAAIAAAAAOHf6P+g7xAc75dCBq9r2QGVivjy0MhT32lDB/Za6FZweGbqI0UA8W//3pEbkvFKkplHuLkyAhWgFgjXN1jI4E82dRjYtoQlEBQzWJZyHgfmQgIaWMuKHyDg/6BEzypM/b+ZX5QMZMYwS+Q+wM+LWLi1RWPIj07a/UdbeFSaU5Bq1YXLDcLEnSGvWDrrZZBv+c6tx95zSeh17rFnYEYjA7d9j4yGQ1mBUsYG5xbjGFNtszW/ov5lq+0/9pJ+A87c/gUVXAs3xtDPaJs/RfwNENDE2qCHOwFImwOqhDRgzAz6AtgouY7LLmzpcQSLvV0YlJ4YTfDxJ4TgGpALHKujkw5q+W+ZKK/UEYPYOqr7v+VqE477BtXOZx6zwSxZKMVCAFkOx21AYPTNo/02J9oy3koCwYGdpgxgs9hxIXNnk9OioJPDVzEYHF9ckvyHnClvdnhNXS5b17RDZlb+L55uCE0BjYJ1wmO+00SRff9MXiH5j30fJPNB3TAvN2Ev+FpgK9R2SlN5YfHoiBhHyKY4PrYd69KaUH2WZ7mTAO+aeF0o0leb8J81NMNY=
java.lang.Exception
      at com.sankuai.xm.protobase.utils.a.c(Native Method)
      at com.sankuai.xm.login.manager.channel.f.c(CryptProcessor.java:5)
      at com.sankuai.xm.login.manager.channel.b.G(ConnectionChannel.java:8)
      at com.sankuai.xm.login.manager.channel.b.F(Unknown Source:30)
      at com.sankuai.xm.login.manager.channel.b.x(ConnectionChannel.java:69)
      at com.sankuai.xm.login.manager.channel.b.w(ConnectionChannel.java:8)
      at com.sankuai.xm.login.manager.channel.b$a.a(ConnectionChannel.java:29)
      at com.sankuai.xm.login.manager.channel.b.a(ConnectionChannel.java:5)
      at com.sankuai.xm.login.manager.channel.e.a(Connector.java:3)
      at com.sankuai.xm.login.net.e.d(NetTcpLink.java:13)
      at com.sankuai.xm.login.net.f.f(SocketPump.java:6)
      at com.sankuai.xm.login.net.f.d(SocketPump.java:9)
      at com.sankuai.xm.login.net.taskqueue.e.b(TaskPump.java:1)
      at com.sankuai.xm.login.net.taskqueue.b$a.run(AbstractQueue.java:17)
      at com.sankuai.android.jarvis.i.run(JarvisRunnableProxy.java:13)
      at java.lang.Thread.run(Thread.java:919)
      at com.sankuai.android.jarvis.p.run(JarvisThreadProxy.java:3)
      at com.sankuai.android.jarvis.m.t(JarvisThreadPoolImpl.java:14)
      at com.sankuai.android.jarvis.m$d.run(Unknown Source:20)
      at java.lang.Thread.run(Thread.java:919)
```
2.模糊判定是初始化数据包,跟进堆栈`com.sankuai.xm.login.manager.channel.b.x(ConnectionChannel.java:69)` 方法太长判断很多 贴出主要代码部分
```
                     if (i3 == 0) {
                            com.sankuai.xm.login.beans.b bVar = (com.sankuai.xm.login.beans.b) this.f89418e;
                            com.sankuai.xm.base.proto.protosingal.h hVar = new com.sankuai.xm.base.proto.protosingal.h();// new 了一个类然后传参数进去
                            hVar.L(bVar.d);
                            hVar.f = bVar.f89407e;//TCP数据参数
                            hVar.g = bVar.f;//TCP数据参数
                            hVar.h = bVar.g;//TCP数据参数
                            hVar.i = bVar.h;   //TCP数据参数
                            o.o().h();
                            hVar.j = 1;   //TCP数据参数
                            hVar.k = o.o().a(); //TCP数据参数
                            o.o().k();
                            hVar.l = 1;   //TCP数据参数
                            hVar.m = bVar.i   //TCP数据参数
                            hVar.n = bVar.j;   //TCP数据参数
                            hVar.o = h2   //TCP数据参数
                            hVar.p = q();   //TCP数据包参数 以上都是传参数进去
                            if (F.d(hVar.f) || F.d(hVar.h)) {
                              d.c("ConnectionChannel::doAuth:: PLoginByPassport, passport or device==null", new Object);
                              u(21, 0, "", "", "", null);
                            } else {
                              byte[] marshall = hVar.marshall();// 这里是取出原数据的方法
                              StringBuilder m2 = android.arch.core.internal.b.m("ConnectionChannel::doAuth:: PLoginByPassport, passport = ");
                              m2.append(bVar.f89407e);
                              m2.append(", device = ");
                              m2.append(bVar.g);
                              m2.append(",deviceData = ");
                              m2.append(hVar.n);
                              m2.append(", crc ");
                              if (marshall != null) {
                                    str = CommonUtil.a(marshall);
                              }
                              m2.append(str);
                              d.f(m2.toString());      
                              F(marshall);    //这里是x(ConnectionChannel.java:69)最后TCP协议发送前处理数据的地方包含加密
                            }

```
4.跟进 `byte[] marshall = hVar.marshall();`
```
public final class h extends g {
    public static ChangeQuickRedirect changeQuickRedirect;
    public String f;
    public String g;
    public String h;
    public String i;
    public short j;
    public int k;
    public short l;
    public String m;
    public String n;
    public boolean o;
    public long p;
    @Override // com.sankuai.xm.base.proto.protobase.g, com.sankuai.xm.base.proto.protobase.b
    public final byte[] marshall() {   //返回原数据的marshall 方法
      O(196721);   //消息标签
      D(this.f);
      D(this.g);
      D(this.h);
      D(this.i);
      C(this.j);
      z(this.k);
      C(this.l);
      D(this.m);
      D(this.n);
      v(Boolean.valueOf(this.o));
      A(this.p);
      D(null);    //以上就是new的一个类 然后把参数全部都传进处理完毕后存放在 public ByteBuffer f87924b;
      return super.marshall(); //返回的是他的父类的marshall方法 跟进去
    }
}
```
5.简单列举一个D方法 传进的 `D(this.f)`;
```
    public final void D(String str) {
      Object[] objArr = {str};
      ChangeQuickRedirect changeQuickRedirect2 = changeQuickRedirect;
      if (PatchProxy.isSupport(objArr, this, changeQuickRedirect2, 444570)) {
            PatchProxy.accessDispatch(objArr, this, changeQuickRedirect2, 444570);
      } else if (str == null) {    //str 不为空
            b(2);
            this.f87924b.putShort(0);
      } else {
            try {
                int d = d(str);
                if (d <= 32767) {
                  short s = (short) d;
                  b(s + 2);
                  this.f87924b.putShort(s);   //传进数据的长度
                  this.f87924b.put(str.getBytes());//传进数据的内容
                  return;
                }
                throw new RuntimeException("string too long");
            } catch (UnsupportedEncodingException e2) {
                throw new RuntimeException(e2);
            }
      }
    }

```
6.跟进父类`super.marshall()` 的marshall方法
```
    public byte[] marshall() {
      int i;
      int i2;
      this.c.d = 0;
      b(0);
      this.c.f87920a = this.f87924b.position();
      this.f87924b.putInt(0, this.c.f87920a);      // TCP数据包的总长度
      this.f87924b.putInt(4, this.c.f87921b);   //数据包标签
      this.f87924b.putShort(8, this.c.c);            //固定
      this.f87924b.putShort(10, this.c.d);         //固定
      this.f87924b.putInt(12, 0);   // CRC32校验
      d dVar = this.c;
      if (dVar.f <= 0) {
            Object[] objArr2 = new Object;
            ChangeQuickRedirect changeQuickRedirect3 = changeQuickRedirect;
            if (PatchProxy.isSupport(objArr2, null, changeQuickRedirect3, 2979355)) {
                i = ((Integer) PatchProxy.accessDispatch(objArr2, null, changeQuickRedirect3, 2979355)).intValue();
            } else {
                synchronized (g.class) {
                  f87925e++;
                  if (f87925e <= 0) {
                        f87925e = 1;
                  }
                  i2 = f87925e;
                }
                i = i2;
            }
            dVar.f = i;
      }
      this.f87924b.putInt(16, this.c.f);   //类似于调用次数
      this.f87924b.putInt(20, this.c.g);    //固定为
      byte[] bArr = new byte;//new 一个byte[]
      this.f87924b.position(0);         
      this.f87924b.get(bArr);                //把数据存放进bArr 里面
      int F = F(bArr);                            //CRC32校验
      this.f87924b.putInt(12, F);          //把CRC32校验数据存放进第12 位置里面
      this.c.f87922e = F;
      this.f87924b.position(0);
      this.f87924b.get(bArr);            //把this.f87924b数据包存进bArr 里面
      this.f87924b = null;
      return bArr;                           //返回最终原数据包
    }
```
7.原始数据包已经分析完毕,现在堆栈处`at com.sankuai.xm.protobase.utils.a.c(Native Method)`最后调用该方法进行`AES/CTR/NoPadding` 加密之后再发送到服务器。
```
public final byte[] c(byte[] bArr) {
      byte[] bArr2;
      if (bArr != null && bArr.length >= 24) {
            try {
                int i = this.c ? 16 : 0;
                byte[] bArr3 = new byte[(bArr.length + i)];
                SecretKeySpec secretKeySpec = new SecretKeySpec(this.f89761b, "AES");
                Cipher instance = Cipher.getInstance("AES/CTR/NoPadding");
                if (i > 0) {
                  bArr2 = new byte;
                  new SecureRandom().nextBytes(bArr2);
                } else {
                  bArr2 = new byte;
                }
                instance.init(1, secretKeySpec, new IvParameterSpec(bArr2));
                byte[] doFinal = instance.doFinal(bArr, 24, bArr.length - 24);// 取整个TCP数据包的第25位到末尾进行doFinal加密
                System.arraycopy(bArr, 0, bArr3, 0, 24);
                if (i > 0) {
                  ByteBuffer allocate = ByteBuffer.allocate(4);
                  allocate.position(0);
                  allocate.put(bArr3, 0, 4);
                  allocate.flip();
                  int i2 = allocate.getInt();
                  allocate.flip();
                  allocate.putInt(i2 + i);
                  allocate.flip();
                  byte[] bArr4 = new byte;
                  allocate.get(bArr4);
                  System.arraycopy(bArr4, 0, bArr3, 0, 4);
                  System.arraycopy(bArr2, 0, bArr3, 24, i);
                }
                System.arraycopy(doFinal, 0, bArr3, i + 24, doFinal.length);//取原数据包的前25位+加密后的数据组成最终TCP服务器认可的数据包
                return bArr3;
            } catch (Exception e2) {
                com.sankuai.xm.log.a.e(e2);
            }
      }
      return bArr;
    }
```
8.**至此 ,初始化数据包并登陆服务器分析完毕,代码没多少就是需要花费点时间调试。**

# 二进制数据包解析:
原始数据包:{ 0, 0, 1, 193, 0, 3, 0, 113, 0, 21, 0, 0, 244, 188, 245, 131, 0, 0, 0, 2, 0, 0, 0, 0, 0, 10, 49, 57, 49, 53, 57, 56, 52, 52, 52, 53, 0, 228, 48, 50, 48, 50, 50, 55, 50, 51, 48, 98, 99, 97, 52, 49, 50, 48, 54, 99, 100, 48, 97, 52, 99, 100, 48, 55, 48, 54, 52, 55, 50, 56, 100, 52, 97, 100, 52, 98, 49, 97, 48, 53, 97, 48, 54, 50, 101, 53, 52, 98, 101, 97, 55, 97, 50, 97, 98, 57, 54, 98, 102, 50, 55, 97, 97, 97, 99, 100, 98, 53, 98, 101, 50, 52, 54, 99, 55, 51, 57, 102, 48, 52, 51, 50, 55, 51, 57, 99, 51, 101, 50, 52, 54, 52, 48, 97, 101, 102, 51, 51, 56, 48, 57, 50, 48, 55, 98, 50, 48, 57, 54, 56, 102, 102, 102, 56, 48, 48, 48, 48, 48, 48, 48, 48, 102, 99, 49, 100, 48, 48, 48, 48, 48, 102, 49, 53, 100, 99, 102, 52, 52, 99, 98, 54, 102, 54, 101, 48, 102, 54, 50, 52, 54, 97, 53, 98, 101, 101, 51, 100, 97, 48, 53, 102, 56, 54, 50, 101, 97, 53, 102, 51, 51, 51, 52, 57, 53, 101, 57, 57, 49, 50, 52, 101, 99, 99, 51, 50, 98, 49, 54, 54, 48, 102, 100, 49, 97, 54, 102, 51, 102, 99, 50, 49, 56, 51, 55, 54, 54, 56, 102, 48, 101, 98, 52, 48, 53, 54, 56, 54, 97, 55, 100, 54, 100, 102, 51, 101, 0, 41, 48, 49, 97, 57, 53, 53, 50, 52, 45, 101, 49, 57, 54, 45, 52, 50, 101, 55, 45, 98, 54, 54, 100, 45, 100, 102, 50, 49, 99, 98, 48, 52, 53, 97, 56, 98, 95, 49, 95, 50, 49, 0, 8, 49, 49, 46, 49, 50, 46, 49, 52, 0, 1, 0, 61, 193, 102, 0, 1, 0, 51, 50, 97, 102, 100, 100, 54, 51, 48, 54, 98, 57, 100, 52, 50, 52, 49, 56, 49, 52, 101, 50, 50, 52, 52, 50, 99, 98, 49, 53, 101, 99, 55, 97, 49, 54, 55, 49, 51, 57, 52, 49, 51, 50, 54, 53, 51, 49, 57, 55, 48, 50, 0, 56, 123, 34, 100, 101, 118, 105, 99, 101, 73, 100, 34, 58, 34, 48, 49, 97, 57, 53, 53, 50, 52, 45, 101, 49, 57, 54, 45, 52, 50, 101, 55, 45, 98, 54, 54, 100, 45, 100, 102, 50, 49, 99, 98, 48, 52, 53, 97, 56, 98, 95, 49, 95, 50, 49, 34, 125, 0, 3, 69, 29, 101, 57, 67, 207, 186, 0, 0 }
```
1. { 0, 0, 1, 193 }   //数据包总长度 449
2. { 0, 3, 0, 113 }    //消息标签【196721】
3. { 0, 21, 0, 0 }      //未知暂时固定
4. { 244, 188, 245, 131 }   //CRC32 数据包校验
5. { 0, 0, 0, 2, 0, 0, 0, 0, 0 }//调用次数固定
6. {10, 49, 57, 49, 53, 57, 56, 52, 52, 52, 53 }//长度为10位的 userid由于是文章 此处设为随机
7. { 228, 48, 50, 48, 50, 50, 55, 50, 51, 48, 98, 99, 97, 52, 49, 50, 48, 54, 99, 100, 48, 97, 52, 99, 100, 48, 55, 48, 54, 52, 55, 50, 56, 100, 52, 97, 100, 52, 98, 49, 97, 48, 53, 97, 48, 54, 50, 101, 53, 52, 98, 101, 97, 55, 97, 50, 97, 98, 57, 54, 98, 102, 50, 55, 97, 97, 97, 99, 100, 98, 53, 98, 101, 50, 52, 54, 99, 55, 51, 57, 102, 48, 52, 51, 50, 55, 51, 57, 99, 51, 101, 50, 52, 54, 52, 48, 97, 101, 102, 51, 51, 56, 48, 57, 50, 48, 55, 98, 50, 48, 57, 54, 56, 102, 102, 102, 56, 48, 48, 48, 48, 48, 48, 48, 48, 102, 99, 49, 100, 48, 48, 48, 48, 48, 102, 49, 53, 100, 99, 102, 52, 52, 99, 98, 54, 102, 54, 101, 48, 102, 54, 50, 52, 54, 97, 53, 98, 101, 101, 51, 100, 97, 48, 53, 102, 56, 54, 50, 101, 97, 53, 102, 51, 51, 51, 52, 57, 53, 101, 57, 57, 49, 50, 52, 101, 99, 99, 51, 50, 98, 49, 54, 54, 48, 102, 100, 49, 97, 54, 102, 51, 102, 99, 50, 49, 56, 51, 55, 54, 54, 56, 102, 48, 101, 98, 52, 48, 53, 54, 56, 54, 97, 55, 100, 54, 100, 102, 51, 101 }    //长度为228位的 token令牌由于是文章 此处设为随机
8. { 41, 48, 49, 97, 57, 53, 53, 50, 52, 45, 101, 49, 57, 54, 45, 52, 50, 101, 55, 45, 98, 54, 54, 100, 45, 100, 102, 50, 49, 99, 98, 48, 52, 53, 97, 56, 98, 95, 49, 95, 50, 49 }   //长度为41 的 devicesid
9. { 8, 49, 49, 46, 49, 50, 46, 49, 52 }// 长度为8 的 APP Vsersion
10. { 1, 0, 61, 193, 102, 0, 1 }//未知固定
11. { 51, 50, 97, 102, 100, 100, 54, 51, 48, 54, 98, 57, 100, 52, 50, 52, 49, 56, 49, 52, 101, 50, 50, 52, 52, 50, 99, 98, 49, 53, 101, 99, 55, 97, 49, 54, 55, 49, 51, 57, 52, 49, 51, 50, 54, 53, 51, 49, 57, 55, 48, 50 }   //长度为51的dpid 设备id
12. { 56, 123, 34, 100, 101, 118, 105, 99, 101, 73, 100, 34, 58, 34, 48, 49, 97, 57, 53, 53, 50, 52, 45, 101, 49, 57, 54, 45, 52, 50, 101, 55, 45, 98, 54, 54, 100, 45, 100, 102, 50, 49, 99, 98, 48, 52, 53, 97, 56, 98, 95, 49, 95, 50, 49, 34, 125 }//长度为56的 devicesid
13. { 0, 3, 69, 29, 101, 57, 67, 207, 186, 0, 0 }    // 未知 固定
```
# 组包发送登陆成功,小号发送消息解密
```
2024年2月15日22时20分45秒 更新通讯密钥成功!

登陆聊天服务器:103.X.X.X:8500 | 连接是否成功: 真
===================================================================
当前心跳包1发送:真
当前心跳包2发送:真
------------------------------------------------
对方回复消息时间: 2024年2月15日22时21分3秒

Content:【 1 】
------------------------------------------------------------------
对方回复消息时间: 2024年2月15日22时21分9秒

Content:【 登录服务器的流程已经完成仅供学习交流 若侵犯了权益,麻烦管理帮忙删除,谢谢 】
------------------------------------------------------------------
```

# 【至此 {登陆服务器}二进制数据包解析完毕!再次声明仅供学习交流 若侵犯了权益,麻烦管理帮忙删除,谢谢】

西枫游戏 发表于 2024-2-16 21:30

ak472pj 发表于 2024-2-16 13:30
谢谢分享,二进制包解析是手动分析的吗?

是的哦看帖子可能觉得比较简单,手动分析记录了好多数据

yixinyixi 发表于 2024-2-15 23:38

感谢分享

Y0uD1 发表于 2024-2-15 23:44

谢谢分享

lj98 发表于 2024-2-16 00:06

谢谢分享,有点深度

52pojieplayer 发表于 2024-2-16 01:37

谢谢分享,收藏学习!

Heybo. 发表于 2024-2-16 04:54


谢谢分享,收藏学习!

kkkkkkkkn 发表于 2024-2-16 08:12

现在网络分析也可以用java了吗(纯小白望解惑){:1_919:}

ltgb 发表于 2024-2-16 08:16

对我们来说不简单

ztqddj007 发表于 2024-2-16 08:16

受益匪浅 谢谢

tzblue 发表于 2024-2-16 09:11

如果获取了本地解密方法,就可以在中途抓数据包解密了,是吧?
页: [1] 2 3
查看完整版本: 简单的分析:某点评 TCP协议 算法-登录(二)