|
|
【破文标题】**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 注册流程及算法分析》。
本文主要分析该软件中采用的作者自定义算法和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€€€9€
下面将其算法用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了。
后记:到这里终于算是功德圆满啦,算是完美注册成功了。最后感谢论坛上多位朋友的支持和鼓励!
----------------------------------------------------
【版权声明】 本文纯属技术交流, 转载请注明作者并保持文章的完整, 谢谢
|
|