系统 : Windows xp

程序 : TDC‘s keygenme

程序下载地址 :http://pan.baidu.com/s/1gdWyt6z

要求 : 脱壳 & 注册机编写

使用工具 :OD & IDA & PEID & LordPE & Import REC

可在“PEDIY CrackMe 2007”中查找关于此程序的分析,标题为“TDC.Crackme10算法分析”。

首先了解一下壳是什么:

简单说来,壳就是用来防止破解者对于程序文件的非法修改。壳又分为压缩壳、加密壳。他们的应用范围也不尽相同,压缩壳主要用来压缩程序文件的体积,对于破解者来说强度不大。而加密壳以加密保护为重点,用尽了各种反跟踪技术,保护重点是在OEP(程序入口点)的隐藏和IAT(输入表)的加密上。

这篇博文中我们来尝试破解一个被压缩壳保护、带有简单反调试的程序。

将程序拖入PEID查看状态:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

显示该程序加了upx壳,可以利用网上的脱壳机来脱壳。这里,为了加深印象,我们尝试手动脱壳。od载入程序,断在外壳处:

 > $              pushad
. BE mov esi,
. 8DBE 0090FDFF lea edi, dword ptr [esi+FFFD9000]
0044633C . push edi
0044633D . 83CD FF or ebp, FFFFFFFF
. EB jmp short

单步执行pushad之后,esp指向0012FFA4 ,键入命令hr 12FFA4下硬件断点,F9运行程序断在此处

   .- E9 73ABFBFF   jmp     0040100B

单步执行跳转进入OEP:

0040100B    6A 0A           push    0A
0040100D B90B0000 push 0BB9
FF35 push dword ptr []
E8 call ; jmp to kernel32.FindResourceA
0040101D A3 1C624000 mov dword ptr [40621C], eax
FF35 1C624000 push dword ptr [40621C]
FF35 push dword ptr []
0040102E E8 call 0040133A ; jmp to kernel32.LoadResource
A3 mov dword ptr [], eax
E8 C3FFFFFF call
0040103D FF35 1C624000 push dword ptr [40621C]
FF35 push dword ptr []
E8 F8020000 call ; jmp to kernel32.SizeofResource
0040104E A3 mov dword ptr [], eax
FF35 push dword ptr []
E8 E2020000 call ; jmp to kernel32.SetHandleCount
0040105E 8BF0 mov esi, eax
A1 mov eax, dword ptr []
83C0 add eax,
push eax
6A push
0040106B E8 C4020000 call ; jmp to kernel32.GlobalAlloc
A3 mov dword ptr [], eax

此时单击菜单Debug->hardware breakpoints删除之前设置的硬件断点。

打开LordPE选择keygenme并单击右键选择“完整转存”:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

保存dump文件之后,再打开输入表重建工具Import REC附加到keygenme:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

填写OEP为“0000100B”,依次单击“自动查找IAT”、“获取输入表”:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

最后,单击“修复转存文件”,选中之前的dump文件,则脱壳成功。

脱壳成功之后我们用IDA载入程序,shift + F12查看字符串表:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

发现提示成功的字串“Congratulations! You are now level 2! :-)”,双击定位,通过交叉引用来到调用它的位置并向上翻找出关键算法:

TDC0:                 cmp     dword ptr [ebp+0Ch], 111h
TDC0: jnz loc_401292
TDC0:0040118E cmp dword ptr [ebp+10h], 3E9h
TDC0: jnz loc_40125C
TDC0:0040119B push 33h
TDC0:0040119D push offset dword_40622C
TDC0:004011A2 push 7D1h
TDC0:004011A7 push dword ptr [ebp+]
TDC0:004011AA call GetDlgItemTextA
TDC0:004011AF imul eax,
TDC0:004011B2 cmp eax, 0FAh
TDC0:004011B7 ja loc_401246
TDC0:004011BD cmp eax, 1Eh
TDC0:004011C0 jb loc_401246
TDC0:004011C6 push
TDC0:004011C8 push
TDC0:004011CA push 7D2h
TDC0:004011CF push dword ptr [ebp+]
TDC0:004011D2 call GetDlgItemInt
;省略一部分关键代码:

这里,选中关键代码,单击“视图”->"图表"->"流程图"显示关键算法的流程:

深入底层逆向分析TDC‘s keygenme(手脱压缩壳)-LMLPHP

可以直观的看出,关键算法处用了不少分支语句,程序中肯定加了不少对输入的判断。

打开OD载入程序,输入命令"bp 004011AA",F9运行,程序断在此处:

0040119B    6A            push
0040119D 2C624000 push 0040622C ; ASCII "pediy"
004011A2 D1070000 push 7D1
004011A7 FF75 push dword ptr [ebp+]
004011AA E8 AF010000 call <jmp.&user32.GetDlgItemTextA>
004011AF 6BC0 imul eax, eax, ; 用户名字串长度*5
004011B2 3D FA000000 cmp eax, 0FA ; 高于0xFA?
004011B7 0F87 ja ; 提示Name must be between 5 and 51 length
004011BD 83F8 1E cmp eax, 1E ; 低于0x1E?
004011C0 0F82 jb ; 提示Name must be between 5 and 51 length
004011C6 6A push
004011C8 6A push
004011CA D2070000 push 7D2
004011CF FF75 push dword ptr [ebp+]
004011D2 E8 call <jmp.&user32.GetDlgItemInt> ; 获取序列号
004011D7 A3 5F624000 mov dword ptr [40625F], eax ; 并保存
004011DC 2C624000 push 0040622C ; 用户名入栈
004011E1 E8 C2000000 call 004012A8

跟入4012A8:

004012A8                  push    ebp
004012A9 8BEC mov ebp, esp
004012AB 803D B4124000 C>cmp byte ptr [4012B4], 0CC ; 检测断点,发现则跳转出错
004012B2 je short
004012B4 33C0 xor eax, eax
004012B6 33C9 xor ecx, ecx
004012B8 803D D4124000 C>cmp byte ptr [4012D4], 0CC ; 检测断点,发现则跳转出错
004012BF je short
004012C1 33D2 xor edx, edx
004012C3 8B55 mov edx, dword ptr [ebp+] ; 取用户名字串
004012C6 8A02 mov al, byte ptr [edx] ; 迭代字串
004012C8 84C0 test al, al ; 迭代完毕?
004012CA je short 004012D4 ; 则跳转
004012CC 03C8 add ecx, eax ; 累加
004012CE C1C1 rol ecx, ; 循环左移8位
004012D1 inc edx ; 循环变量自增
004012D2 ^ EB F2 jmp short 004012C6
004012D4 83F1 xor ecx, ; 结果与2异或
004012D7 83E9 sub ecx,
004012DA 81F1 xor ecx, ; 结果与1337异或
004012E0 push esi
004012E1 :8B35 >mov si, word ptr [] ; 获取当前系统年份
004012E8 :03CE add cx, si ; 加入cx
004012EB 803D FA124000 C>cmp byte ptr [4012FA], 0CC ; 检测断点,发现则跳转出错
004012F2 je short
004012F4 5E pop esi
004012F5 A1 5F624000 mov eax, dword ptr [40625F] ; 获取序列号
004012FA 3BC1 cmp eax, ecx ; 与F(用户名)的结果是否一致?
004012FC jnz short ; 否则置406263为1
004012FE 33C0 xor eax, eax
EB 0E jmp short
33C0 xor eax, eax
inc eax
EB jmp short
C605 >mov byte ptr [],
0040130E EB jmp short
C605 >mov byte ptr [],
C9 leave
C2 retn

该子程序布下了简单的反调试,众所周知,断点的原理是将指定地址的指令替换为int 3中断,int 3的十六进制表示为0CC。通过实时检测载入内存的指令是否是0CC,就可以判断自身有没有被调试。在不改动程序文件的条件下,我们只能尽量少下断点,避免触发反调试。

回到剩下的关键算法:

004011E6    803D  >cmp     byte ptr [],
004011ED je short
004011EF 85C0 test eax, eax
004011F1 jnz short 0040120A
004011F3 push ; ASCII "Congratulations! You are now level 2! :-)"
004011F8 D2070000 push 7D2
004011FD FF75 push dword ptr [ebp+]
E8 call <jmp.&user32.SetDlgItemTextA>
E9 jmp 004012A2
0040120A 803D 1C124000 C>cmp byte ptr [40121C], 0CC
1F je short
803D C>cmp byte ptr [], 0CC
0040121A je short
0040121C 6A push
0040121E D9604000 push 004060D9 ; ASCII "Sorry, that is incorrect my mate. Try again."
D2070000 push 7D2
FF75 push dword ptr [ebp+]
0040122B E8 4C010000 call <jmp.&user32.SetDlgItemTextA>
EB jmp short 004012A2
push ; ASCII "Debugger detected, check cancelled!"
D2070000 push 7D2
0040123C FF75 push dword ptr [ebp+]
0040123F E8 call <jmp.&user32.SetDlgItemTextA>
EB 5C jmp short 004012A2
push ; ASCII "Name must be between 5 and 51 length"
0040124B D2070000 push 7D2
FF75 push dword ptr [ebp+]
E8 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 <= && len >= ){ //格式控制。
unsigned int sum = ; for ( int i = ; i != len ; i++ ){
sum += str[i];
sum = ( sum >> ( - ) ) | ( sum << ); //循环左移8位
} sum = ( (sum ^ ) - 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(手脱压缩壳)-LMLPHP

至此,TDC‘s keygenme的破解就完成了。

PS:情人节到了,愿天下情侣可以天长地久,也希望和我一样的单身汪不要气馁,多多努力,争取早日脱单。

祝诸君,胸中沟壑自成!

05-06 17:41