x86 asm崩溃的应用程序

时间:2010-12-29 23:52:23

标签: c x86

char program[3] = { 0x6a,  0x0a, 0xC3 }; //push 10; ret;
char (*p)(void) = program;
printf("char = %c \n", p());

此代码崩溃。为什么呢?

以下代码可以正常使用:

char program[3] = { 0x90,  0x90, 0xC3 }; //nop; nop; ret
void (*p)(void) = program;
p();

5 个答案:

答案 0 :(得分:5)

因为您需要通过弹出来清理堆栈。阅读ret指令的更多内容 - 它将使用堆栈中的值加载cs:ip!

答案 1 :(得分:3)

首先,你应该明白,你不能只是在装配中“做事”并期望它能够发挥作用。有一种称为应用程序二进制接口的东西,用于指定操作系统中以及代码内部的程序如何表现。

例如,在大多数x86-32平台上的C语言中,一个常见规则是eax应该包含返回值。还会有一组值被压入堆栈(称为函数的堆栈帧)。另一个是需要保留某些寄存器,而其他寄存器(有时称为临时寄存器)可能会留下垃圾。

简而言之,如果您违反这些规则,则会出现问题并且操作系统会为您整理。这不仅取决于您的处理器,还取决于您的操作系统;例如,即使是x64上的linux和windows也是不同的。

我还应该添加char program不是程序,它是现有程序中的一个函数。

最后,nop, nop, ret什么也没做。所以你可以执行这些指令并返回,因为你实际上没有对堆栈帧做任何事情。

答案 2 :(得分:3)

我强烈建议你学习调用约定。对于32位x86,修改堆栈的函数看起来应该更像这样(cdecl):

push ebp
mov ebp, esp ; or ENTER instruction for the push+mov

; Function code!

mov esp, ebp
pop ebp ; or LEAVE instruction for the mov+pop
ret

mov esp, ebp确保您的堆栈返回到函数开头的方式,这意味着返回地址在堆栈上,ret加载并跳转到。

函数中的局部变量放在堆栈上(sub esp, 0x4将为一个32位变量分配空间,可用作[esp + 0])。

64位x86(非Itanium)有自己的调用约定。然后是快速呼叫等。

Wikipedia Article on x86 Calling Conventions值得一看。

答案 3 :(得分:2)

char program[] = {
    0x55,              /* push %ebp */
    0x89, 0xe5,        /* mov %esp, %ebp */
    0x6a, 0x0a,        /* push $0xa */
    0x8b, 0x04, 0x24,  /* mov (%esp), %eax */
    0x89, 0xec,        /* mov %ebp, %esp */
    0x5d,              /* pop %ebp */
    0xc3,              /* ret */
};

那就行了*。你的程序崩溃是因为你弄乱了堆栈布局。

*只要program驻留在内存的可执行部分中,这不一定是真的,并且您使用典型的C调用约定在x86-32上运行

答案 4 :(得分:0)

除了根据你的程序的意图(期望输出10)清理堆栈......

char program[3] = { 0x6a,  0x0a, 0xC3 }; //push 10; ret;
char (*p)(void) = program;
printf("char = %c \n", p())

...,你不应该按10,函数的返回值存储在AX(16位),EAX(如果是32位)

如果要传递参数,清理堆栈取决于调用函数约定,如果它是__fastcall(或pascal),则它是必须进行清理的调用例程;如果它是C调用约定(_cdecl),那么调用者应该清理堆栈