我们已经见识很多种MD5加密算法在软件注册中的应用,同时也得到了一个结论:一味生搬硬套现成的算法而不加改变,即使算法再强大在破解者面前也是“不堪一击”的。所以,我们就见识到了各种各样的奇思妙想,将MD5算法使用得淋漓尽致。但是,算法上做足了功夫却没有注意注册码比较的过程,导致了注册码明码在内存中出现,结果同样会导致“功亏一篑”。所以,必须在算法的使用和注册验证这两个方面同时下手,才可以让一个优秀的算法发挥出它最大的作用。

浅析MD5加密算法在软件注册中的应用

今天,我们选择的这个MD5算法应用的实例,却从另一个角度启发我们究竟什么才叫做灵活应用。在注册过程中,真正的注册码很容易就被发现了,但想要成功注册?呵呵,门都没有!在稍后分析的过程中,大家一定会看着注册码干着急,明明就是正确注册码了,却怎么也用不了。这究竟是为什么呢?让我们带着这个问题开始下面的分析之路吧!

这次的实例是一个CrackMe,用PEiD检查,确定这个小程序没有加壳,接下来我们就可以搬出OD了。用OD打开这个CrackMe,载入之后打开“字符串查找功能”,很容易就发现了关键的注册提示信息,如图1所示,双击找到的注册提示,就可以来到对应的代码处了,

现在我们看到的是“注册提示”也就是来到了告诉我们注册成功与否的地方,注册码计算和注册验证的代码应该就在前面了,一路向上来到以下代码处。

00401C05 . 56 push esi ;在这里设置断点

00401C06 . 8BD8 mov ebx, eax

00401C08 . FFD7 call edi ;取输入的注册码

00401C0A . 8D4424 0C lea eax, [esp+C]

;将注册码保存在EAX中

00401C0E . 8D50 01 lea edx, [eax+1]

;对注册码作相同的检查

00401C11 > 8A08 mov cl, [eax]

;取注册码的每一位

00401C13 . 40 inc eax

00401C14 . 84C9 test cl, cl

00401C16 .^ 75 F9 jnz short 00401C11 ;循环计算

00401C18 . 2BC2 sub eax, edx

;得到输入的注册码的长度

00401C1A . 8BD0 mov edx, eax

;将注册码的长度保存在EDX中

00401C1C . 0F84 C5000000 je 00401CE7

程序首先将我们输入的注册码进行了一个预处理,并把注册码的长度记录下来。

00401C22 . 85DB test ebx, ebx

00401C24 . 0F84 BD000000 je 00401CE7

00401C2A . 33C9 xor ecx, ecx

00401C2C . 85D2 test edx, edx

;开始检验注册码的范围

00401C2E . 7E 29 jle short 00401C59

00401C30 > 8A440C 0C mov al, [esp+ecx+C]

;取注册码的每一位

00401C34 . 3C 30 cmp al, 30

00401C36 . 7C 04 jl short 00401C3C

00401C38 . 3C 39 cmp al, 39

00401C3A . 7E 18 jle short 00401C54

00401C3C > 3C 41 cmp al, 41

00401C3E . 7C 0C jl short 00401C4C

00401C40 . 3C 47 cmp al, 47

00401C42 . 7F 08 jg short 00401C4C

00401C44 . 04 20 add al, 20

00401C46 . 88440C 0C mov [esp+ecx+C], al

00401C4A . EB 08 jmp short 00401C54

00401C4C > 3C 61 cmp al, 61

00401C4E . 7C 65 jl short 00401CB5

00401C50 . 3C 67 cmp al, 67

00401C52 . 7F 61 jg short 00401CB5

00401C54 > 41 inc ecx

00401C55 . 3BCA cmp ecx, edx

00401C57 .^ 7C D7 jl short 00401C30

00401C59 > 33C0 xor eax, eax

00401C5B . 85DB test ebx, ebx

00401C5D . 7E 0D jle short 00401C6C

紧接着就是对注册码的每一位进行验证,主要目的就是检验注册码的每一位是否符合要求。简单的说,就是注册码的每一位必须在数字和字母这个范围内,超出这个范围就会出错了。大家可以注意到,上面的大段代码都是用来检验注册码的,看来我们输入的注册码虽然是“假码”,但这个CrackMe却对这个“假码”很感兴趣,一而再再而三地进行检验。下面又会对这个假码进行怎样的处理呢?我们继续分析。

00401C5F . 8A4C14 0B mov cl, [esp+edx+B]

;取注册码的最后一位

00401C63 > 304C04 40 xor [esp+eax+40], cl

;与注册名的每一位运算

00401C67 . 40 inc eax

;每计算一次EAX加1

00401C68 . 3BC3 cmp eax, ebx

;计算完了吗

00401C6A .^ 7C F7 jl short 00401C63 ;循环计算

00401C6C > 8D4424 40 lea eax, [esp+40]

;保存计算的结果str1

00401C70 . 50 push eax ;参数入栈

这里程序将我们输入的注册码的最后一位单独取了出来,并用它和注册名一一计算。下面把这段代码的详细计算过程给大家说明一下。

第一步:取注册码的最后一位的HEX值;

第二步:取注册名每一位的ASCII码;

第三步:将HEX值与注册名每一位的ASCII码作XOR(异或)运算;

第四步:将计算的结果转换成对应的字母。

完整的过程就是这样的,但感觉还是有些含糊,我们举例来说明吧。在这里我输入的注册名是tcxb,注册码是12345,那么在这段代码中的计算过程就是这样的。

第一步:取注册码的最后一位,也就是这里的5,计算对应的HEX值结果是35;

第二步:取注册名每一位的ASCII码,tcxb这四个字母对应的ASCII码分别是:74、63、78、62;

第三步:将35分别与74、63、78、62进行异或运算,XOR(35.74)=41,XOR(35.63)=56,XOR(35.78)=4D,XOR(35.62)=57;

第四步:因为在ASCII码表中,字母A对应的值就是41,所以注册名第一位计算得到的最终结果就是A。

按照ASCII码表中的对应关系一一计算,可以得到每一个结果,分别是A、V、M、W。最后将这四个字母合并组成一个字符串,也就是我们所说的str1=AVMW。得到了这个字符串之后又要干什么呢?我们接着向下分析。

00401C71 . C64414 0F 00 mov byte ptr [esp+edx+F], 0

00401C76 . E8 75FEFFFF call 00401AF0

;将str1做MD5计算

00401C7B . 83C4 04 add esp, 4

00401C7E . 8D4C24 0C lea ecx, [esp+C]

;取输入的注册码

00401C82 . 51 push ecx ; /String2

00401C83 . 50 push eax ; |String1

00401C84 . FF15 00704000 call [< &KERNEL32.lstrcmpA>]; lstrcmpA

00401C8A . 85C0 test eax, eax ;将两者比较

00401C8C . 6A 00 push 0

00401C8E . 75 33 jnz short 00401CC3

00401C90 . 68 98714000 push 00407198 ;succeed!

00401C95 . 68 88714000 push 00407188 ;great,注册成功!

00401C9A > 8B15 40974000 mov edx, [409740]; |

00401CA0 . 52 push edx ;|hOwner => NULL

00401CA1 . FF15 DC704000 call [< &USER32.MessageBoxA>]; MessageBoxA

这段代码就简单多了,主要作用是将上一步得到的str1即AVMW进行MD5计算,将计算得到的结果与我们输入的注册码相比较,如果相等就注册成功。通过各种方法很容易就可以计算出来MD5(AVMW)=c37985c0c4a3a9e4600d67c5d18af450。按照我们分析的结果,这个MD5值如果没有问题就是注册码了。

现在我们重新运行程序依次输入注册名tcxb,注册码c37985c0c4a3a9e4600d67c5d18af450,很自信地按下“注册”按钮,什么?居然提示“注册码”不正确!怎么回事?我们分析的过程没有错呀!再一次打开OD重新分析一遍,输入注册名tcxb和注册码12345,跟踪到最后,依然是上一步得到的那个计算结果。注册码就在眼前,可为什么不能成功注册呢?

用OD分析的过程是不可能出错的,问题是不是在我们,是不是分析的时候漏掉了什么?整理一下思路,这个CrackMe的计算过程很简单,主要就是把一个字符串用MD5进行了一个加密处理。一般来说,MD5加密的都是注册名或是注册名加固定字符串等这样一些东西,这里也没有什么特别的地方。那么注册码验证的地方就更不用怀疑了,很明显是真假注册码比较,而且还是明码比较,但为什么结果就是不对呢?

大家注意,我们所接触的类似这样将某一个字符串用MD5加密,然后将加密结果作为注册码的情况,被加密的字符串都是由注册名直接变换得出的,比如加上一个固定字符串或者是和某一个数字做计算等,简单的说就是被加密的信息与注册码之间是没有关系的,从头到尾都是对注册名的计算。其实问题就是出在这里了。在这个CrackMe中,大家回忆一下被加密的那个str1是怎样得来的?是用注册码的最后一位与注册名的每一位分别计算后得出的,这样在被加密的信息和输入的注册码之间就有了一个动态关系了,只要输入的注册码发生变化,就直接导致被加密的信息发生变化,被加密的信息都发生变化了,最后的结果肯定会出问题的。