Мы начинаем писать небольшую программу для запуска уязвимого приложения с записью данных, которые переполнят стек. Эта программа имеет различные опции для выбора позиции шеллкода в памяти и для выбора программы для запуска. Данная версия, основанная на статье Aleph One из номера 49 журнала phrack, доступна на сайте Christophe Grenier-а.
Каким образом мы перешлем подготовленный буфер приложению? Обычно вы можете использовать параметры командной строки, как в vulnerable.c, или переменную окружения. Причиной пререполнения может быть также ввод данных или чтение их из файла.
Программа generic_exploit.c вначале выделяет буфер нужного размера, затем копирует туда шеллкод и заполняет буфер адресами и кодами NOP, как было описано выше. Затем, оно подготавливает массив аргументов и запускает атакуемое приложение, используя инструкцию execve(), последнее заменит текущий процесс запускаемым. Программе generic_exploit нужно знать размер буфера для атаки (немного больше, чем реальный размер, чтобы перезаписать адрес возврата), смещение в памяти и выравнивание. Мы указваем будет буфер передан как переменная окружения (var) или из командной строки (novar). Аргумент force/noforce определяет, будет ли вызов запускать функцию setuid()/setgid() из шеллкода.
Чтобы использовать vulnerable.c в своих целях, наш буфр должен быть больше, чем ожидает приложение. Например, мы выбираем 600 байт вместо ожидаемых 500. Мы находим смещение от вершины стека при помощи последовательных испытаний. Адрес, построенный инструкцией addr = get_sp() + offset;, используется для перезаписи адреса возврата, вы получите его... имея небольшое везение! Операция предполагает, что содержимое регистра %esp ненамного отличается в текущем процессе и процессе, вызванном в конце программы. Практически, это не точно: различные события могут изменить состояние стека со времени вычисления до вызова атакуемой программы. Здесь нам удалось запустить переполнение при помощи смещения -1900 байт. Конечно, чтобы закончить опыт, vulnerable должен быть Set-UID root.
$ cc vulnerable.c -o vulnerable
$ cc generic_exploit.c -o generic_exploit
$ su
Password:
# chown root.root vulnerable
# chmod u+s vulnerable
# exit
$ ls -l vulnerable
-rws--x--x 1 root root 11732 Dec 5 15:50 vulnerable
$ ./generic_exploit 600 -1900 0 novar noforce /bin/sh ./vulnerable
bsize 600, offset -1900
Using address: 0lxbffffe54
Shellcode will start /bin/sh
bash# id
uid=1000(raynal) gid=100(users) euid=0(root) groups=100(users)
bash# exit
$ ./generic_exploit 600 -1900 0 novar force /bin/sh /tmp/vulnerable
bsize 600, offset -1900
Using address: 0lxbffffe64
uid 0
Shellcode will start /bin/sh
bash# id
uid=0(root) gid=100(users) groups=100(users)
bash# exit
В первом случае (noforce) наш uid не изменился. Тем не менее, у нас появился новый euid, предоставляющий все права. Поэтому, даже если vi говорит при редактировании /etc/passwd, что он только для чтения, мы все-таки можем сохранить файл, и все изменения будут работать: надо всего лишь записывать при помощи w! :) Параметр force делает uid=euid=0 при запуске.
Чтобы автоматически найти значение смещения для переполнения, можно использовать следующий небольшой шелл скрипт:
В нашем эксплоите мы не принимали в расчет возможную проблему выравнивания. Поэтому возможно, что этот пример не будет работать у вас с теми же значениями или не будет работать вообще из-за выравнивания. (Те, кто хочет протестировть пример в любом случае, должны поменять параметр выравнивания на 1, 2 или 3(у нас 0)). Некоторые системы не позволяют писать в области памяти, не являющиеся полными словами, однако в Linux это не так.