MD5算法应用之“迷魂阵”
文/图 TC-XB
===================================
我们已经见识很多种MD5 算法在软件注册中的应用,同时也得到了一个结论:一味生搬硬*成的算法而不加改变,即使算法再强大在破解者面前也是“不堪一击”的。所以,我们就见识到了各种各样的奇思妙想,将MD5算法使用得淋漓尽致。但是,算法上做足了功夫却没有注意注册码比较的过程,导致了注册码明码在内存中出现,结果同样会导致“功亏一篑”。所以,必须在算法的使用和注册验证这两个方面同时下手,才可以让一个优秀的算法发挥出它最大的作用。
今天,我们选择的这个MD5算法应用的实例,却从另一个角度启发我们究竟什么才叫做灵活应用。在注册过程中,真正的注册码很容易就被发现了,但想要成功注册?呵呵,门都没有!在稍后分析的过程中,大家一定会看着注册码干着急,明明就是正确注册码了,却怎么也用不了。这究竟是为什么呢?让我们带着这个问题开始下面的分析之路吧!
这次的实例是一个CrackMe,用PEiD检查,确定这个小程序没有加壳,接下来我们就可以搬出OD了。用OD打开这个CrackMe,载入之后打开“字符串查找功能”,很容易就发现了关键的注册提示信息,如图1所示,双击找到的注册提示,就可以来到对应的代码处了,如图2所示。
图1
图2
现在我们看到的是“注册提示”也就是来到了告诉我们注册成功与否的地方,注册码计算和注册验证的代码应该就在前面了,一路向上来到以下代码处。
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是怎样得来的?是用注册码的最后一位与注册名的每一位分别计算后得出的,这样在被加密的信息和输入的注册码之间就有了一个动态关系了,只要输入的注册码发生变化,就直接导致被加密的信息发生变化,被加密的信息都发生变化了,最后的结果肯定会出问题的。
还是举一个例子来说吧。依然是tcxb这个注册名和12345这个注册码,经过分析我们可以了解到,现在的重点就在最后的那个数字5上面,因为被加密的字符串str1是由这个5和注册名直接计算而来的。接着我们在OD中找到了所谓的注册码c37985c0c4a3a9e4600d67c5d18af450,这些都没有问题。然而,当我们试图用c37985c0c4a3a9e4600d67c5d18af450这个字符串作为注册码与tcxb进行注册的时候,问题出现了,注册名没有变化,但是注册码的最后一位已不再是5了,而是0了,用5和0这两个不同的数与相同的一个对象作相同的计算,怎么可能得出相同的结果呢?
上一篇: 字节跳动 教育业务后端开发
下一篇: 实用微博营销优化技巧推荐