【破文标题】riijj Crackme (1) 的详解
【对 象】初入门的新手
【下载地址】
http://bbs.pediy.com/upload/files/1084801702.zip
【破解工具】OD
【保护方式】序号
【任 务】找出序号
【破文作者】riijj
【组 织】没有
【破解声明】我的破解很菜,写这篇东西是给刚入门的兄弟可以顺利完成这个 crackme,找到一点信心
【备 注】老手勿看,别浪费你的时间
【电 邮】je6543@yahoo.com
【破解过程】
[PART. 1 第一部分]
对于刚接触破解的新手来说,这个 crackme 是一只没有杀伤力的小兔。
一般破文都是注重处理 antidebug 和序号检查这些精要的部份,可是这个 crackme 实在是太简单,所以
会详尽一点,把新手不懂的地方也说一下
1. 检查壳 (check for protector)
在开始之前,我们都会用 peid 或 Fi 这些工具检查一下壳,如果程序是有加壳的,当然是先脱壳。
(关于脱壳,这里不多说了)
打开 peid ,把 exe 拉进去
peid 显示 Microsoft Visual C++ 6.0
这表示没有加壳,这是以 vc6 编写的原程序。 (注: 有些程序加壳后,会故意修改,装成没有加壳的样子,所以我们不可以
尽信查壳工具)
2. 用 OD 加载
我们可以用的调试工具很多,静态调试的有 IDA , wdasm893 (现在比较少人用),动态调试我们可以用 OD (olly debug)
,或是用 softice 对系统设下断点来破。 我们这里介绍 OD ,如果你喜欢其它工具也是可以的,原理是差不多。
加载后,看到
00401368 >/$ 55 PUSH EBP <------- 我们在这里
00401369 |. 8BEC MOV EBP,ESP
0040136B |. 6A FF PUSH -1
0040136D |. 68 E8404000 PUSH ncrackme.004040E8
00401372 |. 68 9C1E4000 PUSH ncrackme.00401E9C ; SE handler installation
00401377 |. 64:A1 00000000 MOV EAX,DWORD PTR FS:[0]
0040137D |. 50 PUSH EAX
0040137E |. 64:8925 000000>MOV DWORD PTR FS:[0],ESP
00401385 |. 83EC 58 SUB ESP,58
00401388 |. 53 PUSH EBX
这样的一大堆,便是程序开始的地方。一般程序的入口点都是 40xxxx 这样的,如果程序加了壳,入口点会不同。
程序的入口点,是可以由作者自行设定的,没有特别规定
在程序开始段常见的 API : KERNEL32.GetVersion, GetCommandLineA, GetStartupInfoA
我们在这片密麻麻的字海里,是很难找到我们要破解的地方,我们第一步是要找出检查序号的部份。程序多数会用 GetWindowTextA,
GetDlgItemTextA 这类 API 来得到文字方块里的字符串。此外,当我们输入错误序号的时候,程序会弹出一个失败信息,这个信息由
MessageBoxA 所提供。假若我们要分析算法,便需要在 GetWindowTextA 下断,一步一步跟踪。现在我们想从内存中找出序号,比较
快速的方法是在 MessageBoxA 下断。
在 OD 上方的 Plugins ,选 Commandline ,这里可以像 softice 一样输入命令行
设下一个断点 :
bp MessageBoxA
输入这行后,当程序使用 MessageBoxA ,程序便会断下
现在,我们把程序正常运行 (按 F9),输入一个名字,我在里输入了 riijj ,在序号那行输入了 AAAABBBBCCCC
按下 "Register" ,这时候程序遇到断点,停在以下地方
77E16544 > 55 PUSH EBP <---停在这里
77E16545 8BEC MOV EBP,ESP
77E16547 51 PUSH ECX
77E16548 833D 1893E477 00 CMP DWORD PTR DS:[77E49318],0
77E1654F 0F85 EA220100 JNZ USER32.77E2883F
77E16555 6A 00 PUSH 0
77E16557 FF75 14 PUSH DWORD PTR SS:[EBP+14]
77E1655A FF75 10 PUSH DWORD PTR SS:[EBP+10]
77E1655D FF75 0C PUSH DWORD PTR SS:[EBP+C]
77E16560 FF75 08 PUSH DWORD PTR SS:[EBP+8]
77E16563 E8 04000000 CALL USER32.MessageBoxExA
77E16568 C9 LEAVE
77E16569 C2 1000 RETN 10
77E1656C > 55 PUSH EBP
看看 OD 的上方,写着 "CPU - main thread, module USER32" ,这说明了我们身处 user32.dll 的领空里,这是系统的程序
部份,我们是不会修改这里的。
(注 : 如果你把文字窗口向上卷的时候,发现 OD 的编码出现不正常现象 (例如刚刚那行 77E16544 的上方变成了 ??? ) ,
这可能是 OD 的对位出错)
我们要返回 crackme 的领空里,有几个方法
1. 不停接 F8 ,一步一步地执行直至程序遇上 retn ,这是返回指令,它会带我们回去
2. 按 Ctrl + F9 ,这样 OD 会不停执行,直至遇到 retn 停下
3. 按一下返回的 retn ,再按 F4 ,程序会执行到光标所在的地方
4. 打开 OD 的 call stack window,看看我们从那里飞来,便设一个断点在那地方,之后 F9 运行
我们按一下 F2 清除断点,再按一下 77E16569 (retn) 那行,按 F4 执行到那里,再按一下 F7 进入 retn。
00401050 . 817C24 08 1101>CMP DWORD PTR SS:[ESP+8],111
00401058 . 75 74 JNZ SHORT ncrackme.004010CE
0040105A . 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C]
0040105E . 66:3D EA03 CMP AX,3EA
00401062 . 75 42 JNZ SHORT ncrackme.004010A6
00401064 . E8 C7010000 CALL ncrackme.00401230
00401069 . 85C0 TEST EAX,EAX
0040106B . 6A 00 PUSH 0 ; /Style = MB_OK|MB_APPLMODAL
0040106D . 68 80504000 PUSH ncrackme.00405080 ; |Title = "ncrackme"
00401072 . 75 1B JNZ SHORT ncrackme.0040108F ; |
00401074 . A1 B8564000 MOV EAX,DWORD PTR DS:[4056B8] ; |
00401079 . 68 64504000 PUSH ncrackme.00405064 ; |Text = "Registration successful."
0040107E . 50 PUSH EAX ; |hOwner => 001C0218 ('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 ; |Text = "Registration fail."
0040109A . 51 PUSH ECX ; |hOwner => 001C0218 ('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
004010A6 > 66:3D EB03 CMP AX,3EB
004010AA . 75 22 JNZ SHORT ncrackme.004010CE
004010AC . A1 C0564000 MOV EAX,DWORD PTR DS:[4056C0]
004010B1 . 85C0 TEST EAX,EAX
004010B3 . 74 19 JE SHORT ncrackme.004010CE
我们看见了这里是 MessageBox 跳出的程序,这里是成功信息和失败信息的地方。 (读者可能已经大叫,只要把跳往失败信息的跳转修改
一下,便可以成功爆破) 。这是对的,假如我们要爆破它,只要把 00401072 的 JNZ (jump if not zero),修改成 JZ 便可以。
那么,假若我们不想修改软件,而是找出正确的序号,那又怎办呢 ? 我们需要往那个 jump 的上面看看。在 00401069 这里有一个 TEST
,这就是进行比较的指令,它的结果用在其后的 jnz 上。
这句 TEST EAX,EAX 是检查 EAX 是否等于 0 的典型语句,假如 EAX 不等于 0,JNZ 便会跳。我们就是不想它跳,所以我们要找出让 EAX
等于 0 的条件。在 TEST 的上方是 CALL ncrackme.00401230,这个 CALL 的返回值就是 EAX 的值。
我们按右键选 Go to,在 expression 输入 00401230 这个位置
我们来到了检查序号的地方,一看之下便感到这里是一堆很麻烦的运算。今次我们的目的是检出一个给
自己用的序号,我们有没有方法可以从算法中看出序号呢 ?
[PART. 2 第二部分]
按 F7 单步跟踪,细心观察它在干甚么
00401230 /$ 8B0D BC564000 MOV ECX,DWORD PTR DS:[4056BC]
00401236 |. 83EC 30 SUB ESP,30 // 在 stack 划出空间,作为变量
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.GetDlgIte>; 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 => NULL
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] // 从 JNB 来了这里
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 // 再用 GetDlgItemTextA ,得到序号
00401276 |. 0FBE4424 08 MOVSX EAX,BYTE PTR SS:[ESP+8] // 把名字的第一个位,放入 EAX
0040127B |. 0FBE4C24 09 MOVSX ECX,BYTE PTR SS:[ESP+9] // 把名字的第二个位,放入 ECX
00401280 |. 99 CDQ // 把 EAX 扩展,成为 EDX:EAX 的 QWORD(64 位长)
00401281 |. F7F9 IDIV ECX // 把 EDX:EAX 除以 ECX,余数放在 EDX
00401283 |. 8BCA MOV ECX,EDX
00401285 |. 83C8 FF OR EAX,FFFFFFFF // EAX = 0xffffffff
00401288 |. 0FBE5424 0A MOVSX EDX,BYTE PTR SS:[ESP+A] // 把名字的第一个位,放入 EDX
0040128D |. 0FAFCA IMUL ECX,EDX // 把刚才的余数乘以 EDX
00401290 |. 41 INC ECX // ECX 增加 1
00401291 |. 33D2 XOR EDX,EDX // EDX = 0
00401293 |. F7F1 DIV ECX // 以 0xffffffff 除以 ECX
以 C 语言表达,想象成
EAX 的值: 0xFFFFFFFF / (1+(buffer[0] % buffer[1] * buffer[2])
数值相等于 4546e6
[招生]科锐逆向工程师培训(2025年3月11日实地,远程教学同时开班, 第52期)!