简单的分析:某点评 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:【 登录服务器的流程已经完成仅供学习交流 若侵犯了权益,麻烦管理帮忙删除,谢谢 】
------------------------------------------------------------------
```
# 【至此 {登陆服务器}二进制数据包解析完毕!再次声明仅供学习交流 若侵犯了权益,麻烦管理帮忙删除,谢谢】 ak472pj 发表于 2024-2-16 13:30
谢谢分享,二进制包解析是手动分析的吗?
是的哦看帖子可能觉得比较简单,手动分析记录了好多数据 感谢分享 谢谢分享 谢谢分享,有点深度 谢谢分享,收藏学习!
谢谢分享,收藏学习! 现在网络分析也可以用java了吗(纯小白望解惑){:1_919:} 对我们来说不简单 受益匪浅 谢谢 如果获取了本地解密方法,就可以在中途抓数据包解密了,是吧?