[开源]有道词典本地数据库算法结构逆向分析
YoudaoDictDecoder.rar
上周对有道词典的本地数据库进行了分析。这次分析的过程不是很顺利,花了很多时间。把正确的分析方法发一下,失败的探索经过就不发了。
调试
用IDA打开YoudaoDict.exe,识别里面的运行库函数。导出到Map文件。用OllyDbg的MapConv插件载入。这样,POSIX函数就可以被识别出来了。
对CreateFile下断。当访问dicten.db、dictcn.db的时候,栈回溯。发现对数据库文件的访问是POSIX函数fopen`实现的。
对fread下断。发现读取了文件的 0-5 字节。然后验证文件有效性:前5字节必须是FF FF FF FF 01。
F5运行。待下一次触发断点,发现读取了大约一半的文件。对缓冲区下内存访问断点。当内存断点触发的时候就是解密循环了。
分析触发断点的代码。保存加密值和解密结果的是cl寄存器,明文写入指针是eax。解密运算是这样的:
Decrypted = -1 - Crypted;
取消内存断点。跳转到eax,即明文缓冲区。一目了然,这是解密后的数据。接下来就是人脑的分析——数据结构是什么。
继续F5。发起一个查询,发现程序读取了后半段文件的一小部分,用相同的方法解密数据。
数据结构分析
文件前5字节为文件头,必须为FF FF FF FF 01。
接下来的4字节为一个ULONG,表示索引(例如英译汉,则索引为英文)数据的大小。就记作IndexSize吧。
接下来的IndexSize字节为索引数据。一个个索引数据紧挨着,单个元素的大小为sizeof(QueryLength) + QueryLength + sizeof(ResultOffset)。下面是索引数据结构的伪代码表示:
typedef struct _INDEX
{
UCHAR QueryLength;
CHAR Query[QueryLength];
ULONG ResultOffset;
}INDEX, *PINDEX;
解释如下:Query表示查询的字符串,例如”EnglishWord”, QueryLength表示Query的长度。ResultOffset是一个索引,用来快速查找结果。稍后讲。 4. 剩余的全部未访问的数据为结果数据。一个个结果数据紧挨着,直到文件尾结束。通过ResultOffset实现快速查找。下面是结果数据结构的伪代码表示:
typedef struct _RESULT
{
USHORT Unknow;
UCHAR PhoneticSymbolLength;
CHAR PhoneticSymbol[PhoneticSymbolLength];
UCHAR ExplainLength;
CHAR Explain[ExplainLength];
}RESULT, *PRESULT;
数据包括了音标和解释。数据结构的解释参加上文。
查询算法分析
为了给查询加速,同时要减少内存的占用,有道词典使用了这样的策略:首先将全部的INDEX数据读入内存。在查询的时候,根据ResultOffset直接找到查询结果。(写在此时,我似乎猜出了Unknow成员的作用:标识当前RESULT块的总长度。这样,就不用先读取音标长度,再读取音标数据,再读取解释长度,再读取解释数据了。我没有验证的打算。欢迎各位大牛测试。) 大概流程如下: 1. 程序启动,打开词典文件。验证数据有效性。 2. 读取INDEX区段的总长度,解析,加载进内存,缓冲区记为IndexBuffer吧。 3. 用户发起查询请求。在IndexBuffer中匹配字符串,记下ResultOffset。 4. 读取RESULT区段的起始地址 + ResultOffset的数据,2字节。获得当前释义的长度,记作ResultTotalLength吧。 5. 接着读取ResultTotalLength字节,解密,将结果输出到客户端。
大概就是这样。下面附上完整的解密代码和解密后的数据的下载地址: http://code.google.com/p/code-from-e...ictDecoder.rar Have Fun!
PS:由于没有对RESULT.Unknow成员做了解,汉译英的时候可能对应多个结果,但程序没有解析出全部的意思,只给出了第一条解释。至于英文,应该是完全解析了。
我的博客:http://extreme.0ginr.com/blog/
