这个Crackme的提交时间比较早,大约是3月份的,我翻看了本版3月前后的文章,没找到关于它的破解。
也有可能是我粗心,如果有人破解过,希望你告诉我一声。
下载地址:http://www.crackmes.de/users/moofy/moofys_keyme_1/
使用工具:OllyDbg
这个程序加了点壳,由于脱壳过程比较简单,并且也不是重点,这里只简单地提一下:壳的
入口点在地址44C560处,往下翻页很快就能找到“Magic jump”位于44C6C3处(貌似UPX的壳,不
过没有PEID过),只要在这一行上按F4再按F7,就能定位到OEP了。
OEP在地址401220处,往下看在401261处就出现了“msvcrt.atexit”字样,说明这已经是打
算退出程序了,因此程序的main函数应该就在call 00401100里边。跟进第一个call 00401100,
看到4011E2处的call 0040147E以后已经出现了“_cexit”和“ExitProcess”字样,如果程序有
关键内容,那也基本上只能是在这个call里了,跟进这个call,果然已经定位到了算法的核心:
=============================================================
0040147E 55 push ebp
0040147F 89E5 mov ebp, esp
00401481 83EC 18 sub esp, 18
00401484 83E4 F0 and esp, FFFFFFF0
00401487 B8 00000000 mov eax, 0
0040148C 83C0 0F add eax, 0F
0040148F 83C0 0F add eax, 0F
00401492 C1E8 04 shr eax, 4
00401495 C1E0 04 shl eax, 4
00401498 8945 F8 mov [ebp-8], eax
0040149B 8B45 F8 mov eax, [ebp-8]
0040149E E8 3DBD0000 call 0040D1E0
004014A3 E8 78B90000 call 0040CE20
004014A8 E8 E3FEFFFF call 00401390
004014AD A1 58454400 mov eax, [444558]
004014B2 0FAF05 54454400 imul eax, [444554]
004014B9 0305 5C454400 add eax, [44455C]
004014BF 89C2 mov edx, eax
004014C1 2B15 58454400 sub edx, [444558]
004014C7 A1 5C454400 mov eax, [44455C]
004014CC 69C0 DD0C0000 imul eax, eax, 0CDD
004014D2 8D0402 lea eax, [edx+eax]
004014D5 0305 5C454400 add eax, [44455C]
004014DB A3 10444400 mov [444410], eax
004014E0 C70424 24004400 mov dword ptr [esp], 00440024 ; ASCII "Welcome to moofy's keyme
#1.",LF,"The object of this is to find out what makes up the key and make a keygen.",LF,"NO PATCHING!",LF
004014E7 E8 04F30000 call 004107F0 ; jmp 到 msvcrt.printf
004014EC C70424 9A004400 mov dword ptr [esp], 0044009A ; ASCII "Enter your key: "
004014F3 E8 F8F20000 call 004107F0 ; jmp 到 msvcrt.printf
004014F8 8D45 FC lea eax, [ebp-4]
004014FB 894424 04 mov [esp+4], eax
004014FF C70424 AB004400 mov dword ptr [esp], 004400AB ; ASCII "%d"
00401506 E8 D5F20000 call 004107E0 ; jmp 到 msvcrt.scanf
0040150B 8B45 FC mov eax, [ebp-4]
0040150E 3B05 10444400 cmp eax, [444410]
00401514 75 0E jnz short 00401524
00401516 C70424 B0004400 mov dword ptr [esp], 004400B0 ; ASCII LF,LF,"Good Job! Make a keygen
and tutorial and send it in to crackmes.de",LF
0040151D E8 CEF20000 call 004107F0 ; jmp 到 msvcrt.printf
00401522 EB 0C jmp short 00401530
00401524 C70424 F6004400 mov dword ptr [esp], 004400F6 ; ASCII LF,LF,"Try again, sorry!",LF
0040152B E8 C0F20000 call 004107F0 ; jmp 到 msvcrt.printf
00401530 C70424 0B014400 mov dword ptr [esp], 0044010B ; ASCII "PAUSE"
00401537 E8 94F20000 call 004107D0 ; jmp 到 msvcrt.system
0040153C B8 00000000 mov eax, 0
00401541 C9 leave
00401542 C3 retn
=============================================================
这里一眼就能看到关键跳转在401514处,[ebp-4]是scanf函数的参数,也就是用来接受输入的
Key的地方,而比较用的真序列号是存放在[444410]中的。再往上看,[444410]的值是根据
[444554],[444558]和[44455C]三个双字内存的值算出来的,因此关键就在于确定这三个双字
在参与此处运算之前最后一次被改写是在什么地方(注意:这里最好是自己手动单步跟踪并且
监视相关内存单元。下内存断点效果并不好,因为我们所关心的只是最后一次改写,而在这之
前可能也改写过甚至是很多次,但不是我们所关心的,断在这些地方毫无意义。何况内存断点
还有可能断在系统的API里)。用F8键粗跟踪发现上一次改写是发生在4014A8处的
call 00401390处,因此跟进这个call中。仍然继续用寻找最后一次改写的方法,发现除了在
4013FE处的call 00410A90处对上述三个双字改写过以外,其他地方再未改写过。那么这个call
的内容是什么呢?看一下:
=============================================================
004013ED C705 50454400 9>mov dword ptr [444550], 94
004013F7 C70424 50454400 mov dword ptr [esp], 00444550
004013FE E8 8DF60000 call 00410A90 ; jmp 到 kernel32.GetVersionExA
=============================================================
原来就是调用GetVersionEx这个API,MSDN里对这个函数的原型说明是:
BOOL GetVersionEx(struct OSVERSIONINFO * lpVersionInfo);
而这里调用所给的参数是00444550,这应该就是一个OSVERSIONINFO结构体变量的首地址了,
再来看OSVERSIONINFO结构体的声明:
typedef struct _OSVERSIONINFO{
DWORD dwOSVersionInfoSize;
DWORD dwMajorVersion;
DWORD dwMinorVersion;
DWORD dwBuildNumber;
DWORD dwPlatformId;
TCHAR szCSDVersion[ 128 ];
} OSVERSIONINFO;
至此我们得出结论:
[444554] == stMyOSVersion.dwMajorVersion // 我的系统主版本号
[444558] == stMyOSVersion.dwMinorVersion // 我的系统次版本号
[44455C] == stMyOSVersion.dwBuildNumber // 我的系统编译序数
于是,可以写个生成序列号的函数如下:
int KeyGen(void) /* 前提是int的字长为32位 */
{
OSVERSIONINFO OSVer;
GetVersionEx(&OSVer);
/* 用乘法的分配律把算法代码优化成下面这样 */
return OSVer.dwMinorVersion * (OSVer.dwMajorVersion - 1) + OSVer.dwBuildNumber * 0xCDF;
}
笔者用的系统是WinXP,GetVersionEx执行后三个字段的值分别为5、1和0xA28,于是计算出
Key为0x82B8DC也就是十进制的8567004(注意根据scanf的格式符,输入的序号会被解释成带
符号十进制整数)。
我是怕重复上传
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)