管理员
|
阅读:2910回复:0
Win32汇编初探内存补丁GUI技术
楼主#
更多
发布于:2011-12-19 17:43
| | | | 对于每个Cracker,补丁是再熟悉不过的东西了。补丁的形式大致有两种,一种是文件补丁,一种是内存补丁。两者的根本区别在于文件补丁对程序的部分代码是永久性修改,而内存补丁却是在程序代码映射到内存的时候才做出修改,所以内存补丁的一大好处就是保持了程序的完整性,却能使用到修改后的程序。尤其对现在的一些加壳软件,内存补丁的作用越来越明显。 破解调试用到的OD就附带了一个内存补丁的功能。载入OD,对某个语句进行修改,然后按“Ctrl+P”就可以看到OD的内存补丁管理窗口。我们可以通过键盘上的空格键对补丁进行激活或者禁用。OD这个功能对一些破解新手来说是非常有用的,当对关键跳转不确定的时候,直接用个内存补丁运行一下判断是不是真正的关键跳转,如图1所示。但是OD这个功能还有一点小小的不足,就是每次F9运行后要重新载入,然后再找回那个补丁的位置。一两次还好,如果次数多了也挺烦人的。现在我们就用Win32汇编来实现OD这个功能,要有自己的GUI界面,而且不像OD每补一次就重新载入那么麻烦。 初步分析 要修改程序的内存,必须要有足够的权限来打开这个程序的进程,然后才可以通过API函数读写要修改程序的内存数据。确定了总的思路后,我们开始分步来实现。 首先是打开进程,我们可以用CreatProcess函数创建对象程序的进程,然后用ReadProcessMemory和WriteProcessMemory来读取和写入进程的内存地址。界面用输入字符的形式来实现,这里就涉及了字符转16进制数值的问题,我们可以用GetDlgItemText获得字符串,然后进行简单的ASCII码加减来实现转换。 下面简单介绍一下最核心的两个函数,这两个函数都在kernel32.lib输出库里面。ReadProcessMemory函数用来读取指定进程的空间的数据,此空间必须是可以访问的,否则读取操作会失败!函数原型如下。 BOOL ReadProcessMemory( HANDLE hProcess, //目标进程句柄,必须要足够的权限才能打开 LPCVOID lpBaseAddress, //读取数据的起始地址 LPVOID lpBuffer, //存放数据的缓存区地址 DWORD nSize, //要读取的字节数 LPDWORD lpNumberOfBytesRead //实际读取数存放地址 ); BOOL WriteProcessMemory( HANDLE hProcess, //要写进程的句柄,也是要足够权限才能写入 LPVOID lpBaseAddress, //写内存的起始地址 LPVOID lpBuffer, //写入数据的地址 DWORD nSize, //要写的字节数 LPDWORD lpNumberOfBytesWritten //实际写入的字节数 ); 编写代码 我们用一个最简单的例子分析一下。test.exe是一个非常简单的测试程序,双击运行就会出现“Sorry”提示,如图2所示。用OD逆向这个程序,我们发现在00401004那里有个je跳转是跳到“Sorry”提示的,如图3所示。如果我们把je改为jnz或者nop掉,就能成功爆破这个程序了。该句是“74 15”,我们只要改为“75 15”或者“90 90”就可以了。分析完最基本的流程,下面是编写核心程序代码,该代码的作用就是创建进程和读写内存的。 图2 图3 .data? dbOldByte db 2 dup(?) stStartUp STARTUPINFO <?> stProcInfo PROCESS_INFORMATION <?> PATCH_POSITION dd ? ;想爆破的地址 dbPatch dd ? ;爆破前的指令 dbPatched dd ? ;爆破后的指令 .const szExecFilename db "test.exe",0 ;定义文件名 szErrExec db "无法装载执行文件",0 szErrVersion db "执行文件的版本不正确,无法修正",0 //以下为核心子程序,用来修改内存代码 _ProcMemory proc //创建进程 invoke GetStartupInfo,addr stStartUp invoke CreateProcess,offset szExecFilename,0,0,0,0, NORMAL_PRIORITY_CLASS or CREATE_SUSPENDED,0,0, offset stStartUp,offset stProcInfo .if eax ;判断eax == 1 ;读进程内存并验证内容是否正确 invoke ReadProcessMemory,stProcInfo.hProcess,PATCH_POSITION, addr dbOldByte,2,NULL .if eax mov ax,word ptr dbOldByte .if ax == word ptr dbPatch invoke WriteProcessMemory,stProcInfo.hProcess, PATCH_POSITION,addr dbPatched,2,0 invoke ResumeThread,stProcInfo.hThread .else invoke TerminateProcess,stProcInfo.hProcess,-1 invoke MessageBox,0,addr szErrVersion,0, MB_OK or MB_ICONSTOP .endif invoke CloseHandle,stProcInfo.hProcess invoke CloseHandle,stProcInfo.dwThreadId .endif .else ;不能创建进程时显示出错提示 invoke MessageBox,NULL,addr szErrExec,NULL,MB_OK or MB_ICONSTOP .endif ret _ProcMemory endp 完成了核心代码之后,就要考虑GUI界面部分了,要由用户输入想要修改的地址和修改的内容。我们先用RadAsm创建一个对话框,添加三个编辑框和一个按钮,如图4所示。首先是限制用户输入的字符串,因为只能输入16进制的字符串,所以我们就用窗体子类化来实现。下面的子程序就是实现窗体子类化的。 szAllowedChar db "0123456789ABCDEFabcdef",08h ;定义有效按键,08h为退格键 lpOldProcEdit dd ? //编辑框的新窗体过程 _ProcEdit proc uses ebx edi esi hWnd,uMsg,wParam,lParam mov eax,uMsg .if uMsg == WM_CHAR ;只接受我们需要的WM_CHAR字符信息 mov eax,wParam mov edi,offset szAllowedChar mov ecx,sizeof szAllowedChar repnz scasb ;当ZF=0或比较结果不相等,且CX/ECX<>0时重复 ;使用scasb指令查表 .if ZERO? ;判断零标志位是否被置位 .if al > 9 ;判断是否输入字母,因为表中大于9的肯定是字母 and al,not 20h ;将字母转换为大写,not为取反。小写从61h开始,大写从41h开始,只要第六位是0,就肯定是大写了 .endif ;CallWindowProc是将WM_CHAR消息转发给主窗体 invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,eax,lParam ret .endif .else invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret _ProcEdit endp 但是我们有三个输入框,这样要是每个都子类化的话就显得麻烦了,干脆用超类化为这三个输入框建立一个新的类好了。我们用下面的子程序来实现。 //控件超类化,建立新的Edit类,限制输入位数和输入字符 _SuperCla
| | | | |
|