反汇编基本结构.doc_第1页
反汇编基本结构.doc_第2页
反汇编基本结构.doc_第3页
反汇编基本结构.doc_第4页
反汇编基本结构.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

反汇编基本结构 (转) 1、关于函数调用,在分析汇编代码时总是要遇到无数的Call,对于这些Call,尽量要根据Call之前传递的参数和Call的返回值来判断Call的功能,特别当调用的是系统函数时,不要见Call就跟,在Call之前所做的所有PUSH动作以及对寄存器的操作都可能是在给函数传递参数,而函数的返回值十有八九在 EAX里面,因为程序绝大多数都使用高级语言开发,而几乎所有的高级语言编译器都默认将函数的返回值存放到EAX。2、关于堆栈,善于使用 ESP会收到事半功倍的效果,在一个算法中,任何可能使用的常量、变量和函数地址都必然在当前 ESP中留下蛛丝马迹,由于变量是以地址的形势进行存储的,所以建议经常使用dd ESP指令来观察堆栈内容,注意在汇编代码中凡是ESP+XXXXXXXX都代表着堆栈中的第XXXXXXXX个房间住的那个房客。3、关于变量的赋值,能否了解到对变量的赋值过程在算法研究中是非常重要的。由于变量是用地址访问的,因此对形如MOV AAA,BBB的代码要高度关注,它通常是修改变量(地址为AAA,或AAA为寄存器时地址为AAA的值)的值为BBB(BBB为寄存器时取BBB的值)。1.1 调用约定 在分析汇编代码时总是要遇到无数的Call,对于这些Call,尽量要根据Call之前传递的参数和Call的返回值来判断Call的功能。传递参数的工作必须由函数调用者和函数本身来协调,计算机提供了一种被称为栈的数据结构来支持参数传递。当参数个数多于一个时,按照什么顺序把参数压入堆栈。函数调用后,由谁来把堆栈恢复。在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:见图123。【例】按_stdcall约定调用函数test2(Par1, Par2) push par2 ; 参数2push par1 ; 参数1call test2;push ebp ; 保护现场原先的EBP指针mov ebp, esp ; 设置新的EBP指针,指向栈顶mov eax, ebp+0C ; 调用参数2mov ebx, ebp+08 ; 调用参数1sub esp, 8 ; 若函数要用局部变量,则要在堆栈中留出点空间add esp, 8 ; 释放局部变量占用的堆栈pop ebp ; 恢复现场的ebp指针ret 8 ; 返回(相当于ret; add esp,8)其堆栈调用示意图:1.2 局部变量 在子程序内部说明的变量称为局部变量,局部变量的作用域是其所在的子程序。从汇编角度来看,局部变量就是一个临时堆栈缓存,用完释放。例如这个实例:附件:local.zip 其反汇编代码如下(红体字为局部变量):00401000 /$ 6A 04 push 4 ; /Arg2 = 0000000400401002 |. 6A 03 push 3 ; |Arg1 = 0000000300401004 |. E8 16000000 call 0040101F ; Add.0040101F00401009 |. 8BD8 mov ebx, eax0040100B |. 6A 00 push 0 ; /ExitCode = 00040100D . FF15 00204000 call ; ExitProcess0040101F /$ 55 push ebp ; 保护现场原先的EBP指针00401020 |. 8BEC mov ebp, esp ; 设置新的EBP指针,指向栈顶00401022 |. 83EC 04 sub esp, 4 ; 分配局部变量所有空间00401025 |. 8B45 0C mov eax, ebp+C ; 调用参数200401028 |. 8B5D 08 mov ebx, ebp+8 ; 调用参数10040102B |. 895D FC mov ebp-4 , ebx ; 参数1放局部变量里0040102E |. 0345 FC add eax,ebp-4 ; 参数2与局部变量相加00401031 |. 83C4 04 add esp, 4 ; 释放局部变量所有空间00401034 |. 5D pop ebp ; 恢复现场的ebp指针00401035 . C2 0800 retn 81.3 返回值 在调试程序时,不要见Call就跟进,在Call之前所做的所有PUSH动作以及对寄存器的操作都可能是在给函数传递参数,而函数的返回值一般都放在EAX里面,当然这个值可能是一个指针,指向一个数据结构。从汇编角度来看,主要有如下形式:1)通过寄存器返回函数值;2)通过参数按引用方式返回函数值;3)通过全局变量返回函数值;4)通过处理器标志返回函数值;一般情况下,由retrun操作符返回的值放在EAX寄存器之中,如果结果超过这个寄存器的位容量,那么该结果的高32位会加载到EDX寄存器中。 如果返回一个含有几百个字节的结构或者一个近似大小的对象,编译器会在不告诉程序的情况下,给函数传递一个隐式参数,这个指针指向保存的返回结果。1.4.偏移量转换为虚拟地址1.4 启动函数 在编写Win32应用程序时,都必须在源码里实现一个WinMain函数。但Windows程序执行并不是从WinMain函数开始的,首先被执行的是启动函数相关代码,这段代码是编译器生成的。启动代码完成初始化进程,再调用WinMain。标准编译器通常包含启动代码在内的库文件源码,例如Visual C+中,启动代码存放在CRTSRCcrt0.c文件中。所有的C/C+运行时启动函数的作用基本都是相同的:检索指向新进程的命令行指针,检索指向新进程的环境变量指针,全局变量初始化,内存堆栈初始化等。当所有的初始化操作完毕后,启动函数就调用应用程序的进入点函数。调用WinMain如下所示:GetStartupInfo (&StartupInfo);Int nMainRetVal = WinMain(GetModuleHandle(NULL),NULL,pszCommandLineAnsi,( StartupInfo.dwFlags&STARTF_USESHOWWINDOW)?StartupInfo.wShowWindow:SW_SHOWDEFAULT);当进入点返回时,启动函数便调用C运行库期的exit函数,将返回值(nMainRetVal)传递给它,进行一些必要处理,最后调用系统函数ExitProcess退出。 其他一些编译器,如Delphi、BorLand C+开发包中都有相应的启动代码。在绝大数情况下,我们对启动代码并不需要关心。 对于逆向分析人员来说,首要的任务是找到Winmain函数。 WinMain函数原型如下:int WINAPI WinMain(HINSTANCE hInstance, / 当前实例的句柄HINSTANCE hPrevInstance, / 前一个实例的句柄 LPSTR lpCmdLine, / 命令行的指针 int nCmdShow / 窗口的显示状态); 其中参数hInstance一般通过GetModuleHandleA函数进行获取的,这对识别WinMain函数有些帮助。另外,对WinMain的调用通常放在启动函数代码结尾部分,后面通常跟着诸如exit或XcptFilter之内的两、三个函数。例如下面这段代码:.text:004010DC push eax ; nShowCmd.text:004010DD push ebp+lpCmdLine ; lpCmdLine.text:004010E0 push esi ; hPrevInstance.text:004010E1 push esi ; lpModuleName.text:004010E2 call ds:GetModuleHandleA.text:004010E8 push eax ; hInstance.text:004010E9 call WinMain(x,x,x,x).text:004010EE mov ebp+var_60, eax.text:004010F1 push eax ; int.text:004010F2 call _exit许多开发人员可以得到启动源代码的情况下对启动代码进行修改,这样,程序的执行可能不是从WinMain开始,而是从任何其他的函数开始。1.5 API函数 现在很多讲Windows程序设计的书都是讲基于MFC库和OWL库的Windows设计,对Windows实现的细节都鲜有讨论,而调试程序都是和系统底层打交道,所以有必要掌握一些Win32 API函数的知识,这样我们可快捷地找出程序调用错在哪?是哪个参数出了问题。Windows程序模块包括KERNEL、USER和GDI,其中KERNEL完成内存管理、程序的装人与执行和任务调度等功能,它需要调用原MS?DOS中的文件管理、磁盘输入输出和程序执行等功能;USER是一个程序库,它用来对声音、时钟、鼠标器及键盘输入等操作进行管理;GDI是一功能十分丰富的子程序库,它提供了图形与文字输出、图象操作和窗口管理等各种与显示和打印有关的功能。上述KERNEL、USER和GDI模块中的库函数可被应用程序调用,也可被其他程序模块调用。把包含库函数的模块称为输出者(export)。你应明白为什么跟踪软件时经常在KERNEL32!.text和USER32.text等系统领空转的问题吧。这里列出几个经常碰到的Win 32 API函数,它们都是存在Windows系统核心文件KERNEL32.DLL中和视窗管理文件USER32.DLL中。Windows函数是区分字符集的:A表示ANSI,W表示Wide,即Unicode (Wide character-set),前者就是通常使用的单字节方式,但这种方式处理象中文这样的双字节字符不方便,容易出现半个汉字的情况。而后者是双字节方式,方便处理双字节字符。Windows的所有与字符有关的函数都提供两种方式的版本。尽管你编程时使用GetWindowText,但实际上编译程序会根据设置自动调用GetWindowTextA或GetWindowTextW。函数的最后一个字母告诉我们函数是使用单字节还是双字节字符串。1、 Hmemcpy函数void hmemcpy(hpvDest, hpvSource, cbCopy) void _huge* hpvDest; / 目的数据地址 const void _huge* hpvSource; / 源数据地址 long cbCopy; / 数据大小 (Bytes) 这个函数在KERNEL32.DLL中,它很常用,俗称万能断点,但一般的编程书籍上很少提到,原因它是底层的东西,没有特殊需要,一般不直接调用。但的确它是很有用的!有意思的是它执行的操作很简单,只是将内存中的一块数据拷贝到另一个地方。注意:此函数只在Windows 9x系统上有效,在Win NT/2K系统上相关的函数是memcpy,但在Win NT/2K上不同于Windows 9x上,很少再调用memcpy来处理数据了,用此函数设断基本上什么也拦不住。2、 GetWindowText函数此函数在USER32.DLL用户模块中,它的作用是复制指定窗口中的字符到缓冲区。函数原型:int GetWindowText(HWND hWnd/欲获取文字的那个窗口的句柄 LPTSTR lpString /预定义的一个缓冲区,至少有cch+1个字符大小;随同窗口文字载入 int nMaxCount/lpString缓冲区的长度);16位:GetWindowText32位:GetWindowTextA,GetWindowTextW3、 GetDlgItemText此函数在USER32.DLL用户模块中,它的作用是返回对话框中某一个窗口的标题或文字。函数原型:UINT GetDlgItemText(HWND hDlg, / 对话框句柄int nIDDlgItem, /控制标识符LPTSTR lpString, / 预定义的一个字符缓冲区int nMaxCount/ 字符缓冲区的长度);16位:GetDlgItemText32位:GetDlgItemTextA,GetDlgItemTextW4、 MessageBox函数此函数是在USER32.DLL用户模块中,它的作用创建、显示和操作信息框。函数原型:int MessageBox(HWND hWnd, /窗口句柄LPCTSTR lpText, / 信息框中文字的地址LPCTSTR lpCaption, / 信息框标题地址UINT uType / 信息框类型);16位:MessageBox32位:MessageBoxA,MessageBoxW学习API函数最好的资料就是Windows程序设计这本书,下面列出其他一些参考资料:第三课 动态分析技术所谓动态分析是利用调试器,如OllyDBG一步一步地单步执行软件。常见的调试器有SoftICE,OllyDBG(简称OD)等。SoftICE是一款经典的调试工具,运行在Ring0级,可以调试驱动。但平时调试的程序都是Ring3级,因此推荐大家用OllyDBG,这款工具上手容易,功能十分强大,现在论坛上的文章基本都是用OllyDBG来讲解的。 名词解释 1.所谓领空,实际上是指:在某一时刻,CPU 的 CS:IP(EIP) 所指向的某一段代码的所有者所在的区域。3.2 Olldbg常见问题Q: OD中如何运行到光标所在处?A: 将光标移到目标位置,按F4.Q: 如何用OD修改可执行程序?A:直接在反汇编代码区更改,这时可以使用汇编代码更改,然后选中修改后的汇编代码,右击-复制到可执行文件-保存文件.Q:OD中的代码乱码,如:004365E0 db 68 ; CHAR h004365E1 db A4004365E2 db 7A ; CHAR z004365E3 db E5004365E4 db B8004365E5 db E8004365E6 db BBA:OD右键,分析/从模板中删除分析,如不行,按Ctrl+A重新分析Q:OD为什么删除了断点,重新加载的时候,这些断点都会重新出现A:设置ollydbg.ini,将配制文件里改成如下:Backup UDD files=1 (by kanxue)Q:如何还原到OD到分析前的状态?A:右键 分析/从模块中删除扫描Q:什么是UDD?A:OllyDbg 把所有程序或模块相关的信息保存至单独的文件中,并在模块重新加载时继续使用。这些信息包括了标签、注释、断点、监视、分析数据、条件等等Q:OD的数据窗口显示一个下划线,是什么意思?A:重定位加下划线Underline fixups,几乎所有的DLL和一部分程序都包含重定位,这样就可以在内存中的不同基地址加载模块了。当该项开启时,CPU反汇编窗口或CPU数据窗口中的重定位地址都将添加下划线。(xing_xsz)Q:如果已经知道某一CALL的具体作用,能否把后面所有相同的CALL都改成函数名形式?A:比如 CALL 110000 此中已经知道110000是一个核心计算则如下操作,让光标停在CALL 110000 这个语句上,按回车键会跳到110000的地址上去显示,之后让光标停在110000上,按shift 和; (分号) 其实就是完成一个:(冒号)的动作,输入名称,这回所有的调用110000处,都会显示CALL 你刚才输入的名称了.(nig回答)Q:用OD调试一些加壳程序,如Themida等,可能你会发现下断后(包括硬件断点),程序跑到断点时,OD会出现假死现像。A:打开OD配置文件ollydbg.ini,你会发现:Restore windows= 123346 /这个Restore windows可能会是一个很大的值现在只需要将Restore windows0,重新用OD调试程序,假死问题就消失了。 (kanxue)Q:运行A.exe,其会调用B.exe,如果用OD再附加B.exe,OD会死掉A:1.OD菜单,设置OD为即时调试器;2.将B.exe的入口改成CC,即INT 3指令,同时记下原指令3.运行A.exe,其调用B.exe,会导致异常,OD会自动启动加载B.exe,此时你将INT 3指令恢复原指令。4.到这步,你己可以任意调试B.exe了(kanxue)第四课 静态分析技术所谓静态分析即从反汇编出来的程序清单上分析,从提示信息入手进行分析。目前,大多数软件在设计时,都采用了人机对话方式。所谓人机对话,即在软件运行过程中,需要由用户选择的地方,软件即显示相应的提示信息,并等待用户按键选择。而在执行完某一段程序之后,便显示一串提示信息,以反映该段程序运行后的状态,是正常运行,还是出现错误,或者提示用户进行下一步工作的帮助信息。为此,如果我们对静态反汇编出来的程序清单进行阅读,可了解软件的编程思路,以便顺利破解。 常用的静态分析工具有W32DASM、C32Asm和IDA Pro等。4.1 认识PE格式在Win32平台上(包括Windows 95/98/ME/NT/2000/XP/2003/CE),可执行文件是PE(Portable Executable)格式。PE文件使用的是一个平面地址空间,所有代码和数据都被合并在一起,组成一个很大的结构。文件的内容被分割为不同的区块(Section,又称区段、节等),块中包含代码或数据。刚接触这块的朋友只需要简单了解一下PE格式,更具体的PE格式请参考脱壳基础知识入门(2006年版) PE相关名词解释如下:1入口点(Entry Point)程序在执行时的第一行代码的地址应该就是这个值。2文件偏移地址(File Offset)PE文件在磁盘上储存时,各数据的地址称文件偏移地址(File Offset)。用十六进制工具(例如Hex Workshop、WinHex等)打开文件显示的地址就是文件偏移地址。3虚拟地址(Virtual Address,VA)由于Windows程序是运行在386保护模式下,在保护模式下,程序访问存储器所使用的逻辑地址称为虚拟地址(Virual Address,VA)。与实地址模式下的分段地址类似,虚拟地址也可写成段:偏移量的形式,这里的段是指段选择器。4基地址(ImageBase)文件执行时将被映像到指定内存地址中,这个初始内存地址称为基址(ImageBase)。在Windows NT中,缺省的值是10000h;对于DLLs,缺省值为400000h。在Windows 9x中,10000h不能用来装入32位的执行文件,因为该地址处于所有进程共享的线性地址区域,因此Microsoft将Win32可执行文件的缺省基地址改变为400000h。5相对虚拟地址相对虚拟地址(Relative Virual Address,RVA)表示此段代码在内存中相对于基地址的偏移。即:相对虚拟地址(RVA)=虚拟地址(VA)-基址(ImageBase)。4.2 虚拟地址和偏移量转换在OllyDBG,IDA和W32Dasm下显示的地址值是虚拟地址(Virual Address,VA)。而十六进制工具里,如:Hiew、Hex Workshop等显示的地址就是文件地址,称之为偏移量(File offset) 。其转换原理是因为PE文件在磁盘上的数据结构与在内存中的结构是一致的,如下图:在实际操作时,使用 LordPE等工具很容易进行File offset与VA的转换。LordPE打开目标文件,点击FLC按钮,打开如下图的对话框,填入相应地址,点击DO按钮即可转换:4.3 文件类型分析文件分析是静态分析程序的第一步,通过相关工具显示欲调试文件的信息,如它是用什么语言写的,是否加壳等。常用的文件分析工具有PEID,FileInfo等。有关壳的相关知识等入门后,再参考相关教学,如 脱壳基础知识入门(2006年版) 。如果查到文件有壳,此时静态分析是没意义的,但可以用OD动态调试,分析程序算法。1.PEiDPEiD的GUI界面操作非常方便直观。它的原理是利用查特征串搜索来完成识别工作的。各种开发语言都有固定的启动代码部分,利用这点就可识别出是何种语言编编译的。同样,不同的壳也有其特征码,利用这点就可识别是被何种壳所加密。下面PEiD识别出这个软件是用Asprotect 1.2x加的壳。2.FileInfoFileInfo(简称Fi)另一款不错的文件检测工具。FI的具体用法 4.4 W32Dasm简介W32Dasm简介 4.5 IDA pro操作IDA简易教程 IDA里的中文字串 4.6 keymaker内存注册机Q:什么是某个软件的中段地址,指令长度,第一字节,这些数据怎么得到,这些数据在内存注册机中怎么应用?A:青色代表着注册码的保存模式绿色的是中断地址,中断地址一般选择注册码保存模式的下一句,或下几句地址,但必须保证程序中断到这个地址时注册码保存的值没有被任何东西修改或改变。如下面的例子,中断地址可以选在00401205和00401207,但不能选在0040120C这个地址,因为00401207这个Call过后会修改eax的值。红色的是中断的第一个字节红色加上蓝色的字节就是指令长度,如下面的例子选的中断地址是00401207,这个地址上有5个字节,所以指令长度是500401205 50 PUSH EAX ;eax中保存着真注册码00401206 52 PUSH EDX ; edx中保存着假注册码00401207 E8 68 FF FF FF CALL 00401174 ; 比较真假注册码0040120C 85 C0 TEST EAX,EAX ; 测试注册码真假结果0040120E 75 42 JNZ SHORT 00401252 ; 假则跳向错误,真则不跳(小虾回答)软件怎么判断我们是否注册了不要忘了,软件最终是按照人的思维做的,我们回到自身来,“如果是你,你怎么判断别人是否注册了呢”,“我要别人输入用户名和注册码啊”,聪明的想法,很多软件也是这样做的,如豪杰超级解霸。(但是不是所以的软件,方法太多了,友情提示:这个世界没有完全通用的东西,除了你聪明的大脑)具体一点呢?我们把用户名按照某种方法运算得到一个真正的注册码和用户输入的进行比较不就知道了吗?Yeah,也就是真正的注册码 = f(用户名)和Y = f(x) 是一样的然后就是很经典的比较了,为什么说经典呢?因为大概有60%的软件是这么做的,到底是什么比较呢,看看请注意这里会有错误处理的噢,在这之前呢,就是经典比较啊,如果这里的错误处理提示我们诸如:注册错误之类的东西,我们就很容易定位到经典比较了。那么上面的流程在汇编语言里面是怎么实现的呢?比较有2种方式,直接和间接,直接就是用:cmp x, y je (jne) label这里的x和y只是一个符号,实际上可能是寄存器和存储器间接的呢?调用一个子程序比较,如下面的代码if (strcmp(&x, & y) /如果strcmp返回值是1printf(“right”);else./当然是错误拉这里的strcmp也只是一个符号,现实可能有变化用汇编语言描述呢?push &y;push &x;call strcmp;test ax,ax ;判断返回值(也就是出口参数,也可以在子程序里判断)je2为什么可以调试可执行程序呢可能很多菜鸟有我这样的疑问,呵呵调试可执行程序的理论基础:我们都知道在汇编语言里面可以用debug来调试程序。但是为什么可以?其实和简单,因为机器只识别的是0和1(准确的说你高电平和底电平,你可以简单的理解为灯泡亮和黑),我们称之为机器码,而我们的汇编语言与机器码是一一对应的,所以我们可以根据机器码得到对应的汇编代码,也可以反过来通过汇编代码得到对应的机器码,如:在debug下我们可以看到用debug测试一下:-a1370:0100 mov ax,bx1370:0102-u100 1370:0100 89D8 MOV AX,BX这里的89D8就是MOV AX,BX的机器码3破解教程都告诉我们,找到错误提示上面的第一个有条件跳转,改掉就可以爆破,为什么呢?因为程序是顺序执行的,只要我们找到提示出错的地方,那么在此之前必定已经比较完了,所以再往前面找找就看到了关键的比较,关键的地方就是上面的比较,如果我们改变的判断条件呢?如果改成不相等就注册成功,那么。(嘿嘿,某同志传来不怀好意的笑声),那么不管我们输入什么都是“正版”的了,没有交钱的“正版”,这就是我们改变跳转的原因,也就是改变改变的判断条件5下面我们开始实战演习,虽然这只是一个简单的用S-Demo做的动画,但是看完了下面的文章你还是会收获很多,不相信,我晕,把简单的事情做到极限就成功了,呵呵(1).爆破这个很简单就不多讲了,运行程序,随便输入密码,确定,提示:“password wrong”,用ollydbg载入这个动画,查找程序用到的字符串,找到password wrong,下个断点,还记得前面的理论吗?再向前找找就可以找到比较的关键地方了(条件跳转),好的,找到的地址是:0040203F0040202A |. FFB6 A4000000 PUSH DWORD PTR DS:ESI+A4 ; /s2下个断点,看看到底压入了堆栈什么00402030 |. 8D45 CC LEA EAX,DWORD PTR SS:EBP-34 ; |00402033 |. 50 PUSH EAX ; |s100402034 |. FF15 E0234100 CALL DWORD PTR DS: ; _stricmp /这里看到了什么,stricmp,难道这就是传说中的关键比较吗?但是比较之后没有跳转啊,是吗?仔细看看,比较之后的结果放在那里,eax !下面不是有一个je吗?0040203A |. 83C4 20 ADD ESP,20 ;平衡堆栈0040203D |. 85C0 TEST EAX,EAX ;测试返回值0040203F |. 74 15 JE SHORT test.00402056修改为jne,保存,运行,ok,搞定,请你再次回顾前面的流程图(2).寻寻觅觅找密码还记得前面的流程图吗?如果我们在程序比较的时候中断程序会有什么发现呢?呵呵,这个时候会看到真正的密码,重新用ollydbg载入这个动画,下断点00402034为什么要在这里下断点?好问题,因为这里看到了call DWORD PTR DS:,看看流程图,明白了吗,呵呵这里我们在堆栈区域可以看到我们输入试炼码和真正的密码,为什么在堆栈区域,因为在windows下通过堆栈传递参数。请看上面的简单分析,破解补丁的编写(3).文件补丁的编写(c语言简单实现)前面我们把je改成了jne,随便输入密码都可以了实质是把机器码由74h改成75h,因为机器码和汇编指令是一一对应的,那么我们只要写个小东西,修改就可以了。我已经写好了,很短,很好懂。Crack.c,我们分析一下#include #include int main(void)FILE *fp_out; /要写入的文件printf(ntttt Copy Right by ngautn);printf(Cracking.n);/打开文件test.exe if (fp_out = fopen(test.exe, r+)=NULL) printf(error! Can not open test.exe!nn);printf(Press any key to continuen);getchar();exit(0);/定位到要修改的地方,这里是 0x203f,为什么呢?下面给出回答fseek(fp_out, 0x203f, SEEK_SET);fputc(0x75, fp_out); /写入数据0x75,也就是把机器码74改为75,/汇编则是 je 改为了jne fclose(fp_out);这里 0x203f = 0x0040203F 0x00400000了解EBP寄存器 在寄存器里面有很多寄存器虽然他们的功能和使用没有任何的区别,但是在长期的编程和使用中,在程序员习惯中已经默认的给每个寄存器赋上了特殊的含义,比如:EAX一般用来做返回值,ECX用于记数等等。在win32的环境下EBP寄存器用与存放在进入call以后的ESP的值,便于退出的时候回复ESP的值,达到堆栈平衡的目的。 应用以前说过的一段话: 原程序的OEP,通常是一开始以 Push EBP 和MOV Ebp,Esp这两句开始的,不用我多说大家也知道这两句的意思是以EBP代替ESP,作为访问堆栈的指针。 为什么要这样呢?为什么几乎每个程序都是的开头能?因为如果我们写过C等函数的时候就应该清楚,程序的开始是以一个主函数main()为开始的,而函数在访问的过程中最重要的事情就是要确保堆栈的平衡,而在win32的环境下保持平衡的办法是这样的: 1.让EBP保存ESP的值; 2.在结束的时候调用 mov esp,ebp pop ebp retn或者是 leaveretn两个形式是一个意思。 这样做的好处是不用考虑ESP等于多少,PUSH了多少次,要POP多少次了,因为我们知道EBP里面放的是开始时候的ESP值。 2.推广的ESP定律 在寻找OEP的时候,往往下断HW ESP-4不成功,除了壳代码将硬件断点删除了以外,很可能的情况就是因为壳代码在运行到OEP的时候他的ESP已经不再是在EP时候的ESP(12FFC4)了,这样我们下断当然是不成功的。 那么如何找到在壳到达OEP的时候的堆栈的值将是关键。 在这里我们应用的关键是 Push EBPMOV Ebp,Esp-关键是这句我来解释一下,当程序到达OEP的时候Push EBP这句对于ESP的值来说就是ESP-4,然后是ESP-4赋给了EBP,而做为保存ESP值作用的EBP寄存器在这个“最上层的程序”中的值将始终不会改变。虽然他可能在进入子call里面以后会暂时的改变(用于子程序的堆栈平衡)但是在退出了以后依*pop ebp这一句将还原原来的EBP的值。 以这句做为突破口,就是说只要我们能断在“最上层的程序”中,就能通过观察EBP的值得到壳在JMP到OEP的时候的ESP的值了。 3.实战 来看看pespin1.1的壳,在pespin1.0的壳中,我们使用HW 12FFC0能很容易的找到stolen code的地方,但是到pespin1.1的时候,我们就不行了。用HW 12FFC0根本断不下来。 现在我们就使用这个推广的ESP定律,载入程序后来到最后的一个异常 0040ED85 2BDB sub ebx,ebx /停在这里0040ED87 64:8F03 pop dword ptr fs:ebx0040ED8A 58 pop eax0040ED8B 5D pop ebp0040ED8C 2BFF sub edi,edi0040ED8E EB 01 jmp short pespin1_.0040ED910040ED90 C466 81 les esp,fword ptr ds:esi-7F我用使用内存断点办法来到FOEP处 004010D3 0000 add byte ptr ds:eax,al004010D5 0000 add byte ptr ds:eax,al004010D7 0000 add byte ptr ds:eax,al004010D9 0000 add byte ptr ds:eax,al004010DB 0000 add byte ptr ds:eax,al004010DD 0000 add byte ptr ds:eax,al004010DF 75 1B jnz short pespin1_.004010FC /这里是FOEP004010E1 56 push esi 004010E2 FF15 99F44000 call dword ptr ds:40F499004010E8 8BF0 mov esi,eax004010E

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论