逆向工程-栈

为什么栈会逆增长

多数的栈是逆增长的,它会从高地址向低地址增长。

历史原因。当计算机尚未小型化的时候,它还有数个房间那么大。在那个时候,内存就分为两个部分,即"堆/heap"和栈/stack。当然,在程序执行过程中,堆和栈到底会增长到什么地址并不好说,所以人们干脆把它们分开:

逆向工程-栈_第1张图片

 

栈的用途

1)保存函数结束时的返回地址

x86

当程序使用call指令调用其他函数时,call指令结束后的返回地址将被保存在栈里;在call所调用的函数结束之后,程序将执行无条件跳转指令,跳转到这个返回地址。

CALL指令等价于PUSH返回地址和JMP函数地址的指令对。

被调用函数里的RET指令,会从栈中读取返回地址,然后跳转到这个地址,就相当于POP返回地址+JMP返回地址指令。

栈是有隐姓埋名的,溢出它很容易。直接使用无限递归,栈就会满

ARM

如果一个函数不调用其他函数,它就像树上的枝杈末端的叶子那样。这种函数就叫作叶函数leaf function。

叶函数的特点是,它不必保存LR寄存器的值。如果叶函数的代码短到用不了几个寄存器,那么它也可能根本不会使用数据栈。所以,调用叶函数的时候确实可能不会涉及栈操作。

 

2)参数传递

在x86平台的程序中,最常用的参数传递约定是cdecl。

push arg3

push arg2

push arg1

call f

add esp,12

被调用方法函数通过栈指针获取其所需的参数。

ESP

返回地址

ESP+4

arg1,它在IDA里记为arg_0

ESP+8

 

arg2,它在IDA里记为arg_4

ESP+0xC

arg3,它在IDA里记为arg_8

 

3)存储局部变量

通过向栈底调整栈指针的方法,函数即可在数据栈里分配也一片可用于存储局部变量的内存空间。可见,无论函数声明了多少个局部变量,都不影响它分配栈空间的速度。

4)SEH结构化异常处理

5)缓冲区溢出保护

6)alloca()函数

直接使用栈来分配内存,除些之外,它与malloc()比偶没有显著的区别。

函数尾声的代码会还原ESP的值,把数据还原为函数启动之前的状态,直接抛弃由alloca()函数分配的内存。所以程序不需要特地使用free()函数来释放由这个函数申请的内存。

 

栈的噪音

#include

void f1()

{

int a=1,b=2,c=3;

};

void f2()

{

int a,b,c;

printf("%d, %d, %d\n",a,b,c);

}

int main()

{

f1();

f2();

}



MSVC

$SG2752 '%d, %d, %d',0aH,00H

_c$=-12 ;size=4

_b$=-8 ;size=4

_a$=-4 ;size=4

_f1 PROC

push ebp

mov ebp,esp

sub esp,12

mov DWORD PTR _a$[ebp],1

mov DWORD PTR _b$[ebp],2

mov DWORD PTR _c$[ebp],3

mov esp,ebp

pop ebp

ret 0

_f1 ENDP

_c$=-12 ;size=4

_b$=-8 ;size=4

_a$=-4 ;size=4

_f2 PROC

push ebp

mov mov ebp,esp

sub esp,12

mov eax, DWORD PTR _c$[ebp]

push eax

mov ecx, DWORD PTR _b$[ebp]

push ecx

mov edx, DWORD PTR _a$[ebp]

push edx

push OFFSET $SG2752; ‘%d, %d, %d'

call DWORD PTR __imp_printf

add esp,16

mov esp,ebp

pop ebp

ret 0

_f2 ENDP

main PROC

push ebp

mov ebp,esp

call _f1

call _f2

xor eax,eax

pop ebp

ret 0

_main ENDP

在运行第二个函数时,栈中的所有值(即内存中的单元)受前一个函数的影响,而获得了前一个函数的变量的值。来格地说,这些地址的值不是随机值,而是可预测的伪随机值。

你可能感兴趣的:(汇编语言)