4.IDA-导航(跳转到地址、导航按钮、栈帧、调用约定、局部变量布局、IDA的栈视图)

来源:互联网 发布:c语言源程序的扩展名 编辑:程序博客网 时间:2024/06/05 22:43

1.跳转到地址

使用Jump▶Jump to Address命令或在处于活动状态的反汇编窗口中按下热键G,均可以打开Jump to Address对话框,如果把这个对话框看成Go对话框,可能有助于你记住相关的热键。

IDA会记住你在这个对话框中输入的值,并通过一个下拉列表显示,以方便你随后使用

2.导航按钮(导航历史)

导航按钮,每个按钮旁边还有一个历史记录下拉列表,你可以迅速访问导航历史记录列表中的任何位置,而不必遍历整个历史记录列表


3.栈帧

调用一个函数时的详细操作步骤:
(1)调用方将被调用函数所需的任何参数放入到该函数所采用的调用约定指定的位置。如果参数被放到运行时栈上,该操作可能导致程序的栈指针发生改变。
(2)调用方将控制权转交给被调用的函数,然后,返回地址被保存到程序栈或CPU寄存器中。
(3)如有必要,被调用的函数会配置一个栈指针(EBP),并保存调用方希望保持不变的任何寄存器值。
(4)被调用的函数为它可能需要的任何局部变量分配空间。一般,通过调整程序栈指针在运行时栈上保留空间来完成这一任务。
(5)被调用的函数执行其操作,可能生成一个结果。在执行操作的过程中,被调用的函数可能会访问调用函数传递给它的参数。如果函数返回一个结果,此结果通常被放置到一个特定的寄存器中,或者放置到函数返回后调用方可立即访问的寄存器中。
(6)函数完成其操作后,任何为局部变量保留的栈空间将被释放。通常,逆向执行第(4)步中的操作,即可完成这个任务。
(7)如果某个寄存器的值还为调用方保存(第(3)步)着,那么将其恢复到原始值。这包括恢复调用方的帧指针寄存器。
(8)被调用的函数将控制权返还给调用方。根据所使用的调用约定,这一操作可能还会从程序栈中清除一个或多个参数。
(9)调用方一旦重新获得控制权,它可能需要删除程序栈中的参数。这时可能需要对栈进行调整,以将程序栈指针恢复到第(1)步以前的值。
第(3)步和第(4)步通常在进入函数时执行,它们共同称为该函数的序言。同样,第(6)步到第(8)步一般在函数结束时执行,它们共同构成该函数的尾声。而第(5)步则代表函数的主体,它们是调用一个函数时执行的全部操作。

4.调用约定

4.1.C调用约定

调用方按从右到左的顺序将函数参数放入栈中,在被调用的函数完成其操作时,调用方(而不是被调用方)负责从栈中清除参数。
从右到左在栈中放入参数的一个结果是,如果函数被调用,最左边的(第一个)参数将始终位于栈顶。这样,无论该函数需要多少个参数,我们都可轻易找到第一个参数。
如果函数能够接受变参,则调用方非常适于进行这种调整,因为它清楚地知道,它向函数传递了多少个参数,因而能够轻松做出正确的调整。而被调用的函数事先无法知道自己会收到多少个参数,因而很难对栈做出必要的调整,所以如下测试:


答案:__stdcall不适用于传入变参,如果在__stdcall函数中传入变参,系统会自动视为C调用,以下为反汇编

4.2.__stdcall调用约定

使用stdcall调用约定的区别在于:函数结束执行时,应由被调用的函数负责删除栈中的函数参数。
要完成这个任务,它必须清楚知道栈中有多少个参数,这就决定了只有固定参数。因此,printf这种变参的函数不能使用stdcall调用约定。

根据惯例,微软对所有由共享库(DLL)文件输出的参数数量固定的函数使用stdcall约定

4.3.x86 fastcall约定

fastcall约定是stdcall约定的一个变体,它向CPU寄存器(而非程序栈)最多传递两个参数,前两个参数(左边的两个)将分别位于ECX和EDX寄存器中。剩余的其他参数则以类似于stdcall约定的方式从右到左放入栈上
如:
int __fastcall fast(int a,int b,int c,int d)  {      return 0;  }  int _tmain(int argc, _TCHAR* argv[])  {      int a = fast(1,2,3,4);  


由于有两个参数被传递到寄存器中,只需要retn 8即可,如下汇编


4.4.C++调用约定

C++类需要使用this指针,该指针必须由调用方提供,因此,它被作为参数提供。C++语言标准并未规定应如何向非静态成员函数传递this指针,因此,不同编译器使用不同的技巧来传递this指针,Microsoft Visual C++提供thiscall调用约定,它将this传递到ECX寄存器中,并且和在stdcall中一样,它要求函数清除栈中的参数

GNU g++编译器将this看成是任何非静态成员函数的第一个隐含参数,而在所有其他方面与使用cdecl约定相同。因此,对使用g++编译的代码来说,在调用非静态成员函数之前,this被放置到栈顶,且调用方负责在函数返回时删除栈中的参数(至少有一个参数)

4.5.系统调用

通常,系统调用会造成状态转换,由用户模式进入内核模式,x86操作系统一般使用sysenter指令,系统调用的参数位于运行时栈上,并在启动系统调用之前,在EAX寄存器中放入一个系统调用编号


5.局部变量布局

存在规定如何向函数传递参数的调用约定,但不存在规定函数的局部变量布局的约定~即通过检查函数的源代码,通常无法确定函数的局部变量布局。
编译器的第一个任务是,计算出函数的局部变量所需的空间。(以下的计算都不是真实的,因为编译器会额外分配更多的栈空间,可能是因为对齐,或者是因为缓冲区大小
以下面代码为例:
int fun(int a,int b,int c)  {      int x;      char buf[64];      int y;      int z;      return 0;  }    int _tmain(int argc, _TCHAR* argv[])  {      fun(1,2,3);  


如果没有使用帧指针寄存器(即ESP为帧指针)
可直接这样写:
sub esp, 76  


其中的“偏移量”栏显示的是引用栈帧中的任何局部变量或参数所需的基址+位移地址:

但是,在X86中,EBP寄存器通常专门用作栈帧指针,得到的栈帧布局如图:



6.IDA栈视图

IDA会根据变量相对于被保存的返回地址的位置,为变量取名。局部变量位于被保存的返回地址之上,而函数参数则位于被保存的返回地址之下。
0.局部变量默认命名
局部变量名称以var_为前缀,后面跟一个表示变量与被保存的帧指针之间距离(以字节为单位)的十六进制后缀。
如局部变量var_C是一个4字节(dword)变量,它位于所保存的帧指针之上,距离为12字节([ebp-oCh])。

1.函数参数默认命名
函数参数名则以arg_为前缀,后面跟一个表示其与最顶端的参数之间的相对距离的十六进制后缀
因此,最顶端的4字节参数名为arg_0,而随后的参数则分别为arg_4、arg_8、arg_C
arg_0对应ebp+8, 依次类推
我们可以右击汇编中的arg_0(不是摘要中的,是汇编代码中的),菜单中就显示了arg_0的实际位置:

DA只会为那些在函数中直接引用的栈变量自动生成名称。如果DA无法确定任何对[ebp+8](第一个参数的位置)的内存引用,那么arg_0就不会在摘要栈视图中列出

特别注意的是:
var_局部变量的定义顺序和你在代码中的声明顺序关系不大,你必须通过反汇编窗口中的一系列线索,将IDA生成的变量名称与源代码中使用的名称对应起来。
注意观察的和传入参数的关系来关联局部变量

2.双击变量,会跳转到变量的详细视图

显示的两个特殊值分别为s和r(前面均带有空格)。这些伪变量是IDA表示被保存的返回地址(r)和被保存的寄存器值(s仅代表EBP)的特殊方法
原创粉丝点击