基本缓冲区溢出教程
Basic buffer overflow tutorial
我正在学习基本的缓冲区溢出,我有以下 C 代码:
int your_fcn()
{
char buffer[4];
int *ret;
ret = buffer + 8;
(*ret) += 16;
return 1;
}
int main()
{
int mine = 0;
int yours = 0;
yours = your_fcn();
mine = yours + 1;
if(mine > yours)
printf("You lost!\n");
else
printf("You won!\n");
return EXIT_SUCCESS;
}
我的目标是绕过行 mine = yours + 1;
,直接跳到 if
语句比较,这样我就可以 "win"。 main()
不能碰,只有your_fcn()
可以。
我的方法是用缓冲区溢出覆盖 return 地址。所以在这种情况下,我确定 return 地址应该与 buffer
相距 8
字节,因为缓冲区是 4
字节而 EBP
是 4
字节。然后我使用 gdb
来确定我要跳转到的行距函数调用 16
字节。这是 gdb 的结果:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000054a <+0>: lea 0x4(%esp),%ecx
0x0000054e <+4>: and [=11=]xfffffff0,%esp
0x00000551 <+7>: pushl -0x4(%ecx)
0x00000554 <+10>: push %ebp
0x00000555 <+11>: mov %esp,%ebp
0x00000557 <+13>: push %ebx
0x00000558 <+14>: push %ecx
0x00000559 <+15>: sub [=11=]x10,%esp
0x0000055c <+18>: call 0x420 <__x86.get_pc_thunk.bx>
0x00000561 <+23>: add [=11=]x1a77,%ebx
0x00000567 <+29>: movl [=11=]x0,-0xc(%ebp)
0x0000056e <+36>: movl [=11=]x0,-0x10(%ebp)
0x00000575 <+43>: call 0x51d <your_fcn>
0x0000057a <+48>: mov %eax,-0x10(%ebp)
0x0000057d <+51>: mov -0x10(%ebp),%eax
0x00000580 <+54>: add [=11=]x1,%eax
0x00000583 <+57>: mov %eax,-0xc(%ebp)
0x00000586 <+60>: mov -0xc(%ebp),%eax
0x00000589 <+63>: cmp -0x10(%ebp),%eax
0x0000058c <+66>: jle 0x5a2 <main+88>
0x0000058e <+68>: sub [=11=]xc,%esp
0x00000591 <+71>: lea -0x1988(%ebx),%eax
我看到行 0x00000575 <+43>: call 0x51d <your_fcn>
和 0x00000583 <+57>: mov %eax,-0xc(%ebp)
相距四行,这告诉我应该将 ret
偏移 16
字节。但是来自 gdb 的地址说的是不同的东西。也就是说,函数调用从 0x00000575
开始,而我要跳转到的行在 0x00000583
,这意味着它们相距 15
个字节?
无论哪种方式,无论我使用 16
字节还是 15
字节,我都会收到 segmentation fault
错误并且我仍然 "lose".
问题:我做错了什么?为什么 gdb 中给出的地址不是一次 4 个字节,这里实际发生了什么。如何正确跳转到我想要的行?
澄清:这是在虚拟机 运行 linux Ubuntu 上的 x32 计算机上完成的。我正在使用命令 gcc -fno-stack-protector -z execstack -m32 -g guesser.c -o guesser.o
进行编译,该命令会关闭堆栈保护器并强制进行 x32 编译。
gdb of your_fcn()
根据要求:
(gdb) disassemble your_fcn
Dump of assembler code for function your_fcn:
0x0000051d <+0>: push %ebp
0x0000051e <+1>: mov %esp,%ebp
0x00000520 <+3>: sub [=12=]x10,%esp
0x00000523 <+6>: call 0x5c3 <__x86.get_pc_thunk.ax>
0x00000528 <+11>: add [=12=]x1ab0,%eax
0x0000052d <+16>: lea -0x8(%ebp),%eax
0x00000530 <+19>: add [=12=]x8,%eax
0x00000533 <+22>: mov %eax,-0x4(%ebp)
0x00000536 <+25>: mov -0x4(%ebp),%eax
0x00000539 <+28>: mov (%eax),%eax
0x0000053b <+30>: lea 0xc(%eax),%edx
0x0000053e <+33>: mov -0x4(%ebp),%eax
0x00000541 <+36>: mov %edx,(%eax)
0x00000543 <+38>: mov [=12=]x1,%eax
0x00000548 <+43>: leave
0x00000549 <+44>: ret
x86 有可变长度的指令,所以你不能简单地计算指令数并乘以 4。既然你有 gdb 的输出,请相信它来确定每条指令的地址。
函数的return地址是调用指令之后的地址。在显示的代码中,这将是 main+48.
if
语句从 main+60 开始,而不是 main+57。 main+57处的指令将yours+1
存入mine
。所以要将return地址调整为return到if语句,应该加上12(即60 - 48)。
这样做会跳过对 yours
和 mine
的赋值。由于它们都被初始化为 0,所以它会打印 "You won".
我正在学习基本的缓冲区溢出,我有以下 C 代码:
int your_fcn()
{
char buffer[4];
int *ret;
ret = buffer + 8;
(*ret) += 16;
return 1;
}
int main()
{
int mine = 0;
int yours = 0;
yours = your_fcn();
mine = yours + 1;
if(mine > yours)
printf("You lost!\n");
else
printf("You won!\n");
return EXIT_SUCCESS;
}
我的目标是绕过行 mine = yours + 1;
,直接跳到 if
语句比较,这样我就可以 "win"。 main()
不能碰,只有your_fcn()
可以。
我的方法是用缓冲区溢出覆盖 return 地址。所以在这种情况下,我确定 return 地址应该与 buffer
相距 8
字节,因为缓冲区是 4
字节而 EBP
是 4
字节。然后我使用 gdb
来确定我要跳转到的行距函数调用 16
字节。这是 gdb 的结果:
(gdb) disassemble main
Dump of assembler code for function main:
0x0000054a <+0>: lea 0x4(%esp),%ecx
0x0000054e <+4>: and [=11=]xfffffff0,%esp
0x00000551 <+7>: pushl -0x4(%ecx)
0x00000554 <+10>: push %ebp
0x00000555 <+11>: mov %esp,%ebp
0x00000557 <+13>: push %ebx
0x00000558 <+14>: push %ecx
0x00000559 <+15>: sub [=11=]x10,%esp
0x0000055c <+18>: call 0x420 <__x86.get_pc_thunk.bx>
0x00000561 <+23>: add [=11=]x1a77,%ebx
0x00000567 <+29>: movl [=11=]x0,-0xc(%ebp)
0x0000056e <+36>: movl [=11=]x0,-0x10(%ebp)
0x00000575 <+43>: call 0x51d <your_fcn>
0x0000057a <+48>: mov %eax,-0x10(%ebp)
0x0000057d <+51>: mov -0x10(%ebp),%eax
0x00000580 <+54>: add [=11=]x1,%eax
0x00000583 <+57>: mov %eax,-0xc(%ebp)
0x00000586 <+60>: mov -0xc(%ebp),%eax
0x00000589 <+63>: cmp -0x10(%ebp),%eax
0x0000058c <+66>: jle 0x5a2 <main+88>
0x0000058e <+68>: sub [=11=]xc,%esp
0x00000591 <+71>: lea -0x1988(%ebx),%eax
我看到行 0x00000575 <+43>: call 0x51d <your_fcn>
和 0x00000583 <+57>: mov %eax,-0xc(%ebp)
相距四行,这告诉我应该将 ret
偏移 16
字节。但是来自 gdb 的地址说的是不同的东西。也就是说,函数调用从 0x00000575
开始,而我要跳转到的行在 0x00000583
,这意味着它们相距 15
个字节?
无论哪种方式,无论我使用 16
字节还是 15
字节,我都会收到 segmentation fault
错误并且我仍然 "lose".
问题:我做错了什么?为什么 gdb 中给出的地址不是一次 4 个字节,这里实际发生了什么。如何正确跳转到我想要的行?
澄清:这是在虚拟机 运行 linux Ubuntu 上的 x32 计算机上完成的。我正在使用命令 gcc -fno-stack-protector -z execstack -m32 -g guesser.c -o guesser.o
进行编译,该命令会关闭堆栈保护器并强制进行 x32 编译。
gdb of your_fcn()
根据要求:
(gdb) disassemble your_fcn
Dump of assembler code for function your_fcn:
0x0000051d <+0>: push %ebp
0x0000051e <+1>: mov %esp,%ebp
0x00000520 <+3>: sub [=12=]x10,%esp
0x00000523 <+6>: call 0x5c3 <__x86.get_pc_thunk.ax>
0x00000528 <+11>: add [=12=]x1ab0,%eax
0x0000052d <+16>: lea -0x8(%ebp),%eax
0x00000530 <+19>: add [=12=]x8,%eax
0x00000533 <+22>: mov %eax,-0x4(%ebp)
0x00000536 <+25>: mov -0x4(%ebp),%eax
0x00000539 <+28>: mov (%eax),%eax
0x0000053b <+30>: lea 0xc(%eax),%edx
0x0000053e <+33>: mov -0x4(%ebp),%eax
0x00000541 <+36>: mov %edx,(%eax)
0x00000543 <+38>: mov [=12=]x1,%eax
0x00000548 <+43>: leave
0x00000549 <+44>: ret
x86 有可变长度的指令,所以你不能简单地计算指令数并乘以 4。既然你有 gdb 的输出,请相信它来确定每条指令的地址。
函数的return地址是调用指令之后的地址。在显示的代码中,这将是 main+48.
if
语句从 main+60 开始,而不是 main+57。 main+57处的指令将yours+1
存入mine
。所以要将return地址调整为return到if语句,应该加上12(即60 - 48)。
这样做会跳过对 yours
和 mine
的赋值。由于它们都被初始化为 0,所以它会打印 "You won".