解码雅虎通(yahoo messenger)聊天记录
Yahoo Messenger 的聊天记录是登录到相应账户以后才可以查看的,记录内容保存在 Program Files\Yahoo!\Messenger\Profiles\YourYahooId\Archive\Messages 中,每个联系人的聊天记录保存在一个以该联系人 yahoo id 命名的文件夹中,每天一个文件,内容是经过加密的。
如果你遗忘了你的 yahoo id 密码且无法取回的话你就没有办法查看聊天记录了,当我遇到这种情况就打算跟踪一下 Yahoo Messenger 的加密或者解密算法。
登录 Yahoo Messenger, 查看聊天存档,在左边选中一个联系人,你可以看到右上方的列表中显示日期,选定一个日期消息内容就显示在右下方的视图中。用 OllyDbg 附加到 YM 进程, 在 Kernel32.CreateFileW 下断点,继续运行程序,点击一个日期,断点命中,查看调用堆栈,我使用的版本的 YM 在子程序 sub_004D53A7 中调用 CreateFileW,禁止 CreateFileW 的断点,在 004D53A7 重新下断。
我们先看一下这个函数的开头部分:
该函数最后一个压栈的参数是 push esi, 随后判断了 [esi+38] 是否为 0, 如果为 0 则调用 CreateFileA, 否则跳转, 推测 该子程序是某个类的一个方法,esi 是当前实例的起始地址,[esi+38] 应该是该类的一个成员变量 HANDLE hFile, 如果这个句柄为 NULL 则打开文件,否则跳过,继续读取文件内容,单步到 FileName 处可以看到正是保存我们要打开的那天的记录文件。
继续单步,
004D53DC |. 83F8 FF cmp eax, -1
004D53DF |. 8946 38 mov dword ptr [esi+38], eax
004D53E2 |. 75 07 jnz short 004D53EB
004D53E4 |. 33C0 xor eax, eax
004D53E6 |. E9 33010000 jmp 004D551E
004D53EB |> 53 push ebx ; /Origin
004D53EC |. 53 push ebx ; |pOffsetHi
004D53ED |. 53 push ebx ; |OffsetLo
004D53EE |. 50 push eax ; |hFile
004D53EF |. FF15 10D37900 call dword ptr [<&KERNEL32.SetFi>; \SetFilePointer
如果成功打开文件则将文件指针指向文件头
004D53F5 |> \57 push edi
004D53F6 |. 8B3D C8D27900 mov edi, dword ptr [<&KERNEL32.>; kernel32.ReadFile
004D53FC |. 53 push ebx ; /pOverlapped
004D53FD |. 8D45 08 lea eax, dword ptr [ebp+8] ; |
004D5400 |. 50 push eax ; |pBytesRead
004D5401 |. 6A 04 push 4 ; |BytesToRead = 4
004D5403 |. FF75 0C push dword ptr [ebp+C] ; |Buffer
004D5406 |. 895D 08 mov dword ptr [ebp+8], ebx ; |
004D5409 |. FF76 38 push dword ptr [esi+38] ; |hFile
004D540C |. 899D 78F7FFFF mov dword ptr [ebp-888], ebx ; |
004D5412 |. 899D 74F7FFFF mov dword ptr [ebp-88C], ebx ; |
004D5418 |. FFD7 call edi ; \ReadFile
读取 4 个字节, 应该是个 DWORD 吧,下面它判断了一下读取到的字节长度,如果是 0 则 关闭文件句柄 (这里就是判断文件是否已经结束了),否则跳转到 004D5432 连续三次读取 4 个字节,又三个 DWORD
004D541A |. 395D 08 cmp dword ptr [ebp+8], ebx
004D541D |. 75 13 jnz short 004D5432
004D541F |. FF76 38 push dword ptr [esi+38] ; /hObject
004D5422 |. FF15 1CD37900 call dword ptr [<&KERNEL32.Close>; \CloseHandle
004D5428 |. 895E 38 mov dword ptr [esi+38], ebx
004D542B |. 33C0 xor eax, eax
004D542D |. E9 EB000000 jmp 004D551D
004D5432 |> 53 push ebx
004D5433 |. 8D45 08 lea eax, dword ptr [ebp+8]
004D5436 |. 50 push eax
004D5437 |. 6A 04 push 4
004D5439 |. FF75 10 push dword ptr [ebp+10]
004D543C |. FF76 38 push dword ptr [esi+38]
004D543F |. FFD7 call edi ; kernel32.ReadFile
。。。 。。。
————————————————- 以上已经读取 4 个 DWORD 的分割线 —————————————————–
004D5463 |. B8 FF070000 mov eax, 7FF
004D5468 |. 3985 78F7FFFF cmp dword ptr [ebp-888], eax
004D546E |. 76 06 jbe short 004D5476
004D5470 |. 8985 78F7FFFF mov dword ptr [ebp-888], eax
004D5476 |> 53 push ebx
004D5477 |. 8D45 08 lea eax, dword ptr [ebp+8]
004D547A |. 50 push eax
004D547B |. FFB5 78F7FFFF push dword ptr [ebp-888] // ReadFile 的第三个参数就是前面最后一个读取到的 DWORD 值
004D5481 |. 8D85 7CF7FFFF lea eax, dword ptr [ebp-884]
004D5487 |. 50 push eax
004D5488 |. FF76 38 push dword ptr [esi+38]
004D548B |. FFD7 call edi ; kernel32.ReadFile
读取到的第四个 整数 和 0x7FF 作了一个比较 , 如果小于等于则跳转 jbe short 004D5476,否则 mov dword ptr[ebp-888], eax 将其赋值为 0x7FF (2047),看起来像是什么?这条消息的长度了,果然下面读取了此长度的内容,到这里我们大致就搞清楚了 YM archive 文件的格式,每个 Section 有 16 个字节的头部,即4个双字,最后一个 DWORD 是该 Section 中具体聊天内容的长度。
————————————————- 已经获得聊天内容数据的分割线 —————————————————–
004D54A0 |. 50 push eax
004D54A1 |. FFB5 78F7FFFF push dword ptr [ebp-888]
004D54A7 |. 8D85 7CF7FFFF lea eax, dword ptr [ebp-884]
004D54AD |. 50 push eax
004D54AE |. 8BCE mov ecx, esi
004D54B0 |. E8 45F9FFFF call 004D4DFA
呵呵,把读取到的聊天内容压栈 调用子程序 004D4DFA, 要解密了??? 等等看见什么了,你的 yahooid 被压栈了(后来知道你的 yahoo id 就是解密用的 key), 跟进 004D4DFA
————————————————- 快下班了,休息一下,未完待续的分割线 —————————————————–
大清老早的起来准备完成准备完成这篇博客,发现家里电脑上的 Yahoo Messenger 已经被我删掉了,也懒得再装,没汇编给你看了,就瞧瞧我用 c++ 实现的 sub_004D4DFA 吧,
void Decode(BYTE *buffer, int bufferLen, char* key) { int keylen = strlen(key); int idx = 0; char outbuffer[0xff]; do { char c = (char)buffer[idx]; c ^= key[idx % keylen]; outbuffer[idx] = c; idx++; } while (idx < bufferLen); char test[0xff] = {0}; WCHAR wtest[0xff] = {0}; MultiByteToWideChar (CP_UTF8, 0, outbuffer, 0xff, wtest, 0xff); //转换一下,否则中文乱码 WideCharToMultiByte(CP_ACP, 0, wtest, 0xff, test, 0xff, 0, false); printf("%s\n", test); }
其实加密就是这么简单了,把聊天内容字符串和你的 yahoo id 逐个字节异或,超出 yahoo id 长度后再从 id 的首字符开始。
一开始没有写 字符串类型转换那两句,解密出来的内容里中文是乱码,转换一下用 printf 输出就可以了。
一个 section 就是对话中的一句话,前面的其他3个 DWORD 分别是 聊天双方的 数字 id 和该消息的发送时间,读完一个 section 再读下一个,直至文件结束。
=============== 罪恶的聊天记录 :),虎头蛇尾的完结线,Felix 090211 7:00 ===============



才看到具体分析,昨天还在其他文章上留言要具体分析呢,哈。我去试试,厉害。
Reply
fangxiaojun reply on February 25th, 2010 11:27:
Yahoo 聊天记录解码器 可以的把完整的代码发我看下吗?
我正在C++ 给这样的程序~~ 非常感谢~
fangxiao_jun@126.com
Reply
Felix reply on February 26th, 2010 10:37:
最后我是用 C# 写了一个有 GUI 的。
Reply
你好,很佩服看到你的分析文章。
同问,能参考下你的代码吗?
chen@chen@yahoo.cn
Reply
版主,我急需一个这样的程序,或者我把.dat文件给你,你帮我解码,请联系我下,QQ:26896864 谢谢了!!!
Reply
来过,踩下,博主思想不错,哈哈!~
Reply