WhatsApp私信协议实现记录

资讯 作者:看雪学院 2022-08-31 20:14:11 阅读:218


本文为看雪论坛优秀文章

看雪论坛作者ID:xwtwho


最近看了下WhatsApp (android及pc版本),实现了协议发送私信(模板信息也可以发), 记录下学习过程。





软硬件环境


WhatsApp v2.22
IDA 7.5
Frida 14.2.2
Gda3.86
JEB
jadx-gui
unidbg
LineageOs 17.1 (android 10)
小米8
 




记录学习过程


刚开始了解了下WhatsApp,说是消息端对端加密的,信息只能双方解密,服务器都不知道的,初始看了一脸懵,在网上找了下资料:

【翻译】WhatsApp 加密概述(技术白皮书)http://www.caotama.com/1993224.html

WhatsAPP通讯协议端对端加密人工智能
https://blog.csdn.net/BMW33939/article/details/120322512

Signal 协议 
https://blog.csdn.net/yzpbright/article/details/117808556

不过看这些资料其实有点尴尬,刚开始不知道的时候看这些也看不懂,各种密钥概念,加密过程直接绕晕了,等开始分析app,能看懂的时候,发现也搞完了,回过头来看,确实上面写的(特别是白皮书)都是对的,只是初始不了解的时候理解不了,毕竟上面文章不是实操流程。

首先看下数据流,确定下网络传输方式,结合Wireshark,通过hook及下断点等方式确定了是TCP:

查了下IP:157.240.199.61香港 Facebook

知道发送点后,再结合JNI函数(根据名称就可以确定重要的模块libwhatsapp.so,libcurve25519.so),逐步确定调用线。

看到上面so的名称,查了下知识点:
Curve25519 是目前最高水平的 Diffie-Hellman 函数,适用于广泛的场景,由 Daniel J. Bernstein 教授设计。

在密码学中,Curve25519 是一个椭圆曲线提供 128 位安全性,
设计用于椭圆曲线 Diffie-Hellman(ECDH)密钥协商方案。它是最快的 ECC 曲线之一,并未被任何已知专利所涵盖。


libcurve25519.so boolean org.whispersystems.curve25519.NativeCurve25519Provider.smokeCheck(int) 0x9d782548 func: 0x78b8406288 0x0  iOffset: 4288
libcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.generatePrivateKey(byte[]) 0x9d782638 func: 0x78b8405a2c 0x0  iOffset: 3a2c
libcurve25519.so byte[] org.whispersystems.curve25519.NativeCurve25519Provider.calculateAgreement(byte[], byte[]) 0x9d7825c0 func: 0x78b8405b68 0x0  iOffset: 3b68

生成密钥:
retval: [object Object]
java.lang.Exception
        at org.whispersystems.curve25519.NativeCurve25519Provider.generatePublicKey(Native Method)
        at org.whispersystems.curve25519.OpportunisticCurve25519Provider.generatePublicKey(:750206)

找到了发送信息的明文("11"):
[MI 10::com.whatsapp]-> byteArray,byte src : [10,2,49,49]        protobuf格式
byteArray,md5str:
11
java.lang.Exception
        at X.1FH.A02(Native Method)
        at com.whatsapp.jobqueue.job.SendE2EMessageJob.writeObject(:271863)
        at java.lang.reflect.Method.invoke(Native Method)

顺着流程,会发现很多加密相关类的调用:
java.security.MessageDigest
javax.crypto.Mac
javax.crypto.Cipher

可以直接hook了看数据流的变化,这个时候对加密模式就有了一定了解。

客户端跟服务器有一个加密方式AES-256-GCM,每个包的加密IV都不同,如果发送的数据包是私信内容的,那里面的私信内容是第二层的加密(aes-256-cbc),这一层的数据因为key的生成用到了对方的公钥做DH得到,
所以只能接收方才能解密,每条私信内容加密的key也是不同的。

每次打开app都会重新发起TCP连接(已经是用验证码登录的情况,后续的打开app),这个时候要初始化一对密钥,公钥会在连接建立后的第一个发送包中包含,发给服务器。

这里还会用到其它几种密钥(自己的identity_key,标记登录会话类似抖音session token的key,服务器的公钥),这些key相互组合通过calculateAgreement
及HKDF扩展得到中间数据和密钥,包括用来加密下面的数据。

第一个发送包中包含有手机环境信息:
这个数据校验通过后,就是连接正常建立了,后面就可以发送私信了。

前面提到,私信内容的加密其实又是一种加密模式,这个数据是发送者和接收者交互用的,服务器也解密不了的,它只是转发加密后的数据。

私信内容加密是aes-256-cbc,这里会用到消息密钥(Message Key), 80 个字节的值,用于加密消息内容。

32 个字节用于 AES-256 密钥,32 个字节用于 HMAC-SHA256 密钥,16 个字节用于 IV。

HMAC-SHA256密钥用于计算私信内容aes加密的结果的消息认证码,只取结果的前8字节。

这个消息密钥(Message Key)的计算涉及到一个棘轮变换,每加密一条消息后,就要通过sha256的组合算法计算下一条消息加密用的Message Key了。

私信消息的发送,有个会话的概念,双方建立端对端通信需要建立一个会话,就是构造约定好密钥,建立会话后,双方就可以保存这个相关环境参数,直接按消息序号就可以根据当前的密钥计算出要加解密的Message Key,用于加解密消息了。就算之后重新打开app,私信内容的加解密也可以继续按之前的会话继续,不用重新建立会话。

所以刚开始分析的时候,为了简单,就是在app会话建立的基础上分析的,这样只用hook拿到当前的消息的Message Key,就可以计算出指定消息序号的Message Key,直接加密信息发送就可以了。

然后就开始实现建立会话,这个过程可以参考白皮书:

会话发起人为接收人申请身份公钥(public Identity Key)、已签名的预共享公钥(public Signed Pre Key)和一个一次性预共享密钥(One-Time Pre Key)。

服务器返回所请求的公钥。一次性预共享密钥(One-Time Pre Key)仅使用一次,因此请求完成后将从服务器删除。如果一次性预共享密钥(One-Time Pre Key)被用完且尚未补充,则返回空。

发起人将接收人的身份密钥(Identity Key)存为 Irecipient,将已签名的预共享密钥(Signed Pre Key)存为 Srecipient,将一次性预共享密钥(One-Time Pre Key)存为 Orecipient。

发起者生成一个临时的 Curve25519 密钥对  Einitiator
发起者加载自己的身份密钥(Identity Key)作为 Iinitiator
发起者计算主密钥 master_secret = ECDH ( Iinitiator, Srecipient ) || ECDH ( Einitiator, Irecipient ) || ECDH ( Einitiator, Srecipient )  || ECDH ( Einitiator, Orecipient ) 。如果没有一次性预共享密钥(One-Time Pre Key),最终 ECDH 将被忽略。
发起者使用 HKDF 算法从 master_secret 创建一个根密钥(Root Key)和链密钥(Chain Keys)。

过程是这样,但是这个要实际跟一遍,才能比较了解,涉及到的算法本身不复杂,主要是这些key的变换流程。

对建立会话,初始还要创建2组密钥对(OurBaseKey和ourRatchetKey):
按流程实现后测试,发送信息后对方能收到,但是看不到内容:


查了下,网上有说是发送方换设备了就可能这样,那我这个肯定不属于这个情况。

当时一直没搞定,开始认为是加密算法还原有问题,反复核对了几遍,按照hook的数据及参数加密结果跟实际的完全一致。

反复的核对这种数据,搞得没脾气了,后来就静下心来想了下,要实现这个端对端加密应该怎么做,最后发现一个不确定的点,就是oneTimePreKey,
对方没法知道用的是哪个。

这个请求的时候,服务器直接返回了一个对方的一次性预共享密钥,这个接收方是提供了一批存到服务器的,用一个就删除一个,如果会话最后没建立成功,服务器应该也不会同步给接收方的,并且实际发送建立会话的数据中也没看到有回发这个过去,那接收方要能正确解密,肯定要能知道当前会话用的是哪个一次性预共享密钥。

后来想到应该是有个ID来标识的,当时也确实发现发送建立会话的数据中有几个字段值不确定,但是从返回的接收方key数据和发送的私信数据中没找到这个共同的值(后来发现其实是数据格式不同)。

一直没解决,就新开了条战线,分析PC版本,希望能找出这个差异点,解决这个问题。

PC上的WhatsApp 7个进程,也是首先找收发数据点,最后确定下面的进程是处理网络数据的,根据命令行参数也可以看出来:


跟踪发现PC上发送是用的websocket,直接下载ssl源码,参考标出函数:


找到了数据收发点:


跟踪的时候发现,接收的数据没有进一步的逻辑处理,有时候就请求清空内存了,就猜测可能是发到其它进程去了,看到有MojoMessages相关的。

查了下:Mojom是chromium最新的跨平台进程通信框架。关注点不在这,没细看。

最后发现,这个进程只是处理网络层的,具体的私信逻辑在另一个进程:



后面就是顺着调用跟踪加密流程了,弄清楚后,可以直接上frida hook PC数据(注入dll也行):

最后核对数据,确定了是有个ID来标记共享密钥:


完善这个后,再测试发送,对方就能看到内容了:
模板信息也是可以的:


顺便提一下,第一次拿码登录流程是走的http模式,数据加密也会用到密钥对的生成。

学习总结:
  1. 学习了端对端通信实现,对这种加密模式有了一定了解。

  2. 熟悉了Curve25519应用。




看雪ID:xwtwho

https://bbs.pediy.com/user-home-44250.htm

*本文由看雪论坛 xwtwho 原创,转载请注明来自看雪社区



# 往期推荐

1.Android4.4和8.0 DexClassLoader加载流程分析之寻找脱壳点

2.逆向篇:解决tiktok切换前后置虚拟摄像头卡住问题

3.House of cat新型glibc中IO利用手法解析 & 第六届强网杯House of cat详解

4.对一个随身WIFI设备的漏洞挖掘尝试

5.[安全运维向]模拟搭建小型企业内网

6.某设备CoAP协议漏洞挖掘实战






球分享

球点赞

球在看



点击“阅读原文”,了解更多!

在线申请SSL证书行业最低 =>立即申请

[广告]赞助链接:

关注数据与安全,洞悉企业级服务市场:https://www.ijiandao.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

#
公众号 关注KnowSafe微信公众号
随时掌握互联网精彩
赞助链接