您现在的位置: Tracy‘Blog > 博客 > 黑人黑事 > 正文
校园网那些事(八)
  

Chapter 5——校验码-揭秘MD5算法

上章节中大概的分析了下我们所接收到的数据包。并单步跟踪了程序处理数据包的整个流程,之后我们发现,程序大概做了如下的事情:

1、取第一位判定数据包功能类型。(以下所说的数据包,皆是指解密后的数据包)

2、取第二位得到数据包总长度。

3、取每个功能代码位(即我们上章节中分析出来的08090A0B0C0D),按相应的功能代码对其后跟随的内容进行处理。

A、对08,跳转到时间判定处,对比数据包中包含的时间与拨号上网时间。若不同,则丢弃数据包。

B、对其他的功能代码位大致都是,取其后跟随的字符减去2得到内容长度。再利用长度和起始地址将功能位对应的内容复制到内存其他位置,供程序调用。(如,将要弹出的消息保存到某一地址,调用时,直接先push此地址。)

那么,所有的都分析完了后,我们发现,程序并没有对数据包的第3-18位进行任何操作。难道,这16个字符是没有用的?等等,16个字符,十六个字符你能想到哪些呢?

嗯,反正,我的第一反应就是MD5(可能是早年拿站留下的阴影吧,注入后得到的大多是16位)。为了证实我们的想法,用peid对已脱壳的程序用算法分析插件进行分析,结果如下。

那就是说,我们又猜对了,的确是MD5,但是,问题又来了,他是对什么内容进行的哈希呢?对整个数据包?但这16个字符原本就是在数据包里面的啊。对数据包中的某一个部分?还有就是,这16个字符是用来干嘛的呢?

那么,我们还是首先来了解下MD5吧。

Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。MD5的典型应用是对一段信息Message)产生信息摘要Message-Digest),以防止被篡改。

因为MD5是不可逆的,不能够还原出真实密码,所以,把它说成加密算法也不太合适(为此在复试的时候被导师批过)。

那,从上面的简介我们可以看出,它的典型应用就是对一段信息进行信息摘要,以防被篡改,这个功能可能就是我们今天遇到的问题了。同时呢,你可能会想,既然它不能逆向解密出原文本,那它是如何在网站上实现密码验证的呢?很简单,密码通过MD5散列后,得到16字节或者32字节散列值,再与数据库中保存的1632位散列值进行对比。为了安全考虑,数据库中保存的密码绝对不能是明文的,而是在你设置密码的时候,通过MD5将你的密码MD5值写入数据库。而后,下次判定的时候,就对比这两个生成的散列值。

刚看了MD5的原理,接下来,我们看看,具体的算法吧。继续百度百科

MD5算法简要的叙述可以为:MD5512位分组来处理输入的信息,且每一分组又被划分为1632位子分组,经过了一系列的处理后,算法的输出由四个32位分组组成,将这四个32位分组级联后将生成一个128位散列值。

MD5算法中,首先需要对信息进行填充,使其位长对512求余的结果等于448。因此,信息的位长(Bits Length)将被扩展至N*512+448N为一个非负整数,N可以是零。填充的方法如下,在信息的后面填充一个1和无数个0,直到满足上面的条件时才停止用0对信息的填充。然后,在这个结果后面附加一个以64位二进制表示的填充前信息长度。经过这两步的处理,现在的信息的位长=N*512+448+64=(N+1*512,即长度恰好是512的整数倍。这样做的原因是为满足后面处理中对信息长度的要求。总体流程如下图所示,表示第i个分组,每次的运算都由前一轮的128位结果值和第i512bit值进行运算。初始的128位值为初始链接变量,这些参数用于第一轮的运算,以大端字节序来表示,他们分别为:A=0x01234567B=0x89ABCDEFC=0xFEDCBA98D=0x76543210

每一分组的算法流程如下:

第一分组需要将上面四个链接变量复制到另外四个变量中:AaBbCcDd。从第二分组开始的变量为上一分组的运算结果。

主循环有四轮,以一下是每次操作中用到的四个非线性函数(每轮一个)。

F(X,Y,Z) =(X&Y)|((~X)&Z)

G(X,Y,Z) =(X&Z)|(Y&(~Z))

H(X,Y,Z) =X^Y^Z

I(X,Y,Z)=Y^(X|(~Z))

&;是与,|是或,~是非,^是异或)

FF(a,b,c,d,Mj,s,ti)表示 a = b + ((a + F(b,c,d) + Mj + ti) << s)

GG(a,b,c,d,Mj,s,ti)表示 a = b + ((a + G(b,c,d) + Mj + ti) << s)

HH(a,b,c,d,Mj,s,ti)表示 a = b + ((a + H(b,c,d) + Mj + ti) << s)

a,b,c,d,Mj,s,ti)表示 a = b + ((a + I(b,c,d) + Mj + ti) << s)

这四轮(64步)是:

第一轮

FF(a,b,c,d,M0,7,0xd76aa478

FF(d,a,b,c,M1,12,0xe8c7b756

FF(c,d,a,b,M2,17,0x242070db)

FF(b,c,d,a,M3,22,0xc1bdceee)

FF(a,b,c,d,M4,7,0xf57c0faf)

FF(d,a,b,c,M5,12,0x4787c62a)

FF(c,d,a,b,M6,17,0xa8304613

FF(b,c,d,a,M7,22,0xfd469501

FF(a,b,c,d,M8,7,0x698098d8

FF(d,a,b,c,M9,12,0x8b44f7af)

FF(c,d,a,b,M10,17,0xffff5bb1

FF(b,c,d,a,M11,22,0x895cd7be)

FF(a,b,c,d,M12,7,0x6b901122

FF(d,a,b,c,M13,12,0xfd987193

FF(c,d,a,b,M14,17,0xa679438e)

FF(b,c,d,a,M15,22,0x49b40821

第二轮

GG(a,b,c,d,M1,5,0xf61e2562

GG(d,a,b,c,M6,9,0xc040b340

GG(c,d,a,b,M11,14,0x265e5a51

GG(b,c,d,a,M0,20,0xe9b6c7aa)

GG(a,b,c,d,M5,5,0xd62f105d)

GG(d,a,b,c,M10,9,0x02441453

GG(c,d,a,b,M15,14,0xd8a1e681

GG(b,c,d,a,M4,20,0xe7d3fbc8

GG(a,b,c,d,M9,5,0x21e1cde6

GG(d,a,b,c,M14,9,0xc33707d6

GG(c,d,a,b,M3,14,0xf4d50d87

GG(b,c,d,a,M8,20,0x455a14ed)

GG(a,b,c,d,M13,5,0xa9e3e905

GG(d,a,b,c,M2,9,0xfcefa3f8

GG(c,d,a,b,M7,14,0x676f02d9

GG(b,c,d,a,M12,20,0x8d2a4c8a)

第三轮

HH(a,b,c,d,M5,4,0xfffa3942

HH(d,a,b,c,M8,11,0x8771f681

HH(c,d,a,b,M11,16,0x6d9d6122

HH(b,c,d,a,M14,23,0xfde5380c)

HH(a,b,c,d,M1,4,0xa4beea44

HH(d,a,b,c,M4,11,0x4bdecfa9

HH(c,d,a,b,M7,16,0xf6bb4b60

HH(b,c,d,a,M10,23,0xbebfbc70

HH(a,b,c,d,M13,4,0x289b7ec6

HH(d,a,b,c,M0,11,0xeaa127fa)

HH(c,d,a,b,M3,16,0xd4ef3085

HH(b,c,d,a,M6,23,0x04881d05

HH(a,b,c,d,M9,4,0xd9d4d039

HH(d,a,b,c,M12,11,0xe6db99e5

HH(c,d,a,b,M15,16,0x1fa27cf8

HH(b,c,d,a,M2,23,0xc4ac5665

第四轮

a,b,c,d,M0,6,0xf4292244

d,a,b,c,M7,10,0x432aff97

c,d,a,b,M14,15,0xab9423a7

b,c,d,a,M5,21,0xfc93a039

a,b,c,d,M12,6,0x655b59c3

d,a,b,c,M3,10,0x8f0ccc92

c,d,a,b,M10,15,0xffeff47d)

b,c,d,a,M1,21,0x85845dd1

a,b,c,d,M8,6,0x6fa87e4f)

d,a,b,c,M15,10,0xfe2ce6e0)

c,d,a,b,M6,15,0xa3014314

b,c,d,a,M13,21,0x4e0811a1

a,b,c,d,M4,6,0xf7537e82

d,a,b,c,M11,10,0xbd3af235

c,d,a,b,M2,15,0x2ad7d2bb)

b,c,d,a,M9,21,0xeb86d391

所有这些完成之后,将ABCD分别加上abcd。然后用下一分组数据继续运行算法,最后的输出是ABCD的级联。

看着晕么?其实我那会儿看,也看得很晕,包括看看雪的《加密解密二》上的MD5那章,发现怎么也理解不了,之后,自己跟了一遍,也就明了了,所以,咱先不着急弄清楚上面说了些什么,跟着后面的实例来看看他是怎么一步步在程序中实现的。(当然,你还可以直接下一个MD5的源代码去看,更省事~)咱们先记住,他有四大轮,16小轮。然后每次用到的M值都不同。额,在看雪看到下面这篇,貌似说的比较简单明了。

http://www.pediy.com/kssd/pediy04/kanxue202.htm

MD5算法: 

第一步:增加填充 

增加padding使得数据长度(bit为单位)模512448。如果数据长度正好是模512448,增加512个填充bit,也就是说填充的个数为1-512。第一个bit1,其余全部为0

第二步:补足长度 

将数据长度转换为64bit的数值,如果长度超过64bit所能表示的数据长度的范围,值保留最后64bit,增加到前面填充的数据后面,使得最后的数据为512bit的整数倍。也就是32bit16倍的整数倍。在RFC1321中,32bit称为一个word 

第三步:初始化变量: 

用到4个变量,分别为ABCD,均为32bit长。初始化为:

A: 01 23 45 67 

B: 89 ab cd ef 

C: fe dc ba 98 

D: 76 54 32 10 

第四步:数据处理: 

首先定义4个辅助函数: 

F(X,Y,Z) = XY v not(X) Z 

G(X,Y,Z) = XZ v Y not(Z) 

H(X,Y,Z) = X xor Y xor Z 

I(X,Y,Z) = Y xor (X v not(Z)) 

其中:XY表示按位与,X v Y表示按位或,not(X)表示按位取反。xor表示按位异或。 

函数中的XYZ均为32bit 

定义一个需要用到的数组:T(i),i取值1-64,T(i)等于abs(sin(i))4294967296倍的整数部分,i为弧度。 

假设前三步处理后的数据长度为32*16*Nbit 

第五步:输出: 

最后得到的ABCD为输出结果,共128bitA为低位,D为高位。 

带着问题,我们继续深入吧,OD载入,下断点,先发正常的数据包。上章中,我们单步后发现,数据包处理是在00408CD9处的call中,所以,我们直接先进入此call。然后F8过掉其中的每一个call,直到正常弹出消息框和网站,记录其中过掉每个call后的环节,特别是后面跟随判定跳转的call

然后,根据唯一变量原则,我们再发送一个改动过的数据包,像上章中说的,0D0C之类的。然后再次跟踪,继续F8步过所有的call,看条件转移,这时,我们会发现,进入00408CD9call中的第一个jnz由原先的不跳转变成了跳转。

004090F8  |.  E8 D3190000   call 1_2_9?0040AAD0                 

004090FD  |.  83C4 08       add esp,0x8

00409100  |.  84C0          test al,al                              

00409102  |.  0F84 ED010000 je 1_2_9?004092F5       

于是,我们自然的想到前,其上面的call值得怀疑。

好,我们接下来的任务就是看看这个call做了些什么。跟进去(解释就放到注释里了):

0040AAD0  /$  8B5424 04     mov edx,dword ptr ss:[esp+0x4]                   ;  数据包首地址edx

0040AAD4  |.  81EC FC050000 sub esp,0x5FC                                    ;  抬高栈顶

0040AADA  |.  85D2          test edx,edx

0040AADC  |.  0F848F000000 je 1_2_9?0040AB71

0040AAE2  |.  53            push ebx

0040AAE3  |.  56            push esi

0040AAE4  |.  57            push edi

0040AAE5  |.  B9 77010000   mov ecx,0x177

0040AAEA  |.  33C0          xor eax,eax

0040AAEC  |.  8D7C242C     lea edi,dword ptr ss:[esp+0x2C]

0040AAF0  |.  F3:AB         rep stos dword ptr es:[edi]                      ;  清空堆栈

0040AAF2  |.  8B8424 100600>mov eax,dword ptr ss:[esp+0x610]                 ;  数据包总长度放入eax

0040AAF9  |.  8BF2          mov esi,edx

0040AAFB  |.  8BC8          mov ecx,eax

0040AAFD  |.  8D7C242C     lea edi,dword ptr ss:[esp+0x2C]

0040AB01  |.  8BD9          mov ebx,ecx

0040AB03  |.  83C2 02       add edx,0x2                                      ;  2后,就可以从第3位开始

0040AB06  |.  C1E9 02       shr ecx,0x2                                      ;  4ecx用作计数器。

0040AB09  |.  F3:A5         rep movs dword ptr es:[edi],dword ptr ds:[esi]   ;  将内容复制到刚开辟的堆栈空间中去。用的是movs,这也是为什么上一步要将总长度除4、先给edx2了。

0040AB0B  |.  8BCB          mov ecx,ebx

0040AB0D  |.  83E1 03       and ecx,0x3                                      ;  处理当长度不是2的整数倍的情况

0040AB10  |.  F3:A4         rep movs byte ptr es:[edi],byte ptr ds:[esi]

0040AB12  |.  8B0A          mov ecx,dword ptr ds:[edx]

0040AB14  |.  894C240C     mov dword ptr ss:[esp+0xC],ecx                   ;  1

0040AB18  |.  8B4A04       mov ecx,dword ptr ds:[edx+0x4]

0040AB1B  |.  894C24 10     mov dword ptr ss:[esp+0x10],ecx                  ;  2

0040AB1F  |.  8B4A08       mov ecx,dword ptr ds:[edx+0x8]

0040AB22  |.  894C24 14     mov dword ptr ss:[esp+0x14],ecx                  ;  3

0040AB26  |.  33C9          xor ecx,ecx

0040AB28  |.  8B520C       mov edx,dword ptr ds:[edx+0xC]

0040AB2B  |.  894C24 2E     mov dword ptr ss:[esp+0x2E],ecx                  ;  将保存到堆栈中的3-6位清零

0040AB2F  |.  895424 18     mov dword ptr ss:[esp+0x18],edx                  ;  4,这四步刚好将数据包中3-18位保存到堆栈中

0040AB33  |.  8D54241C     lea edx,dword ptr ss:[esp+0x1C]

0040AB37  |.  52            push edx

0040AB38  |.  894C24 36     mov dword ptr ss:[esp+0x36],ecx                  ;  将保存到堆栈中的7-10位清零

0040AB3C  |.  50            push eax

0040AB3D  |.  8D4424 34     lea eax,dword ptr ss:[esp+0x34]                  ;  堆栈中数据包的地址

0040AB41  |.  894C24 3E     mov dword ptr ss:[esp+0x3E],ecx                  ;  将保存到堆栈中的11-14位清零

0040AB45  |.  50            push eax

0040AB46  |.  894C24 46     mov dword ptr ss:[esp+0x46],ecx                  ;  将保存到堆栈中的15-18位清零

0040AB4A  |.  E8 C1AEFFFF   call 1_2_9?00405A10

0040AB4F  |.  83C40C       add esp,0xC

0040AB52  |.  B9 04000000   mov ecx,0x4

0040AB57  |.  8D7C240C     lea edi,dword ptr ss:[esp+0xC]

0040AB5B  |.  8D74241C     lea esi,dword ptr ss:[esp+0x1C]

0040AB5F  |.  33D2          xor edx,edx

0040AB61  |.  F3:A7         repe cmps dword ptr es:[edi],dword ptr ds:[esi]

0040AB63  |.  5F            pop edi

0040AB64  |.  5E            pop esi

0040AB65  |.  5B            pop ebx

0040AB66  |.  75 09         jnz short 1_2_9?0040AB71                        ;  这个跳转作为设置al的,可以作为去除MD5校验位处

0040AB68  |.  B0 01         mov al,0x1

0040AB6A  |.  81C4 FC050000 add esp,0x5FC

0040AB70  |.  C3            retn

0040AB71  |>  32C0          xor al,al

0040AB73  |.  81C4 FC050000 add esp,0x5FC

0040AB79  \.  C3            retn

所以,瞬间0040AB4A处的call,就显得“高大”了起来。其中必有蹊跷,我们跟进去看看。还有一点值得怀疑的是,他为什么要把3-18位清零呢?难道是对清零后的数据包进行MD5,然后将生成的值与原数据包的3-18位进行比对?应该是这样的,那,我们直接去网上找了一段代码,然后对这个清零后的数据包做一次MD5,可得到的结果明显不是我们想要的(时间关系我这里就不演示了,不例证了),这该如何是好呢?也罢也罢,我们还是继续调试吧,或许进行MD5的并不是我们的数据包呢。恩,继续跟进,这里有一个小技巧(当我们看到如下所示有多个指定地址的call时,我们可以直接enter进去,看看缘由)。

当看到的都是一些系统调用和retn时,就可以果断选择过掉这个call了,如下图。

于是,我们的第一个call,直接pass掉。

我们直接F4到第二个call上,enter看到了如下代码:

004064F0  /$  8BD1          mov edx,ecx

004064F2  |.  57            push edi

004064F3  |.  B9 10000000   mov ecx,0x10

004064F8  |.  33C0          xor eax,eax

004064FA  |.  8D7A04       lea edi,dword ptr ds:[edx+0x4]

004064FD  |.  C702 B00D4300 mov dword ptr ds:[edx],1_2_9?00430DB0

00406503  |.  F3:AB         rep stos dword ptr es:[edi]

00406505  |.  8942 48       mov dword ptr ds:[edx+0x48],eax

00406508  |.  8942 44       mov dword ptr ds:[edx+0x44],eax

0040650B  |.  C7424C01234>mov dword ptr ds:[edx+0x4C],0x67452301

00406512  |.  C742 50 89ABC>mov dword ptr ds:[edx+0x50],0xEFCDAB89

00406519  |.  C742 54 FEDCB>mov dword ptr ds:[edx+0x54],0x98BADCFE

00406520  |.  C742 58 76543>mov dword ptr ds:[edx+0x58],0x10325476

00406527  |.  8BC2          mov eax,edx

00406529  |.  5F            pop edi

0040652A  \.  C3            retn

里面并没有call,但是呢,出现了012345678ABCDEFFEDCBA9876543210。还记得前面百度百科里说的初始链值么?就是他了。由于没有其他的内容,我们可以选择不进行跟踪。暂时看看它放到哪里去了就行。本程序中将初始链值放入00F2F0D 处。

接着看第三个call,发现其中还是不同的,有那么两个call

……

0040664F  |.  E8 7CF4FFFF   call 1_2_9?00405AD0                             ; \1_2_9?00405AD0

00406654  |.  8BFD          mov edi,ebp

00406656  |.  8D753F       lea esi,dword ptr ss:[ebp+0x3F]

00406659  |.  8B6C24 18     mov ebp,dword ptr ss:[esp+0x18]

0040665D  |.  3BF5          cmp esi,ebp

0040665F  |.  731A         jnb short 1_2_9?0040667B

00406661  |>  8B5424 14     /mov edx,dword ptr ss:[esp+0x14]

00406665  |.  8BCB          |mov ecx,ebx

00406667  |.  8D4432 C1     |lea eax,dword ptr ds:[edx+esi-0x3F]

0040666B  |.  50            |push eax                                        ; /Arg1

0040666C  |.  E8 5FF4FFFF   |call 1_2_9?00405AD0                            ; \1_2_9?00405AD0

00406671  |.  83C6 40       |add esi,0x40

00406674  |.  83C7 40       |add edi,0x40

00406677  |.  3BF5          |cmp esi,ebp

00406679  |.^ 72 E6         \jb short 1_2_9?00406661

而且,你会发现,这两个call居然是调用一个函数。那么我们enter进入00405AD0,看看它是干嘛的(用enter进入,Alt+c返回当前执行处)。

看到有下面摸样的代码:

00405B2C  |.  8D8C0178A46A>lea ecx,dword ptr ds:[ecx+eax+0xD76AA478]

等等,D76AA478?不就是我们前面原理里面的FF(a,b,c,d,M0,7,0xd76aa478)中的么?于是,我们可以判定, 00405AD0处的函数是进行MD5运算的。其中必定包含4大轮16小轮全部算法(数了下类似上面的代码总共64个,也就证实了我们的猜想)。将其生产的128位结果用作下一512位数据分组的初始链值。那,照这么说,在0040664Fcall后面的种种判定,必然是为了看看整个数据包被分成了多少个512位的分组,然后有多少个分组就进行多少次循环咯。最终输出的432位链值,结合起来就是最终的MD5值了。

嗯,我们再来就先不进这个call了(因为在调试的时候,F7跟入只是为了更好的确定这个call的作用,既然我们现在知道了这个call的作用了,我们也就不用再去单步调试了)。

我们数据包总长度为B8,化作10进制是184字节。也就是1472位。除以512,等于2,余数为448。瞬间恍惚了,还记得原理里面说的么?要进行填充,使其位长度与512求与等于448。这里居然就刚好~~~然后,通过看雪中发的那个帖子,当求余结果刚好为448的时候,需要再增加一个512分组,并且,从448那里开始一直用100000填充,到最后八个字节(64bits)再用数据长度填充。

单步后,发现正如我们推测那样,第一个call是用来执行对前512位操作的,要是长度小于等于512,则只要进行着一步就行了,后面的循环就是其他对剩余的各512位分组操作了。在这里,我们发现,也就只循环了一次,所以,我们还剩448位没进行处理。

继续单步跟踪下去。看看有没有其他的发现。

返回后来到前面图中的第四个call处:

00405A60  |.  E8 1B0B0000   call 1_2_9?00406580                             ; \1_2_9?00406580

还是先enter键进去看看:

00406580  /$  83EC 08       sub esp,0x8

00406583  |.  8D4424 00     lea eax,dword ptr ss:[esp]

00406587  |.  56            push esi

00406588  |.  8BF1          mov esi,ecx

0040658A  |.  57            push edi

0040658B  |.  6A08         push 0x8

0040658D  |.  8D7E 44       lea edi,dword ptr ds:[esi+0x44]

00406590  |.  57            push edi

00406591  |.  50            push eax

00406592  |.  E8 99FFFFFF   call 1_2_9?00406530

00406597  |.  8B07          mov eax,dword ptr ds:[edi]

00406599  |.  B9 38000000   mov ecx,0x38

0040659E  |.  C1E8 03       shr eax,0x3

004065A1  |.  83E03F       and eax,0x3F

004065A4  |.  83F8 38       cmp eax,0x38

004065A7  |.  72 05         jb short 1_2_9?004065AE

004065A9  |.  B9 78000000   mov ecx,0x78

004065AE  |>  2BC8          sub ecx,eax

004065B0  |.  51            push ecx

004065B1  |.  6820C94300   push 1_2_9?0043C920

004065B6  |.  8BCE          mov ecx,esi

004065B8  |.  E8 33000000   call 1_2_9?004065F0

004065BD  |.  8D4C24 08     lea ecx,dword ptr ss:[esp+0x8]

004065C1  |.  6A08         push 0x8

004065C3  |.  51            push ecx

004065C4  |.  8BCE          mov ecx,esi

004065C6  |.  E8 25000000   call 1_2_9?004065F0

004065CB  |.  8B7C24 14     mov edi,dword ptr ss:[esp+0x14]

004065CF  |.  8D564C       lea edx,dword ptr ds:[esi+0x4C]

004065D2  |.  6A10         push 0x10

004065D4  |.  52            push edx

004065D5  |.  57            push edi

004065D6  |.  8BCE          mov ecx,esi

004065D8  |.  E8 53FFFFFF   call 1_2_9?00406530

004065DD  |.  8BC7          mov eax,edi

004065DF  |.  5F            pop edi

004065E0  |.  5E            pop esi

004065E1  |.  83C4 08       add esp,0x8

004065E4  \.  C2 0400       retn 0x4

又发现了4call,其中的第三个和第四个call为:

……

004065B8  |.  E8 33000000   call 1_2_9?004065F0

……

004065C6  |.  E8 25000000   call 1_2_9?004065F0

怎么看起来那么眼熟呢?哦。004065F0不就是刚刚我们才分析用来进行MD5散列的函数么。那就是说,00405A60处的call就是对剩余的448个字节进行处理的。

那,我们且来看看它是怎么处理的吧。F7进入。跟到这里时,我们发现

传入的arg1,就是我们接下来要处理的64个字节。也就对应于数据包中最后56个字节链接上1000000000,这在原理中也说过了。 我们知道00405AD0是用来进行MD5的,所以,直接F8步过。

接下来的跳转成功跳过,而后,进入第四个call,同样来到0040664F的时候,堆栈和数据窗口如下:

说明了什么呢,是按照前面的原理来的。

之后我们来到

看到edx中保存的地址处对应的16个字节就是我们数据包中的3-18位,根据刚才的跟进也可以看到,他是在程序中生成的,而不是从数据包中复制过来的。

接下来的循环,是将这个生成的数值复制一下,结果如下:

为什么要这么做?可能是为了方便对比吧。接下来我们就看看对比处在哪。经历各种retn后,来到:

0040AB61  |.  F3:A7         repe cmps dword ptr es:[edi],dword ptr ds:[esi]

这句代码,就是用来对生成的校验码进行对比的。而下面的那句jnz则可用做爆破点。(如果你想逐个研究数据包中每个功能的作用的话)

至此,整个16字节校验码分析完毕。

其实吧,在以前破解这个客户端到写这篇文章之前,我一直以为它用的是变形MD5加密,以至于我敲了很多变形md5加密,之后,今天一调试,发现是正常的MD5算法。

那,是什么误导了我呢?分析了下以前写的源代码,大概原因有两点:

1、 不知道MD5算法在当求与结果大于等于448时的处理情况。原理没弄清楚。

2、 当时用的是网上下载的一段MD5源代码,我放上数据包后,发现的到的结果不正确。(当然,也有可能是我没用好源代码)

总之,被自己骗了近一年了。既然说到变形MD5,也来聊聊什么是变形MD5吧。记得在看雪上看过一篇介绍变形MD5文章,大概有三种:

1、 改变初始幻数(0123456789ABCDEF那个)

2、 改变填充方式。如100000改为110000等。

3、 改变算法中用到的Mj后的数的值。(如FF(a,b,c,d,M0,7,0xd76aa478)中的0xd76aa478)

 

好吧,就写到这里了,后面大概也就还剩一个功能实现,和一个系统的编写,以及个人总结了。大概就两三章节的样子吧。

 

--Tracy   2013/11/17

发表评论(1)
1楼 小白  发表于  2013-11-17 21:33:56
Tracy 好高端……
[博主回复]  不高端不高端~
姓名 *
电子邮件
QQ
评论内容 *
验证码 *图片看不清?点击重新得到验证码请输入图片后链接字符‘a’