坦白真相之路

发布时间:

### 前言 其实我当初在弄这个东西的时候也没有想到会如此之火,QQ的一天访问量达到了四万余次。再加上今天有许多人问我关于实现解码的思路,所以就写下这篇文章来记录一下。 注:当我在写这篇文章的时候,QQ坦白说团队已经在后台新添了一项URL验证机制并修复了一系列特殊机型上的bug,所以文章中提到的数据获取方式已经失效。 ### 开端 在第一次看到坦白说的时候我以为是一个新版QQ内部的插件,所以当时并没有想到破解的事情。但是有一次网速较慢的时候打开坦白说,屏幕上方闪过了一行“网页由ti.qq.com提供”,这时这个新出炉的项目就引起了我的兴趣,让我决定去看一下数据包的具体格式。 ### 数据收集 项目的第一步是从抓包开始的。我使用的工具是[Fiddler](https://www.telerik.com/fiddler),使用它在局域网内搭建了一个代理服务器,从而获取手机的数据信息。(关于如何抓取手机数据及如何解包https网上已经有很多教程了,在此不再赘述)在手机上打开坦白说之后,通过筛选Fiddler中`ti.qq.com`的数据记录可以看到网站的大致加载流程。在这里面就可以找到一个请求为`/cgi-node/honest-say/receive/mine`及`/cgi-node/honest-say/receive/friends`的JSON记录,通过JSON的内容可以知道这些是我们想要的数据。 JSON包的大致结构如下: ```json { "code":0, "data":{ "list":[{ "fromNick":"匿名昵称", "fromEncodeUin":"*S1*加密ID", "fromFaceUrl":"匿名头像", "fromGender":0, //男0女1 "toUin":123456789, //被评价人ID "toNick":"被评价人昵称", "topicId":1234, //评价编号 "topicName":"评价内容", "timestamp":1234567890 //时间戳 }], "maxUnread":0, "cookie":"下一页码标记", "finish":0 //是否为最后一页 } } ``` 可以看到在`fromEncodeUin`字段里有一串看不懂的字符串,而通过键名可以猜出这一串是通过某种方式加密之后的QQ号。 为了尝试了解字符串的加密方式,首先需要大量的数据进行相互对照。由于给我评价的人并不愿意透露自己的真实身份,所以只能利用当时在iOS系统上的bug——通过搜索评价获取真实QQ。在手动搜索之后,就获得了QQ号与加密字符串的一一对应关系。 ### 踩坑 鉴于加密字符串都有一个公共的前缀“\*S1\*”,所以可以肯定这个前缀和QQ号没有任何关联。那么对于剩下的部分,可以看到都是有大小写字母和数字无规律交错构成的,于是第一反应是:这东西是不是Base64编码加上某种加密算法? 在对字符串做了Base64解码之后,得到的是一串二进制串。这里有一个有趣的现象:十位数QQ对应的解码结果是十个字节,九位数QQ对应九个字节。也正是这个现象把我开始的解决思路带到了坑里,让我开始默认以为一个数字就对应一个字节。 在对二进制串转十六进制之后,又发现了一个有趣的现象:开头数字相同的QQ号对应的前几位十六进制也是一样的。这时就感觉不是用对称/非对称加密算法做的加密了,因为上述的加密算法只要有一位不同就会对加密结果有较大的影响。虽然这时的思路是数字固定对应某一十六进制数,但是通过观察发现,第一位的数字在第二位上的对应关系会发生改变,而且第一位上的数字会影响第二位上的内容。如在第一位上的3对应a2,但是如果第一位是1,后面的3就变成了对应aa;如果第一位是2,后面的3就变成了0a。 这时我的思路倾向于第一位是固定对应的,然后后面数字的对应是通过默认编码和前面的编码进行异或或者其他的操作得出的。这时就陷入了一个大坑当中,因为无论怎么设计运算方式,总有数字是不满足这个运算关系的。于是只能继续对数据进行观察,发现有时在中间的编码是和第一位的对应关系一模一样的,当发现这个现象的时候,就接近最终的编码思路了。 ### 发现规律 通过验证,发现第1,4,7位的数据总是同一个对应关系。这时就做了一个假设,认为QQ加密是通过3位一循环的方式进行的。在这个假设下,经过继续观察不难发现每组(三位数字一组)中的数字分别对应3,1.5,1.5个十六进制数。这时其实有些疑惑,为什么要通过取半个数的方式进行加密呢?后来在准备收工的时候看到原始字符串,想到Base64解码操作,才发现是被这个长的像Base64编码的东西给误导了。 解释一下,Base64中一个字符对应的是6位数(6bit),而十六进制对应的是4位数(4bit)。如果把十六进制中的3,1.5以6位数来看的话,恰好对应的是2和1。即3\*4=2\*6。 这时也就理解了最终的解码思路,即我在空间中发布的三位循环查表方式。至于对应编码表,只要数据足够,总结出这个对应表也不是什么难题。当然,在总结规律的时候也发现了第10位的编码和前几位不同,由于收集到的数据中10位QQ数量不足,所以得到的编码表并没有考虑第10位(而且一般情况下知道前9位就能知道是哪位)。 ### 后记 现在的坦白说团队对请求网址多加了一个token字段验证(在参数项中),这个token字段是由用户的sKey信息进行一系列的移位操作得到的。但是由于sKey数据的使用过于危险(sKey相当于用户的一个动态密码),为防止恶意使用,在此不再描述如何获取sKey及如何构造token。 至于默认只加载最近20条的问题,只需要在请求的URL中加上cookie字段即可实现翻页效果,此字段的值为上一页JSON中的cookie字段的值(不明白的可以看开头的JSON结构)。 最后祝你~~被水淹没,不知所措~~happy coding!