本帖最后由 yysniper 于 2015-3-18 23:15 编辑
一个简单crackme破解及注册机编写——FOR新手
【破解作者】yysniper 【面向范围】新手小白 【使用工具】原版OD、PEID 【破解平台】windows xp sp3 【目 标】 【破解说明】无壳、算法简单 【相关附件】
【更多教程】参考我的另一个资源帖http://www.52pojie.cn/thread-338571-1-1.html,本文中的crackme就是该帖附件第十四章中的
【破解过程】
第一步:运行软件查看情况
第二步:查壳
第三步:开始破解
任务一:禁用Splash Screen
载入OD停在了入口处。然后搜索字符串
上图中我们可以看到类似“Splish,Splash”或者“Splash_Class”的字符串,比较可疑呀!果断在第一个“Splash_Class”下断,快捷键F2搞定(如下图)。
F9运行程序,断在了4014DD处
上图中我们可以看到断点上面有个LoadBitmapA调用,下面的两个API调用,分别是CreateWindowExA和ShowWindow。这些看起来像是Splash Screen的显示代码。当然要让该窗口不显示的话,这里提供两种方法: 第一种:ShowWindow函数有两个参数,hWnd参数是要显示窗口的句柄,那么这个句柄从哪来的呢?看代码PUSH DWORD PTR DS:[403211],这里有个地址。我们往上找,有个MOV DWORD PTR DS:[403211],EAX。显然该地址中存放的是EAX的值,再往上是对CreateWindowExA的调用。那么EAX就是CreateWindowExA的返回值。所以我们可以修改EAX的值为0,让hWnd值为0,那么ShowWindow就不会显示任何窗口。不过我们不能每次都修改寄存器吧!我可以将PUSH DWORD PTR DS:[403211]改为PUSH 0,那么不管CreateWindowExA返回什么都和我们没有关系了,双击PUSH DWORD PTR DS:[403211]这行
第二种:就是我直接让ShowWindow函数消失,将该函数直接改成nop。右键CPU窗口:
点击那个Fill with NOPs就直接将ShowWindow的指令变成了nop,也就是什么都不做。 这里我用的是第二种方法。改完了,我们要将改动保存到exe文件中,这样以后就不用管这个启动窗口了。那么问题来了,改动后的汇编代码怎么保存到exe文件中让其生效呢?看下图:
没错,依然是右击CPU窗口,在弹出的菜单中选择Copy to executable->All modifications,这时会弹出
这里我们点击Copy all,然后弹出
在File窗口(也就是上图)中右击选择Save File
然后在弹出的另存为对话框中点击保存,这里我将改动后的程序命名为Splish_noSplash.exe
保存后我们双击Splish_noSplash.exe,可以看到启动窗口不见了,我们的任务一完成了。
任务二:找到Hard Coded序列号。
我们在第一步中可以看到,点击check hardcoded按钮后,弹出的对话框中有句话“sorry,please try again.”。所以我们依然搜索字符串试试看。OD载入我们修改后的Splish_noSplash.exe,然后停在了入口处,依然右键CPU窗口,查找字符串
F2果断下断。F9运行起来(任务一后,没有启动窗口吧),随便输入一个hard coded,我这里输入yysniper
然后点击Check Hardcoded按钮,不久程序暂停了,停在了我们刚下的断点处
我们看到在“sorry,please try again”上面就是祝贺找到硬编码的提示框代码。我们点击提示失败的代码的第一行也就是PUSH 0(如下图),我们看到左边有个红色箭头,说明是从301386跳转过来的。
我们分析从40136F到40138A的代码,可以看出程序是在对EAX和EBX中的内容进行逐个的比较,如果不同就跳转到提示失败的消息框处。那么这肯定对比的是我们输入的字符串和正确的字符串。我们再40137B处下断,看看EAX和EBX中到底是什么。F9继续运行程序,再次点击Check Hardcoded按钮,程序停下了
我可以看到图片中红色箭头处,EAX中是“HardCoded”,EBX则是我输入的“yysniper”。好吧我们来试试“HardCoded”是不是我们要找的
看来我们找对了,任务二完成。
任务三:对Name/Serial部分写注册机。
先删除我们刚才下的所有断点:
我们在Name和Serial中随便输入内容,点击后同样弹出了“sorry,please try again”,我们可不可以通过搜索字符串然后下断呢?当然可以。不过这里我们换一个方法,右键CPU窗口,如下图:
我们点击Name (label)in current module Ctrl+N,也就是搜索当前模块中调用的API,弹出如下窗口:
我们看到了GetWindowTextA函数,一般程序可以通过该函数获取到用户输入在文本框中的内容,我们对它下断试试:
这里不要再F2了,右击该函数然后选择Toggle breakpoint on import或者第二项Follow import in Disassembler后在CPU窗口中再F2。这里我选择Toggle breakpoint on import。
断点窗口中我们可以看到,我们的断点下在了USER32模块中。 F9运行程序,窗口出现了,我在Name那填入yysniper,Serial那填入456987,如下图:
然后点击,Name/Serial Check按钮。程序暂停了,停在了下面这里
我们看窗口标题栏那个USER32,断点停在了系统领空,我们可以按下Ctrl+F9或者如下图
效果是一样的。然后我的停在了红色箭头处如下图,你的不一定停在这,有可能直接回到了程序领空。不过没关系,如果你的停在了箭头处,再按一下F7就行了,就回到了程序领空。
回到程序领空后如下图:
注意上面的四个箭头,很明显两个GetWindowTextA,两个MessageBoxA。可以看到这两个MessageBoxA是用来提醒用户输入Name和Serial的,如果你留空的话。我们将CPU窗口往下拉,可以看到另外两个MessageBoxA,如下图:
我们看到了两个MessageBoxA,其中一个有成功的提示。那么我们看看从GetWindowTextA一直到提示成功的汇编代码,发现没有其他的call,说明我们输入的name和serial是在这些汇编代码中进行处理的。而且在提示成功的上面我们可以明显的看出是一个按字节进行比较判断跳转的代码。继续我们刚才的断点,我们的程序停在了第一个GetWindowTextA的后面,我们看看该函数的返回值,也就是EAX寄存器的值如下图:
咦!!!EAX怎么是个6,我们输入的内容呢?表着急,这时你可以查下MSDN或百度,看看GetWindowTextA的说明。我们看到GetWindowTextA上面有个Buffer,没错我们输入的内容就在这个Buffer中,也就是403242这个地址中。我们选中该行,可以在说明窗口中看到
这不就是我们输入的serial吗,EAX中保存的是我输入serial的长度。记住这个403242地址。 然后F8单步执行直到第二个GetWindowTextA执行完毕,我们输入的name在403236这个地址中。 下面我们分析第二个GetWindowTextA到提示“please enter your name”的消息框中的汇编代码。我们发现正是这些代码对我们输入的name和serial进行操作,算出正确的序列号。下面按行分析代码
我将代码拷贝下来了,follow me:
[Asm] 纯文本查看 复制代码 00401612 |. 85C0 TEST EAX,EAX
00401614 |. 74 68 JE SHORT Splish_n.0040167E ;这两行用来判断name框是不是空的,如果是就跳转提示输入name
00401616 |. A3 63344000 MOV DWORD PTR DS:[403463],EAX ;从这开始对我输入的name进行处理,
0040161B |. 33C9 XOR ECX,ECX ;ECX清零
0040161D |. 33DB XOR EBX,EBX ;EBX清零
0040161F |. 33D2 XOR EDX,EDX ;EDX清零
00401621 |. 8D35 36324000 LEA ESI,DWORD PTR DS:[403236] ;将name地址存入ESI,通过403236这个地址我们知道是对name的处理,我前面不是让你记住这个地址了吗
00401627 |. 8D3D 58324000 LEA EDI,DWORD PTR DS:[403258] ;EDI清零,要想知道为嘛,你可以看看该地址中的内容
0040162D |. B9 0A000000 MOV ECX,0A ;ECX赋值10
00401632 |> 0FBE041E /MOVSX EAX,BYTE PTR DS:[ESI+EBX] ;按字节将ESI内容存入EAX
00401636 |. 99 |CDQ ;EAX符号位扩展至EDX
00401637 |. F7F9 |IDIV ECX ;除法操作,没什么好说的
00401639 |. 33D3 |XOR EDX,EBX ;这里注意,EDX保存的是除法的余数,与EBX进行异或操作然后在保存在EDX中
0040163B |. 83C2 02 |ADD EDX,2 ;EDX加2
0040163E |. 80FA 0A |CMP DL,0A ;其实就是EDX与10比大小
00401641 |. 7C 03 |JL SHORT Splish_n.00401646 ;如果小于就跳转到401640
00401643 |. 80EA 0A |SUB DL,0A ;否则就减去10
00401646 |> 88141F |MOV BYTE PTR DS:[EDI+EBX],DL ;将EDX存入EDI
00401649 |. 43 |INC EBX ;EBX加1
0040164A |. 3B1D 63344000 |CMP EBX,DWORD PTR DS:[403463] ;与字符串长度进行比较
00401650 |.^ 75 E0 \JNZ SHORT Splish_n.00401632 ;跳转进行下一个字符处理
下面的汇编代码是对我输入的序列号进行处理,与上面的类似,相对简单就不注释了
[Asm] 纯文本查看 复制代码 00401652 |. 33C9 XOR ECX,ECX
00401654 |. 33DB XOR EBX,EBX
00401656 |. 33D2 XOR EDX,EDX
00401658 |. 8D35 42324000 LEA ESI,DWORD PTR DS:[403242]
0040165E |. 8D3D 4D324000 LEA EDI,DWORD PTR DS:[40324D]
00401664 |. B9 0A000000 MOV ECX,0A
00401669 |> 0FBE041E /MOVSX EAX,BYTE PTR DS:[ESI+EBX]
0040166D |. 99 |CDQ
0040166E |. F7F9 |IDIV ECX
00401670 |. 88141F |MOV BYTE PTR DS:[EDI+EBX],DL
00401673 |. 43 |INC EBX
00401674 |. 3B1D 67344000 |CMP EBX,DWORD PTR DS:[403467]
0040167A |.^ 75 ED \JNZ SHORT Splish_n.00401669
0040167C |. EB 2A JMP SHORT Splish_n.004016A8
分析上面的两段代码,可以看到处理后的name和serial分别存放在403258和40324D中。我们再回头看看提示成功的消息框前面的代码:
上面的代码中两个地址存放的正是处理后的name和serial,然后对两个处理后的数据进行比较,如果相同则提示成功。 好了,关键代码找到了,也分析完了,是时候编写注册机了。这里我用脚本语言python写,也推荐大家学学python,强大好用。我按照汇编的代码步骤进行编写,先写处理name的代码,汇编与python对照着写:
[Asm] 纯文本查看 复制代码
XOR ECX,ECX
XOR EBX,EBX
XOR EDX,EDX
LEA ESI,DWORD PTR DS:[403236]
LEA EDI,DWORD PTR DS:[403258]
MOV ECX,0A
MOVSX EAX,BYTE PTR DS:[ESI+EBX]
CDQ
IDIV ECX
XOR EDX,EBX
ADD EDX,2
CMP DL,0A
JL SHORT Splish_n.00401646
SUB DL,0A
MOV BYTE PTR DS:[EDI+EBX],DL
INC EBX
CMP EBX,DWORD PTR DS:[403463]
JNZ SHORT Splish_n.00401632 | [Python] 纯文本查看 复制代码 #这里我就用寄存器的名字作为变量,注意是变量和#寄存器没有关系的,最终结果在变量edi中
ecx=0
ebx=0
edx=0
esi="yysniper"
edi=""
ecx=10
for i in esi:
eax=i
edx=ord(eax) % ecx
edx=edx^ebx
edx=edx+2
if edx<10:
edi=edi+str(edx)
else:
edi=edi+str(edx-10)
ebx=ebx+1 |
同样编写处理serial的代码:
[Asm] 纯文本查看 复制代码 XOR ECX,ECX
XOR EBX,EBX
XOR EDX,EDX
LEA ESI,DWORD PTR DS:[403242]
LEA EDI,DWORD PTR DS:[40324D]
MOV ECX,0A
MOVSX EAX,BYTE PTR DS:[ESI+EBX]
CDQ
IDIV ECX
MOV BYTE PTR DS:[EDI+EBX],DL
INC EBX
CMP EBX,DWORD PTR DS:[403467]
JNZ SHORT Splish_n.00401669 | [Python] 纯文本查看 复制代码 #和上面的处理name的一样,用寄存器的名字做变#量名,最终结果在edi中
ecx=0
ebx=0
edx=0
esi="456987"
edi=""
ecx=10
for i in esi:
eax=i
edx=ord(eax) % ecx
edi=edi+str(edx) |
我们可以看到,对serial的处理其实就是逐个取用户输入的序列号,然后再对10取余,只要余数与对应的name处理结果一致就行。所以我们只要将name的处理结果的每一位数字加上10的倍数就行,当然得是可打印显示的asc字符,所以同一个name可以有不唯一的序列号。下面我将上面的python代码进行改良,贴出完整的keygen代码(该代码可在python3环境下执行):
[Python] 纯文本查看 复制代码 #coding=utf8
def procname(strname):
"""
对用户输入的name进行处理,得到中间码
"""
ecx=0
ebx=0
edx=0
esi=strname
edi=""
ecx=10
for i in esi:
eax=i
edx=ord(eax) % ecx
edx=edx^ebx
edx=edx+2
if edx<10:
edi=edi+str(edx)
else:
edi=edi+str(edx-10)
ebx=ebx+1
return edi
def procserial(strserial):
"""
对用户输入的serial进行处理,得到中间码。这里只是给出程序作者的处理代码,其实注册机用不到
这段代码
"""
ecx=0
ebx=0
edx=0
esi=strserial
edi=""
ecx=10
for i in esi:
eax=i
edx=ord(eax) % ecx
edi=edi+str(edx)
def keygen(strmidcode):
realserial=""
for i in strmidcode:
realserial=realserial+chr(int(i)+70)
return realserial
#我们看看yysniper的对应serial是多少
print(keygen(procname("yysniper")))
最后计算出yysniper的serial是IHOKIOOK。我们运行试试:
完美解决,任务三完成。 所有的任务完成。
感言:第一次写教程,虽然很简单,但是真是不容易,无论是写教程还是发帖子。写完后特别感谢那些写教程的大牛,因为你们真是不容易,感谢你们。最后请大家批评指正!!!
|