直接用frida开始hook,虽然不太清楚具体的结构,但是可以先hook一些比较底层的函数,然后分析数据再把hook点一步步的网上移,最后挂到反序列化、解密操作之后的地方。
1. 从网络层开始
用脚本先Hook native里的以下函数:
connectclosegetpeernamegetsocknamesendsendtowriterecvrecvfromread这一步可以拿到fd, peer, ip:port, 收发方向, 包大小。
然后分析包的前十几个bytes,发现了疑似mtproto transport的类型。
例如:
0xef -> mtproto-abridged-init0xeeeeeeee -> mtproto-intermediate-init0xdddddddd -> mtproto-padded-intermediate-init这里看到的只是原始数据,是看不到明文的。
2. hook java层的ConnectionsManager.sendRequest
可以hook:
org.telegram.tgnet.ConnectionsManager.sendRequest在这里直接遍历:
const ConnectionsManager = Java.use('org.telegram.tgnet.ConnectionsManager');const overloads = ConnectionsManager.sendRequest.overloads;然后打印第一参数,也就是tl的请求对象:
const req = arguments[0];reqName = req ? req.$className : '<null>';fields = summarizeTlObject(req);在日志里就能够看到:
TL_messages_getHistoryTL_messages_readHistoryTL_messages_sendMessageTL_messages_getMessagesReactions3. hook入站
真正获取到明文消息的内容的位置是在:
org.telegram.messenger.MessagesController.processUpdates在这里只选择第一参数是:
org.telegram.messenger.MessagesController.processUpdates的overload。
可以做个简单的逻辑判断:
if (firstArg !== 'org.telegram.tgnet.TLRPC$Updates') { return;}然后调用:
logIncomingUpdates('MessagesController.processUpdates', updates, fromQueue);这里已经是 Telegram 客户端处理服务端更新的业务层了。也就是说:
MTProto 网络包 -> native/tgnet 解密 -> TL 反序列化 -> 得到 TLRPC$Updates -> 进入 MessagesController.processUpdates在这里抓到的就不是密文了,而是java对象。
接下来需要做的是对updates.updates里的东西进行筛选:
TL_updateNewMessageTL_updateNewChannelMessageTL_updateShortMessageTL_updateShortChatMessage如果发现有message字段,就进一步去尝试获取:
message.iddateoutmessagefrom_idpeer_idmedia4. 聊天框中的消息对象MessageObject
找到的最贴近聊天窗口的地方是在:
org.telegram.messenger.MessageObjecthook一下MessageObject的构造函数:
MessageObject.$init.overloads.forEach(function (overload) { overload.implementation = function () { const ret = overload.apply(this, arguments); logMessageObject(this, 'MessageObject.<init>'); return ret; };});这个MessageObject是一个用的很多的消息包装对象,文本、图片、媒体状态都会被包成这个对象然后交给ui。
logMessageObject()主要功能是:
const owner = readField(obj, 'messageOwner');const summary = owner ? summarizeMessage(owner) : {};const messageText = readField(obj, 'messageText');其中:messageOwner 是原始 TLRPC$MessagemessageText 是 UI 层处理后的文本summarizeMessage(owner) 会继续解析消息、发送方、会话、媒体
所以这一步拿到的其实就是已经解密、反序列化之后的马上要给ui去用的明文消息。
大概的格式是这样的:
{ "api": "MessageObject.<init>", "message_object": { "class": "org.telegram.tgnet.TLRPC$TL_message", "id": "20", "out": "false", "message": "", "media_unread": "true", "media": { "class": "org.telegram.tgnet.TLRPC$TL_messageMediaPhoto", "ttl_seconds": "2147483647", "photo": { "class": "org.telegram.tgnet.TLRPC$TL_photo", "id": "...", "dc_id": "5" } }, "messageText": "阅后即焚图片" }}这里的ttl_seconds应该是这张图片过期的时间,2147483647是最大的int值。在app里处理的时候也有判断ttl_seconds == Interger.Max的,如果是那就说明这是一张view once图片;如果ttl_seconds > 0说明不是一张普通的图片;如果0 < ttl_seconds < Interger.Max那就计算其过期时间,时间到了就调用相应的销毁逻辑。
1. hook getPathToAttach
在找到以上信息之后,为了找到对应的图片处理逻辑,直接尝试hook了getPathToAttach这个函数。
每当从对话列表点到对话框里的时候,就会调用这个函数去加载照片。
对于普通的图片和阅后即焚的图片保存路径是不一样的,阅后即焚的照片保存路径在
/storage/emulated/0/Android/data/org.telegram.messenger.web/cache并且不是以明文的形式保存的。通过对这个目录下的一些文件进行分析,发现文件名有点意思,可以看看:
-6070991742858629533_120.jpg.enc-6070991742858629543_121.jpg.enc-6070991742858629543_121.jpg.enc.key-6070991742858629543_121.temp.enc除了开头的那个-之外,60709917428586295这一段相同,后面的结尾有点不同。
所以有理由相信,这些就是同一组照片在cache中的不同的派生文件,简单来说就是这是一起的。
所以接下来需要找的就是谁加载了这些文件,然后加载之后去了哪里,在哪里解密的?
2. .enc文件调用链
为了找到是谁打开了这个.enc,在native层我hook了:
openopen64openatopenat64readclose在java层hook了:
org.telegram.messenger.secretmedia.EncryptedFileInputStream中的
read(byte[], int, int)skip(long)decryptBytesWithKeyFile(byte[], int, int, java.io.File)decryptBytesWithKeyFile(byte[], int, int, org.telegram.messenger.SecureDocumentKey)还有
org.telegram.messenger.secretmedia.EncryptedFileDataSource中的
open(com.google.android.exoplayer2.upstream.DataSpec)read(byte[], int, int)最终确定了调用链:
/data/user/0/org.telegram.messenger.web/cache/-6070991742858629543_121.jpg.enc.key -> 读取 32 bytes key -> 读取 16 bytes iv/storage/emulated/0/Android/data/org.telegram.messenger.web/cache/-6070991742858629543_121.jpg.enc -> EncryptedFileInputStream.read(...) -> Utilities.aesCtrDecryptionByteArray(...)总结就是:.enc图片的实际路径是:cache/-6070991742858629543_121.jpg.enc对应的key是:
/data/user/0/org.telegram.messenger.web/cache/-6070991742858629543_121.jpg.enc.key其中.key文件的结构是前32字节的aes key, 后16字节的iv。
调用的解密函数是:
org.telegram.messenger.Utilities.aesCtrDecryptionByteArray([B, [B, [B, int, long, int)caller是:
org.telegram.messenger.secretmedia.EncryptedFileInputStream.read([B, int, int)使用的模式是mode=0, 对应的是ctr模式。
完整的链路如下:
ImageLoader / CacheOutTask -> EncryptedFileInputStream -> 读取 .jpg.enc.key: key(32) + iv(16) -> read .jpg.enc chunk -> Utilities.aesCtrDecryptionByteArray(buffer, key, iv, offset, length, fileOffset) -> 得到解密后的图片字节流第二点里已经讲到了加密文件的存储路径以及对应的解密函数了,这里简单讲一下解密之后怎么去重建一个图片。
1. Hook aes解密的函数
hook这个可以打印一些信息,但是hook这个最主要的方法是,确定app真的调用了这个函数解密。
2. hook EncryptedFileInputStream.read(byte[], int, int)
这个是核心,同时这个也是解密函数的caller。hook这个可以直接拿到明文。
3. 重建图片
接下来就比较简单,把拿到的明文拼接一下,判断一下数据的开头和结尾,命中了之后直接保存就完事,扩展名是.jpg,然后直接保存完事。
4. 关于ai
以上的分析深度的结合了ai,现在的ai能够干大量人不好干的工作,比如看汇编, smali代码, frida脚本跑出来的大量的未经处理的日志等等这种对人来说晦涩难懂的东西。ai看这些东西看的又快又准,还能帮着写hook脚本,又快又好。但是整体过程中还是有很多地方需要人去把控,纯让ai做容易跑偏。
还有就是一个ai的正义感问题,涉及到隐私功能的分析ai直接不干了,还得让人去拆解成一个个的小目标然后一点点的摸索。
总的来说ai在数据分析方面优势很明显,可以信任但是不能100%信任,每个流程还需要自己把控一下。
如果还需要学习技术,一定要在ai做完了之后多复盘,把技术细节和有疑问的地方搞清楚。

看雪ID:mb_hfjfruhl
https://bbs.kanxue.com/user-home-964485.htm

# 往期推荐


球分享

球点赞

球在看

夜雨聆风