2018-04-04
本文首发于,作者initialize_Zero,转载请注明作者以及本文链接
近期腾讯在手Q上线了新功能--坦白说,可以匿名对好友打上标签,许多人玩的不亦乐乎,也引发了各个平台上的大讨论,趁着假期写篇文章
既然是刚刚上线的新功能,必然有不完善的地方,大神们各显神通,只为了找到坦白说背后的发送者
经过整理大致有以下几种方法:
1.将对方所发图片收藏,然后进入我的收藏,即可看到
2.iOS版手Q可以通过搜索消息记录的方式搜索到发送方
3.API数据接口 https://ti.qq.com/cgi-node/honest-say/receive/mine ,在手Q中打开此链接,获取并解码相关json数据
截止4月3日,第1,2种方法已失效,下面我们重点分析第三种方法.
4月1日之前,对API接口请求可得到如下所示的json数据:
{
"code":0,
"data":{
"list":[
{
"fromNick":"一个认识y年的男生",
"fromEncodeUin":"*S1*oKoezon5",
"fromFaceUrl":"man.png",
"fromGender":0,
"toUin":1088668866,
"toNick":"",
"topicId":666,
"topicName":"别人对你的匿名评价",
"timestamp":1534435200
},
{
"fromNick":"一个x岁的女生",
"fromEncodeUin":"*S1*oKoezon5",
"fromFaceUrl":"woman.png",
"fromGender":1,
"toUin":1088668866,
"toNick":"",
"topicId":666,
"topicName":"别人对你的匿名评价",
"timestamp":1534435200
}
]
}
}
不过tx也很快地封杀了第三种方法,4月1日后直接对API请求会返回如下结果:
{
"code":2333333,
"msg":"皮这一下你很开心么ヽ( ̄▽ ̄)ノ"
}
皮一下可以,皮几万就不行了,这更加激发了我的好奇心,身为程序猿的我,怎能因为这样的一点小困难止步不前
下面祭出神器
Packet Capture (又名无root抓包)
Packet Capture
Packet capture/Network traffic sniffer app with SSL decryption.
Not that feature rich yet, but it's a powerful debugging tool especially when developing an app.
Features:
- Capture network packets and record them.
- SSL decryption using man-in-the-middle technique.
- No root required.
- Easy to use.
- Show packet in either hex or text.
简单来说此软件可以抓取并记录各种APP的网络请求数据包,安装自带的证书后还可以解密https内容.
下面我们介绍使用方法
1.安装
酷安APP中搜索并下载,安装完成后打开
- 点击GetStarted
- Continue
- 这里一定要点击 Install Certificate , 安装配套证书,不然在稍后操作中无法看到https内容
- 会弹出Android 系统证书添加界面,点击确定
- 这样就进入到主界面,左上角两个绿色按钮分别是抓取单一APP网络请求与抓取全部请求
这样就完成了抓取前的准备工作
2.抓取请求
抓取前尽可能关掉除QQ与Packet Capture以外的所有软件,提高成功率,还可以避免抓到其他无关数据
下面是重点,请严格按照如图所示操作
1.确保QQ列表中有"坦白说"这一会话
2.切换到Packet Capture,点击右上角的绿色按钮,开始抓取
第一次使用会提示,点击"确定"即可
3.绿色按钮变为红色表示正在抓取中,同时出现提示信息
4.切换回QQ,点击坦白说会话进入
5.继续点击进入,如果有下图提示,一定点击继续访问,原因是我们抓取数据时用了Packet Capture自带证书替换了原证书.
6.右上角点击进入"收到的坦白说"
7.进入此列表,等待一会,一定保证列表加载完全
8.切换到Packet Capture,点击右上角的红色停止按钮,结束本次抓包,这样我们就有了一次记录,点击进入
9.重点寻找QQ发出/接收的数据包,包含坦白说列表数据的数据包大小在10KB以上,且为SSL类型
(抓到的数据包大约在30~50个左右,耐心寻找)
10.点击进入此记录,如果找到了具有如下类型请求的数据包,恭喜你,离成功又近了一步!
(GET /honest-say/main.html , Host: ti.qq.com)
11.这个数据包很大,使劲往下翻,如果类似于发现下图中的数据,就是我们所需分析的重点内容
(可以长按文本进行选择,复制等操作)
接下来重点分析这些数据
{
"code":0,
"data":{
"list":[
{
"fromNick":"一个认识y年的男生",
"fromEncodeUin":"*S1*oKoezon5",
"fromFaceUrl":"man.png",
"fromGender":0,
"toUin":1088668866,
"toNick":"",
"topicId":666,
"topicName":"别人对你的匿名评价",
"timestamp":1534435200
},
{
"fromNick":"一个x岁的女生",
"fromEncodeUin":"*S1*oKoezon5",
"fromFaceUrl":"woman.png",
"fromGender":1,
"toUin":1088668866,
"toNick":"",
"topicId":666,
"topicName":"别人对你的匿名评价",
"timestamp":1534435200
}
]
}
}
参数名 | 含义 | 备注 |
---|---|---|
fromNick | 对方所显示的匿名昵称 | e.g."一个认识3年的男生" "一个南京的女生" |
fromEncodeUin | 编码后的对方QQ | 重点,从这里入手 |
fromFaceUrl | 对方头像图片文件 | |
fromGender | 对方性别 | 0为男,1为女 |
toUin | 你的QQ号 | |
toNick | 你的昵称 | 似乎都是空 |
topicId | 话题ID | 话题索引 |
topicName | 话题名称 | e.g. "我身边的最强大脑" "将来能干一番大事" "拥有迷人的长发" 也是对方给你发送的第一条消息 |
timestamp | 时间戳 | 1970年1月1日(UTC/GMT的午夜)开始所经过的秒数 |
各参数说明如下:
参数名 | 含义 | 备注 |
---|---|---|
fromNick | 对方所显示的匿名昵称 | e.g."一个认识3年的男生" "一个南京的女生" |
fromEncodeUin | 编码后的对方QQ | 重点,从这里入手 |
fromFaceUrl | 对方头像图片文件 | |
fromGender | 对方性别 | 0为男,1为女 |
toUin | 你的QQ号 | |
toNick | 你的昵称 | 似乎都是空 |
topicId | 话题ID | 话题索引 |
topicName | 话题名称 | e.g. "我身边的最强大脑" "将来能干一番大事" "拥有迷人的长发" 也是对方给你发送的第一条消息 |
timestamp | 时间戳 | 1970年1月1日(UTC/GMT的午夜)开始所经过的秒数 |
接下来就是如何解码fromEncodeUin的问题,我们以*S1*oKoezon5为例,写出具体解码过程:
1.去除*S1*这四个字符
结果变为 oKoezon5
2.对照表格解码
下图是之前某大神总结出的部分规律(有点像AES加密中的S盒,不过对应关系简单了许多)
经过测试,发现部分对应关系存在问题(比如3同时对应了oi,o与i,很明显这里有问题,个人猜测原因是分析样本数据不足),重新测试十几条数据后,增添以下六组对应关系
为了解码方便,列出如下规则:
规则1: 优先使用2字符对应1数字的关系,其次使用1字符对应1数字的关系
e.g. oion6 解码为 301,而不是33301
即'oi'对应3,'on'对应0,'6'对应1的方式
不应按照'o'对应3,'i'对应3,'o'对应3,'n'对应0,'6'对应1的方式规则2: 遇到某一位无法解码,跳过该位从下一位进行解码
oKoezon5
'oK', 'oe', 'z', 'on', '5'
10001
这样就成功得到了对方QQ号,剩下怎么做就不需要我说了吧(滑稽)
对于懒于动手的同学,我提供了C++代码(附在最后),可复制到 VisualStudio / Dev Cpp / Code::blocks等IDE中运行
或者在以下网站在线编译运行
运行前将main()函数中的str数组内容修改为fromEncodeUin即可
http://www.dooccn.com/cpp/
严正声明:
本文中所提到的技术/方法仅能用于个人学习与交流,请勿用于商业以及其他目的;请勿进行非法操作,请勿损害他人/社会/国家利益,请遵守相关法律法规,否则由此带来的一切后果均由操作者本人承担,与本文作者无关.
C++ Code
#include
#include
static char* table[] = {
"oe", "oK", "ow", "oi", "7e", "7K", "7w", "7i", "Ne", "NK",
"n", "6", "-", "o", "v", "4", "C", "S", "c", "E",
"z", "5", "A", "i", "P", "k", "s", "l", "F", "q"
};
int matchChar(char a, char b, char* mNum);
int decode(char* src, char * buf);
int main() {
/*待解码fromEncodeUin放在这里,注意不要包含*S1*这四个字符*/
char str[30] = { "oKoezon5" };
char buf[30];
memset(buf, 0, 30);
decode(str, buf);
std::cout << "解码结果:\n" << buf << std::endl;
return 0;
}
int matchChar(char a, char b, char* mNum) {
if (NULL == mNum)
return -1;
int i;
if ('o' == a) {
i = 0;
while (i < 4) {
if (table[i][1] == b) {
*mNum = i % 10 + 48;
return 2;
}
i++;
}
/*增加多条特殊情况,if语句太多了-_-*/
if ('n' == b) {
*mNum = 0 + 48;
return 2;
}
if ('z' == b) {
*mNum = 3 + 48;
return 2;
}
*mNum = 3 + 48;
return 1;
}
else if ('7' == a) {
i = 4;
while (i < 8) {
if (table[i][1] == b) {
*mNum = i % 10 + 48;
return 2;
}
i++;
}
if ('v' == b) {
*mNum = 5 + 48;
return 2;
}
if ('z' == b) {
*mNum = 7 + 48;
return 2;
}
return -1;
}
else if ('N' == a) {
i = 8;
while (i < 10) {
if (table[i][1] == b) {
*mNum = i % 10 + 48;
return 2;
}
i++;
}
if ('v' == b) {
*mNum = 9 + 48;
return 2;
}
if ('n' == b) {
*mNum = 8 + 48;
return 2;
}
return -1;
}
i = 10;
while (i < 30) {
if (table[i][0] == a) {
*mNum = i % 10 + 48;
return 1;
}
i++;
}
return -1;
}
int decode(char* src, char * buf) {
if (NULL == src || NULL == buf) {
return -1;
}
char* p = src;
char* q = buf;
int rc = 0;
while ('\0' != *p && -1 != (rc = matchChar(*p, *(p + 1), q))) {
p += rc;
q++;
}
return 0;
}