分享

【新提醒】[教程]逆向反汇编第二课

 求真我 2014-04-20

本站超高价值资源逐渐增多 !更多原创,更多精品,更多高清视频教学内容期待与你分享。

论坛原创及精品教程采用原 格式共享,不加密,不压缩。保证质量和观看效果。

本站拥有大量编程学习资料 ,为聚集大家一起学习形成个良好的学习环境

而努力如果你觉得本站好, 请帮忙多宣传告诉你的朋友。

本站域名:http://www./

QQ: 1462109921

既然说的是函数,那么参数肯定不可缺少了,函数是有参数的,可是他们的参数传递却并不全部相同.函数传递参数有三种方式:堆栈方式 寄存器方式和通过全局变量进行隐含参数的传递.如果参数是通过堆栈传递的,就需要定义参数在堆栈中的顺序,并约定函数被调用后,由谁来平衡堆栈如果参数是通过寄存器传递的,就要确定信息存放在那个寄存器中.每种机制都有优缺点,而且这个还是和使用的语言也有关系的.
利用堆栈传递参数
堆栈是一种"先进后出"的存储区,栈顶指针ESP之象堆栈中第一个可用的数据项.调用函数时,调用者依次把参数压站,然后调用函数,函数被调用后,在堆栈中取得数
据,并且进行计算.函数计算结束后,或者调用者,或者函数本身修改堆栈,使堆栈恢复原形(这就是传说中的堆栈平衡).
在参数传递中,有两个很重要的问题必须得到明确的说明:当参数个数大于1时,是按照什么顺序把参数压入堆栈的?函数结束后,由谁来平衡堆栈?这些都必须有个明确的规定,这种程序设计语言中为了实现函数而建立的协议称为调用约定.这种协议规定了函数中的参数传递方式 参数是否可变和由谁来处理堆栈等问题.不同的编程语言有着不同的调用约定.
_Cdelc(C规范)是从左到右而且由调用者来平衡堆栈的,并且允许使用VARARG.C规范(也就是_cdecl)函数参数按照从右稻作的顺序入栈,由调用者来负责清除堆栈,_codecl是C和C++程序的默认调用约定.C/C++和MFC程序默认使用调用约定是_cdecl,也可以在函数声明时加上_codecl关键字来手动指定.
PASCAL也是从左到右,是由子程序自己平衡堆栈的,但是不允许使用VARARG. PASCAL规范按从左到右的顺序压入堆栈,要求被调用函数负责清除堆栈.
stdcall却是从右到左调用参数,由子程序自己来平衡堆栈,也是可以使用VARARG,32位的ASM就是由这种方式调用的.这个调用约定是Win 32API函数采用的约定方式,踏实"标准调用"的意思,它结合C的约定入栈方式和PASCAL调用约定的调整栈指针方式,也就是函数入口参数按从右到左的顺序入栈,并由调用的函数在返回前清理传送参数的内存栈,函数参数个数固定.由函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条"retn"指令直接清理传递参数的堆栈.在Win 32API中,也有一些函数是_cdecl调用的,比如wsprintf.
Fastcall的调用方法时使用寄存器和堆栈来完成的,平衡堆栈也是使用子程序,到了64的ASM就变成这种方式了.
所谓的VARARG表示参数的个数可以是不确定的;如果stdcall使用了VARARG,那么是由调用程序来平衡堆栈的,否则是由被调用程序来平衡的.
为了更形象的了解不同类型约定的处理方式,来看看这个例子.架设调用函数test(var1,var2,var3),按_cdecl PASCAL和stdcall的调用约定,他们的反汇编代码分别如下:

欢迎来到Bysimont编程技术论坛学习 www. 因你而精彩
  1. _cdecl:
  2. psuh var3 ;参数按从右到左传递
  3. push var2
  4. push var1
  5. call test
  6. add esp,0c ;平衡堆栈
  7. PASCALL:
  8. push var1 ;参数按左到右传递
  9. push var2
  10. push var3
  11. call test1 ;函数内平衡堆栈
  12. stdcall:
  13. psuh var3 ;参数按从左到右传递
  14. push var2
  15. push var1
  16. call test ;参数内平衡堆栈.

可以很清楚的看到,_cedcl类型和_stdcall类型是把右边参数压入堆栈,而PASCAL则相反.在堆栈平衡上,cdecl类型是调用者用"add esp,0c"指令把12个字节参数空间清除,而PASCALL
和_stdcall类型则是子程序负责清除.
函数对参数的存取和局部变量都是通过堆栈来定义的,非优化编译器用一个专门的寄存器(通常是ebp)对参数进行寻址.C/C++,PASCALL等高级语言的函数(子程序/过程)执行过程基本上都是一致的.情况如下:


  • 调用者将哈双女户(子程序)执行完毕时应返回的地址 参数压入堆栈;
  • 子程序使用"EBP指针+偏移量"对堆栈中的参数寻址,并取出,完成操作;
  • 子程序使用ret/retf指令返回.此时,CPU将eip置为堆栈中保存的地址,并继续予以执行.当前栈顶,堆栈操作的对象只能是字操作(占4个字节).例如,按stdcall约定调用函数test(var1,var2)(有两个参数),其汇编代码大致如下:

欢迎来到Bysimont编程技术论坛学习 www. 因你而精彩
  1. push var2 ;参数2
  2. push var1 ;参数1
  3. call test ;调用子程序test()
  4. {
  5. push ebp ;保护现场原先的ebp指针
  6. mov ebp,esp ;设置新的ebp指针,指向栈顶
  7. mov eax,dword ptr [ebp+0c] ;调用参数2
  8. mov ebx,dword ptr [ebp+08] ;调用参数1
  9. sub esp,8 ;若函数要用局部变量,则要在堆栈中留出点空间
  10. .........
  11. add esp,8 ;释放局部变量占用的堆栈
  12. pop ebp ;恢复现场的ebp指针
  13. ret 8 ;ret后的值是参数个数乘以4h
  14. }

因为esp是堆栈指针,所以一般使用ebp来存取堆栈.其肚子喊建立情况如下:


  • 此例函数中有两个参数,架设执行函数前堆栈指针的esp为X;
  • 根据stdcall调用约定,先将参数var2压入堆栈,此时esp为X-04h
  • 再将参数var1压入堆栈,此时esp为K-08h
  • 参数进栈结束后,程序开始执行call指令,call指令把返回地址压入堆栈,这时候esp为X-0ch;
  • 这个时候已经在子程序中了,可以开始使用EBP来存取参数了,但为了在返回时回复ebp的值,用"push ebp"保存ebp的值,这时esp为X-10h
  • 在执行一句"mov ebp,esp",ebp被用来在堆栈中寻找调用者压入的参数,这时候[ebp+8]就是参数1,[ebp+c]就是参数2;
  • "sub esp,8",在堆栈中定义局部变量,局部变量1和2对应的地址分别是[ebp-4]和[ebp-8].函数结束时,调用"add eso,8"释放局部变量占用的堆栈.局部变量的范围从它的定义所在的代码块的结束为止,也就是说,当函数调用结束后局部变量也就消失了.
  • 最后调用"ret 8"指令来平衡堆栈,ret指令后面加一个操作数表示在ret后把堆栈指针esp加上操作数,完成同样的功能.
  • 处理完毕后,就可以开始用ebp存取参数和局部变量了.


此外,还有一组指令,也就是enter和leave,他们可以帮助进行堆栈的维护.enter语句的作用就是"push ebp/mov ebp,esp/sub esp,xxxx",而leave则是完成"add esp,xxxx/pop ebp"的功能.所以,上面的程序可以改成:
reter xxxx,0 ;0表示创建xxxx空间存放局部变量
........
leave ;恢复现场
ret 8 ;返回
在许多情况下,编译器会按优化方式编译程序,堆栈寻址稍有不同,这时编译器为了把ebp寄存器省下来或尽可能减少代码以提高速度,会直接通过esp对参数进行寻址.esp的值在函数执行期间要发生变化,该变化出现在每次有数据进出堆栈时,要确定是对那个变量进行寻址,就需要知道程序当前位置的esp值是多少,为此必须从函数的开始部分跟踪.
举个用堆栈传递参数的例子:

欢迎来到Bysimont编程技术论坛学习 www. 因你而精彩
  1. 00401000 >/$ 6A 04 push 4
  2. 00401002 |. 6A 03 push 3
  3. 00401004 |. E8 16000000 call local.0040101F
  4. 00401009 |. 8BD8 mov ebx, eax
  5. 0040100B |. 6A 00 push 0 ; /ExitCode = 0
  6. 0040100D /. FF15 00204000 call near dword ptr ds:[<&KERNEL32.Ex>; /ExitProcess
  7. ........................................
  8. 0040101F /$ 55 push ebp ; 保护现场原先的EBP指针
  9. 00401020 |. 8BEC mov ebp, esp ; 设置新的ESP指针,指向堆栈栈顶
  10. 00401022 |. 83EC 04 sub esp, 4 ; 局部变量分配空间
  11. 00401025 |. 8B45 0C mov eax, dword ptr ss:[ebp+C] ; 调用参数2,也就是"4"
  12. 00401028 |. 8B5D 08 mov ebx, dword ptr ss:[ebp+8] ; 调用参数1,也就是"3"
  13. 0040102B |. 895D FC mov dword ptr ss:[ebp-4], ebx ; 参数1放到局部变量中
  14. 0040102E |. 0345 FC add eax, dword ptr ss:[ebp-4] ; 参数2加局部变量
  15. 00401031 |. 83C4 04 add esp, 4 ; 释放局部变量所用空间
  16. 00401034 |. 5D pop ebp ; 恢复现场的ebp指针
  17. 00401035 /. C2 0800 retn 8 ; 返回

我们在找CALL的时候,有时候不能确定我们要找的CALL是有参CALL还是无参,像这种[ebp-*]的这是局部变量,存放的数值不确定,也就是QB为什么要大家在找基址的时候不能找[ebp-*]的.
这时程序就用esp来传递参数了.


  • 假设执行函数欠堆栈指针esp的值为X;
  • 根据stdcall调用约定,先将参数var3压入堆栈,此时esp为X-04h
  • 再将var2压入堆栈,此时esp为X-08h
  • 最后将var3压入堆栈,此时esp是X-0Ch
  • 参数进栈结束后,程序开始执行call指令,call指令把返回地址压入堆栈,这个时候esp为X-10h
  • 这个时候易经在子程序中了,可以开始使用esp来存取参数了.
欢迎来到Bysimont编程技术论坛学习 www. 因你而精彩

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多