CHM 3.50 注册流程及算法分析

【破文标题】**CHM 3.50 注册流程及算法分析(续)
【破文作者】Ptero
【破解工具】FI,OllyDbg,Dede,IDA,MD5工具
【注册方式】序列号+KeyFile
【保护方式】花指令,自校检,进程检测,API断点检测
【加壳方式】UPX v0.94-1.90
【加密算法】MD5+AES-256(Rijndael)+ZLib+RC4
【软件限制】功能限制
【破解声明】初学Crack,只是感兴趣,没有其它目的。失误之处敬请诸位大侠赐教!

----------------------------------------------------

【破解分析】

有关该软件中的AES-256(Rijndael)和ZLib算法的分析,请参见拙作《****CHM 3.50 注册流程及算法分析 (http://bbs.pediy.com/showthread.php?threadid=35623)》。

本文主要分析该软件中采用的作者自定义算法和RC4算法。
因为因为本文算法较少,再加上我也是初涉密码学,所以尽可能写详细一点,自己分析起来方便,大家看着也方便,呵呵。

在继续闯关之前,先简单介绍一下RC4算法:

RC4加密算法是大名鼎鼎的RSA三人组中的头号人物Ron Rivest在1987年设计的密钥长度可变的流加密算法簇。之所以称其为簇,是由于其核心部分的S-Box长度可为任意,但一般为256字节。该算法的速度可以达到DES加密的10倍左右。
在开始的7年中该算法享有专利,直到1994年,有人匿名发布了它的源码,此后它就不再是一个商业秘密了。
RC4属于对称算法中的序列密码,按字节逐个对明文加密。

(以下代码均用类C语言描述)

先看S-Box的初始化,共分为2步:

1. 线性填充S-Box(S[i])。代码如下:

for(i=0;i<256;i++)
S[i]=i;

2. 根据密钥(K[i])扰乱S-Box。代码如下:

for(i=j=0;i<256;i++)
{
j=(j+S[i]+K[i])%256;
交换S[i]和S[j];
}

RC4的加密/解密也很简单,也分为2步:

1. 产生随机字节k,代码如下:

i=j=0;
while(明文未结束)
{
i=(i+1)%256;
j=(j+s[i])%256;
交换S[i]和S[j];
t=(S[i]+S[j])%256;
k=S[t];
}

2. 将字节k与明文异或。

因为是异或运算,所以加密/解密算法相同。


下面言归正传,继续闯关。

++++++++++++++++++++++++++++++++++++++++ 第11关:检测KeyFile黑名单 ++++++++++++++++++++++++++++++++++++++++

话说Ptero闯过10关后,发现程序注册菜单消失,自以为注册成功,正沾沾自喜中,忽又发现被程序作者骗了,制作出的chm还是显示未注册版本。
于是又打开OD、Dede、IDA等等,继续分析,终于找到了隐藏的11关入口:

00503DF3 8D95 58FEFFFF lea edx, [ebp-1A8]
00503DF9 8B45 F0 mov eax, [ebp-10] ; KeyFile文件名
00503DFC E8 0763FCFF call ****CHM.004CA108 ; 计算KeyFile文件的MD5值
00503E01 8B85 58FEFFFF mov eax, [ebp-1A8] ; MD5值
00503E07 8B55 EC mov edx, [ebp-14]
00503E0A E8 25B5FCFF call ****CHM.004CF334
00503E0F 84C0 test al, al
00503E11 0F84 3F010000 je ****CHM.00503F56 ; 这里跳的话的Game Over啦

004CF334这个Call,读取了3个MD5值,与KeyFile的MD5比较。估计是检测流传出去的KeyFile的黑名单吧。如果相等的话,嘿嘿,就删除KeyFile,删除注册信息。
我自己构造了一个KeyFile,肯定不在黑名单里啦。所以直接pass,呵呵。

之后来到这里:

00503FBC 8B85 64FDFFFF mov eax, [ebp-29C] ; 用户名
00503FC2 8B4D F0 mov ecx, [ebp-10] ; KeyFile文件名
00503FC5 5A pop edx
00503FC6 E8 BD65FDFF call ****CHM.004DA588 ; 解密KeyFile,详细算法参见第7、8、9关
00503FCB 8B85 70FDFFFF mov eax, [ebp-290]
00503FD1 50 push eax ; KeyFile解密后的字串
00503FD2 8D85 58FDFFFF lea eax, [ebp-2A8]
00503FD8 8D95 F3FEFFFF lea edx, [ebp-10D]
00503FDE E8 390FF0FF call ****CHM.00404F1C ; System.@LStrFromString(String;String;ShortString;ShortString);
00503FE3 8B85 58FDFFFF mov eax, [ebp-2A8] ; 加密后的注册码
00503FE9 8D95 5CFDFFFF lea edx, [ebp-2A4]
00503FEF E8 2C24F3FF call ****CHM.00436420 ; 解密
00503FF4 8B85 5CFDFFFF mov eax, [ebp-2A4] ; 注册码
00503FFA 50 push eax
00503FFB 8D85 50FDFFFF lea eax, [ebp-2B0]
00504001 8D95 8EFEFFFF lea edx, [ebp-172]
00504007 E8 100FF0FF call ****CHM.00404F1C ; System.@LStrFromString(String;String;ShortString;ShortString);
0050400C 8B85 50FDFFFF mov eax, [ebp-2B0] ; 加密后的用户名
00504012 8D95 54FDFFFF lea edx, [ebp-2AC]
00504018 E8 0324F3FF call ****CHM.00436420 ; 解密
0050401D 8B95 54FDFFFF mov edx, [ebp-2AC] ; 用户名
00504023 59 pop ecx ; 注册码
00504024 58 pop eax ; KeyFile解密后的字串
00504025 E8 1667FDFF call ****CHM.004DA740 ; 12、13关
0050402A 84C0 test al, al
0050402C 74 10 je short ****CHM.0050403E
0050402E EB 06 jmp short ****CHM.00504036

004DA740就是关键Call啦,F7跟进:

++++++++++++++++++++++++++++++++++++++++ 第12关:KeyFile的压缩变换 ++++++++++++++++++++++++++++++++++++++++

取KeyFile的第110位,记为a,并转换成数字:

004DA7E2 8D45 D8 lea eax, [ebp-28]
004DA7E5 50 push eax
004DA7E6 B9 01000000 mov ecx, 1
004DA7EB BA 6E000000 mov edx, 6E
004DA7F0 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DA7F3 E8 FCA9F2FF call ****CHM.004051F4 ; System.@LStrCopy;
004DA7F8 8B45 D8 mov eax, [ebp-28]
004DA7FB E8 E0F6F2FF call ****CHM.00409EE0 ; SysUtils.StrToInt(AnsiString):Integer;
004DA800 8BD8 mov ebx, eax
004DA802 EB 04 jmp short ****CHM.004DA808

取KeyFile的第1-109位:

004DA83A 8D45 E0 lea eax, [ebp-20]
004DA83D 50 push eax
004DA83E B9 6D000000 mov ecx, 6D
004DA843 BA 01000000 mov edx, 1
004DA848 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DA84B E8 A4A9F2FF call ****CHM.004051F4 ; System.@LStrCopy;
004DA850 EB 04 jmp short ****CHM.004DA856

取KeyFile的(a+130)-(a+143)位:

004DA86A 8D45 DC lea eax, [ebp-24]
004DA86D 50 push eax
004DA86E 8D93 8D000000 lea edx, [ebx+8D]
004DA874 B9 04000000 mov ecx, 4
004DA879 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DA87C E8 73A9F2FF call ****CHM.004051F4 ; System.@LStrCopy;

转换成10进制,记为b:

004DA8B9 33D2 xor edx, edx
004DA8BB 8B45 DC mov eax, [ebp-24]
004DA8BE E8 59F6F2FF call ****CHM.00409F1C ; SysUtils.StrToIntDef(AnsiString;Integer):Integer;
004DA8C3 8BF0 mov esi, eax

b是下面字符串的长度。接着取紧随其后的b个字符,记为Str1:

004DA8E1 8D45 E4 lea eax, [ebp-1C]
004DA8E4 50 push eax
004DA8E5 8D93 91000000 lea edx, [ebx+91] ; a+144
004DA8EB 8BCE mov ecx, esi ; b
004DA8ED 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DA8F0 E8 FFA8F2FF call ****CHM.004051F4 ; System.@LStrCopy;

计算KeyFile的第1-109位的Hash值:

004DA94D 8D4D D0 lea ecx, [ebp-30]
004DA950 33D2 xor edx, edx
004DA952 8B45 E0 mov eax, [ebp-20] ; 字符串前110位
004DA955 E8 EE56FFFF call ****CHM.004D0048 ; 自定义的Hash算法,详细算法参见第6关
004DA95A 8B45 D0 mov eax, [ebp-30] ; KeyFile的第1-109位的Hash字符串
004DA95D 50 push eax

注意:这里计算出了一个Hash字串,下一关要用到。

下面是本关的重头戏,压缩变换:

004DA95E 8D55 CC lea edx, [ebp-34]
004DA961 8B45 E4 mov eax, [ebp-1C] ; 取出的b个字节的字符串Str1
004DA964 E8 A74CFFFF call ****CHM.004CF610 ; 进行压缩变换,F7跟进可看到算法

F7跟进:

---------------------------------------- 004CF610 ----------------------------------------

004CF610 55 push ebp

…………
…………
…………

004CF64A 8D55 F4 lea edx, [ebp-C]
004CF64D 8B45 FC mov eax, [ebp-4]
004CF650 E8 D3000000 call ****CHM.004CF728 ; 3字节→1字节压缩变换并逆序,F7跟进可看到算法

下面是一个循环:

004CF680 46 inc esi ; 变换后字符串长度,作循环计数器
004CF681 33FF xor edi, edi
004CF683 8B45 F4 mov eax, [ebp-C] ; 变换后的字符串
004CF686 8A5C38 FF mov bl, [eax+edi-1]
004CF68A 8B45 F4 mov eax, [ebp-C]
004CF68D E8 0259F3FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004CF692 2BC7 sub eax, edi
004CF694 8B55 F4 mov edx, [ebp-C]
004CF697 8A0402 mov al, [edx+eax]
004CF69A 50 push eax
004CF69B 8D45 F4 lea eax, [ebp-C]
004CF69E E8 495BF3FF call ****CHM.004051EC
004CF6A3 5A pop edx
004CF6A4 885438 FF mov [eax+edi-1], dl
004CF6A8 8B45 F4 mov eax, [ebp-C]
004CF6AB E8 E458F3FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004CF6B0 2BC7 sub eax, edi
004CF6B2 50 push eax
004CF6B3 8D45 F4 lea eax, [ebp-C]
004CF6B6 E8 315BF3FF call ****CHM.004051EC
004CF6BB 5A pop edx
004CF6BC 881C10 mov [eax+edx], bl
004CF6BF 8B45 F4 mov eax, [ebp-C]
004CF6C2 E8 CD58F3FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004CF6C7 D1F8 sar eax, 1
004CF6C9 79 03 jns short ****CHM.004CF6CE
004CF6CB 83D0 00 adc eax, 0
004CF6CE 3BF8 cmp edi, eax
004CF6D0 7D 04 jge short ****CHM.004CF6D6
004CF6D2 47 inc edi
004CF6D3 4E dec esi
004CF6D4 ^ 75 AD jnz short ****CHM.004CF683

这段循环看着有些长,实际上就是将压缩逆序变换后的字符串再次逆序,负负得正。

以下就返回了……

--------------------------------------------------------------------------------


3字节→1字节压缩变换并逆序:

---------------------------------------- 004CF728 ----------------------------------------

004CF728 55 push ebp

…………
…………
…………

字符串长度除以3作为循环次数:

004CF757 8B45 FC mov eax, [ebp-4] ; 字符串
004CF75A E8 3558F3FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004CF75F B9 03000000 mov ecx, 3
004CF764 99 cdq
004CF765 F7F9 idiv ecx
004CF767 8945 F4 mov [ebp-C], eax

循环,进行压缩变换:

004CF787 8B5D F4 mov ebx, [ebp-C]
004CF78A 85DB test ebx, ebx
004CF78C 7E 40 jle short ****CHM.004CF7CE
004CF78E BF 01000000 mov edi, 1
004CF793 8D45 EC lea eax, [ebp-14]
004CF796 50 push eax
004CF797 8BC7 mov eax, edi
004CF799 48 dec eax
004CF79A 8D1440 lea edx, [eax+eax*2]
004CF79D 42 inc edx
004CF79E B9 03000000 mov ecx, 3
004CF7A3 8B45 FC mov eax, [ebp-4] ; 字符串
004CF7A6 E8 495AF3FF call ****CHM.004051F4 ; System.@LStrCopy;
004CF7AB 8B45 EC mov eax, [ebp-14]
004CF7AE E8 2DA7F3FF call ****CHM.00409EE0 ; SysUtils.StrToInt(AnsiString):Integer;
004CF7B3 8BD0 mov edx, eax
004CF7B5 80F2 FF xor dl, 0FF ; 和0FFh异或就是求反
004CF7B8 8D45 F0 lea eax, [ebp-10]
004CF7BB E8 E056F3FF call ****CHM.00404EA0 ; System.@LStrFromChar(String;String;Char);
004CF7C0 8B55 F0 mov edx, [ebp-10]
004CF7C3 8BC6 mov eax, esi
004CF7C5 E8 D257F3FF call ****CHM.00404F9C ; System.@LStrCat;
004CF7CA 47 inc edi
004CF7CB 4B dec ebx
004CF7CC ^ 75 C5 jnz short ****CHM.004CF793

这是一个3字节→1字节的压缩变换,变换规则是依次将3字节的数字转换成16进制,再求反。
例如:
转换 求反
31 38 34(Ascii:184)--→B8(184==0B8h)--→47

下面又是一轮循环:

004CF7E6 BF 01000000 mov edi, 1
004CF7EB 8D45 E8 lea eax, [ebp-18]
004CF7EE 8B55 F4 mov edx, [ebp-C]
004CF7F1 2BD7 sub edx, edi
004CF7F3 8B4D F8 mov ecx, [ebp-8] ; 压缩后的字符串
004CF7F6 8A1411 mov dl, [ecx+edx] ; 逆序依次取出字符
004CF7F9 E8 A256F3FF call ****CHM.00404EA0 ; System.@LStrFromChar(String;String;Char);
004CF7FE 8B55 E8 mov edx, [ebp-18]
004CF801 8BC6 mov eax, esi
004CF803 E8 9457F3FF call ****CHM.00404F9C ; System.@LStrCat;
004CF808 47 inc edi
004CF809 4B dec ebx
004CF80A ^ 75 DF jnz short ****CHM.004CF7EB

这个循环将压缩变换后的字符串逆序。

以下就返回了……

--------------------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++ 第13关:生成RC4密钥 ++++++++++++++++++++++++++++++++++++++++

004D901E 8B45 F8 mov eax, [ebp-8] ; Hash字串
004D9021 E8 92FEFFFF call ****CHM.004D8EB8 ; 生成随机数种子,F7跟进可看到算法
004D9026 8B15 48CD5500 mov edx, [55CD48]
004D902C 8902 mov [edx], eax ; 保存计算得到的值

下面是一个循环:

004D902E BB FF000000 mov ebx, 0FF ; 循环256次
004D9033 8DB5 F2FDFFFF lea esi, [ebp-20E]
004D9039 B8 FF000000 mov eax, 0FF
004D903E E8 61A6F2FF call ****CHM.004036A4 ; system.@RandInt,对这个函数的说明见下面
004D9043 8806 mov [esi], al ; 保存
004D9045 46 inc esi
004D9046 4B dec ebx
004D9047 ^ 75 F0 jnz short ****CHM.004D9039

这段循环是生成一个256字节的伪随机序列。这就是RC4的密钥了。

下面是这关用到的2个Call:

生成随机数种子:

---------------------------------------- 004D8EB8 ----------------------------------------

004D8EB8 55 push ebp

…………
…………
…………

外层循环:

004D8EEF C745 F8 010000>mov dword ptr [ebp-8], 1 ; 循环计数器
004D8EF6 33C0 xor eax, eax
004D8EF8 55 push ebp
004D8EF9 68 588F4D00 push ****CHM.004D8F58
004D8EFE 64:FF30 push dword ptr fs:[eax]
004D8F01 64:8920 mov fs:[eax], esp
004D8F04 8B45 FC mov eax, [ebp-4]
004D8F07 8B55 F8 mov edx, [ebp-8]
004D8F0A 0FB64410 FF movzx eax, byte ptr [eax+edx-1]
004D8F0F 8D55 EC lea edx, [ebp-14]
004D8F12 E8 5D0EF3FF call ****CHM.00409D74 ; SysUtils.IntToStr(Integer):AnsiString;overload;
004D8F17 8B55 EC mov edx, [ebp-14]
004D8F1A 8D45 F0 lea eax, [ebp-10]
004D8F1D E8 7AC0F2FF call ****CHM.00404F9C ; System.@LStrCat;
004D8F22 8B45 FC mov eax, [ebp-4]
004D8F25 E8 6AC0F2FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004D8F2A 8BD8 mov ebx, eax
004D8F2C 85DB test ebx, ebx
004D8F2E 7E 1E jle short ****CHM.004D8F4E

内层循环:

004D8F30 BE 01000000 mov esi, 1
004D8F35 8B45 F0 mov eax, [ebp-10]
004D8F38 E8 A30FF3FF call ****CHM.00409EE0 ; SysUtils.StrToInt(AnsiString):Integer;
004D8F3D 8B55 FC mov edx, [ebp-4]
004D8F40 0FB65432 FF movzx edx, byte ptr [edx+esi-1]
004D8F45 F7EA imul edx
004D8F47 8945 F4 mov [ebp-C], eax
004D8F4A 46 inc esi
004D8F4B 4B dec ebx
004D8F4C ^ 75 E7 jnz short ****CHM.004D8F35

外循环的作用是将字符串的前3位依次取Ascii码,转换成10进制,再连成一个字符串。
内循环的作用是将次字符串转为数字,再与原字符串的最后1个字符相乘。

以下就返回了……

--------------------------------------------------------------------------------


system.@RandInt:

说明:
注意函数中的常数,或者叫作Magic Number:8088405!仅仅在看雪搜索这个数值8088405,就可以找到N篇帖子,而且算法竟然一模一样!所以这个函数不可能是作者的算法,一定Delphi的系统函数,但是Dede没有给出分析,不知道这个函数的作用。
后来终于在这里(http://bbs.pediy.com/showthread.php?threadid=13290)找到了,原来这个函数是system.@RandInt(奇怪,不知道为什么我的Dede没有分析出来)。以后看到8088405这个值就知道是这个函数了,可以避免重复劳动。
这里程序用system.@RandInt来生成一个伪随机数作为RC4的密钥。

---------------------------------------- 004036A4(system.@RandInt) ----------------------------------------

004036A4 53 push ebx
004036A5 31DB xor ebx, ebx
004036A7 6993 08605500 >imul edx, [ebx+556008], 8088405 ; 将随机数种子乘以8088405h
004036B1 42 inc edx ; 再+1
004036B2 8993 08605500 mov [ebx+556008], edx ; 保存
004036B8 F7E2 mul edx ; 将此值再与eax相乘
004036BA 89D0 mov eax, edx ; 取高32位
004036BC 5B pop ebx
004036BD C3 ret

--------------------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++ 第14关:生成RC4密文 +++++++++++++++++++++++++++++++++++++++

上一关的密钥生成之后,就来到14关:

004D9049 8D95 ECFBFFFF lea edx, [ebp-414]
004D904F 8B45 FC mov eax, [ebp-4] ; Str1经压缩变换后的字符串
004D9052 E8 2D010000 call ****CHM.004D9184 ; 4字节→3字节压缩变换,将Str1转换成密文

这一关的内容不多,就是这一个Call了。关键是它的算法。
F7跟进:

---------------------------------------- 004D9184 ----------------------------------------

004D9184 55 push ebp

…………
…………
…………

004D91AD C645 F7 00 mov byte ptr [ebp-9], 0
004D91B1 33C0 xor eax, eax
004D91B3 8945 F8 mov [ebp-8], eax
004D91B6 BB 01000000 mov ebx, 1
004D91BB 33F6 xor esi, esi
004D91BD EB 5A jmp short ****CHM.004D9219

循环,压缩变换:

004D91BF 85F6 test esi, esi
004D91C1 75 18 jnz short ****CHM.004D91DB
004D91C3 33C0 xor eax, eax
004D91C5 8A441F FF mov al, [edi+ebx-1]
004D91C9 8A80 BCAA5500 mov al, [eax+55AABC] ; 查表替换
004D91CF 8845 F7 mov [ebp-9], al
004D91D2 C745 F8 020000>mov dword ptr [ebp-8], 2
004D91D9 EB 39 jmp short ****CHM.004D9214
004D91DB 8D45 F0 lea eax, [ebp-10]
004D91DE 8B4D F8 mov ecx, [ebp-8]
004D91E1 33D2 xor edx, edx
004D91E3 8A55 F7 mov dl, [ebp-9]
004D91E6 D3E2 shl edx, cl
004D91E8 81E2 C0000000 and edx, 0C0
004D91EE 33C9 xor ecx, ecx
004D91F0 8A4C1F FF mov cl, [edi+ebx-1]
004D91F4 0FB689 BCAA550>movzx ecx, byte ptr [ecx+55AABC] ; 查表替换
004D91FB 0BD1 or edx, ecx
004D91FD E8 9EBCF2FF call ****CHM.00404EA0 ; System.@LStrFromChar(String;String;Char);
004D9202 8B55 F0 mov edx, [ebp-10]
004D9205 8B45 FC mov eax, [ebp-4]
004D9208 E8 8FBDF2FF call ****CHM.00404F9C ; System.@LStrCat;
004D920D 8B45 FC mov eax, [ebp-4]
004D9210 8345 F8 02 add dword ptr [ebp-8], 2
004D9214 46 inc esi
004D9215 83E6 03 and esi, 3
004D9218 43 inc ebx
004D9219 8BC7 mov eax, edi
004D921B E8 74BDF2FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004D9220 3BD8 cmp ebx, eax
004D9222 7F 07 jg short ****CHM.004D922B
004D9224 807C1F FF 2E cmp byte ptr [edi+ebx-1], 2E
004D9229 ^ 75 94 jnz short ****CHM.004D91BF

以下就返回了……

上面这个循环很让人头痛,不知道是作者从哪里找来的算法。我是在IDA当中分析的。

这是所查的表,姑且也算一个S-Box吧,设为S[i]。
表中有效数值从0-39h:

0055AABC 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????
0055AACC 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 80 ????????
0055AADC 80 80 80 3A 80 3B 3C 80 80 80 3D 3E 80 3F 80 80 ???<??>??
0055AAEC 80 80 00 01 02 03 04 05 06 07 80 80 80 80 80 80 ?.???
0055AAFC 80 08 09 0A 0B 0C 0D 0E 0F 80 10 11 12 13 14 80 ?....??
0055AB0C 15 16 17 18 19 1A 1B 1C 1D 1E 1F 80 80 80 80 80 ???
0055AB1C 80 20 21 22 23 24 25 26 27 28 29 2A 80 2B 2C 2D ?!"#$%&'()*?,-
0055AB2C 2E 2F 30 31 32 33 34 35 36 37 38 80 80 80 39 80 ./012345678???

下面将其算法用C语言给出:

for(i=0;i<=sizeof(str)&&strIn[i]!=0x2E;i++)
{
if(!(i%4))
{
colume=2;
key=S[strIn[i]];
}
else
{
strOut[i-1]=((key<<colume)&0xC0)|S[strIn[i]];
colume+=2;
}
}


以上算法以4字节为一组,第1字节为子密钥,将其后的3字节解密,如果遇到2Eh,则停止解密。

下面介绍一下该算法原理。

看算法中的Magic Number:0C0h,为什么要用它呢?
将它转换成2进制看看:1100 0000。
它实际上就是一个掩码。
子密钥的15、14位为0,0-13位为下面3字节密文的15、14位的掩码。
密文的15、14位由子密钥和掩码决定,0-13位由查表得出。
因为子密钥和密文字节都只用到0-13位,也就是0-39h,这就解释了上面S-Box有效值的范围。

至于加密算法嘛,先将明文按3字节分组,然后将3个密文字节的15、14位组合成密钥,再换成在S-Box中的位置。密文的0-13位也分别换成在S-Box中的位置。

--------------------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++ 第15关:RC4解密 +++++++++++++++++++++++++++++++++++++++

既然密文、密钥都有了,下面就是解密啦:

004D90B3 8D95 F2FDFFFF lea edx, [ebp-20E] ; RC4密钥
004D90B9 8D85 F2FBFFFF lea eax, [ebp-40E]
004D90BF B9 FF000000 mov ecx, 0FF
004D90C4 E8 67FCFFFF call ****CHM.004D8D30 ; S-Box初始化,F7跟进可看到算法
004D90C9 68 FF000000 push 0FF ; 密钥长度
004D90CE 8D8D F1FEFFFF lea ecx, [ebp-10F] ; 解密后明文地址,覆盖掉密文
004D90D4 8D95 F1FEFFFF lea edx, [ebp-10F] ; 上一关解密后字串作明文
004D90DA 8D85 F2FBFFFF lea eax, [ebp-40E] ; S-Box
004D90E0 E8 1FFDFFFF call ****CHM.004D8E04 ; RC4加密

RC4加密的代码比较长,我就不贴了。其实算法还是很简单的,见本文开头的说明。

下面是对S-Box的初始化:

---------------------------------------- 004D8D30 ----------------------------------------

004D8D30 53 push ebx
004D8D31 56 push esi
004D8D32 57 push edi
004D8D33 55 push ebp
004D8D34 81C4 FCFEFFFF add esp, -104
004D8D3A 8BEA mov ebp, edx
004D8D3C 890424 mov [esp], eax
004D8D3F 85C9 test ecx, ecx
004D8D41 7E 08 jle short ****CHM.004D8D4B
004D8D43 81F9 00010000 cmp ecx, 100 ; 密钥长度不能大于256位,否则就产生异常啦
004D8D49 7E 16 jle short ****CHM.004D8D61
004D8D4B B9 E08D4D00 mov ecx, ****CHM.004D8DE0 ; ASCII "Invalid key length"
004D8D50 B2 01 mov dl, 1
004D8D52 A1 3C884000 mov eax, [40883C]
004D8D57 E8 0C52F3FF call ****CHM.0040DF68 ; AxCtrls.TOleStream.Create(TOleStream;boolean;IStream);
004D8D5C E8 ABB8F2FF call ****CHM.0040460C ; System.@RaiseExcept;

下面的循环对S-Box进行线性填充,生成0-0FFh的数列:

004D8D61 33DB xor ebx, ebx
004D8D63 8B3424 mov esi, [esp]
004D8D66 8D7C24 04 lea edi, [esp+4]
004D8D6A 881E mov [esi], bl
004D8D6C 8BC3 mov eax, ebx
004D8D6E 99 cdq
004D8D6F F7F9 idiv ecx
004D8D71 03D5 add edx, ebp
004D8D73 8A02 mov al, [edx]
004D8D75 8807 mov [edi], al
004D8D77 43 inc ebx
004D8D78 47 inc edi
004D8D79 46 inc esi
004D8D7A 81FB 00010000 cmp ebx, 100
004D8D80 ^ 75 E8 jnz short ****CHM.004D8D6A

下面的循环根据密钥(K[i])扰乱S-Box:

004D8D82 33F6 xor esi, esi
004D8D84 BB 00010000 mov ebx, 100
004D8D89 8B0424 mov eax, [esp] ; S-Box,S[i]
004D8D8C 8D7C24 04 lea edi, [esp+4] ; 密钥,K[i]
004D8D90 8A10 mov dl, [eax]
004D8D92 33C9 xor ecx, ecx
004D8D94 8ACA mov cl, dl
004D8D96 03F1 add esi, ecx
004D8D98 33C9 xor ecx, ecx
004D8D9A 8A0F mov cl, [edi]
004D8D9C 03F1 add esi, ecx
004D8D9E 81E6 FF000000 and esi, 0FF
004D8DA4 8B0C24 mov ecx, [esp]
004D8DA7 8A0C31 mov cl, [ecx+esi]
004D8DAA 8808 mov [eax], cl
004D8DAC 8B0C24 mov ecx, [esp]
004D8DAF 881431 mov [ecx+esi], dl
004D8DB2 47 inc edi
004D8DB3 40 inc eax
004D8DB4 4B dec ebx
004D8DB5 ^ 75 D9 jnz short ****CHM.004D8D90

以下就返回了……

--------------------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++ 第16关:数字和的验证 +++++++++++++++++++++++++++++++++++++++

还记得12关中从KeyFile读取的那个数字b吗?它是Str1的长度。

下面的代码读取KeyFile的(b+244)-(b+247)字节:

004DA9A2 8D45 E8 lea eax, [ebp-18]
004DA9A5 50 push eax
004DA9A6 8D941E F500000>lea edx, [esi+ebx+F5]
004DA9AD 03D3 add edx, ebx
004DA9AF B9 04000000 mov ecx, 4
004DA9B4 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DA9B7 E8 38A8F2FF call ****CHM.004051F4 ; System.@LStrCopy;

转换成10进制,记为c:

004DA9F4 33D2 xor edx, edx
004DA9F6 8B45 E8 mov eax, [ebp-18]
004DA9F9 E8 1EF5F2FF call ****CHM.00409F1C ; SysUtils.StrToIntDef(AnsiString;Integer):Integer;
004DA9FE 8BF8 mov edi, eax
004DAA00 EB 04 jmp short ****CHM.004DAA06

和b一样,c也是一个字符串的长度。是哪个字符串呢?就是下面要读取的,记为Str2:

004DAA1A 8D45 EC lea eax, [ebp-14]
004DAA1D 50 push eax
004DAA1E 8D941E F500000>lea edx, [esi+ebx+F5]
004DAA25 03D3 add edx, ebx
004DAA27 83C2 04 add edx, 4
004DAA2A 8BCF mov ecx, edi
004DAA2C 8B45 FC mov eax, [ebp-4] ; KeyFile解密后的字串
004DAA2F E8 C0A7F2FF call ****CHM.004051F4 ; System.@LStrCopy;

004DAA6C 8B45 EC mov eax, [ebp-14] ; Str2
004DAA6F E8 70EBFFFF call ****CHM.004D95E4 ; 取字符串的数字和的十位(如只有1位,就取个位),F7跟进可看到算法
004DAA74 3BD8 cmp ebx, eax
004DAA76 74 0B je short ****CHM.004DAA83
004DAA78 EB 04 jmp short ****CHM.004DAA7E ; 不为0就Game Over啦

呵呵,还真苛刻呢,字符串里每个数字累加的和的十位必须要是0才行。

下面就是这个取十位的算法:

---------------------------------------- 004DAA7E ----------------------------------------

004D95E4 55 push ebp

…………
…………
…………

进入一个循环:

004D962D BE 01000000 mov esi, 1
004D9632 EB 06 jmp short ****CHM.004D963A

依次取出一个字符:

004D963A 8D45 F8 lea eax, [ebp-8]
004D963D 50 push eax
004D963E B9 01000000 mov ecx, 1
004D9643 8BD6 mov edx, esi
004D9645 8B45 FC mov eax, [ebp-4]
004D9648 E8 A7BBF2FF call ****CHM.004051F4 ; System.@LStrCopy;
004D964D EB 04 jmp short ****CHM.004D9653

004D9653 33D2 xor edx, edx
004D9655 8B45 F8 mov eax, [ebp-8]
004D9658 E8 BF08F3FF call ****CHM.00409F1C ; SysUtils.StrToIntDef(AnsiString;Integer):Integer;
004D965D 03F8 add edi, eax
004D965F 46 inc esi
004D9660 4B dec ebx
004D9661 ^ 75 CF jnz short ****CHM.004D9632

这个循环的作用是将字符串的每一位转换成数字并求和。

取和的十位数:

004D9687 8BD0 mov edx, eax ; 数字位数
004D9689 4A dec edx
004D968A B9 01000000 mov ecx, 1
004D968F 8B45 F8 mov eax, [ebp-8] ; 求和的数字转换成的字符串
004D9692 E8 5DBBF2FF call ****CHM.004051F4 ; System.@LStrCopy;
004D9697 8B45 F4 mov eax, [ebp-C]
004D969A 33D2 xor edx, edx
004D969C E8 7B08F3FF call ****CHM.00409F1C ; SysUtils.StrToIntDef(AnsiString;Integer):Integer;

以下就返回了……

--------------------------------------------------------------------------------

++++++++++++++++++++++++++++++++++++++++ 第17关:再次RC4解密 +++++++++++++++++++++++++++++++++++++++

004DAA8B 8D45 C0 lea eax, [ebp-40]
004DAA8E 8B4D F4 mov ecx, [ebp-C] ; 注册码
004DAA91 8B55 F8 mov edx, [ebp-8] ; 用户名
004DAA94 E8 47A5F2FF call ****CHM.00404FE0 ; System.@LStrCat3;
004DAA99 8B45 C0 mov eax, [ebp-40] ; 用户名+注册码
004DAA9C 8D4D C4 lea ecx, [ebp-3C]
004DAA9F 33D2 xor edx, edx
004DAAA1 E8 4A56FFFF call ****CHM.004D00F0 ; 又是加上那个长长的字符串再取MD5,这个Call在第7关和第10关都出现过,这里是出现第3次了
004DAAA6 8B45 C4 mov eax, [ebp-3C] ; 算出的MD5作为初始密钥
004DAAA9 50 push eax
004DAAAA 8D55 BC lea edx, [ebp-44]
004DAAAD 8B45 EC mov eax, [ebp-14] ; Str2
004DAAB0 E8 734CFFFF call ****CHM.004CF728 ; 压缩变换并逆序,这个Call在第12关中见过(注意:第12关中是2次逆序,负负得正;而这里是1次逆序)
004DAAB5 8B55 BC mov edx, [ebp-44] ; Str2经压缩变换并逆序后的字符串
004DAAB8 8D4D C8 lea ecx, [ebp-38]
004DAABB 58 pop eax ; MD5值
004DAABC E8 0FECFFFF call ****CHM.004D96D0 ; 这里是RC4从初始化到解密,又把13、14、15关过了一遍

把前面几关的算法研究透了,这关其实很简单的。

++++++++++++++++++++++++++++++++++++++++ 第18关:最后的比较 +++++++++++++++++++++++++++++++++++++++

比较Str1和Str2解密后的长度:

004DAAD2 8B45 EC mov eax, [ebp-14]
004DAAD5 E8 BAA4F2FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004DAADA 8BD8 mov ebx, eax
004DAADC 8B45 E4 mov eax, [ebp-1C]
004DAADF E8 B0A4F2FF call ****CHM.00404F94 ; System.@LStrLen(String):Integer;
004DAAE4 3BD8 cmp ebx, eax
004DAAE6 74 08 je short ****CHM.004DAAF0

比较Str1和Str2解密后的明文:

004DAAF8 8B45 EC mov eax, [ebp-14]
004DAAFB 8B55 E4 mov edx, [ebp-1C]
004DAAFE E8 DDA5F2FF call ****CHM.004050E0 ; System.@LStrCmp;
004DAB03 74 08 je short ****CHM.004DAB0D

再次比较明文,并与用户名比较:

004DAB13 8B45 EC mov eax, [ebp-14]
004DAB16 8B55 E4 mov edx, [ebp-1C]
004DAB19 E8 C2A5F2FF call ****CHM.004050E0 ; System.@LStrCmp;
004DAB1E 75 0D jnz short ****CHM.004DAB2D
004DAB20 8B45 F8 mov eax, [ebp-8] ; 用户名
004DAB23 8B55 EC mov edx, [ebp-14]
004DAB26 E8 B5A5F2FF call ****CHM.004050E0 ; System.@LStrCmp;
004DAB2B 74 04 je short ****CHM.004DAB31
004DAB2D 33C0 xor eax, eax
004DAB2F EB 02 jmp short ****CHM.004DAB33
004DAB31 B0 01 mov al, 1
004DAB33 8845 F3 mov [ebp-D], al

呵呵,最后原来是跟用户名比较。
如果这3次比较都相等的话,呵呵,我发誓,不会再有第19关啦。到这里就彻底通关啦!

至于KeyFile的构造,只要将上面的算法逆序,生成2个密钥,再将用户名用RC4加密2次,再用前面提到的算法加密,最后写到KeyFile里,再将KeyFile用ZLib压缩,最后AES-256加密就行啦。

其实要顺利通关,这里还有一个小问题:

Str2解密后必须为用户名,那万一Str2的数字和的十位不为0,第16关过不去怎么办?
回顾一下第14关,生成RC4密文的时候,判断了字符串中是否遇到2Eh,如果遇到,就结束循环。那么就可以利用这一点,在后面加上2Eh,然后就可以在后面添加任何数字来使数字和的十位为0了。



后记:到这里终于算是功德圆满啦,算是完美注册成功了。最后感谢论坛上多位朋友的支持和鼓励!

----------------------------------------------------

【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢

转载于:https://www.cnblogs.com/pengyingh/articles/2499381.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值