【文章标题】: 菜鸟的第一篇破文
【软件名称】: CrackMe3
【保护方式】: 序列号,有anti-debug
【使用工具】: OD, IDA
【操作平台】: WINDOWS XP SP2
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
暑假时才开始学逆向,这是我的从第一篇破文。经验不足,如果有一些错误,还请各位大虾莫见笑。:-)
这个CrackMe是师兄发来当练习的,可能经过一些修改,存在一些BUG,来源信息被删了,所以真实来源我无法考究。这篇破文是在我的练习报告的基础上改的,在此斗胆贴出来,希望版主能发发慈悲给个邀请码,呵呵。
首先直接运行CrackMe3,输入用户名overwind和假码0123456789,并没有期待的错误提示信息。用OD加载运行,连个界面都不出来便自动退出了,开了HideOD V0.181插件也未见成效。身为菜鸟的我刚开始还真是束手无策。后来在论坛上看到这种自动退出的情况可以尝试命令bp ExitProcess,试了一下,果然在004022CB处断了下来:
0040227D . 8BF4 mov esi, esp
0040227F . 8D4D B8 lea ecx, dword ptr [ebp-48]
00402282 . 51 push ecx ; /pStartupinfo
00402283 . FF15 5C734100 call dword ptr [<&KERNEL32.GetStartupInfoA>] ; \GetStartupInfoA
00402289 . 3BF4 cmp esi, esp
0040228B . E8 44F8FFFF call <jmp.&MSVCRTD._chkesp>
00402290 . 837D C8 00 cmp dword ptr [ebp-38], 0 ; 以下是对返回的STARTUPINFO结构一些成员的值进行检查
00402294 . 75 31 jnz short 004022C7 ; 若发现调试器则调用ExitProcess退出,可改为jmp short 004022D8爆破之
00402296 . 837D CC 00 cmp dword ptr [ebp-34], 0
0040229A . 75 2B jnz short 004022C7
0040229C . 837D D8 00 cmp dword ptr [ebp-28], 0
004022A0 . 75 25 jnz short 004022C7
004022A2 . 837D DC 00 cmp dword ptr [ebp-24], 0
004022A6 . 75 1F jnz short 004022C7
004022A8 . 837D E0 00 cmp dword ptr [ebp-20], 0
004022AC . 75 19 jnz short 004022C7
004022AE . 837D D0 00 cmp dword ptr [ebp-30], 0
004022B2 . 75 13 jnz short 004022C7
004022B4 . 837D D4 00 cmp dword ptr [ebp-2C], 0
004022B8 . 75 0D jnz short 004022C7
004022BA . 8B55 E4 mov edx, dword ptr [ebp-1C]
004022BD . 81E2 80000000 and edx, 80
004022C3 . 85D2 test edx, edx
004022C5 . 74 11 je short 004022D8 ; 若没有发现被调试,跳过,继续执行
004022C7 > 8BF4 mov esi, esp ; 否则调用ExitProcess退出
004022C9 . 6A 00 push 0 ; /ExitCode = 0
004022CB . FF15 60734100 call dword ptr [<&KERNEL32.ExitProcess>] ; \ExitProcess
004022D1 . 3BF4 cmp esi, esp
004022D3 . E8 FCF7FFFF call <jmp.&MSVCRTD._chkesp>
004022D8 > B8 01000000 mov eax, 1 ; 通过反调试的入口地址
004022DD . 5F pop edi
004022DE . 5E pop esi
004022DF . 5B pop ebx
004022E0 . 81C4 88000000 add esp, 88
004022E6 . 3BEC cmp ebp, esp
004022E8 . E8 E7F7FFFF call <jmp.&MSVCRTD._chkesp>
004022ED . 8BE5 mov esp, ebp
004022EF . 5D pop ebp
004022F0 . C3 retn
由上面的代码可以看出,它的反调试主要是调用GetStartupInfoA函数,根据返回的STARTUPINFO结构,来判断程序是否被调试。(关于GetStartupInfoA和STARTUPINFO结构可参考MSDN)可将00402294处改为jmp short 004022D8,之后便可进行动态跟踪了。
上面的对反调试的破解是后来才发现的,身为菜鸟的我起初是偶然发现当用户名长度不足5位时有个错误提示,于是便从搜索提示字符串"用户名长度必须大于4!"入手,来到注册算法部分,用IDA进行静态反汇编的。以下是注册算法:
首先是函数的初始化
00401EA0 /> \55 push ebp
00401EA1 |. 8BEC mov ebp, esp
00401EA3 |. 81EC D4000000 sub esp, 0D4
00401EA9 |. 53 push ebx
00401EAA |. 56 push esi
00401EAB |. 57 push edi
00401EAC |. 51 push ecx
00401EAD |. 8DBD 2CFFFFFF lea edi, dword ptr [ebp-D4]
00401EB3 |. B9 35000000 mov ecx, 35
00401EB8 |. B8 CCCCCCCC mov eax, CCCCCCCC
00401EBD |. F3:AB rep stos dword ptr es:[edi]
00401EBF |. 59 pop ecx
00401EC0 |. 894D FC mov dword ptr [ebp-4], ecx
00401EC3 |. 6A 01 push 1
00401EC5 |. 8B4D FC mov ecx, dword ptr [ebp-4]
00401EC8 |. E8 43F2FFFF call <jmp.&MFC42D.#5056_CWnd::UpdateData>
一些局部变量字串的初始化
00401ECD |. C745 9C 01000>mov dword ptr [ebp-64], 1 ; 初始化[ebp-64]为1,待用
00401ED4 |. A1 7C514100 mov eax, dword ptr [41517C] ; "bxm"
00401ED9 |. 8945 98 mov dword ptr [ebp-68], eax ; 将校验字串放在[ebp-68]之后
00401EDC |. 8B0D 64524100 mov ecx, dword ptr [415264] ; "恭喜你"
00401EE2 |. 894D 90 mov dword ptr [ebp-70], ecx ; 与下面四句配合,将字串"恭喜你"放在[ebp-70]之后
00401EE5 |. 66:8B15 68524>mov dx, word ptr [415268]
00401EEC |. 66:8955 94 mov word ptr [ebp-6C], dx
00401EF0 |. A0 6A524100 mov al, byte ptr [41526A]
00401EF5 |. 8845 96 mov byte ptr [ebp-6A], al
00401EF8 |. B9 05000000 mov ecx, 5
00401EFD |. BE 0C534100 mov esi, 0041530C ; "用户名长度必须大于4!"
00401F02 |. 8DBD 78FFFFFF lea edi, dword ptr [ebp-88]
00401F08 |. F3:A5 rep movs dword ptr es:[edi], dword ptr [esi] ; 将"用户名长度必须大于4!"放在[ebp-88]之后
00401F0A |. 66:A5 movs word ptr es:[edi], word ptr [esi]
00401F0C |. 8B0D 58504100 mov ecx, dword ptr [415058] ; 字符串"真的很厉害!"
00401F12 |. 898D 6CFFFFFF mov dword ptr [ebp-94], ecx ; 与下面四句配合,将"真的很厉害!"放在[ebp-94]之后
00401F18 |. 8B15 5C504100 mov edx, dword ptr [41505C]
00401F1E |. 8995 70FFFFFF mov dword ptr [ebp-90], edx
00401F24 |. A1 60504100 mov eax, dword ptr [415060]
00401F29 |. 8985 74FFFFFF mov dword ptr [ebp-8C], eax
取用户名,并判断长度是否大于4,不满足则弹出出错提示框
00401F2F |. 8B4D FC mov ecx, dword ptr [ebp-4]
00401F32 |. 83C1 60 add ecx, 60
00401F35 |. E8 DCF1FFFF call <jmp.&MFC42D.#880_CString::operator char const *> ; eax返回用户名地址
00401F3A |. 50 push eax ; /src
00401F3B |. 8D4D E4 lea ecx, dword ptr [ebp-1C] ; |
00401F3E |. 51 push ecx ; |dest
00401F3F |. E8 90F1FFFF call <jmp.&MSVCRTD.strcpy> ; \strcpy
00401F44 |. 83C4 08 add esp, 8 ; 取用户名放在[ebp-1C]之后
00401F47 |. 8B4D FC mov ecx, dword ptr [ebp-4]
00401F4A |. 83C1 60 add ecx, 60
00401F4D |. E8 12F2FFFF call <jmp.&MFC42D.#2640_CString::GetLength> ; 求用户名长度
00401F52 |. 83F8 05 cmp eax, 5 ; 检查用户名长度是否大于4
00401F55 |. 7D 18 jge short 00401F6F ; 满足跳过
00401F57 |. 6A 00 push 0 ; 否则......
00401F59 |. 6A 00 push 0
00401F5B |. 8D95 78FFFFFF lea edx, dword ptr [ebp-88] ; "用户名长度必须大于4!"
00401F61 |. 52 push edx
00401F62 |. 8B4D FC mov ecx, dword ptr [ebp-4]
00401F65 |. E8 00F2FFFF call <jmp.&MFC42D.#3517_CWnd::MessageBoxA> ; 弹出出错提示框
00401F6A |. E9 F8010000 jmp 00402167 ; 玩完
接着将用户名字串各字符的ASCII码进行相乘,乘积放[ebp-64],[ebp-64]在前面已初始化为1。
00401F6F |> 8B4D FC mov ecx, dword ptr [ebp-4]
00401F72 |. 83C1 60 add ecx, 60
00401F75 |. E8 EAF1FFFF call <jmp.&MFC42D.#2640_CString::GetLength> ; 求用户名长度
00401F7A |. 8945 A8 mov dword ptr [ebp-58], eax ; 放入[ebp-58]
00401F7D |. 8B45 A8 mov eax, dword ptr [ebp-58]
00401F80 |. 8945 AC mov dword ptr [ebp-54], eax ; 用户名长度放入循环变量[ebp-54]
00401F83 |. EB 09 jmp short 00401F8E ; 跳入for循环体
00401F85 |> 8B4D AC /mov ecx, dword ptr [ebp-54]
00401F88 |. 83E9 01 |sub ecx, 1
00401F8B |. 894D AC |mov dword ptr [ebp-54], ecx ; 循环变量[ebp-54]自减一
00401F8E |> 837D AC 01 cmp dword ptr [ebp-54], 1 ; 若循环变量[ebp-54]大于1,则继续循环
00401F92 |. 7E 13 |jle short 00401FA7 ; 否则跳出
00401F94 |. 8B55 AC |mov edx, dword ptr [ebp-54]
00401F97 |. 0FBE4415 E4 |movsx eax, byte ptr [ebp+edx-1C] ; 取用户名第[ebp-54]个(从0开始)字符放入eax
00401F9C |. 8B4D 9C |mov ecx, dword ptr [ebp-64]
00401F9F |. 0FAFC8 |imul ecx, eax
00401FA2 |. 894D 9C |mov dword ptr [ebp-64], ecx ; [ebp-64] *= eax; [ebp-64]放本循环对用户名的运算结果
00401FA5 |.^ EB DE \jmp short 00401F85
这里程序可能有一个相当严重的BUG,在取串中各字符时,是从字串末尾的结束符'\0'开始依次往前取的,而'\0'的ASCII码是0,这样无论如何,最终乘积的结果[ebp-64]总是为0。该部分对应C源程序大致如下:
ebp_58 = strlen(sName);
for (ebp_54=ebp_58; ebp_54>1; ebp_54--)
ebp_64 *= (DWORD)sName[ebp_54];
在此推测作者的原意大概是:
ebp_58 = strlen(sName);
for (ebp_54=ebp_58-1; ebp_54>0; ebp_54--)
ebp_64 *= (DWORD)sName[ebp_54];
呵呵,这只是个人推测,或许是这个CrackMe被修改了吧 然后是将上面求得的乘积ebp_64转化为十进制数字符串(ebp-4C)。
00401FA7 |> C745 AC 00000>mov dword ptr [ebp-54], 0 ; 一个do-while循环的开始,索引变量[ebp-54]初始化为0
00401FAE |> 8B45 9C /mov eax, dword ptr [ebp-64] ; 将[ebp-64]放入eax
00401FB1 |. 99 |cdq ; 扩展为EDX:EAX
00401FB2 |. B9 0A000000 |mov ecx, 0A
00401FB7 |. F7F9 |idiv ecx ; EDX:EAX对0A求模
00401FB9 |. 83C2 30 |add edx, 30 ; 再将结果加上30h,转化为数字ASSII码
00401FBC |. 8B45 AC |mov eax, dword ptr [ebp-54]
00401FBF |. 885405 B4 |mov byte ptr [ebp+eax-4C], dl ; 再将生成的数字放入[ebp-4C+[ebp-54]]; ebp-4C显然是字串首地址
00401FC3 |. 8B45 9C |mov eax, dword ptr [ebp-64] ; 将[ebp-64]放入eax
00401FC6 |. 99 |cdq ; 扩展为EDX:EAX
00401FC7 |. B9 0A000000 |mov ecx, 0A
00401FCC |. F7F9 |idiv ecx ; EDX:EAX对0A求商
00401FCE |. 8945 9C |mov dword ptr [ebp-64], eax ; 将商放回[ebp-64]
00401FD1 |. 8B55 AC |mov edx, dword ptr [ebp-54]
00401FD4 |. 83C2 01 |add edx, 1
00401FD7 |. 8955 AC |mov dword ptr [ebp-54], edx ; 索引变量[ebp-54]加一
00401FDA |. 837D 9C 00 |cmp dword ptr [ebp-64], 0
00401FDE |.^ 75 CE \jnz short 00401FAE ; 循环直到[ebp-64]为0
00401FE0 |. 8B45 AC mov eax, dword ptr [ebp-54]
00401FE3 |. C64405 B4 00 mov byte ptr [ebp+eax-4C], 0 ; 在字串(ebp-4C)末尾加'\0',标记字串结束
00401FE8 |. 8D4D B4 lea ecx, dword ptr [ebp-4C]
00401FEB |. 51 push ecx ; /s
00401FEC |. E8 E9F0FFFF call <jmp.&MSVCRTD.strlen> ; \strlen
00401FF1 |. 83C4 04 add esp, 4 ; 求生成的字串(ebp-4C)的长度
00401FF4 |. 8945 A4 mov dword ptr [ebp-5C], eax ; 放入[ebp-5C]
该部分C源程序大致描述为:
ebp_54 = 0;
do {
ebp_4C[ebp_54] = ebp_64 % 0Ah + 30h;
ebp_64 /= 0Ah;
ebp_54++;
} while (ebp_64);
ebp_4C[ebp_54] = '\0';
ebp_5C = strlen(ebp_4C);
由于ebp_64总是为0,所以总有ebp_4C="0" 再然后,是对用户名字串进行处理,将用户名各字符分别加上循环变量(也即该字符在串中的索引值)的2倍,所得结果再放回串中。
00401FF7 |. C745 AC 00000>mov dword ptr [ebp-54], 0 ; 循环变量[ebp-54]初始化为0
00401FFE |. EB 09 jmp short 00402009 ; 又一个for循环的开始,跳入循环体
00402000 |> 8B55 AC /mov edx, dword ptr [ebp-54]
00402003 |. 83C2 01 |add edx, 1
00402006 |. 8955 AC |mov dword ptr [ebp-54], edx ; 循环变量[ebp-54]自增一
00402009 |> 8B45 AC mov eax, dword ptr [ebp-54]
0040200C |. 3B45 A8 |cmp eax, dword ptr [ebp-58] ; 比较[ebp-54]与[ebp-58]的大小
0040200F |. 7D 31 |jge short 00402042 ; 若[ebp-54]>=[ebp-58]则跳出循环,否则继续
00402011 |. 8B4D AC |mov ecx, dword ptr [ebp-54]
00402014 |. 0FBE540D E4 |movsx edx, byte ptr [ebp+ecx-1C] ; 取用户名第[ebp-54]个(从0开始)字符放入edx
00402019 |. 8B45 AC |mov eax, dword ptr [ebp-54]
0040201C |. 8D0C42 |lea ecx, dword ptr [edx+eax*2] ; ecx = edx + [ebp-54]*2
0040201F |. 8B55 AC |mov edx, dword ptr [ebp-54]
00402022 |. 884C15 E4 |mov byte ptr [ebp+edx-1C], cl ; 再将运算结果的低8位放回用户名第[ebp-54]个(从0开始)字符位置
00402026 |. 8B45 AC |mov eax, dword ptr [ebp-54]
00402029 |. 0FBE4C05 E4 |movsx ecx, byte ptr [ebp+eax-1C] ; 再取出
0040202E |. 83F9 7A |cmp ecx, 7A ; 将该字符与'z'比较
00402031 |. 7E 0D |jle short 00402040 ; 若小于等于'z'则跳过
00402033 |. 8B55 AC |mov edx, dword ptr [ebp-54] ; 否则......
00402036 |. 83C2 61 |add edx, 61 ; 将该字符加上'a'
00402039 |. 8B45 AC |mov eax, dword ptr [ebp-54]
0040203C |. 885405 E4 |mov byte ptr [ebp+eax-1C], dl ; 再放回
00402040 |>^ EB BE \jmp short 00402000
用C源程序描述大概是:
for (ebp_54=0; ebp_54<ebp_58; ebp_54++) {
sName[ebp_54] += var_54 * 2;
if (sName[ebp_54] > 'z')
sName[ebp_54] = ebp_54 + 'a';
} 接下来,程序开始构造另一个字串(ebp-3C),在此之前,它先比较处理后的用户名字串和(ebp-4C)字串的长度大小,[ebp-60]取较小的长度,[ebp-50]指向较小者的串尾(即'\0'处)。
00402042 |> 8B4D A8 mov ecx, dword ptr [ebp-58] ; 用户名长度放ecx
00402045 |. 3B4D A4 cmp ecx, dword ptr [ebp-5C] ; 与(ebp-4C)字串长度比较
00402048 |. 7D 12 jge short 0040205C ; 若用户名较长则跳
0040204A |. 8B55 A8 mov edx, dword ptr [ebp-58]
0040204D |. 8955 A0 mov dword ptr [ebp-60], edx ; [ebp-60] = [ebp-58]; 用户名长度
00402050 |. 8B45 A0 mov eax, dword ptr [ebp-60]
00402053 |. 8D4C05 E4 lea ecx, dword ptr [ebp+eax-1C]
00402057 |. 894D B0 mov dword ptr [ebp-50], ecx ; [ebp-50] = [ebp-1C+[ebp-60]]; 即指向用户名串尾
0040205A |. EB 10 jmp short 0040206C
0040205C |> 8B55 A4 mov edx, dword ptr [ebp-5C]
0040205F |. 8955 A0 mov dword ptr [ebp-60], edx ; [ebp-60] = [ebp-5C]; (ebp-4C)串长
00402062 |. 8B45 A0 mov eax, dword ptr [ebp-60]
00402065 |. 8D4C05 B4 lea ecx, dword ptr [ebp+eax-4C]
00402069 |. 894D B0 mov dword ptr [ebp-50], ecx ; [ebp-50] = [ebp-4C+[ebp-60]]; 即指向(ebp-4C)串尾
字串(ebp-3C)的构造就是将处理后的用户名字串和(ebp-4C)字串轮流从头开始取字符添加到字串(ebp-3C)之后,直到其中一个字串取完字符。例如处理后的用户名为"abcd",字串(ebp-4C)为"123456"。则字串(ebp-3C)最后为"a1b2c3d4"。
0040206C |> C745 AC 00000>mov dword ptr [ebp-54], 0 ; 循环变量[ebp-54]初始化为0
00402073 |. EB 09 jmp short 0040207E ; 又一个for循环的开始,跳入循环体
00402075 |> 8B55 AC /mov edx, dword ptr [ebp-54]
00402078 |. 83C2 01 |add edx, 1
0040207B |. 8955 AC |mov dword ptr [ebp-54], edx ; 循环变量[ebp-54]自增一
0040207E |> 8B45 AC mov eax, dword ptr [ebp-54]
00402081 |. 3B45 A0 |cmp eax, dword ptr [ebp-60]
00402084 |. 7D 1E |jge short 004020A4 ; 若[ebp-54]>=[ebp-60]则跳出循环
00402086 |. 8B4D AC |mov ecx, dword ptr [ebp-54]
00402089 |. 8B55 AC |mov edx, dword ptr [ebp-54]
0040208C |. 8A4415 B4 |mov al, byte ptr [ebp+edx-4C] ; 将字串(ebp-4C)第[ebp-54]个(从0开始)字符
00402090 |. 88444D C4 |mov byte ptr [ebp+ecx*2-3C], al ; 放入字串(ebp-3C)第[ebp-54]*2个(从0开始)字符位置
00402094 |. 8B4D AC |mov ecx, dword ptr [ebp-54]
00402097 |. 8B55 AC |mov edx, dword ptr [ebp-54] ; 将用户名第[ebp-54]个(从0开始)字符
0040209A |. 8A4415 E4 |mov al, byte ptr [ebp+edx-1C]
0040209E |. 88444D C5 |mov byte ptr [ebp+ecx*2-3B], al ; 放入字串(ebp-3C)第[ebp-54]*2+1个(从0开始)字符位置
004020A2 |.^ EB D1 \jmp short 00402075
004020A4 |> 8B4D AC mov ecx, dword ptr [ebp-54]
004020A7 |. C6444D C4 00 mov byte ptr [ebp+ecx*2-3C], 0 ; 在新生成的字串(ebp-3C)串尾加'\0'标记结束
下面的这部分是多余的,前面[ebp-50]指向了较短字串的串尾,所以strcat并未改变字串(ebp-3C)。
004020AC |. 8B55 B0 mov edx, dword ptr [ebp-50] ; [ebp-50]指向了用户名和字串(ebp-4C)较短串的串尾
004020AF |. 52 push edx ; /src
004020B0 |. 8D45 C4 lea eax, dword ptr [ebp-3C] ; |
004020B3 |. 50 push eax ; |dest
004020B4 |. E8 51F0FFFF call <jmp.&MSVCRTD.strcat> ; \strcat
004020B9 |. 83C4 08 add esp, 8 ; 将[ebp-58]所指字串接到字串(ebp-3C)之后
004020BC |. 8D4D C4 lea ecx, dword ptr [ebp-3C]
004020BF |. 51 push ecx ; /s
004020C0 |. E8 15F0FFFF call <jmp.&MSVCRTD.strlen> ; \strlen
004020C5 |. 83C4 04 add esp, 4 ; 求连接后字串(ebp-3C)的长度
004020C8 |. 8BF0 mov esi, eax ; 暂放在esi
以上构造字串(ebp-3C)的代码可用C语言大致描述为:
if (ebp_58 < ebp_5C) {
ebp_60 = ebp_58;
ebp_50 = sName + ebp_60;
} else {
ebp_60 = ebp_5C;
ebp_50 = ebp_4C + ebp_60;
}
for (ebp_54=0; ebp_54<ebp_60; ebp_54++) {
ebp_3C[ebp_54*2] = ebp_4C[ebp_54];
ebp_3C[ebp_54*2+1] = sName[ebp_54];
}
ebp_3C[ebp_54] = '\0';
strcat(ebp_3C, ebp_50);
这里可能有一处BUG,就是ebp_50的赋值,ebp_50似乎应指向较长字串的剩余部分,而非较短字串的串尾,这样后面的strcat才有意义。或许这又是因为被修改了吧
由于之前必有ebp_4C="0",故(ebp-3C)只有两个字符,"0X",其中X是用户名的首字母。 跟踪到这里,输入的假码一直没出场,看看后面的代码也不多了,正纳闷着,后面几句终于冒出来了。
004020CA |. 8B4D FC mov ecx, dword ptr [ebp-4]
004020CD |. 83C1 64 add ecx, 64
004020D0 |. E8 41F0FFFF call <jmp.&MFC42D.#880_CString::operator char const *> ; eax返回输入的序列号(以下称假码)的地址
004020D5 |. 50 push eax ; /s
004020D6 |. E8 FFEFFFFF call <jmp.&MSVCRTD.strlen> ; \strlen
004020DB |. 83C4 04 add esp, 4 ; 求假码长度
004020DE |. 3BF0 cmp esi, eax ; 比较字串(ebp-3C)与假码长度
004020E0 |. 0F85 81000000 jnz 00402167 ; 若不相等玩完
检查字串(ebp-3C)与假码长度是否相等之后,进行如下校验:
首先从字串(ebp-3C)和假码中依次各取一个字符,两者进行异或,将结果暂存至esi。
然后从校验字串"bxm"中依次取一个字符,暂存至edx。若'm'取完则下次再从'b'开始。
接着比较esi与edx是否相等,若相等则继续循环,比较下一位,直至串尾,否则跳出循环。
004020E6 |. C745 AC 00000>mov dword ptr [ebp-54], 0 ; 循环变量[ebp-54]初始化为0
004020ED |. EB 09 jmp short 004020F8 ; 又一个for循环的开始,跳入循环体
004020EF |> 8B55 AC /mov edx, dword ptr [ebp-54]
004020F2 |. 83C2 01 |add edx, 1
004020F5 |. 8955 AC |mov dword ptr [ebp-54], edx ; 循环变量[ebp-54]自增一
004020F8 |> 8D45 C4 lea eax, dword ptr [ebp-3C]
004020FB |. 50 |push eax ; /s
004020FC |. E8 D9EFFFFF |call <jmp.&MSVCRTD.strlen> ; \strlen
00402101 |. 83C4 04 |add esp, 4 ; 求字串(ebp-3C)的长度
00402104 |. 3945 AC |cmp dword ptr [ebp-54], eax
00402107 |. 73 34 |jnb short 0040213D ; 若[ebp-54]不小于这个长度则跳出循环
00402109 |. 8B4D AC |mov ecx, dword ptr [ebp-54]
0040210C |. 0FBE740D C4 |movsx esi, byte ptr [ebp+ecx-3C] ; 将字串(ebp-3C)第[ebp-54]个(从0开始)字符放入esi
00402111 |. 8B55 AC |mov edx, dword ptr [ebp-54]
00402114 |. 52 |push edx
00402115 |. 8B4D FC |mov ecx, dword ptr [ebp-4]
00402118 |. 83C1 64 |add ecx, 64
0040211B |. E8 50F0FFFF |call <jmp.&MFC42D.#850_CString::operator[]> ; 取假码第[ebp-54]个(从0开始)字符
00402120 |. 0FBEC0 |movsx eax, al ; 放入eax
00402123 |. 33F0 |xor esi, eax ; esi ^= eax
00402125 |. 8B45 AC |mov eax, dword ptr [ebp-54]
00402128 |. 99 |cdq
00402129 |. B9 03000000 |mov ecx, 3
0040212E |. F7F9 |idiv ecx ; edx = [ebp-54] % 3
00402130 |. 0FBE5415 98 |movsx edx, byte ptr [ebp+edx-68] ; 将校验字串"bxm"第edx个(从0开始)字符放入edx
00402135 |. 3BF2 |cmp esi, edx ; 比较esi与edx
00402137 |. 74 02 |je short 0040213B ; 若相等,继续循环
00402139 |. EB 02 |jmp short 0040213D ; 否则,跳出循环
0040213B |>^ EB B2 \jmp short 004020EF
下面求字串(ebp-3C)长度,若字串(ebp-3C)长度不为0则注册成功。
0040213D |> 8D45 C4 lea eax, dword ptr [ebp-3C]
00402140 |. 50 push eax ; /s
00402141 |. E8 94EFFFFF call <jmp.&MSVCRTD.strlen> ; \strlen
00402146 |. 83C4 04 add esp, 4 ; 求字串(ebp-3C)的长度
00402149 |. 8945 AC mov dword ptr [ebp-54], eax
0040214C |. 837D AC 00 cmp dword ptr [ebp-54], 0
00402150 |. 74 15 je short 00402167 ; 若长度为0,跳走玩完
00402152 |. 6A 00 push 0 ; 终于到了
00402154 |. 8D4D 90 lea ecx, dword ptr [ebp-70] ; "恭喜你"
00402157 |. 51 push ecx
00402158 |. 8D95 6CFFFFFF lea edx, dword ptr [ebp-94] ; "真的很厉害!"
0040215E |. 52 push edx
0040215F |. 8B4D FC mov ecx, dword ptr [ebp-4]
00402162 |. E8 03F0FFFF call <jmp.&MFC42D.#3517_CWnd::MessageBoxA> ; 弹出注册成功对话框
以上部分用C语言大致描述为:
if (strlen(ebp_3C) == strlen(sWrongSerial)) {
for (ebp_54=0; ebp_54<strlen(ebp_3C); ebp_54++)
if ((ebp_3C[ebp_54] ^ sWrongSerial[ebp_54]) != ebp_68[ebp_54 % 3])
break;
if (strlen(ebp_3C)) {
成功了
}
}
上面通过求字串(ebp-3C)长度来判断检验结果很令人费解,按理应是比较循环变量[ebp-54]与字串(ebp-3C)的长度是否相等,即检查循环是否正常退出。下面这两句代码多少说明了一些问题:
00402149 |. 8945 AC mov dword ptr [ebp-54], eax
0040214C |. 837D AC 00 cmp dword ptr [ebp-54], 0
在此作者可能在写程序时犯了一个错误,将比较语句:
if (ebp_54 == strlen(ebp_3C)) {注册成功}
写成了:
if (ebp_54 = strlen(ebp_3C)) {注册成功}
于是造成了前一部分的校验形同虚设,只要字串(ebp-3C)不为空,即可注册成功。整个注册校验唯一需要满足条件的就是以下这两句判断:
004020DE |. 3BF0 cmp esi, eax ; 比较字串(ebp-3C)与假码长度
004020E0 |. 0F85 81000000 jnz 00402167 ; 若不相等玩完
结合前面分析的可知,字串(ebp-3C)总是只有两个字母,所以注册码只要是任意的两个字符就可以了。 最后是函数的收尾工作......
00402167 |> 6A 00 push 0
00402169 |. 8B4D FC mov ecx, dword ptr [ebp-4]
0040216C |. E8 9FEFFFFF call <jmp.&MFC42D.#5056_CWnd::UpdateData>
00402171 |. 5F pop edi
00402172 |. 5E pop esi
00402173 |. 5B pop ebx
00402174 |. 81C4 D4000000 add esp, 0D4
0040217A |. 3BEC cmp ebp, esp
0040217C |. E8 53F9FFFF call <jmp.&MSVCRTD._chkesp>
00402181 |. 8BE5 mov esp, ebp
00402183 |. 5D pop ebp
00402184 \. C3 retn 这个CrackMe的算法对于菜鸟我来说分析起来还是颇费一番工夫的,在算法逆向过程中学到了不少的东西。可能是被修改过的缘故,程序存在一些BUG,而CrackMe的真实来源又找不到,实在可惜。
另外,回过头来看,与其说这是一篇破文,不如说是一篇逆文,贴了这么长的代码主要是为了说明对原算法的逆向与分析,破解倒成为次要的了。:-) 初次写这种文章,把握得不是很好,也没什么技术含量,还请高手们不要见笑了。就当是我发上来赚个发帖量吧,不要嫌我占了地方哟!
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
上传的附件: