缓冲区溢出在使用gdb时有效,但不使用gdb无效。

55

我正在使用 CentOS 6.4 32 位系统,尝试在一个程序中引发缓冲区溢出。在 GDB 中它可以工作。这是输出:

[root@localhost bufferoverflow]# gdb stack
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-60.el6_4.1)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /root/bufferoverflow/stack...done.
(gdb) r
Starting program: /root/bufferoverflow/stack
process 6003 is executing new program: /bin/bash
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.107.el6_4.2.i686
sh-4.1#

然而,当我单独运行程序堆栈时,它会出现段错误。为什么会这样呢?


"seg faults" 是由于缓冲区溢出引起的,你已经做了正确的事情。当你运行代码时,操作系统会向你的进程(=正在执行的程序)发送 SIGSEGV 信号,以便在内存违规时给你一个分段错误的消息 - 这个信号是由于你正在对有效内存进行无效访问。 (我猜你试图在结尾处写入/修改 "constantstring")。 - Grijesh Chauhan
我知道。它应该执行一个 shell。在 GDB 中它执行了 shell。当我在 GDB 之外运行程序时,它不会运行 shell,因此导致了段错误。 - thaweatherman
这是因为当你在GDB之外运行代码时,它会产生C标准中的未定义行为。而GDB处理SIGSEGV信号,以便它可以指向分段错误点。 - Grijesh Chauhan
@GrijeshChauhan:GDB会处理SIGSEGV 并报告它发生了。但这里没有这样的报告。我完全期望在bash返回后出现这种情况,但段错误甚至在它开始之前就会发生。 - cHao
@cHao :( :) (: :) 不知道,我得自己尝试一下实验。 - Grijesh Chauhan
8个回答

138

如果你没有充分考虑导致调试过程中出现不确定性的因素,那么漏洞开发可能会带来严重的问题。特别是,在调试器中的堆栈地址可能与正常执行期间的地址不匹配。这种现象发生是因为操作系统加载程序参数和环境变量时,会将它们放在堆栈的开始位置之前

Process layout

由于你的易受攻击程序不接受任何参数,因此环境变量很可能是罪魁祸首。确保在shell和调试器中都使用相同的环境变量。为此,你可以将调用包装在env中:

env - /path/to/stack

使用调试器:

env - gdb /path/to/stack
($) show env
LINES=24
COLUMNS=80

在上面的例子中,gdb设置了两个环境变量,您可以进一步禁用它们:
unset env LINES
unset env COLUMNS

现在,show env 应该返回一个空列表。此时,您可以开始调试过程,找到您想要跳转到的绝对堆栈地址(例如,0xbffffa8b),并将其硬编码到您的攻击程序中。
还有一个微妙但重要的细节:调用 ./stack/path/to/stack 之间存在差异:由于 argv[0] 存储了您所调用的程序,您需要确保调用字符串相同。这就是为什么我在上面的示例中使用了 /path/to/stack 而不仅仅是 ./stackgdb stack
当学习使用内存安全漏洞进行攻击时,建议使用下面的包装程序,它会处理繁重的工作并确保堆栈偏移量相等:
$ invoke stack         # just call the executable
$ invoke -d stack      # run the executable in GDB

这里是脚本:

#!/bin/sh

while getopts "dte:h?" opt ; do
  case "$opt" in
    h|\?)
      printf "usage: %s -e KEY=VALUE prog [args...]\n" $(basename $0)
      exit 0
      ;;
    t)
      tty=1
      gdb=1
      ;;
    d)
      gdb=1
      ;;
    e)
      env=$OPTARG
      ;;
  esac
done

shift $(expr $OPTIND - 1)
prog=$(readlink -f $1)
shift
if [ -n "$gdb" ] ; then
  if [ -n "$tty" ]; then
    touch /tmp/gdb-debug-pty
    exec env - $env TERM=screen PWD=$PWD gdb -tty /tmp/gdb-debug-pty --args $prog "$@"
  else
    exec env - $env TERM=screen PWD=$PWD gdb --args $prog "$@"
  fi
else
  exec env - $env TERM=screen PWD=$PWD $prog "$@"
fi

4
我按照你建议的做法,将程序作为/root/bufferflow/stack运行,并且它正常工作了。谢谢。 - thaweatherman
7
非常有用的信息!谢谢您还分享了脚本。 - alecov
@RdlP 我也是一样的情况:即使使用绝对路径和“清理过”的环境来调用我的程序,%esp在溢出应该发生的地方也不同。你必须手动更改绝对地址,或者使用另一种方法。 - Nicolas Garnier
为什么"./stack"和"/path/to/stack"不同? - sww
我只是在尝试弄清楚的是,gdb在放置参数时是否使用完整路径作为原因? - sww
显示剩余7条评论

11

这是在终端和gdb中使用相同堆栈的简单方式:

首先确保您的程序没有启用堆栈保护功能:

gcc -m32 -fno-stack-protector -z execstack -o shelltest shelltest.c -g

并且禁用了地址空间布局随机化(ASLR):

echo 0 > /proc/sys/kernel/randomize_va_space

注意:我的计算机上默认值为2,更改前请查看您的计算机上的默认值。

然后以以下方式运行您的程序(分别在终端和gdb中):

env -i PWD="/root/Documents/MSec" SHELL="/bin/bash" SHLVL=0 /root/Documents/MSec/shelltest
env -i PWD="/root/Documents/MSec" SHELL="/bin/bash" SHLVL=0 gdb /root/Documents/MSec/shelltest

gdb 中,请确保取消设置 LINESCOLUMNS

注意:我通过玩弄一个测试程序得到了这些环境变量。

这两个运行将为您提供指向堆栈顶部的相同指针,因此如果您正在尝试利用远程托管的二进制文件,则不需要远程脚本诡计。


10

在使用gdb调试代码时,栈帧指针的地址与正常运行时不同。因此,在gdb模式下可能会破坏返回地址,但在正常模式下可能不会出现这种情况。主要原因是两种情况下环境变量不同。

由于这只是一个演示,您可以更改受害者代码并打印缓冲区的地址。然后将返回地址更改为偏移量+缓冲区地址。

然而,在实际情况中,您需要在恶意代码之前猜测返回地址并加上NOP sled。您可能需要多次猜测以获得正确的地址,因为您的猜测可能不正确。

希望这可以帮助您。


7
你的缓冲区溢出在gdb下可以工作,但在其他情况下会导致段错误的原因是gdb禁用了地址空间布局随机化。我相信这在gdb版本7中默认启用。
你可以通过运行以下命令来检查此设置:
show disable-randomization

并使用以下代码进行设置:
set disable-randomization on

或者

set disable-randomization off

1
在GDB中它被禁用了。我也认为我通过sysctl -w kernel.randomize_va_space=0在操作系统中将其关闭了。 - thaweatherman

4
我尝试了这里接受的解决方案,但它对我不起作用。我知道gdb添加环境变量,因此堆栈地址不匹配,但即使删除了这些变量,我也无法在没有gdb的情况下使用我的漏洞利用(我还尝试了接受的解决方案中发布的脚本)。
但是,在网上搜索后,我找到了其他适合我的脚本:https://github.com/hellman/fixenv/blob/master/r.sh 使用方法基本与接受的解决方案中的脚本相同:
  • r.sh gdb ./program [args] 在gdb中运行程序
  • r.sh ./program [args] 在没有gdb的情况下运行程序
这个脚本对我有用。

1
我在CentOS 6.4 32位系统上尝试在程序中引发缓冲区溢出,但是当我只运行程序堆栈本身时,它会导致段错误。您还应确保FORTIFY_SOURCE不影响您的结果。段错误听起来像FORTIFY_SOURCE可能是问题的原因,因为FORTIFY_SOURCE将插入“更安全”的函数调用,以防止某些类型的缓冲区溢出。如果编译器可以推断目标缓冲区大小,则会检查大小,并在违规时调用abort()(即,导致段错误)。为了关闭测试中的FORTIFY_SOURCE,您应该使用-U_FORTIFY_SOURCE-D_FORTIFY_SOURCE=0进行编译。

0

gdb的主要功能之一是清零内存,这是在gdb外部不会发生的事情。很可能在代码中的某个地方,您没有初始化内存,导致它获取垃圾值。Gdb自动清除您分配的所有内存,隐藏了这些类型的错误。

例如:以下内容应该在gdb中工作,但在其外部不起作用:

int main(){
    int **temp = (int**)malloc(2*sizeof(int*)); //temp[0] and temp[1] are NULL in gdb, but not outside
    if (temp[0] != NULL){
        *temp[0] = 1; //segfault outside of gdb
    }
    return 0;
}

尝试在valgrind下运行您的程序,以查看它是否能够检测到此问题。


这个易受攻击的程序实际上是将文件读入缓冲区,因此文件就成为了漏洞字符串。然后它调用一个函数,将原始缓冲区复制到一个更小的缓冲区中。因此,在这些函数之外,它从未初始化内存。 - thaweatherman

0

我认为对我来说最好的方法是使用gdb附加二进制文件的进程,并使用setarch -R <binary>仅暂时禁用二进制文件的ASLR保护。这样,堆栈帧应该在gdb内外是相同的。


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