标 题: 《QQ 2005 贺岁版登录口令加密算法》完结篇
发帖人:Binny
时 间: 2005-03-05 16:32
原文链接:http://bbs.pediy.com/showthread.php?threadid=11735
详细信息:

  QQ 2005贺岁版登录口令加密算法及其源代码
                      Binny(Binny@vip.163.com)

拿到QQ 2005贺岁版后,发现其加密原理并没有新的改变,经过跟踪和分析,编制出暴力破解本地QQ密码的程序。

需要说明的是,我的QQ标本其版本号为12.87.0.8059(按鼠标右键看QQ.exe的属性),如果你手头的版本与我的有差异,请理解我的意图后再动手做实验。

QQ密码在正确登陆后,会将加密的结果保存在用户目录的ewh.db文件中,加密采用公开的MD5算法,通过N次循环以及异或后求反,最终计算出加密的结果,与用户的ewh.db文件中的密文比较后,发出“输入密码与上次成功登录的密码不一致,$0A是否到服务器验证?”(这条信息在BasicCtrlDll.dll的资源中,$0AC的格式化中为回车)。根据这个提示,完成本地QQ密码的暴力破解。

QQ系统中,“QD”标志代表QQ Data,例如,我们可以在文件User.dbewh.db中找到这个以QD开头的数据结构。

一、        ewh.db原始数据

51 44 01 01 03 00 04 03 00 BD AF A8 04 00 00 00

00 2E 06 00 07 03 00 B9 AB B4 10 00 00 00 07 22

AA 96 56 19 A3 9E 82 19 B7 2B BD 2D 34 4A 04 03

00 A9 B5 B2 04 00 00 00 3C A8 93 06

其中,红色为AST循环次数,蓝色为EWH加密字符串,绿色为UIN QQ号(110340156=0x0693A83CIntel体系内存中排列顺序为:3CA89306)。

二、        ewh.db数据结构

HEX

偏移

DEC

偏移

数据

注释

变量

标志

0000

1

51 44

QDQQ Data 数据标志

Flag

0002

3

01 01

保留的数据结构

Reserve

0004

5

03 00

总数据段(Data Sections)的个数

Sections

0006

7

04

第一个数据段(简称1S,下同)的类型,可以从0x010x0F04代表本数据没经过加密处理。

Type1S

0007

8

03 00

1S标志的长度。

LenFlag1S

0009

10

BD AF A8

1S标志(例如ASTUINEWH等),是经过简单的异或并求反计算处理的,此处是AST,可能是Algorithm Shift Times Axxx Switch Time,管他的呢!

Flag1S

000C

13

04 00 00 00

1S数据的长度

LenData1S

0010

17

00 2E 06 00

= (404992)

1S数据,这里是进行MD5转换的次数。

这个数据是同计算机的性能有关的,性能越高的计算机,在QQ注册成功后产生的这个循环控制变量就越大。

Data1S

0014

21

07

2S数据的类型,07代表使用MD5进行加密

Type2S

0015

22

03 00

2S标志的长度

LenFlag2S

0017

24

B9 AB B4

2S标志,此处是EWH,代表本数据段是EWH密码数据,可能是Encrypt With Hash的缩写

Flag2S

001A

27

10 00 00 00

2S数据的长度

LenData2S

001E

31

07 22 AA 96

56 19 A3 9E

82 19 B7 2B

BD 2D 34 4A

2S数据,是经过MD5加密计算后产生的数据,当然还要经过异或并求反的计算处理,参考下面程序中的1000B858 行代码。

Data2S

002E

47

04

3S数据的类型

Type3S

002F

48

03 00

3S标志的长度

LenFlag3S

0031

50

A9 B5 B2

3S标志,此处是UIN,代表本数据段是QQ号码,可能是:User Identifier Number的缩写

Flag3S

0034

53

04 00 00 00

3S数据的长度

LenData3S

0038

57

3C A8 93 06

3S数据,3C A8 93 06 = 110340156

Data3S

三、        加密原理

下面VB伪代码的部分符号引自以上第二点《结构说明》中的变量标志,请注意理解:

Pwd = MD5(Pwd, Len(Pwd)) 

' Pwd为用户输入的密码,第一轮MD5后,Pwd成为16位字节长度的MD5串。

XorKey As Long = 0  'XorKey为用于解密的字节

For k = 1 To Data1S – 1  '因为前面已经做过一轮,所以此处要减一

 Pwd = MD5(Pwd, 16)

Next k

XorKey = XorKey And &HFFFF

XorKey = (LenData2S And &HFF) Xor (LenData2S \ 256)

XorKey = &HFF - XorKey '求反

For k = 1 To 16

 Pwd(k) = Pwd(k) Xor XorKey

Next k

If Pwd <> Data2S Then

  MsgBox "输入密码与上次成功登录的密码不一致," & vbcrlf & "是否到服务器验证?"

End If

 

通过以上的流程,我真的佩服QQ的设计者,如此巨大的循环量,加上循环次数的随机性,如果希望产生一个QQ MD5词典简直不可能。虽然理论上,可以产生一个MD5字典,但是,这个字典将有1.15E+77*16个字节之巨,因此,只好根据ewh.db文件提供的数据暴力破解了,不知是不是有更好的方法呢?

不过我的感觉是,循环次数加多了,应该会产生更多的MD5碰撞,不见得是个好事。

还有一种破解思路,也许更加直接,将在后面的文章中详细探讨。但是我只有在有时间做完实验后才有资格评述,不在本文章的讨论范围内。

四、        破解算法

重复进行数十万次MD5加密,会消耗计算机很多时间,如果使用传统的VBVC,对于一个密码的等待时间也是很可观的(例如使用VB代码,消耗的时间可能是汇编的400倍),因此,我使用汇编语言来编制低层加密解密算法,通过MASM32编译连接,最后用高级语言调用。通过提供算法动态库的方式,方便其他有兴趣的读者自己增加丰富的功能。例如增加多线程等,这也将在以后的探讨中实现。在此不做深入讨论。

附带的例子为VBVC调用汇编语言动态库的例子,VB代码简单实现了通过密码字典进行单线程破解的功能,读者可以丰富其内容。增加更多的功能。

五、        QQ数据结构分析

下面为动态库BasicCtrlDll.dll中反汇编的代码以及代码分析,主要用于分析EWH.DB中数据结构以及QQ数据解调算法的问题。另外,这个算法也可以将User.db中的数据提取出来。深入研究下去,做一个聊天记录查看器之类的软件也非难事。

 

1000B71D

mov eax,BasicCtr.100116AC

 

1000B722

call BasicCtr.1000FDB0

 

1000B727

sub esp,3C

 

1000B72A

mov eax,dword ptr ss:[ebp+8]

将数据的开始地址赋给EAX,实际数据为**DataEAX=*Data

1000B72D

push ebx

 

1000B72E

push esi

 

1000B72F

push edi

 

1000B730

mov esi,dword ptr ds:[eax]

需要转换的字符串,EAX指示一个结构,第一个成员为实际的数据指针

1000B732

mov dword ptr ss:[ebp-28],ecx

局部变量[ebp-28]保存全局的标志结构,ECX为全局参数地址,在调用本函数时跟入

1000B735

mov eax,dword ptr ds:[esi-8]

CString结构中长度的成员,表示总共多少个字节

1000B738

cmp eax,6

如果长度小于6,则为无效的数据

1000B73B

jb BasicCtr.1000B9C2

如果比6小则跳转退出,说明数据量不够解调的

1000B741

cmp byte ptr ds:[esi],51

是否为 QQ Data 的数据,QDQQ数据标志

1000B744

jnz BasicCtr.1000B9C2

 

1000B74A

cmp byte ptr ds:[esi+1],44

 

1000B74E

jnz BasicCtr.1000B9C2

 

1000B754

mov di,word ptr ds:[esi+4]

对于EWH来说,为第4+1个字节,为0003

1000B758

add esi,4

指向数据段(Sections)的个数

1000B75B

inc esi

 

1000B75C

add eax,-6

EAX去掉6个字节,对于EWH来说,剩下36H字节

1000B75F

inc esi

 

1000B760

mov dword ptr ss:[ebp+8],eax

指向第一个数据段

1000B763

call BasicCtr.1000BD36

在内存(ECX+9C)处开辟一个(100H)字节的空间,空间地址返回到EAX

1000B768

and dword ptr ss:[ebp-20],0

局部变量[ebp-20]清零

1000B76C

movzx eax,di

转换到EAX,对于EWHdi=3。表示有3段数据

1000B76F

test eax,eax

 

1000B771

mov dword ptr ss:[ebp-48],eax

局部变量[ebp-48]保存数据的段数

1000B774

jle BasicCtr.1000B99B

 

1000B77A

cmp dword ptr ss:[ebp+8],7

如果整个长度小于7,则剩下的应该是QQ号了。第一次进入时=36H

1000B77E

jb BasicCtr.1000B9C2

如果剩余的数据长度小于7则退出

1000B784

mov al,byte ptr ds:[esi]

第一次进入时,ESI指向第7个数据即数据段的类型,例如 04

1000B786

mov cx,word ptr ds:[esi+1]

CX=后一个数据,及数据段标志的长度,例如 0003

1000B78A

inc esi

 

1000B78B

mov edx,dword ptr ss:[ebp+8]

剩余的数据长度,如36H

1000B78E

sub dword ptr ss:[ebp+8],3

去掉3个,例如成为33H=51

1000B792

mov dword ptr ss:[ebp-38],ecx

局部变量[ebp-38]保存数据段中标志长度

1000B795

movzx edi,cx

EDI为标志长度了

1000B798

inc esi

 

1000B799

mov byte ptr ss:[ebp-1C],al

局部变量[ebp-1C]保存本段的类型

1000B79C

lea ecx,dword ptr ds:[edi+4]

ECX为取得的标志长度再加上4,例如=7

1000B79F

inc esi

第一次时,ESI指向第一个数据段的标志字段了,例如指向BDAFA8,第9个数据开始

1000B7A0

cmp dword ptr ss:[ebp+8],ecx

如果剩余的数据比“标志长度+4还少,则没有数据,因此不进行处理

1000B7A3

jb BasicCtr.1000B9C2

 

1000B7A9

mov ecx,dword ptr ds:[edi+esi]

跳过数据段的标志部分,将数据段的长度赋值给ECX。例如[edi+esi]=4

1000B7AC

lea ebx,dword ptr ds:[edi+esi]

EBX保存新的指针,指向数据段中数据部分的长度部分,例如,[EBX]=4

1000B7AF

mov dword ptr ss:[ebp-3C],ebx

局部变量[ebp-3C]保存数据段中数据部分的长度指针