我做了一个简单的C程序来尝试理解缓冲区溢出。我试图溢出输入缓冲区,以便标志变为true,程序输出"到达这里"。该程序在这里(假设你有密码.txt,其中包含嘿):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv){
int flag = 0;
char pwd[5];
char input[5];
FILE *f = fopen("password.txt", "r");
fscanf(f, "%s", pwd);
fclose(f);
strcpy(input, argv[1]);
if(strcmp(input, pwd)){
printf("wrong password!\n");
}
else{
flag = 1;
printf("correct password!\n");
}
if(flag){
printf("you got into the secret place\n");
}
return 0;
}
所以在堆栈上我们有这样的东西我想:
[high addresses]
flag ---> 4 bytes
pwd ---> 8 bytes
input ---> 8 bytes
[low addresses]
所以我想我只需要给程序只有17个字节来覆盖flag变量。所以我给了它 aaaaaaaaaaaaaaaaaa ,但那并没有奏效。我不得不给它 a 23次,所以23个字节。为什么17个字节不够?
答案 0 :(得分:2)
尝试此操作时可能会遇到多个问题。
堆栈上局部变量的顺序不一定与代码中的顺序相同。编译器可以随意重新排列它们。
编译器经常将数据与边界对齐以加快执行速度。这意味着不同局部变量之间的空间可能比您想象的要大得多。看到堆栈变量在8或16字节边界上对齐的情况并不罕见,即使它的内容要小得多。
您可以使用objdump -D
对程序进行反汇编,也可以使用gdb
进行调试。这将使您更好地了解程序中的堆栈对齐。
答案 1 :(得分:2)
免责声明:我使用Ubuntu 14.04和gcc版本4.8.4,并且遵守了你的
因此代码gcc -m32 -g -ansi -pedantic -Wall temp.c -o temp
。不同
编译器或gcc的不同选项很可能会给出
不同的结果。
我还稍微修改了你的代码,以便更容易找到东西,
int flag = 0x41414141;
if(flag==1){
编译后,我在GDB下运行可执行文件,并设置了断点 主要的。然后拆卸主要(在设置拆卸味道后 英特尔),我们得到:
(gdb) disass
Dump of assembler code for function main:
0x0804857d <+0>: push ebp
0x0804857e <+1>: mov ebp,esp
0x08048580 <+3>: and esp,0xfffffff0
0x08048583 <+6>: sub esp,0x30
0x08048586 <+9>: mov eax,DWORD PTR [ebp+0xc]
0x08048589 <+12>: mov DWORD PTR [esp+0xc],eax
=> 0x0804858d <+16>: mov eax,gs:0x14
0x08048593 <+22>: mov DWORD PTR [esp+0x2c],eax
0x08048597 <+26>: xor eax,eax
0x08048599 <+28>: mov DWORD PTR [esp+0x18],0x41414141
前四行是main
的序言。重要的是。{
notice是我们正在设置的行sub esp,0x30
堆栈框架的功能。如您所见,我们减去了48个字节
来自esp。 (实际上更多一点,因为我们首先对齐堆栈帧
到16字节的边界。)
现在,我们可以通过查看值来查看堆栈帧的位置 对于ESP和EBP:
(gdb) info registers esp
esp 0xffffd110 0xffffd110
(gdb) info registers ebp
ebp 0xffffd148 0xffffd148
我们可以找到堆栈框架中的位置;
(gdb) print &pwd
$3 = (char (*)[5]) 0xffffd132
(gdb) print &flag
$4 = (int *) 0xffffd128
(gdb) print &input
$5 = (char (*)[5]) 0xffffd137
(gdb) print &f
$6 = (FILE **) 0xffffd12c
由此我们可以推断出我们的堆栈布局。这个记忆图像是 在程序读取命令行之后运行 参数是字符串BBBBB(回想一下B的ASCII码是0x42,所以很容易看到一个0x42字节的序列)
(gdb) x/56xb $esp
0xffffd110: 0x37 0xd1 0xff 0xff
0xffffd114: 0xbf 0xd3 0xff 0xff
0xffffd118: 0x32 0xd1 0xff 0xff
0xffffd11c: 0xe4 0xd1 0xff 0xff
0xffffd120: 0x02 0x00 0x00 0x00
0xffffd124: 0xe4 0xd1 0xff 0xff
0xffffd128: 0x41 0x41 0x41 0x41 (flag)
0xffffd12c: 0x08 0xb0 0x04 0x08 (f)
0xffffd130: 0xc4 0xf3
0xffffd132: 0x68 0x65 0x79 0x00 0xff (pwd buffer)
0xffffd137: 0x42 0x42 0x42 0x42 0x42 (input buffer)
0xffffd13c: 0x00 0xd5 0x61 0x5d
0xffffd140: 0x60 0x86 0x04 0x08
0xffffd144: 0x00 0x00 0x00 0x00
另请注意,如果我将命令行参数设置为BBBBBBBB,我们就会这样做 对于我们的堆栈框架的内容
(gdb) x/56xb $esp
0xffffd110: 0x37 0xd1 0xff 0xff 0xbc 0xd3 0xff 0xff
0xffffd118: 0x32 0xd1 0xff 0xff 0xe4 0xd1 0xff 0xff
0xffffd120: 0x02 0x00 0x00 0x00 0xe4 0xd1 0xff 0xff
0xffffd128: 0x41 0x41 0x41 0x41 0x08 0xb0 0x04 0x08
0xffffd130: 0xc4 0xf3 0x68 0x65 0x79 0x00 0xff 0x42
0xffffd138: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x00
0xffffd140: 0x60 0x86 0x04 0x08 0x00 0x00 0x00 0x00
请注意,flag变量的内容保持不变,但是现在溢出的输入缓冲区的内容会移动到堆栈的顶部。回想一下,在x86中,堆栈向下增长(较低的内存地址)。此外,因为缓冲区向上增长是我们可以使用缓冲区溢出来过度写入堆栈中存储的EIP的原因。
所以在我的系统上,我不相信可以用用户输入覆盖flag
变量。您的系统可能会在堆栈上以不同的方式进行布局(您需要执行类似的练习来验证这一点)。
另请注意,堆栈中变量的位置与源文件中声明的顺序无关。