【文章标题】: Riijj crackme 10 anniversary 算法简析
【文章作者】: hawking
【作者邮箱】: rich_hawking@hotmail.com
【软件名称】: riijjcm10f2.exe
【软件大小】: 376k
【下载地址】: http://bbs.pediy.com/showthread.php?s=&threadid=36800
【保护方式】: 启动检测 key file
【编写语言】: VC++6
【使用工具】: OD PEiD
【操作平台】: win2k sp4
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
这是一个纯粹娱乐的 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 <sub_4014B0> ; 我们就是从这里返回的
004011CC |. 8D4C24 40 lea ecx , dword ptr [esp +40]
004011D0 |. E8 8B1A0000 call <sub_402C60> ; closefile
004011D5 |. 85C0 test eax , eax
004011D7 |. 75 13 jnz short <loc_4011EC>
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 <sub_401440>
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 [<dbl_4241D8>] ; 0.0
00401202 |. DD5424 18 fst qword ptr [esp +18] ; n2 = 0
00401206 |. DD05 D8414200 fld qword ptr [<dbl_4241D8>] ; 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 <loc_401368>
这一段代码先初始化两个浮点数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 [<dbl_4241D0>] ; 1.2
00401259 |. |D9CA |fxch st (2)
0040125B |. |DEC1 |faddp st (1), st
0040125D |. |DC0D C8414200 |fmul qword ptr [<dbl_4241C8>] ; 1.3
00401263 |.^\7C D8 \jl short <loc_40123D>
00401265 |. DD5C24 10 fstp qword ptr [esp +10] ; n1
00401269 |. DD5C24 18 fstp qword ptr [esp +18] ; n2
这里对用户名作运算,反复取用户名的每一位,分别乘以1.3和1.2并累加至n1和n2 。
0040126D >|> /55 push ebp
0040126E |. |E8 5C810000 call <sub_4093CF> ; heapfree
00401273 |. |DD4424 14 fld qword ptr [esp +14] ; n1
00401277 |. |DC0D C0414200 fmul qword ptr [<dbl_4241C0>] ; 5.0
0040127D |. |DD4424 1C fld qword ptr [esp +1C] ; n2
00401281 |. |DC0D B8414200 fmul qword ptr [<dbl_4241B8>] ; 9.0
00401287 |. |83C4 04 add esp , 4
0040128A |. |DEC1 faddp st (1), st
0040128C |. |DD8424 E80000>fld qword ptr [esp +E8] ; f2 key file 中33至40字节所代表的浮点数
00401293 |. |DC0D B0414200 fmul qword ptr [<dbl_4241B0>] ; 7.0
00401299 |. |DEC1 faddp st (1), st
0040129B |. |DD8424 E00000>fld qword ptr [esp +E0] ; f1 key file 中25至32字节所代表的浮点数
004012A2 |. |DCC0 fadd st , st
004012A4 |. |DEC1 faddp st (1), st
004012A6 |. |DC25 A8414200 fsub qword ptr [<dbl_4241A8>] ; 50.0
004012AC |. |E8 C7460100 call <__ftol> ; 取整
这里对n1 n2 f1 f2作运算( 5 * n1 + 9 * n2 + 7 * f2 + f1 + f1 - 50.0)并将结果取整(result1)。
004012B1 |. 99 cdq
004012B2 |. 33C2 xor eax , edx
004012B4 |. 5E pop esi
004012B5 |. 2BC2 sub eax , edx
004012B7 |. 5D pop ebp
004012B8 |. 894424 04 mov dword ptr [esp +4], eax ; 取result1绝对值
004012BC |. DB4424 04 fild dword ptr [esp +4]
004012C0 |. DC1D A0414200 fcomp qword ptr [<dbl_4241A0>] ; 0.01
004012C6 |. DFE0 fstsw ax ; 将结果与0.01比较
004012C8 |. F6C4 01 test ah , 1 ;
004012CB |. 0F84 A0000000 je <loc_401371> ; 大于则跳 Game Over
这里要求result1的绝对值要小于0.01,由于result1是整数,所以result1只能为0 ,也就是说( 5*n1 + 9*n2 + 7*f2 + f1 + f1 - 50.0 )只能大于-1且小于1 。
004012D1 |. DD4424 08 fld qword ptr [esp +8] ; n1
004012D5 |. DC8424 E00000>fadd qword ptr [esp +E0] ; f2
004012DC |. DC0D 98414200 fmul qword ptr [<dbl_424198>] ; 4.0
004012E2 |. DD4424 10 fld qword ptr [esp +10] ; n2
004012E6 |. DC0D B0414200 fmul qword ptr [<dbl_4241B0>] ; 7.0
004012EC |. DEC1 faddp st (1), st
004012EE |. DD8424 D80000>fld qword ptr [esp +D8] ; f1
004012F5 |. DC0D 90414200 fmul qword ptr [<dbl_424190>] ; 3.0
004012FB |. DEC1 faddp st (1), st
004012FD |. DC25 88414200 fsub qword ptr [<dbl_424188>] ; 40.0
00401303 |. E8 70460100 call <__ftol>
00401308 |. 99 cdq
00401309 |. 33C2 xor eax , edx
0040130B |. 2BC2 sub eax , edx
0040130D |. 894424 04 mov dword ptr [esp +4], eax
00401311 |. DB4424 04 fild dword ptr [esp +4]
00401315 |. DC1D A0414200 fcomp qword ptr [<dbl_4241A0>] ; 0.01
0040131B |. DFE0 fstsw ax
0040131D |. F6C4 01 test ah , 1
00401320 |. 74 4F je short <loc_401371>
同上,要求 -1 < ( n1 + f2 ) * 4 + 7 * n2 + 3 * f1 - 40.0 < 1 不等式成立。
00401322 |. 8D8C24 8C0000>lea ecx , dword ptr [esp +8C] ; 成功的话走这里
00401329 |. C78424 080100>mov dword ptr [esp +108], -1
00401334 |. E8 47020000 call <sub_401580>
00401339 |. 8D8C24 8C0000>lea ecx , dword ptr [esp +8C]
00401340 |. C78424 8C0000>mov dword ptr [esp +8C], offset <off_>
0040134B |. E8 887E0000 call <std ::ios_base::~ios_base(void)>
00401350 |. B0 01 mov al , 1 ; 设标志flag = 1
00401352 |. 5F pop edi
00401353 |. 8B8C24 FC0000>mov ecx , dword ptr [esp +FC]
0040135A |. 64:890D 00000>mov dword ptr fs :[0], ecx
00401361 |. 81C4 08010000 add esp , 108
00401367 |. C3 retn
00401368 >|> DDD8 fstp st
0040136A |. DDD8 fstp st
0040136C |.^ E9 FCFEFFFF jmp <loc_40126D>
00401371 >|> 8D8C24 8C0000>lea ecx , dword ptr [esp +8C] ; 失败的话走这里
00401378 |. C78424 080100>mov dword ptr [esp +108], -1
00401383 |. E8 F8010000 call <sub_401580>
00401388 |. 8D8C24 8C0000>lea ecx , dword ptr [esp +8C]
0040138F |. C78424 8C0000>mov dword ptr [esp +8C], offset <off_>
0040139A |. E8 397E0000 call <std ::ios_base::~ios_base(void)>
0040139F |. EB 23 jmp short <loc_4013C4>
004013A1 >|> 8D8C24 8C0000>lea ecx , dword ptr [esp +8C]
004013A8 |. C78424 080100>mov dword ptr [esp +108], -1
004013B3 |. E8 C8010000 call <sub_401580>
004013B8 |. 8D8C24 8C0000>lea ecx , dword ptr [esp +8C]
004013BF |. E8 6C000000 call <sub_401430>
004013C4 >|> 8B8C24 000100>mov ecx , dword ptr [esp +100]
004013CB |. 32C0 xor al , al ; 设标志flag = 0
004013CD |. 5F pop edi
004013CE |. 64:890D 00000>mov dword ptr fs :[0], ecx
004013D5 |. 81C4 08010000 add esp , 108
004013DB \. C3 retn
最终返回到这里:
00405F2F >|> \8B4C24 08 mov ecx , dword ptr [esp +8] ; loc_405F2F
00405F33 |. 51 push ecx ; /ShowState
00405F34 |. 50 push eax ; |hWnd
00405F35 |. FF15 64414200 call dword ptr [<&USER32.ShowWindow>] ; \ShowWindow
00405F3B |. 8B15 C4E34200 mov edx , dword ptr [<hWnd>]
00405F41 |. 52 push edx ; /hWnd => 001B04AA ('Riijj crackme 10 - anniversary',class='riijj')
00405F42 |. FF15 5C414200 call dword ptr [<&USER32.UpdateWindow>; \UpdateWindow
00405F48 |. E8 33B1FFFF call <sub_401080> ; 关键call
00405F4D |. A2 F8E24200 mov byte ptr [<byte_42E2F8>], al ; [42E2F8]处保存的是标志位,程序后面就是根据这个标志来显示不同的画面
00405F52 |. FF15 BC404200 call dword ptr [<&KERNEL32.GetTickCou>; [GetTickCount
00405F58 |. 50 push eax
00405F59 |. E8 9D030100 call <sub_4162FB>
00405F5E |. 83C4 04 add esp , 4
00405F61 |. E8 7A000000 call <sub_405FE0>
00405F66 |. B8 01000000 mov eax , 1
00405F6B \. C3 retn
三、注册机(C#)
算法其实不难,主要是从key file中读取到的用户名生成两个浮点数n1 n2 ,并且将key file文件中特定的两个位置的数据看作两个浮点数f1 f2
要求下面两个不等式成立,则注册成功。
-1 < 5 * n1 + 9 * n2 + 7 * f2 + f1 + f1 - 50.0 < 1
-1 < ( n1 + f2 ) * 4 + 7 * n2 + 3 * f1 - 40.0 < 1
最后我们再来看看一个可用的key file中包含信息
07 00 68 61 77 6B 69 6E 67 00 00 00 00 00 00 00 .hawking.......
00 00 00 00 00 00 00 00 F1 12 28 3F 00 6C A8 C0 ........?(?.l?
37 BE E8 12 6E F9 A6 C0 7捐n?
上面的数据结构中,前2位代表用户名长度,后面22位代表用户名(也就是说用户名最大有效长度为22位),再往后就是2个浮点数了。
struct RegisteInfo
{
ushort Length ;
char UserName[22] ;
double Fnum1 ;
double Fnum2 ;
};
注册机见附件。
--------------------------------------------------------------------------------
【版权声明】: 感谢看雪论坛、一蓑烟雨, 转载请注明作者并保持文章的完整, 谢谢!
2007年03月14日 18:53:17
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: