注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

风雨夜归人

专业收集资料,个人爱好!

 
 
 

日志

 
 

逆向_第二课  

2010-03-10 16:21:27|  分类: 汇编 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
       既然说的是函数,那么参数肯定不可缺少了,函数是有参数的,可是他们的参数传递却并不全部相同.函数传递参数有三种方式:堆栈方式 寄存器方式和通过全局变量进行隐含参数的传
递.如果参数是通过堆栈传递的,就需要定义参数在堆栈中的顺序,并约定函数被调用后,由谁来平衡堆栈如果参数是通过寄存器传递的,就要确定信息存放在那个寄存器中.每种机制都有优
缺点,而且这个还是和使用的语言也有关系的.
        利用堆栈传递参数
       我在视频中也讲过,堆栈是一种"先进后出"的存储区,栈顶指针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的调用约定,他们的反汇编代码分别如下:
_cdecl:
psuh var3 ;参数按从右到左传递
push var2
push var1
call test
add esp,0c ;平衡堆栈
PASCALL:
push var1 ;参数按左到右传递
push var2
push var3
call test1 ;函数内平衡堆栈
stdcall:
psuh var3 ;参数按从左到右传递
push var2
push var1
call test ;参数内平衡堆栈.
       可以很清楚的看到,_cedcl类型和_stdcall类型是把右边参数压入堆栈,而PASCAL则相反.在堆栈平衡上,cdecl类型是调用者用"add esp,0c"指令把12个字节参数空间清除,而PASCALL
和_stdcall类型则是子程序负责清除.
      函数对参数的存取和局部变量都是通过堆栈来定义的,非优化编译器用一个专门的寄存器(通常是ebp)对参数进行寻址.C/C++,PASCALL等高级语言的函数(子程序/过程)执行过程基本上都是一致的.情况如下:
1)调用者将哈双女户(子程序)执行完毕时应返回的地址 参数压入堆栈;
2)子程序使用"EBP指针+偏移量"对堆栈中的参数寻址,并取出,完成操作;
3)子程序使用ret/retf指令返回.此时,CPU将eip置为堆栈中保存的地址,并继续予以执行.当前栈顶,堆栈操作的对象只能是字操作(占4个字节).例如,按stdcall约定调用函数test(var1,var2)(有两
个参数),其汇编代码大致如下:
push var2 ;参数2
push var1 ;参数1
call test ;调用子程序test()
{
push ebp ;保护现场原先的ebp指针
mov ebp,esp ;设置新的ebp指针,指向栈顶
mov eax,dword ptr [ebp+0c] ;调用参数2
mov ebx,dword ptr [ebp+08] ;调用参数1
sub esp,8 ;若函数要用局部变量,则要在堆栈中留出点空间
.........
add esp,8 ;释放局部变量占用的堆栈
pop ebp ;恢复现场的ebp指针
ret 8 ;ret后的值是参数个数乘以4h
}
       因为esp是堆栈指针,所以一般使用ebp来存取堆栈.其肚子喊建立情况如下:
    (1)此例函数中有两个参数,架设执行函数前堆栈指针的esp为X;
    (2)根据stdcall调用约定,先将参数var2压入堆栈,此时esp为X-04h
    (3)再将参数var1压入堆栈,此时esp为K-08h
    (4)参数进栈结束后,程序开始执行call指令,call指令把返回地址压入堆栈,这时候esp为X-0ch;
    (5)这个时候已经在子程序中了,可以开始使用EBP来存取参数了,但为了在返回时回复ebp的值,用"push ebp"保存ebp的值,这时esp为X-10h
    (6)在执行一句"mov ebp,esp",ebp被用来在堆栈中寻找调用者压入的参数,这时候[ebp+8]就是参数1,[ebp+c]就是参数2;
    (7)"sub esp,8",在堆栈中定义局部变量,局部变量1和2对应的地址分别是[ebp-4]和[ebp-8].函数结束时,调用"add eso,8"释放局部变量占用的堆栈.局部变量的范围从它的定义所在的代码块
的结束为止,也就是说,当函数调用结束后局部变量也就消失了.
    (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值是多少,为此必须从函数的开始部分跟踪.
举个用堆栈传递参数的例子:
00401000 >/$  6A 04         push    4
00401002  |.  6A 03         push    3
00401004  |.  E8 16000000   call    local.0040101F
00401009  |.  8BD8          mov     ebx, eax
0040100B  |.  6A 00         push    0                                ; /ExitCode = 0
0040100D  \.  FF15 00204000 call    near dword ptr ds:[<&KERNEL32.Ex>; \ExitProcess
........................................
0040101F  /$  55            push    ebp                              ;  保护现场原先的EBP指针
00401020  |.  8BEC          mov     ebp, esp                         ;  设置新的ESP指针,指向堆栈栈顶
00401022  |.  83EC 04       sub     esp, 4                           ;  局部变量分配空间
00401025  |.  8B45 0C       mov     eax, dword ptr ss:[ebp+C]        ;  调用参数2,也就是"4"
00401028  |.  8B5D 08       mov     ebx, dword ptr ss:[ebp+8]        ;  调用参数1,也就是"3"
0040102B  |.  895D FC       mov     dword ptr ss:[ebp-4], ebx        ;  参数1放到局部变量中
0040102E  |.  0345 FC       add     eax, dword ptr ss:[ebp-4]        ;  参数2加局部变量
00401031  |.  83C4 04       add     esp, 4                           ;  释放局部变量所用空间
00401034  |.  5D            pop     ebp                              ;  恢复现场的ebp指针
00401035  \.  C2 0800       retn    8                                ;  返回


我们在找CALL的时候,有时候不能确定我们要找的CALL是有参CALL还是无参,像这种[ebp-*]的这是局部变量,存放的数值不确定,也就是QB为什么要大家在找基址的时候不能找[ebp-*]的.
这时程序就用esp来传递参数了.
1)假设执行函数欠堆栈指针esp的值为X;
2)根据stdcall调用约定,先将参数var3压入堆栈,此时esp为X-04h
3)再将var2压入堆栈,此时esp为X-08h
4)最后将var3压入堆栈,此时esp是X-0Ch
5)参数进栈结束后,程序开始执行call指令,call指令把返回地址压入堆栈,这个时候esp为X-10h
6)这个时候易经在子程序中了,可以开始使用esp来存取参数了.
  评论这张
 
阅读(435)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017