08年的时候,根据他人对列表格式的分析写了个提取工具方便提取列表以供它用。一晃5年过去了,最近又想起这件事,于是就找来新版看看,估摸着新版的加密方法应该改掉了吧,果然。于是开始吭哧吭哧研究新版本怎么提取列表。
软件:龙卷风收音机
版本:4.1.2013.413
新版的算法比以前复杂了很多,看了几个小时的汇编也没有彻底弄明白是怎么算的,只是找到了一些线索:
- 列表格式基本与以前相同
- 知道了哪几个函数会做解码
- 只有加密,没有压缩
- 8个字符一组进行加密,涉及循环,逻辑,算术运算,然后将各组加密后的 raw data 连接在一起作为加密后的数据。
看累了休息的时候,翻出好久没玩的CSS又战了几盘,于是又想到了魔兽,于是又想到了星际。忽然想到了以前做的一个东西:给星际自动保存的录像增加时间戳。既然已经找到了解密字符串的函数,为什么不利用一下,让它把完整列表解密再输出呢?就是以前做keygen的办法。很多时候都是如此,传统方案有困难的时候,都需要冷静下来好好分析一下问题,看有没有其他可行方案。
先来回顾一下文件格式。
Data\chs目录下有若干文件。
tree*.dat 这种都是直接保存为文本文件,就是打开软件后看到的收藏列表,按照列表的树结构保存的。需要关注的只有每行数据的第3段,如:
0/-1/2691/国际广播CRI 怀旧金曲频道
2691就是要关注的内容,它是对应电台在列表中的ID.
另外3个文件就和完整的列表有直接关系:
data.idi 每8 bytes 表示一项,前4 bytes 表示了对应的ID,后4 bytes 表示在data.idp文件中的index
data.idp 每4 bytes 表示一项,表示改index对应的电台在 data.dat 中的offset.
data.dat 以电台ID开头,"\r\n"作为每个列表的结尾,"\t"是每个字段的分隔符。电台ID和第一个"\t"至"\r\t"中间的部分是加密的部分。
以上面的例子继续加以说明:
0n2691 = 0xA83
先在data.idi找到0xA83所处的位置:
00005410h: 83 0A 00 00 0C 0A 00 00
再在 data.idp 中找到0xA0C对应的位置。因 data.idp 中每4 bytes 一项,因此offset为:
(0xA0C - 1) * 4 = 0x282C
0000282Ch: A3 8E 04 00
再在 data.dat 中来到0x48EA3所在:
00048ea3h: 32 36 39 31 09 2A 9F 49 31 A2 6A 9E 06 D8 78 2D ; 2691.*烮1?豿-
00048eb3h: 85 1F 59 B8 26 86 1A 1F 10 3C AC 2D FD 89 37 63 ; ?Y??..<?龎7c
00048ec3h: 6E 08 53 49 1A 59 F3 3F C2 0B 3A AD 53 FC 00 FE ; n.SI.Y??:璖??
00048ed3h: 6E F6 FA 9F B0 9F F5 59 31 21 91 26 90 21 15 A5 ; n鳅煱燉Y1!??.?
00048ee3h: 8F 0A 79 59 48 5B 7C 97 CD F3 AA B4 9C C9 CD 4A ; ?yYH[|椡螵礈赏J
00048ef3h: 2B 36 26 28 D3 F0 4F 71 95 46 28 4D 2C 73 D3 37 ; +6&(羽Oq旻(M,s?
00048f03h: A5 14 EE A6 EE ED 32 D5 D4 F1 A5 50 34 9C 7B E9 ; ?瞀铐2赵癀P4渰?
00048f13h: 45 23 D7 D7 46 0D 0A ; E#鬃F..
这段数据即我们要找的ID为2691的电台对应的加密后数据。
回到怎么"以彼之道,还施彼身"。
经分析,软件内解码数据库的主要函数是sub_549EE0。
.text:0057BE5A mov eax, [ebx+360h]
.text:0057BE60 mov edx, esi
.text:0057BE62 call sub_549EE0
因程序是Delphi写的,根据Delphi编译的特点,call一个函数时,参数是以eax,edx,ecx,stack的方式依次传入的。 来看这个地方:eax看起来像是保存解码后结果的一个结构指针,而edx则是电台ID,比如前面的2691。 sub_549EE0(pBuffer, Index)大约就是这个函数的原型。当然,在我摘录这段代码的之前和之后,还有其他工作要做,比如验证是否空指针,填结构内的其他成员等。但这个函数是我们最关心的部分。
基本思想是:在"按原计划"执行此函数前,我们先让它跳转到其他地方去,执行我们要做的额外工作,再跳转回来,继续原来的计划。 因此要做的工作分以下几部分:
- 在原文件内增加一个新的可执行代码段
- 在常数区内填好需要用到的常量,主要是字符串
- 导入表内新增msvcrt.dll,增加需要用到的函数。基本就是些文件操作函数,fopen, fclose, fread, fwrite等等。
- 修改上述代码使其跳转到新增加的区间,保存现场,增加我们自己的代码,恢复现场,跳回上面的地方继续执行。
新增的代码大致可表示为:
FILE *fpIndex = fopen("d:\\datb.idi","rb");
FILE *fp = fopen("out.txt","wb+");
Char * pBuffer;
foreach(ID in fpIndex)
{
fputc(ID,fp);
sub_549EE0(pBuffer, ID)
fputc('\t',fp);
while(pBuffer != '\0')
{
fwrite(pBuffer++,1,1,fp);
}
fputc('\r',fp);
fputc('\n',fp);
}
翻译成汇编大约200行不到,写进新的空间,测试,改了几个笔误,通过。
看看2691提取的结果:
2691 国际广播CRI 怀旧金曲频道 综合 中国 中央 中文 2 4 128K http://gb.cri.cn/radio/ mms://live.cri.cn/oldies
结果中发现有些过长的项目不完整,超过某一长度就无法用单一函数sub_549EE0来做了,猜测是将数据分段处理了。不过大部分结果OK已经足够说明办法可行了,懒得继续弄下去了。
另外发现两个有趣的东西:
00124fcch: BB 82 E7 D6 B6 F1 FB 75 ; 粋缰恶鹵
在分析解密函数的时候发现该序列经常被用到,似乎是magic number
00124be8h: 73 68 61 67 75 61 ; shagua
这个字符串在解密函数中也用到,这是作者在自嘲么
自己研究着玩就不给下载了。
P.S.上班时候经常用这个听CNN lol
[课程]Linux pwn 探索篇!