C 缓冲区溢出 - 输入多少字节
C Buffer Overflow - how many bytes to input
我编写了一个简单的 C 程序来尝试理解缓冲区溢出。我试图溢出输入缓冲区,以便将标志更改为 true 并且程序输出 "got here"。程序在这里(假设你有 password.txt 其中有 hey):
#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 个字节来覆盖标志变量。所以我给了它 aaaaaaaaaaaaaaaaa 但这没有用。我不得不给它 a 23 次,所以 23 个字节。为什么 17 个字节不够?
尝试此操作时,您可能 运行 遇到多个问题。
局部变量在栈上的顺序不一定与代码中的顺序相同。编译器可以随意重新排列它们。
编译器通常将数据与边界对齐以加快执行速度。这意味着不同局部变量之间的 space 可能比您想象的要大得多。堆栈变量在 8 或 16 字节边界上对齐的情况并不少见,即使它的内容要小得多。
您可以使用 objdump -D
反汇编您的程序或使用 gdb
调试它。这将使您更好地了解程序中的堆栈对齐方式。
免责声明:我将 Ubuntu 14.04 与 gcc 版本 4.8.4 一起使用,并符合您的要求
因此代码 gcc -m32 -g -ansi -pedantic -Wall temp.c -o temp
。不同的
编译器或 gcc 的不同选项很可能会给出
不同的结果。
我还稍微修改了你的代码,以便更容易找到东西,
- 我将第 6 行更改为
int flag = 0x41414141;
- 我将第 25 行更改为
if(flag==1){
编译后我运行GDB下的可执行文件,设置了断点
在主要。然后反汇编 main (在将 disassembly-flavor 设置为
英特尔),我们得到:
(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
的序言
注意是 sub esp,0x30
行,我们在其中设置
函数的栈帧。如您所见,我们正在减去 48 个字节
从特别是(实际上多了一点,因为我们首先对齐了栈帧
到 16 字节边界)。
现在,我们可以通过查看值来查看堆栈框架的位置
对于 ESP 和 EBP:
(gdb) info registers esp
esp 0xffffd110 0xffffd110
(gdb) info registers ebp
ebp 0xffffd148 0xffffd148
我们可以找到堆栈帧中的位置;
(gdb) print &pwd
= (char (*)[5]) 0xffffd132
(gdb) print &flag
= (int *) 0xffffd128
(gdb) print &input
= (char (*)[5]) 0xffffd137
(gdb) print &f
= (FILE **) 0xffffd12c
由此我们现在可以推断出我们的堆栈布局。这个记忆影像是
在程序读取命令行的 运行ning 之后采取
参数是字符串 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
注意标志变量的内容保持不变,但现在溢出的输入缓冲区的内容移向堆栈的顶部。回想一下,在 x86 中,堆栈向下增长(较低的内存地址)。另外,因为缓冲区向上增长,所以我们可以使用缓冲区溢出来覆盖堆栈上存储的 EIP。
所以在我的系统上,我认为不可能用用户输入覆盖 flag
变量。您的系统在堆栈上的布局可能不同(您需要做类似的练习来验证这一点)。
另外请注意,变量在堆栈上的位置与它们在源文件中声明的顺序无关。
我编写了一个简单的 C 程序来尝试理解缓冲区溢出。我试图溢出输入缓冲区,以便将标志更改为 true 并且程序输出 "got here"。程序在这里(假设你有 password.txt 其中有 hey):
#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 个字节来覆盖标志变量。所以我给了它 aaaaaaaaaaaaaaaaa 但这没有用。我不得不给它 a 23 次,所以 23 个字节。为什么 17 个字节不够?
尝试此操作时,您可能 运行 遇到多个问题。
局部变量在栈上的顺序不一定与代码中的顺序相同。编译器可以随意重新排列它们。
编译器通常将数据与边界对齐以加快执行速度。这意味着不同局部变量之间的 space 可能比您想象的要大得多。堆栈变量在 8 或 16 字节边界上对齐的情况并不少见,即使它的内容要小得多。
您可以使用 objdump -D
反汇编您的程序或使用 gdb
调试它。这将使您更好地了解程序中的堆栈对齐方式。
免责声明:我将 Ubuntu 14.04 与 gcc 版本 4.8.4 一起使用,并符合您的要求
因此代码 gcc -m32 -g -ansi -pedantic -Wall temp.c -o temp
。不同的
编译器或 gcc 的不同选项很可能会给出
不同的结果。
我还稍微修改了你的代码,以便更容易找到东西,
- 我将第 6 行更改为
int flag = 0x41414141;
- 我将第 25 行更改为
if(flag==1){
编译后我运行GDB下的可执行文件,设置了断点 在主要。然后反汇编 main (在将 disassembly-flavor 设置为 英特尔),我们得到:
(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
的序言
注意是 sub esp,0x30
行,我们在其中设置
函数的栈帧。如您所见,我们正在减去 48 个字节
从特别是(实际上多了一点,因为我们首先对齐了栈帧
到 16 字节边界)。
现在,我们可以通过查看值来查看堆栈框架的位置 对于 ESP 和 EBP:
(gdb) info registers esp
esp 0xffffd110 0xffffd110
(gdb) info registers ebp
ebp 0xffffd148 0xffffd148
我们可以找到堆栈帧中的位置;
(gdb) print &pwd
= (char (*)[5]) 0xffffd132
(gdb) print &flag
= (int *) 0xffffd128
(gdb) print &input
= (char (*)[5]) 0xffffd137
(gdb) print &f
= (FILE **) 0xffffd12c
由此我们现在可以推断出我们的堆栈布局。这个记忆影像是 在程序读取命令行的 运行ning 之后采取 参数是字符串 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
注意标志变量的内容保持不变,但现在溢出的输入缓冲区的内容移向堆栈的顶部。回想一下,在 x86 中,堆栈向下增长(较低的内存地址)。另外,因为缓冲区向上增长,所以我们可以使用缓冲区溢出来覆盖堆栈上存储的 EIP。
所以在我的系统上,我认为不可能用用户输入覆盖 flag
变量。您的系统在堆栈上的布局可能不同(您需要做类似的练习来验证这一点)。
另外请注意,变量在堆栈上的位置与它们在源文件中声明的顺序无关。