Переполнение буфера работает в GDB, но не без него

Я нахожусь на 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#

Однако, когда я запускаю стек программ сам по себе, он вызывает ошибки. Почему это может быть?

8 ответов

Решение

Разработка эксплойтов может привести к серьезным головным болям, если вы не будете адекватно учитывать факторы, которые привносят недетерминизм в процесс отладки. В частности, адреса стека в отладчике могут не совпадать с адресами во время обычного выполнения. Этот артефакт возникает потому, что загрузчик операционной системы помещает как переменные среды, так и программные аргументы перед началом стека:

Схема процесса

Поскольку ваша уязвимая программа не принимает никаких аргументов, переменные среды, вероятно, являются виновником. Убедитесь, что они одинаковы в обоих вызовах, в оболочке и в отладчике. С этой целью вы можете обернуть ваш вызов в 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 в приведенных выше примерах, а не только ./stack а также gdb 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

Вот простой способ запуска вашей программы с одинаковыми стеками в терминале и в 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, убедись в unsetLINES а также COLUMNS,

Примечание: я получил эти переменные среды, играя с тестовой программой.

Эти два запуска дадут вам идентичные указатели на вершину стека, поэтому нет необходимости в удаленных сценариях, если вы пытаетесь использовать двоичный файл, размещенный удаленно.

Адрес указателя фрейма стека при запуске кода в GDB отличается от его обычного запуска. Таким образом, вы можете испортить адрес возврата прямо в режиме GDB, но он может не работать при работе в обычном режиме. Основная причина этого заключается в том, что переменные среды различаются между двумя ситуациями.

Поскольку это всего лишь демонстрация, вы можете изменить код жертвы и распечатать адрес буфера. Затем измените свой обратный адрес на смещение + адрес буфера.

В действительности, однако, вам нужно угадать обратный адрес добавить NOP салазок перед вашим вредоносным кодом. И вы можете угадать несколько раз, чтобы получить правильный адрес, так как ваше предположение может быть неверным.

Надеюсь, это поможет вам.

Причина, по которой переполнение буфера работает в gdb и segfaults, в противном случае в том, что gdb отключает рандомизацию размещения адресного пространства. Я считаю, что это было включено по умолчанию в GDB версии 7.

Вы можете проверить это, выполнив эту команду:

show disable-randomization

И установить его с

set disable-randomization on

или же

set disable-randomization off

Я попробовал решение, принятое здесь, и оно не работает (для меня). Я знал, что GDB добавил переменные окружения и по этой причине адрес стека не совпадает, но даже удаляя эти переменные, я не могу использовать свой эксплойт без GDB (я также пробовал скрипт, размещенный в принятом решении).

Но при поиске в Интернете я нашел другой скрипт, который мне подходит: https://github.com/hellman/fixenv/blob/master/r.sh

Использование в основном такое же, как сценарий в принятом решении:

  • r.sh gdb./program [args] для запуска программы в gdb
  • r.sh./program [args] для запуска программы без GDB

И этот сценарий работает для меня.

Я нахожусь на CentOS 6.4 32-разрядной и пытаюсь вызвать переполнение буфера в программе... Однако, когда я запускаю программный стек только сам по себе, это вызывает ошибки.

Вы также должны убедиться, что FORTIFY_SOURCE не влияет на ваши результаты. Ошибка seg звучит так, как будто FORTIFY_SOURCE может быть проблемой, потому что FORTIFY_SOURCE будет вставлять "более безопасные" вызовы функций для защиты от некоторых типов переполнения буфера. Если компилятор может определить размер буфера назначения, то размер проверяется и abort() вызывается на нарушение (т. е. ваша ошибка сегмента).

Чтобы отключить FORTIFY_SOURCE для тестирования, вы должны скомпилировать с -U_FORTIFY_SOURCE или же -D_FORTIFY_SOURCE=0,

Я думаю, что лучший способ для меня - присоединить процесс бинарника к gdb и использовать setarch -R <binary>временно отключить защиту ASLR только для бинарного файла. Таким образом, кадр стека должен быть одинаковым внутри и без gdb.

Одна из главных вещей, которые делает 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, чтобы увидеть, может ли она обнаружить эту проблему.

Другие вопросы по тегам