* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040111C(C)
|
:0040110C 0FBE840D48FFFFFFmovsx eax, byte ptr [ebp+ecx-000000B8];依次取用户名的字符
:00401114 41 inc ecx;ECX为循环变量
:00401115 33C1xor eax, ecx;取的字符与循环变量XOR
:00401117 03D8add ebx, eax;把结果累加到EBX
:00401119 3B4DD8 cmp ecx, dword ptr [ebp-28] ;循环变量与用户名长度相比
:0040111C 75EEjne 0040110C;如果未取完就跳回继续
:0040111E 6BC006 imul eax, 00000006 ;最后一轮计算的结果在EAX, 乘6
:00401121 C1E307 shl ebx, 07;前面累加结果左移7位
:00401124 03C3add eax, ebx;相加
:00401126 8945C8 mov dword ptr [ebp-38], eax
:00401129 FF75C8 push [ebp-38];把上面结果压栈
* Possible StringData Ref from Data Obj ->"%lX"
|
:0040112C 6838B44000 push 0040B438;一个转换的标识
:00401131 8D8D80FEFFFFlea ecx, dword ptr [ebp+FFFFFE80]
:00401137 51 push ecx;存放转换结果的地址
:00401138 E8873D0000 call 00404EC4;数字转为十六进制字串
:0040113D 83C40C add esp, 0000000C
:00401140 8D8580FEFFFFlea eax, dword ptr [ebp+FFFFFE80]
:00401146 50 push eax;上面转换的字串
:00401147 8D95E4FEFFFFlea edx, dword ptr [ebp+FFFFFEE4]
:0040114D 52 push edx;假注册码
* Reference To: KERNEL32.lstrcmpA, Ord:0000h
|
:0040114E E8339C0000 Call 0040AD86;比较
:00401153 85C0test eax, eax
:00401155 750Djne 00401164;这里就是关键的跳转
* Possible StringData Ref from Data Obj ->"Congratulations! IF this number "
->"comes *FROM YOUR* keygen, Write "
->"a tutorial dude ;)."
|
:00401157 683CB44000 push 0040B43C;指向表示成功的字符串
:0040115C 56 push esi;ESI还记得么?那个提示文本框的句柄
* Reference To: USER32.SetWindowTextA, Ord:0000h
|
:0040115D E8289B0000 Call 0040AC8A;显示出来
:00401162 EB18jmp 0040117C
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:00401155(C)
|
* Possible StringData Ref from Data Obj ->"This serial is *NOT* Valid!! Try "
->"again... : UNREGISTERED"
|
:00401164 6890B44000 push 0040B490;开始时停在这句,向上找跳转
:00401169 56 push esi;ESI提示文本框的句柄
* Reference To: USER32.SetWindowTextA, Ord:0000h
|
:0040116A E81B9B0000 Call 0040AC8A
:0040116F EB0Bjmp 0040117C
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:004010F4(C), :00401106(C)
|
* Possible StringData Ref from Data Obj ->"Name must contain more than 4 "
->"chars and less than 50 chars !!"
|
:00401171 68C9B44000 push 0040B4C9;用户名不符合要求跳到这里
:00401176 56 push esi;ESI提示文本框的句柄
* Reference To: USER32.SetWindowTextA, Ord:0000h
|
:00401177 E80E9B0000 Call 0040AC8A
* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:00401162(U), :0040116F(U)
|
:0040117C 5F pop edi
:0040117D 5E pop esi
:0040117E 5B pop ebx
:0040117F 8BE5mov esp, ebp
:00401181 5D pop ebp;整理一下返回了
:00401182 C3 ret
--------------------------------------------------------------------------------
双击后光标停在401164这一句。很明显,如果我们来到这句时我们就死翘翘了,而如果我们的注册码正确的话当然不会来到这一句(废话太多了:p)那么这一句上面就肯定会有一个条件跳转的指令。(这是找爆破点时的基本思想)向上找找看,找到了:
:00401155 750Djne 00401164
正好跳到401164错误信息那一句。呵呵,如果你想爆破的话,只要把750D改成740D(je,把条件反过来,注册码错误就显示正确信息:D)或者改成EB0D(jmp,无条件跳转,不管三七二十一就正确)。
OK,我们不能满足于此啊,咱们看看它的算法是怎样的,也像那些神秘兮兮的高手似的写一个注册机出来。:D
我先给各位补一点课,就是对函数的调用。除了一些DELPHI程序之外,对函数参数的传递大都用堆栈来完成,简单地说就是把函数的各个参数先PUSH进去,然后再CALL这个函数。在函数内部呢,一般[ebp+8]是第一个参数,[ebp+C]是第二个参数,每次多加4依此类推。而函数内部的局部变量常用[ebp-4][ebp-8]...等等。(原因讲起来有点复杂,先记住就行了)函数的返回值在EAX里。
一般来说,软件的判断注册部分都是一个函数,在函数开头最经典的两句就是
push ebp
mov ebp,esp
这和堆栈处理有关,我们菜鸟先不用太明白,知道这通常是一个函数的开始就行了。向上找找有没有丫,找到了没有,在最上面哪(我上面没有列出来)。如果你想完整地判断它的算法的话,一般从这里开始就行了。在这个程序中前面都是一些初始化之类的东东,所以我把前面一部分省略了。(这也是破解时的原则,不要在无关紧要的地方费功夫,在高级语言中,代码有很大部分是机器自动生成的,电脑一行行写代码不知道累,人脑一行行读代码怎么受得了?你的脑袋是几GHz的CPU?常见有些没有破解经验的汇编高手,完全懂得每行代码的意思,就是找不到关键的地方,原来他跟了半天都是在API里转,白做无用功了。)
好了,现在可以动态调试了。我们在开头这里下个断点,一步步向下看,出现了一个CALL GetDlgItem,我们来看一看函数说明(手头一份这个是必需的)
HWND GetDlgItem(
HWND hDlg, // handle of dialog box
int nIDDlgItem // identifier of control
);
呵呵,简单的说这个函数就是让程序确定一个对话框上的控件,第一个参数是对话框的句柄,第二个参数是对话框上某个控件的ID,函数会返回该控件的句柄,这样在下面就可以用这个句柄来操作了。看程序:
代码:--------------------------------------------------------------------------------
* Possible Reference to Dialog: DialogID_0001, CONTROL_ID:0066, ""
|
:00401085 6A66push 00000066;控件ID
:00401087 53 push ebx;对话框句柄
* Reference To: USER32.GetDlgItem, Ord:0000h
|
:00401088 E8159C0000 Call 0040ACA2
--------------------------------------------------------------------------------
看见第一行没有,DialogID_0001,CONTROL_ID:0066,压进去了一个66作为第二个参数:控件ID(注意API调用是从右至左,也就是最后面一个参数先PUSH),用 资源查看工具 看看ID为66的是甚么呀,呵呵就是那个输入用户名的文本框嘛。好了,现在我们有文本框的句柄了,存在EAX里。接着向下看,又一个API,是GetWindowText。看看说明:
int GetWindowText(
HWND hWnd, // handle of window or control with text
LPTSTR lpString, // address of buffer for text
int nMaxCount // maximum number of characters to copy
);
从名字也能猜出来了,这个函数就是得到一个窗口类控件的文本。第一个参数是该控件的句柄,第二个是存放得到的文本的缓冲区的地址,第三个参数设定取文本的最大长度。看程序:
代码:--------------------------------------------------------------------------------
:0040108D 6A64push 00000064;最大长度
:0040108F 8D9548FFFFFFlea edx, dword ptr [ebp+FFFFFF48];把[ebp+FFFFFF48]先放在EDX里
:00401095 52 push edx;缓冲区地址[ebp+FFFFFF48]
:00401096 50 push eax;EAX?是上面那个API的返回值呀,控件句柄
* Reference To: USER32.GetWindowTextA, Ord:0000h
|
:00401097 E8129C0000 Call 0040ACAE
--------------------------------------------------------------------------------
好了,现在 D ebp+FFFFFF48 看看,是不是输入的用户名?(说明,在OLLY或SICE里这个是[ebp-B8]的形式,其实是一样的)
下面有类似的操作,只是控件ID成了68,可想而知就是得到注册码了,注册码放在[ebp+FFFFFEE4]。
呵呵,继续,到了这么几句:
代码:--------------------------------------------------------------------------------
:004010BD 8D8548FFFFFFlea eax, dword ptr [ebp+FFFFFF48]
:004010C3 50 push eax
:004010C4 E867050000 call 00401630
--------------------------------------------------------------------------------