系统 : Windows xp
程序 : crackme
程序下载地址 :http://pan.baidu.com/s/1kUrbcAr
要求 : 注册机编写 & 去除Nag窗口
使用工具 : IDA Pro & OD
可在“PEDIY CrackMe 2007”中查找关于此程序的讨论,标题为“Pj一个超简单的Crackme,适合像我一样的新手~_~”。
运行该程序,发现一开始就给了我们一个大大的警告窗口:
这不找抽吗?载入IDA查看字串表,发现字串“Patch Me if you can”,双击定位到定义位置,再双击交叉引用来到调用处。发现程序调用MessageBox来显示窗口,OD载入程序,将call MessageBox函数这一行改为add esp 10:
运行程序:
咦?我保存了的啊,什么还是显示了Nag窗口?而且我的断点也失效了?
初步判断,该程序开始运行时,进行清除断点的工作。并且,对载入内存的MessageBox部分代码进行了还原。
想要去除Nag窗口,可能就需要书后面的反跟踪以及打补丁的技巧。这里,我们暂时忍一下,先放过它。
重新返回字串表,发现该程序没有错误提示。。。找到成功注册的提示,向上翻找定位关键代码:
00401219 . C705 643C4000>mov dword ptr [403C64], <jmp.&user32.GetWindowTextA> ; 将函数地址存入 403C64
00401223 . 832D 643C4000>sub dword ptr [403C64], 9
0040122A > 68 FF010000 push 1FF ; 循环两次读入用户名和密码。
0040122F . 83FB 01 cmp ebx, 1 ; 用户名存入403054,序列号403654
00401232 . 75 0D jnz short 00401241
00401234 . 68 54304000 push 00403054 ; ASCII "pediy"
00401239 . FF35 4C304000 push dword ptr [40304C]
0040123F . EB 0B jmp short 0040124C
00401241 > 68 54364000 push 00403654 ; ASCII "1111111111"
00401246 . FF35 50304000 push dword ptr [403050]
0040124C > 83FB 01 cmp ebx, 1
0040124F . 74 07 je short 00401258
00401251 . 8305 643C4000>add dword ptr [403C64], 9
00401258 > FF15 643C4000 call dword ptr [403C64] ; <jmp.&user32.GetWindowTextA>
0040125E . 43 inc ebx
0040125F . 83FB 02 cmp ebx, 2
00401262 .^ 7C C6 jl short 0040122A
00401264 . C705 703C4000>mov dword ptr [403C70], <jmp.&kernel32.lstrlenA>
0040126E . FF0D 703C4000 dec dword ptr [403C70] ; crackme.004016DC
00401274 . C705 683C4000>mov dword ptr [403C68], <jmp.&user32.GetWindowTextLeng>
0040127E . FF05 683C4000 inc dword ptr [403C68] ; crackme.00401670
00401284 . FF35 4C304000 push dword ptr [40304C]
0040128A . FF0D 683C4000 dec dword ptr [403C68] ; crackme.00401670
00401290 . FF15 683C4000 call dword ptr [403C68] ; <jmp.&user32.GetWindowTextLengthA>
00401296 . 83F8 05 cmp eax, 5 ; 用户名是否小于5?
00401299 . 0F8C 43010000 jl 004013E2 ; 是则跳转至出错代码
0040129F . 83F8 08 cmp eax, 8 ; 用户名是否大于等于8?
004012A2 . 0F8D 3A010000 jge 004013E2 ; 是则跳转至出错代码
004012A8 . 8BC8 mov ecx, eax
004012AA . 890D 543C4000 mov dword ptr [403C54], ecx ; 长度存入内存
004012B0 . 33DB xor ebx, ebx
004012B2 . 8D35 54304000 lea esi, dword ptr [403054] ; 取用户名
004012B8 . 8D3D 54324000 lea edi, dword ptr [403254] ; 开辟内存储存密钥
004012BE > 8A4431 FF mov al, byte ptr [ecx+esi-1] ; 倒序迭代字串
004012C2 . 80F9 04 cmp cl, 4 ; 长度为4?
004012C5 . 74 13 je short 004012DA ; 是则存储‘-’字符并结束本次循环
004012C7 . 3C 4D cmp al, 4D ; 是否小于0x4D?
004012C9 . 7C 04 jl short 004012CF
004012CB . 2C 11 sub al, 11 ; 大于0x4D则减去0x11
004012CD . EB 02 jmp short 004012D1
004012CF > 04 15 add al, 15
004012D1 > 32C1 xor al, cl ; 处理过的字符值 ^ 长度
004012D3 . 34 02 xor al, 2 ; 处理过的字符值^2
004012D5 . 88043B mov byte ptr [ebx+edi], al ; 保存密钥
004012D8 . EB 04 jmp short 004012DE
004012DA > C6043B 2D mov byte ptr [ebx+edi], 2D
004012DE > 43 inc ebx ; 指向下一个存储单元
004012DF .^ E0 DD loopdne short 004012BE ; 类似loop,另外测试ZF标志位是否为1则退出循环
004012E1 . 8D35 54324000 lea esi, dword ptr [403254] ; 取序列号
004012E7 . 8D3D 54344000 lea edi, dword ptr [403454] ; 开辟内存储存新密钥
004012ED . 8B0D 543C4000 mov ecx, dword ptr [403C54] ; 长度存入ecx
004012F3 . 33DB xor ebx, ebx
004012F5 . C705 5C3C4000>mov dword ptr [403C5C], <jmp.&kernel32.lstrcatA>
004012FF . 832D 5C3C4000>sub dword ptr [403C5C], 2
00401306 . C705 783C4000>mov dword ptr [403C78], 0040207F ; ASCII ":-)"
00401310 . C705 7C3C4000>mov dword ptr [403C7C], 00402070 ; ASCII "Correct Serial"
0040131A . 832D 783C4000>sub dword ptr [403C78], 5
00401321 . 832D 7C3C4000>sub dword ptr [403C7C], 5
00401328 > 80F9 04 cmp cl, 4 ; 长度为4?
0040132B . 74 17 je short 00401344 ; 是则存储‘-’字符并结束本次循环
0040132D . 8A4431 FF mov al, byte ptr [ecx+esi-1] ; 倒序迭代密钥
00401331 . 3C 4D cmp al, 4D ; 是否小于0x4D?
00401333 . 7C 04 jl short 00401339 ; 小于0x4D则加上0x15
00401335 . 2C 11 sub al, 11 ; 大于0x4D则减去0x11
00401337 . EB 02 jmp short 0040133B
00401339 > 04 15 add al, 15
0040133B > 32C1 xor al, cl ; 处理过的字符值 ^ 长度
0040133D . 34 02 xor al, 2 ; 处理过的字符值^2
0040133F . 88043B mov byte ptr [ebx+edi], al ; 保存密钥
00401342 . EB 04 jmp short 00401348
00401344 > C6043B 2D mov byte ptr [ebx+edi], 2D
00401348 > 43 inc ebx ; 指向下一个存储单元
00401349 .^ E0 DD loopdne short 00401328 ; 类似loop,另外测试ZF标志位是否为1则退出循环
0040134B . C705 603C4000>mov dword ptr [403C60], <jmp.&user32.MessageBoxA> ; Entry address
00401355 . 832D 603C4000>sub dword ptr [403C60], 7
0040135C . 68 54324000 push 00403254 ; ASCII "o-RT\"
00401361 . 68 54344000 push 00403454 ; ASCII "L-@B]o-RT\"
00401366 . 8305 5C3C4000>add dword ptr [403C5C], 2 ; ↓将旧密钥连接到新密钥后面
0040136D . FF15 5C3C4000 call dword ptr [403C5C] ; <jmp.&kernel32.lstrcatA>
00401373 . 68 54344000 push 00403454 ; ASCII "L-@B]o-RT\"
00401378 . FF05 703C4000 inc dword ptr [403C70] ; crackme.004016DC
0040137E . FF15 703C4000 call dword ptr [403C70] ; <jmp.&kernel32.lstrlenA>
00401384 . A3 743C4000 mov dword ptr [403C74], eax ; ↑算出密钥长度↓算出输入的序列号长度
00401389 . 68 54364000 push 00403654 ; ASCII "1111111111"
0040138E . FF15 703C4000 call dword ptr [403C70] ; <jmp.&kernel32.lstrlenA>
00401394 . 3B05 743C4000 cmp eax, dword ptr [403C74] ; 长度一样?
0040139A . 75 46 jnz short 004013E2
0040139C . FF35 543C4000 push dword ptr [403C54] ; 长度入栈
004013A2 . 68 54344000 push 00403454 ; ASCII "L-@B]o-RT\"
004013A7 . 68 54364000 push 00403654 ; ASCII "1111111111"
004013AC . E8 9F000000 call 00401450 ; 字串是否相等?
004013B1 . 83F8 01 cmp eax, 1 ; 不相等则跳转出错
004013B4 . 74 2C je short 004013E2 ; 相等则提示成功
这是一个F(用户名)=序列号的例子,我们将F(用户名)用高级语言写出来即可完成注册机。
打开打开http://www.cnblogs.com/ZRBYYXDM/p/5115596.html中搭建的框架,为CKengen_TemplateDlg添加成员函数CreateSerial:
CString CKengen_TemplateDlg::CreateSerial(CString str)
{
CString res = "";
int len = str.GetLength();
int i = len - 1,count = len;
for ( ; i != -1 ; i--,count-- ){
if ( count == 4 ){
res += ‘-‘;
continue;
}
if ( str[i] < 0x4D ) //小于0x4D
str.SetAt( i,str[i] + 0x15 );
else
str.SetAt( i,str[i] - 0x11 );
str.SetAt( i,str[i] ^ ( i+1 ) ^ 2 );
res += str[i];
}
return res;
}
并修改OnBtnDecrypt如下:
void CKengen_TemplateDlg::OnBtnDecrypt()
{
// TODO: Add your control notification handler code here
CString str;
GetDlgItemText( IDC_EDIT_NAME,str ); //获取用户名字串基本信息。
int len = str.GetLength();
if ( len >= 5 && len < 8 ){ //格式控制。
CString Temp = CreateSerial( str ); //第一个密钥
CString Serial = CreateSerial( Temp ); //第二个密钥
Serial += Temp;
SetDlgItemText( IDC_EDIT_PASSWORD,Serial );
}
else
MessageBox( "用户名格式错误!" );
}
再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("CRACKME_Keygen"));
运行效果:
-------------------------------------------------------------------------------------------------------------------
后记:看了pediy crackme 2007中的原破文受了启发,既然反跟踪和恢复MessageBox函数都是作者之前实现的,那找到它nop掉不就行了?这里,一步步跟踪开头的代码:发现调用401477子程序之后,MessageBox被恢复了:
00401000 >/$ 6A 00 push 0 ; /pModule = NULL
00401002 |. E8 B7060000 call <jmp.&kernel32.GetModuleHandleA> ; \GetModuleHandleA
00401007 |. A3 40304000 mov dword ptr [403040], eax
0040100C |. E8 A1060000 call <jmp.&kernel32.GetCommandLineA> ; [GetCommandLineA
00401011 |. A3 44304000 mov dword ptr [403044], eax
00401016 |. 6A 0A push 0A
00401018 |. FF35 44304000 push dword ptr [403044]
0040101E |. 6A 00 push 0
00401020 |. FF35 40304000 push dword ptr [403040] ; crackme.00400000
00401026 |. E8 06000000 call 00401031
0040102B |. 50 push eax ; /ExitCode
0040102C \. E8 7B060000 call <jmp.&kernel32.ExitProcess> ; \ExitProcess
00401031 /$ 55 push ebp
00401032 |. 8BEC mov ebp, esp
00401034 |. 83C4 B0 add esp, -50
00401037 E8 3B040000 call 00401477
我们nop掉它就OK了^▼^
但运行保存后的程序,单击check之后会出错,这又是为什么?帖子评论里就有牛人来讲解了:
程序一开始调用4次WriteProcessMemory恢复40103C处的机器码
并在按下check后调用ReadProcessMemory检查自身一大段机器码
动态SMC 值得学习
原来的确运用了补丁技术,而且断点失效并非我一开始推测的断点全部清除。我们都知道断点其实就是int 3指令,WriteProcessMemory恢复了MessageBox的机器码,自然就覆盖了断点的int 3指令,所以MessageBox处的断点失效而注册算法的断点没有失效。
原文:http://www.cnblogs.com/ZRBYYXDM/p/5118105.html