【详细过程】
这是一个纯粹娱乐的 crackme,是riijj兄台作为圣诞礼物奉献给看雪论坛的全体同仁的。当初水平不够,看到浮点指令头大,连爆破都没能做到。想想心有不甘,根据warshon兄弟提供的一些线索,现对其注册过程作简单分析。
一、寻找关键代码
用PEiD检查,程序没有加壳,是Microsoft Visual C++ 6.0程序。用IDA打开分析,然后创建MAP文件,再用OD载入,导入MAP文件。
根据riijj的介绍,我们知道这个Crackme是通过启动时检测 key file来注册的。下断点 bp CreateFileA 然后F9运行。断下来之后我们看一下堆栈:
0012FD28 00420957 /CALL 到 CreateFileA 来自 riijjcm1.00420951
0012FD2C 0042A0B0 |FileName = "dinner.bin"
0012FD30 80000000 |Access = GENERIC_READ
0012FD34 00000003 |ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
0012FD38 0012FD54 |pSecurity = 0012FD54
0012FD3C 00000003 |Mode = OPEN_EXISTING
0012FD40 00000080 |Attributes = NORMAL
0012FD44 00000000 hTemplateFile = NULL
0012FD48 00424552 riijjcm1.00424552
从这里我们知道了key file文件名是dinner.bin。我们新建一个空白文本文件随便输入一些文字并命名为dinner.bin之后复制到Crackme所在的文件夹。
Ctrl+F2重新运行程序,下断点 bp ReadFile 然后F9运行。断下来之后的堆栈:
0012FD20 0041F9C5 /CALL 到 ReadFile 来自 riijjcm1.0041F9BF
0012FD24 00000080 |hFile = 00000080 (window)
0012FD28 008D33C0 |Buffer = 008D33C0
0012FD2C 00001000 |BytesToRead = 1000 (4096.)
0012FD30 0012FD44 |pBytesRead = 0012FD44
0012FD34 00000000 pOverlapped = NULL
从这里可以看出程序从key file中读取数据,并将读取到的数据保存在内存某一块地址当中。不断地Ctrl+F9返回之后我们来到了这里。
004011C1 >|> 56 push esi
004011C2 |. 55 push ebp
004011C3 |. 8D4C24 40 lea ecx, dword ptr [esp+40]
004011C7 |. E8 E4020000 call ; 我们就是从这里返回的
004011CC |. 8D4C24 40 lea ecx, dword ptr [esp+40]
004011D0 |. E8 8B1A0000 call ; closefile
004011D5 |. 85C0 test eax, eax
004011D7 |. 75 13 jnz short
004011D9 |. 50 push eax
004011DA |. 8B4424 3C mov eax, dword ptr [esp+3C]
004011DE |. 6A 02 push 2
004011E0 |. 8B48 04 mov ecx, dword ptr [eax+4]
004011E3 |. 8D4C0C 40 lea ecx, dword ptr [esp+ecx+40]
004011E7 |. E8 54020000 call
004011EC >|> B9 0A000000 mov ecx, 0A
004011F1 |. 8BF5 mov esi, ebp ; 这里的ebp指向了刚刚我们从key file中读取到的数据
004011F3 |. 8DBC24 C80000>lea edi, dword ptr [esp+C8] ; buffer
004011FA |. F3:A5 rep movs dword ptr es:[edi], dword p>; 复制40个读取到的字节到buffer
这里往下出现了不少浮点指令,关键代码段就是下面了。这里我们可以看出,程序真正用到的也只是key file中的前40个字节。
二、算法简析
004011EC >|> B9 0A000000 mov ecx, 0A
004011F1 |. 8BF5 mov esi, ebp ; 这里的ebp指向了刚刚我们从key file中读取到的数据
004011F3 |. 8DBC24 C80000>lea edi, dword ptr [esp+C8] ; buffer
004011FA |. F3:A5 rep movs dword ptr es:[edi], dword p>; 复制40个读取到的字节到buffer
004011FC |. DD05 D8414200 fld qword ptr [] ; 0.0
00401202 |. DD5424 18 fst qword ptr [esp+18] ; n2 = 0
00401206 |. DD05 D8414200 fld qword ptr [] ; 0.0
0040120C |. DD5424 10 fst qword ptr [esp+10] ; n1 = 0
00401210 |. 0FBF9424 C800>movsx edx, word ptr [esp+C8] ; 取buffer前2个字节(len)
00401218 |. 8BCA mov ecx, edx
0040121A |. 8DB424 CA0000>lea esi, dword ptr [esp+CA]
00401221 |. 8BC1 mov eax, ecx
00401223 |. 8D7C24 20 lea edi, dword ptr [esp+20]
00401227 |. C1E9 02 shr ecx, 2
0040122A |. F3:A5 rep movs dword ptr es:[edi], dword p>
0040122C |. 8BC8 mov ecx, eax
0040122E |. 33C0 xor eax, eax ; i=0
00401230 |. 83E1 03 and ecx, 3
00401233 |. 85D2 test edx, edx
00401235 |. F3:A4 rep movs byte ptr es:[edi], byte ptr>
00401237 |. 0F8E 2B010000 jle
这一段代码先初始化两个浮点数n1和n2,使其全部为0,然后再取buffer当中的前两位(len),其实buffer中的前两位代表长度,也就是用户名的长度。再将buffer当中从第3位起,长度为len的字节复制到内存当中的另一个地方待用。
其实可以这么看,buffer前2位代表用户名长度len,从第3位开始长len的数据代表的就是用户名name。
0040123D >|> /0FBE4C04 20 /movsx ecx, byte ptr [esp+eax+20] ; name(i)
00401242 |. |894C24 0C |mov dword ptr [esp+C], ecx
00401246 |. |40 |inc eax ; i++
00401247 |. |DB4424 0C |fild dword ptr [esp+C]
0040124B |. |3BC2 |cmp eax, edx
0040124D |. |D9C0 |fld st
0040124F |. |DEC3 |faddp st(3), st
00401251 |. |D9CA |fxch st(2)
00401253 |. |DC0D D0414200 |fmul qword ptr [] ; 1.2
00401259 |. |D9CA |fxch st(2)
0040125B |. |DEC1 |faddp st(1), st
0040125D |. |DC0D C8414200 |fmul qword ptr [] ; 1.3
00401263 |.^7C D8 jl short
00401265 |. DD5C24 10 fstp qword ptr [esp+10] ; n1
00401269 |. DD5C24 18 fstp qword ptr [esp+18] ; n2
来源:网络转载 [hr]