系统 : Windows xp
程序 : TDC‘s keygenme
程序下载地址 :http://pan.baidu.com/s/1gdWyt6z
要求 : 脱壳 & 注册机编写
使用工具 :OD & IDA & PEID & LordPE & Import REC
可在“PEDIY CrackMe 2007”中查找关于此程序的分析,标题为“TDC.Crackme10算法分析”。
首先了解一下壳是什么:
作者编好软件后,编译成exe可执行文件。
1.有一些版权信息需要保护起来,不想让别人随便改动,如作者的姓名等,即为了保护软件不被破解,通常都是采用加壳来进行保护。
2.需要把程序搞的小一点,从而方便使用。于是,需要用到一些软件,它们能将exe可执行文件压缩,
3.在黑客界给木马等软件加壳脱壳以躲避杀毒软件。
--源自百度百科
简单说来,壳就是用来防止破解者对于程序文件的非法修改。壳又分为压缩壳、加密壳。他们的应用范围也不尽相同,压缩壳主要用来压缩程序文件的体积,对于破解者来说强度不大。而加密壳以加密保护为重点,用尽了各种反跟踪技术,保护重点是在OEP(程序入口点)的隐藏和IAT(输入表)的加密上。
这篇博文中我们来尝试破解一个被压缩壳保护、带有简单反调试的程序。
将程序拖入PEID查看状态:
显示该程序加了upx壳,可以利用网上的脱壳机来脱壳。这里,为了加深印象,我们尝试手动脱壳。od载入程序,断在外壳处:
00446330 > $ 60 pushad
00446331 . BE 00804200 mov esi, 00428000
00446336 . 8DBE 0090FDFF lea edi, dword ptr [esi+FFFD9000]
0044633C . 57 push edi
0044633D . 83CD FF or ebp, FFFFFFFF
00446340 . EB 10 jmp short 00446352
单步执行pushad之后,esp指向0012FFA4 ,键入命令hr 12FFA4下硬件断点,F9运行程序断在此处
00446493 .- E9 73ABFBFF jmp 0040100B
单步执行跳转进入OEP:
0040100B 6A 0A push 0A
0040100D 68 B90B0000 push 0BB9
00401012 FF35 18624000 push dword ptr [406218]
00401018 E8 05030000 call 00401322 ; jmp to kernel32.FindResourceA
0040101D A3 1C624000 mov dword ptr [40621C], eax
00401022 FF35 1C624000 push dword ptr [40621C]
00401028 FF35 18624000 push dword ptr [406218]
0040102E E8 07030000 call 0040133A ; jmp to kernel32.LoadResource
00401033 A3 20624000 mov dword ptr [406220], eax
00401038 E8 C3FFFFFF call 00401000
0040103D FF35 1C624000 push dword ptr [40621C]
00401043 FF35 18624000 push dword ptr [406218]
00401049 E8 F8020000 call 00401346 ; jmp to kernel32.SizeofResource
0040104E A3 24624000 mov dword ptr [406224], eax
00401053 FF35 20624000 push dword ptr [406220]
00401059 E8 E2020000 call 00401340 ; jmp to kernel32.SetHandleCount
0040105E 8BF0 mov esi, eax
00401060 A1 24624000 mov eax, dword ptr [406224]
00401065 83C0 04 add eax, 4
00401068 50 push eax
00401069 6A 40 push 40
0040106B E8 C4020000 call 00401334 ; jmp to kernel32.GlobalAlloc
00401070 A3 28624000 mov dword ptr [406228], eax
此时单击菜单Debug->hardware breakpoints删除之前设置的硬件断点。
打开LordPE选择keygenme并单击右键选择“完整转存”:
保存dump文件之后,再打开输入表重建工具Import REC附加到keygenme:
填写OEP为“0000100B”,依次单击“自动查找IAT”、“获取输入表”:
最后,单击“修复转存文件”,选中之前的dump文件,则脱壳成功。
脱壳成功之后我们用IDA载入程序,shift + F12查看字符串表:
发现提示成功的字串“Congratulations! You are now level 2! :-)”,双击定位,通过交叉引用来到调用它的位置并向上翻找出关键算法:
TDC0:00401181 cmp dword ptr [ebp+0Ch], 111h
TDC0:00401188 jnz loc_401292
TDC0:0040118E cmp dword ptr [ebp+10h], 3E9h
TDC0:00401195 jnz loc_40125C
TDC0:0040119B push 33h
TDC0:0040119D push offset dword_40622C
TDC0:004011A2 push 7D1h
TDC0:004011A7 push dword ptr [ebp+8]
TDC0:004011AA call GetDlgItemTextA
TDC0:004011AF imul eax, 5
TDC0:004011B2 cmp eax, 0FAh
TDC0:004011B7 ja loc_401246
TDC0:004011BD cmp eax, 1Eh
TDC0:004011C0 jb loc_401246
TDC0:004011C6 push 0
TDC0:004011C8 push 0
TDC0:004011CA push 7D2h
TDC0:004011CF push dword ptr [ebp+8]
TDC0:004011D2 call GetDlgItemInt
;省略一部分关键代码:
这里,选中关键代码,单击“视图”->"图表"->"流程图"显示关键算法的流程:
可以直观的看出,关键算法处用了不少分支语句,程序中肯定加了不少对输入的判断。
打开OD载入程序,输入命令"bp 004011AA",F9运行,程序断在此处:
0040119B 6A 33 push 33
0040119D 68 2C624000 push 0040622C ; ASCII "pediy"
004011A2 68 D1070000 push 7D1
004011A7 FF75 08 push dword ptr [ebp+8]
004011AA E8 AF010000 call <jmp.&user32.GetDlgItemTextA>
004011AF 6BC0 05 imul eax, eax, 5 ; 用户名字串长度*5
004011B2 3D FA000000 cmp eax, 0FA ; 高于0xFA?
004011B7 0F87 89000000 ja 00401246 ; 提示Name must be between 5 and 51 length
004011BD 83F8 1E cmp eax, 1E ; 低于0x1E?
004011C0 0F82 80000000 jb 00401246 ; 提示Name must be between 5 and 51 length
004011C6 6A 00 push 0
004011C8 6A 00 push 0
004011CA 68 D2070000 push 7D2
004011CF FF75 08 push dword ptr [ebp+8]
004011D2 E8 81010000 call <jmp.&user32.GetDlgItemInt> ; 获取序列号
004011D7 A3 5F624000 mov dword ptr [40625F], eax ; 并保存
004011DC 68 2C624000 push 0040622C ; 用户名入栈
004011E1 E8 C2000000 call 004012A8
跟入4012A8:
004012A8 55 push ebp
004012A9 8BEC mov ebp, esp
004012AB 803D B4124000 C>cmp byte ptr [4012B4], 0CC ; 检测断点,发现则跳转出错
004012B2 74 53 je short 00401307
004012B4 33C0 xor eax, eax
004012B6 33C9 xor ecx, ecx
004012B8 803D D4124000 C>cmp byte ptr [4012D4], 0CC ; 检测断点,发现则跳转出错
004012BF 74 46 je short 00401307
004012C1 33D2 xor edx, edx
004012C3 8B55 08 mov edx, dword ptr [ebp+8] ; 取用户名字串
004012C6 8A02 mov al, byte ptr [edx] ; 迭代字串
004012C8 84C0 test al, al ; 迭代完毕?
004012CA 74 08 je short 004012D4 ; 则跳转
004012CC 03C8 add ecx, eax ; 累加
004012CE C1C1 08 rol ecx, 8 ; 循环左移8位
004012D1 42 inc edx ; 循环变量自增
004012D2 ^ EB F2 jmp short 004012C6
004012D4 83F1 02 xor ecx, 2 ; 结果与2异或
004012D7 83E9 50 sub ecx, 50
004012DA 81F1 37130000 xor ecx, 1337 ; 结果与1337异或
004012E0 56 push esi
004012E1 66:8B35 7961400>mov si, word ptr [406179] ; 获取当前系统年份
004012E8 66:03CE add cx, si ; 加入cx
004012EB 803D FA124000 C>cmp byte ptr [4012FA], 0CC ; 检测断点,发现则跳转出错
004012F2 74 13 je short 00401307
004012F4 5E pop esi
004012F5 A1 5F624000 mov eax, dword ptr [40625F] ; 获取序列号
004012FA 3BC1 cmp eax, ecx ; 与F(用户名)的结果是否一致?
004012FC 75 04 jnz short 00401302 ; 否则置406263为1
004012FE 33C0 xor eax, eax
00401300 EB 0E jmp short 00401310
00401302 33C0 xor eax, eax
00401304 40 inc eax
00401305 EB 10 jmp short 00401317
00401307 C605 63624000 0>mov byte ptr [406263], 1
0040130E EB 07 jmp short 00401317
00401310 C605 63624000 0>mov byte ptr [406263], 0
00401317 C9 leave
00401318 C2 0400 retn 4
该子程序布下了简单的反调试,众所周知,断点的原理是将指定地址的指令替换为int 3中断,int 3的十六进制表示为0CC。通过实时检测载入内存的指令是否是0CC,就可以判断自身有没有被调试。在不改动程序文件的条件下,我们只能尽量少下断点,避免触发反调试。
回到剩下的关键算法:
004011E6 803D 63624000 0>cmp byte ptr [406263], 1
004011ED 74 43 je short 00401232
004011EF 85C0 test eax, eax
004011F1 75 17 jnz short 0040120A
004011F3 68 06614000 push 00406106 ; ASCII "Congratulations! You are now level 2! :-)"
004011F8 68 D2070000 push 7D2
004011FD FF75 08 push dword ptr [ebp+8]
00401200 E8 77010000 call <jmp.&user32.SetDlgItemTextA>
00401205 E9 98000000 jmp 004012A2
0040120A 803D 1C124000 C>cmp byte ptr [40121C], 0CC
00401211 74 1F je short 00401232
00401213 803D 23124000 C>cmp byte ptr [401223], 0CC
0040121A 74 16 je short 00401232
0040121C 6A 00 push 0
0040121E 68 D9604000 push 004060D9 ; ASCII "Sorry, that is incorrect my mate. Try again."
00401223 68 D2070000 push 7D2
00401228 FF75 08 push dword ptr [ebp+8]
0040122B E8 4C010000 call <jmp.&user32.SetDlgItemTextA>
00401230 EB 70 jmp short 004012A2
00401232 68 55614000 push 00406155 ; ASCII "Debugger detected, check cancelled!"
00401237 68 D2070000 push 7D2
0040123C FF75 08 push dword ptr [ebp+8]
0040123F E8 38010000 call <jmp.&user32.SetDlgItemTextA>
00401244 EB 5C jmp short 004012A2
00401246 68 30614000 push 00406130 ; ASCII "Name must be between 5 and 51 length"
0040124B 68 D2070000 push 7D2
00401250 FF75 08 push dword ptr [ebp+8]
00401253 E8 24010000 call <jmp.&user32.SetDlgItemTextA>
至此,程序算法流程已经分析完毕。马上动手编写注册机。
我们直接打开http://www.cnblogs.com/ZRBYYXDM/p/5115596.html中搭建的框架,并修改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 <= 50 && len >= 6 ){ //格式控制。
unsigned int sum = 0;
for ( int i = 0 ; i != len ; i++ ){
sum += str[i];
sum = ( sum >> (32 - 8) ) | ( sum << 8 ); //循环左移8位
}
sum = ( (sum ^ 2) - 0x50 ) ^ 0x1337;
SYSTEMTIME st;
CString strDate,strTime;
GetLocalTime(&st); //获取系统时间
int year = st.wYear;
__asm{ //为了防止进位内嵌汇编进行低位相加。
push eax;
push ebx;
mov eax,year;
mov ebx,sum;
add bx,ax;
mov sum,ebx;
pop ebx;
pop eax;
}
CString PassWord;
PassWord.Format( "%u",sum );
SetDlgItemText( IDC_EDIT_PASSWORD,PassWord );
}
else
MessageBox( "用户名格式错误!" );
再在OnInitDialog中添加此代码修改标题:SetWindowText(_T("TDC‘s keygenme_Keygen"));
运行效果:
至此,TDC‘s keygenme的破解就完成了。
PS:情人节到了,愿天下情侣可以天长地久,也希望和我一样的单身汪不要气馁,多多努力,争取早日脱单。
祝诸君,胸中沟壑自成!