GDB显示的地址与代码中不同

11

我正在尝试实现缓冲区溢出攻击,我需要知道我要溢出的缓冲区的地址。

使用GDB显示的地址与只在代码中执行时显示的地址是不同的:

精确代码:

#include<stdio.h>

int main() {
   char buffer[20];
   printf("%p\n", buffer); // 0xbffff320

   return 0;
}

然而,在gdb中,如果我执行以下操作:
p &buffer

我得到的地址是:0xbffff330
为什么会有差异?这会影响我的缓冲区溢出攻击吗?
我已经禁用了ALSR和栈保护。
谢谢。
编辑1:即使我通过gdb逐步执行并遇到打印行,我也会得到0xbffff320作为地址。
编辑2:
环境:在Windows 7上运行的虚拟盒中的Ubuntu Linux 9映像。
GDB版本:6.8-debian。
使用GCC编译,例如:gcc -g -fno-stack-protector filename.c 立即执行:./a.out 打印的地址:0xbffff320
然后像这样在调试器中打开:gdb ./a.out 然后输入:b main 然后输入:run 然后输入:p & buffer
然后地址是0xbffff330
编辑3:
这是重现此行为的GDB日志: $ gdb ./a.out
b main
run p & buffer /*此处的地址与运行可执行文件时显示的不同*/
逐步执行程序到printf语句 /*此处的地址与运行程序时打印的不同*/

1
你的意思是,在gdb中同一次运行中,printf和gdb的print输出不同的值? - hdl
2
@Olaf,这是一个大学项目。我们正在学习如何进行堆栈溢出攻击。 - Kingamere
1
@hdl,我们只允许在禁用堆栈保护的情况下进行编译。因此,我们唯一可以提供的标志是-fno-stack-protector。(除了可选的标志,如-g用于调试,-o用于重命名可执行文件等。) - Kingamere
那么你应该问你的导师。这所大学很奇怪(猜测其他询问类似问题的人都来自于这所大学)。最好学会如何制作土豆泥。 - too honest for this site
3
如果在gdb中p &buffer打印的地址与在gdb中跳过printf语句后的地址相同,则这与gdb仅仅向环境添加内容是一致的,正如@ouah的答案所解释的那样。这里还有另一个不同之处:argv[0]。我已经添加了一个答案来解决这个问题。 - Mark Plotnick
显示剩余26条评论
4个回答

17

据我理解,问题是为什么在从shell启动程序和从gdb启动程序时,main函数中的局部变量地址不同。

下面是一个示例程序来展示这种差异:

mp@ubuntu:~$ cat s.c
#include<stdio.h>

int main(int argc, char **argv) {
  char buffer[20];
  system("env");
  printf("%s %p\n", argv[0], buffer);

  return 0;
}

我们将在一个干净的环境中运行它。(我还禁用了ASLR)。

mp@ubuntu:~$ env -i sh
$ ./s
PWD=/home/mp
./s 0xbffffe48

 

$ gdb ./s
(gdb) run
Starting program: /home/mp/s 
COLUMNS=80
PWD=/home/mp
LINES=42
/home/mp/s 0xbffffe08

使用gdb命令print &buffer输出的结果与程序内部地址相同,但与在shell中运行程序时不同。

(gdb) b 6
Breakpoint 1 at 0x804849c: file s.c, line 6.
(gdb) run
Starting program: /home/mp/s 
COLUMNS=80
PWD=/home/mp
LINES=42

Breakpoint 1, main (argc=1, argv=0xbffffed4) at s.c:6
6      printf("%s %p\n", argv[0], buffer);
(gdb) p &buffer
$1 = (char (*)[20]) 0xbffffe08
(gdb) n
/home/mp/s 0xbffffe08
8      return 0;

造成差异的原因有几个:

  • gdb使用绝对路径调用程序,因此argv数组更大。
  • gdb设置(或在本例中添加)了两个环境变量。这是在readline/shell.c:sh_set_lines_and_columns()中完成的。因此environ数组更大。

要从环境中删除这两个变量,可以使用unset environmentset exec-wrapper运行env -u ...。这样,在gdb下运行程序时,其地址与在shell中运行时相同(如果我们使用绝对路径)。

$ `pwd`/s
PWD=/home/mp
/home/mp/s 0xbffffe28

$ gdb `pwd`/s
(gdb) set exec-wrapper env -u LINES -u COLUMNS
(gdb) run
Starting program: /home/mp/s 
PWD=/home/mp
/home/mp/s 0xbffffe28

啊,我明白了,谢谢。这个问题困扰了我一段时间。环境变量、行和列是什么? - Kingamere
1
是的,gdb使用的readline包设置它们。它们是您终端窗口的尺寸。我还没有深入研究为什么会这样。它可能在内部使用这些值,以便可以正确显示超过一个物理终端行的命令行。 - Mark Plotnick
好的,知道了。再次感谢。 - Kingamere

1

你的系统中的数组对象存储在堆栈中。在堆栈的顶部,除了其他内容外,还有环境。当你使用 gdb 运行程序时,gdb 会提供不同的环境变量及其值,这解释了地址差异。

你可以通过在 gdb 中运行 show environment 命令,并将输出与你的 shell 中的 set 命令进行比较,来检查差异。


谢谢,我现在会看一下。 - Kingamere
对我来说这没有任何意义。我不需要导出某些环境变量才能看到地址在每次运行后发生变化!它们已经在每次运行后发生变化了。奇怪的是,就像执行两次相同的printf并获得不同的结果一样。 - hdl
3
如果您正在使用gdb,ASLR的状态可能会有所不同,您可以使用set disable-randomization off在gdb中禁用它。 - ouah
就像我说的那样,这与ASLR无关(在我的情况下已经禁用),因为地址不会从运行到运行改变,它们在每次运行时都是一致的,但彼此之间是不同的。 - Kingamere
@ouah,我知道只要运行它们,地址就会不同。但我不确定你的意思是什么。 - Kingamere
显示剩余12条评论

0

目前,我能想到的唯一原因是:

  • 您尝试在程序终止打印print &buffer。解决方案:尝试在mainrunnext上设置断点以执行printfprint &buffer
  • 您首先在gdb之外运行了程序,然后在gdb内运行它,但忘记使用next执行printf行。
  • 您的gdb版本中存在错误
  • 您的gcc版本中存在错误(gcc可能会生成不正确的调试信息:请参见12

是的,我做了:“b main”,然后“run”,然后立即执行了“p&buffer”。 - Kingamere
@Ikshvaku,那你是否使用next执行了printf指令?(请参见更新的答案) - hdl
其实可能只是我的GDB版本太老了(6.8)。你的GDB版本是多少? - Kingamere
我的版本是7.7.1,可能已经有人报告了一个错误,并且已经修复了。尽管这相当奇怪,考虑到gdb的整体质量... - hdl
1
这次地址仍然不同。尽管地址不同,我实际上能够解决我的缓冲区溢出问题。这只是一个非常小的问题,不会对攻击产生太大影响。我相信,正如我的教授所说,这只是GDB导致的行为。因此,我将标记问题已回答。 - Kingamere
显示剩余2条评论

0
发现这是旧版本GDB(我的是6.8-debian)中预期的行为,如果你正确构造缓冲区溢出攻击,就可以解决这个问题,不会成为问题。

你能分享一些参考资料吗?你是怎么找到的呢? :) - hdl
我的唯一参考是我的教授告诉我,gdb会在堆栈底部添加一些指令,以便进行适当的调试。我的观点是,在执行缓冲区溢出攻击的情况下,这种地址差异不应该对攻击产生太大影响,你仍然可以成功地执行它。 - Kingamere

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接