/*
* CrackMe 下载链接 : http://bbs.pediy.com/upload/files/1084801702.zip
* 源码类型 : VC6 project files
* 使用工具 : Ollydbg
* 作者: 木瓜枫叶(block)
*/
小弟新手,逆向接触的晚,目前正是学习阶段,为了提高,正准备将论坛上的一些CrackMe 都翻出来,然后写一下注册机。
还是先从爆破开始,然后再说一下算法的逆向
开始:
一、爆破
爆破比较简单,直接一下搞定。
定位方法,随便输入UserName和key ,会提示fail,右键查找参考字串。
定位到提示部分,一目了然。
直接 00401069 EAX置0 就爆了。
Code:
00401069 . 85C0 TEST EAX,EAX ; 爆破点 EAX置0就OK
0040106B . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
0040106D . 68 80504000 PUSH ncrackme.00405080 ; |ncrackme
00401072 . 75 1B JNZ SHORT ncrackme.0040108F ; |
00401074 . A1 B8564000 MOV EAX,DWORD PTR DS:[4056B8] ; |
00401079 . 68 64504000 PUSH ncrackme.00405064 ; |Registration successful.
0040107E . 50 PUSH EAX ; |hOwner => 000901F2 ('Newbie smallsize crackme -
v1',class='myWindowClass')
0040107F . FF15 C0404000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
00401085 . E8 A6020000 CALL ncrackme.00401330
0040108A . 33C0 XOR EAX,EAX
0040108C . C2 1000 RETN 10
0040108F > 8B0D B8564000 MOV ECX,DWORD PTR DS:[4056B8] ; |
00401095 . 68 50504000 PUSH ncrackme.00405050 ; |Registration fail.
0040109A . 51 PUSH ECX ; |hOwner => 000901F2 ('Newbie smallsize crackme -
v1',class='myWindowClass')
0040109B . FF15 C0404000 CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
004010A1 . 33C0 XOR EAX,EAX
004010A3 . C2 1000 RETN 10
二、算法逆向
步骤
第一步:先根据用户名的前两个字节,相除取得余数,然后与第三个字节相乘后再加1.
作为第一个运算的基数,将基数放入全局变量。
第二步:根据上面计算得出的值,在运算15次。运算代码如下,
0040134A /$ A1 AC504000 MOV EAX,DWORD PTR DS:[4050AC]
0040134F |. 69C0 FD430300 IMUL EAX,EAX,343FD
00401355 |. 05 C39E2600 ADD EAX,269EC3
0040135A |. A3 AC504000 MOV DWORD PTR DS:[4050AC],EAX
0040135F |. C1F8 10 SAR EAX,10
00401362 |. 25 FF7F0000 AND EAX,7FFF
00401367 \. C3 RETN
将结果再次放入全局变量。
上面再次作为第三次也就是真正计算key 运算的基数。
第三步: 循环UserName 每个字符,然后计算一下运算的次数。循环运算。
然后 与ECX (0x1a) 相除取得余数。就是key 的字符。
具体分析如下,
算法分析
00401230 /$ 8B0D BC564000 MOV ECX,DWORD PTR DS:[4056BC] ; 控件句柄
00401236 |. 83EC 30 SUB ESP,30
00401239 |. 8D4424 00 LEA EAX,DWORD PTR SS:[ESP]
0040123D |. 53 PUSH EBX ; 取用户名文本框
0040123E |. 56 PUSH ESI
0040123F |. 8B35 94404000 MOV ESI,DWORD PTR DS:[<&USER32.GetDlgItemTextA>] ; USER32.GetDlgItemTextA
00401245 |. 6A 10 PUSH 10 ; /Count = 10 (16.)
00401247 |. 50 PUSH EAX ; |Buffer
00401248 |. 68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
0040124D |. 51 PUSH ECX ; |hWnd => 000701F4
(class='#32770',parent=000901F2)
0040124E |. 33DB XOR EBX,EBX ; |
00401250 |. FFD6 CALL ESI ; \GetDlgItemTextA
00401252 |. 83F8 03 CMP EAX,3 ; 比较用户名长度是否小于3
00401255 |. 73 0B JNB SHORT ncrackme.00401262
00401257 |. 5E POP ESI
00401258 |. B8 01000000 MOV EAX,1
0040125D |. 5B POP EBX
0040125E |. 83C4 30 ADD ESP,30
00401261 |. C3 RETN
00401262 |> A1 BC564000 MOV EAX,DWORD PTR DS:[4056BC] ; 控件句柄
00401267 |. 8D5424 28 LEA EDX,DWORD PTR SS:[ESP+28]
0040126B |. 6A 10 PUSH 10
0040126D |. 52 PUSH EDX
0040126E |. 68 E9030000 PUSH 3E9
00401273 |. 50 PUSH EAX
00401274 |. FFD6 CALL ESI ; key
00401276 |. 0FBE4424 08 MOVSX EAX,BYTE PTR SS:[ESP+8] ; 获取用户名第一个字符
0040127B |. 0FBE4C24 09 MOVSX ECX,BYTE PTR SS:[ESP+9] ; 获取用户名第二个字符
00401280 |. 99 CDQ ; 相除运算
00401281 |. F7F9 IDIV ECX ; EAX%ECX 将结果放入到EDX中
00401283 |. 8BCA MOV ECX,EDX ; 将结果赋值给ECX
00401285 |. 83C8 FF OR EAX,FFFFFFFF ; EAX=-1
00401288 |. 0FBE5424 0A MOVSX EDX,BYTE PTR SS:[ESP+A] ; 获取用户名第三个字符
0040128D |. 0FAFCA IMUL ECX,EDX ; 将上次取得余数与第三个字符相乘 结果放入到ECX
00401290 |. 41 INC ECX ; ECX++
00401291 |. 33D2 XOR EDX,EDX ; EDX=0
00401293 |. F7F1 DIV ECX ; EAX%ECX将商放入到EAX
00401295 |. 50 PUSH EAX ; EAX 是运算key 第一步基数
00401296 |. E8 A5000000 CALL ncrackme.00401340 ; 将eax 放入全局变量保存
0040129B |. 83C4 04 ADD ESP,4
0040129E |. 33F6 XOR ESI,ESI ; esi=0
004012A0 |> E8 A5000000 /CALL ncrackme.0040134A ; 此循环运算15次,具体运算在 0040134A 内部
/*
0040134A 内部CODE:
0040134A /$ A1 AC504000 MOV EAX,DWORD PTR DS:[4050AC]
0040134F |. 69C0 FD430300 IMUL EAX,EAX,343FD
00401355 |. 05 C39E2600 ADD EAX,269EC3
0040135A |. A3 AC504000 MOV DWORD PTR DS:[4050AC],EAX
0040135F |. C1F8 10 SAR EAX,10
00401362 |. 25 FF7F0000 AND EAX,7FFF
00401367 \. C3 RETN
*/
004012A5 |. 99 |CDQ
004012A6 |. B9 1A000000 |MOV ECX,1A
004012AB |. F7F9 |IDIV ECX
004012AD |. 80C2 41 |ADD DL,41
004012B0 |. 885434 18 |MOV BYTE PTR SS:[ESP+ESI+18],DL ; 计算得到key 计算所需要的的字符串
004012B4 |. 46 |INC ESI
004012B5 |. 83FE 0F |CMP ESI,0F ; 判断循环条件退出条件
004012B8 |.^ 72 E6 \JB SHORT ncrackme.004012A0
004012BA |. 57 PUSH EDI ; 最后将结果放入到全局变量
004012BB |. 8D7C24 0C LEA EDI,DWORD PTR SS:[ESP+C]
004012BF |. 83C9 FF OR ECX,FFFFFFFF
004012C2 |. 33C0 XOR EAX,EAX
004012C4 |. 33F6 XOR ESI,ESI
004012C6 |. F2:AE REPNE SCAS BYTE PTR ES:[EDI] ; 计算用户名的长度
004012C8 |. F7D1 NOT ECX
004012CA |. 49 DEC ECX ; 真正计算key 的运算
004012CB |. 74 59 JE SHORT ncrackme.00401326
004012CD |> 8A4434 0C /MOV AL,BYTE PTR SS:[ESP+ESI+C] ; 取出每一个字符
004012D1 |. C0F8 05 |SAR AL,5 ; 右移 5 位
004012D4 |. 0FBEC0 |MOVSX EAX,AL
004012D7 |. 8D1480 |LEA EDX,DWORD PTR DS:[EAX+EAX*4]
004012DA |. 8D04D0 |LEA EAX,DWORD PTR DS:[EAX+EDX*8]
004012DD |. 8D0440 |LEA EAX,DWORD PTR DS:[EAX+EAX*2] ; 这四步是计算当前个字符串所要循环运算的次数
004012E0 |. 85C0 |TEST EAX,EAX
004012E2 |. 7E 0A |JLE SHORT ncrackme.004012EE
004012E4 |. 8BF8 |MOV EDI,EAX ; 将次数存放到EDI,作为循环退出的条件
004012E6 |> E8 5F000000 |/CALL ncrackme.0040134A ; 根据上面计算的结果的值。循环对应的次数运算
004012EB |. 4F ||DEC EDI
004012EC |.^ 75 F8 |\JNZ SHORT ncrackme.004012E6
004012EE |> E8 57000000 |CALL ncrackme.0040134A ; 退出后在运算一次
004012F3 |. 99 |CDQ
004012F4 |. B9 1A000000 |MOV ECX,1A ; 从这开始就是计算每个key字符的地方
004012F9 |. 8D7C24 0C |LEA EDI,DWORD PTR SS:[ESP+C]
004012FD |. F7F9 |IDIV ECX ; 计算DL 要根据上面计算所得的EAX和ECX
004012FF |. 0FBE4C34 2C |MOVSX ECX,BYTE PTR SS:[ESP+ESI+2C]
00401304 |. 80C2 41 |ADD DL,41 ; DL + 0x41 目前就是key 的字符了
00401307 |. 0FBEC2 |MOVSX EAX,DL
0040130A |. 2BC1 |SUB EAX,ECX
0040130C |. 885434 1C |MOV BYTE PTR SS:[ESP+ESI+1C],DL
00401310 |. 99 |CDQ
00401311 |. 33C2 |XOR EAX,EDX
00401313 |. 83C9 FF |OR ECX,FFFFFFFF
00401316 |. 2BC2 |SUB EAX,EDX
00401318 |. 03D8 |ADD EBX,EAX
0040131A |. 33C0 |XOR EAX,EAX
0040131C |. 46 |INC ESI
0040131D |. F2:AE |REPNE SCAS BYTE PTR ES:[EDI] ; 计算用户名长度
0040131F |. F7D1 |NOT ECX
00401321 |. 49 |DEC ECX
00401322 |. 3BF1 |CMP ESI,ECX ; 循环退出的条件,根据用户名长度来计算
00401324 |.^ 72 A7 \JB SHORT ncrackme.004012CD
计算出来的:
asdfg23-YNMKBOX
asdfg-YNMKB
注册机代码:
int DefValue=0;
int _tmain(int argc, _TCHAR* argv[])
{
char keyStr[6]={0};//这个长度根据用户名来,我这里写死了,注意修改
char *UserName="asdfg";
//1.先通过用户名算出运算基数
int _EAX=0;
int _ECX=0;
int _EDX=0;
_EAX=(int)(UserName[0]);
_ECX=(int)(UserName[1]);
_EDX=_EAX%_ECX;
_ECX=_EDX;
_EAX=-1;
_EDX=(int)(UserName[2]);
_ECX=_EDX*_ECX;
_ECX++;
_asm MOV EAX,-1;
_asm MOV ECX,_ECX;
_asm XOR EDX,EDX;
_asm DIV ECX;
_asm MOV _EAX,EAX;
printf("_EAX=%x\r\n",_EAX);
//将结果放入全局变量
DefValue=_EAX;
//2.开始循环计算15次
int _ESI=0;
do
{
_EAX=DefValue;
_asm MOV EAX,_EAX;
_asm IMUL EAX,EAX,0x343FD;
_asm ADD EAX,0x269EC3;
_asm MOV DefValue,EAX
_asm SAR EAX,0x10
_asm AND EAX,0x7FFF
_ESI++;
}while(_ESI<15);
printf("DefValue=%x\r\n",DefValue);
//3.开始真正的key运算
int UserLength=strlen(UserName);
int _EDI=0;
for(int i=0;i<UserLength;i++)
{
int _DL=0;
int _AL=(int)(UserName[i]);
_asm XOR EAX,EAX;
_asm MOV EAX,_AL;
_asm SAR AL,5;
_asm MOVSX EAX,AL;
_asm LEA EDX,DWORD PTR DS:[EAX+EAX*4]
_asm LEA EAX,DWORD PTR DS:[EAX+EDX*8]
_asm LEA EAX,DWORD PTR DS:[EAX+EAX*2]
_asm MOV _EDI,EAX;
if(_EDI>0)
{
do
{
_EAX=DefValue;
_asm MOV EAX,_EAX;
_asm IMUL EAX,EAX,0x343FD;
_asm ADD EAX,0x269EC3;
_asm MOV DefValue,EAX
_asm SAR EAX,0x10
_asm AND EAX,0x7FFF
_EDI--;
}while(_EDI>0);
}
//这里还要来一次运算
_EAX=DefValue;
_asm MOV EAX,_EAX;
_asm IMUL EAX,EAX,0x343FD;
_asm ADD EAX,0x269EC3;
_asm MOV DefValue,EAX;
_asm SAR EAX,0x10;
_asm AND EAX,0x7FFF;
_asm MOV ECX,0x1A;
_asm XOR EDX,EDX;
_asm IDIV ECX;
_asm ADD DL,0x41;
_asm MOV _DL,EDX;
keyStr[i]=_DL;
}
printf("keyStr=%s\r\n",keyStr);
getchar();
return 0;
}
代码写的不是很好,大家凑活看。
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)