系统 : 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:情人节到了,愿天下情侣可以天长地久,也希望和我一样的单身汪不要气馁,多多努力,争取早日脱单。
祝诸君,胸中沟壑自成!