Мы видели здесь простые эксплоиты. Используя тот же принцип, мы можем получить оболочку, передавая шеллкод или через argv[] или переменную окружения уязвимой программе. Мы просто должны установить правильный адрес (т.е. адрес вызова оболочки) в секции .dtors.
В данный момент мы знаем:
как исследовать стек разумных пределах (теоретически, предела нет, однако становится довольно трудно достаточно быстро забирать слова из стека одно за одним);
как записать ожидаемое значение по нужному адресу.
Однако, в реальной жизни, уязвимая программа не настолько хороша, как в примере. Мы представим метод, который позволяет нам поместить шеллкод в память и находить его точный адрес (это значит: больше не нужны NOP-ы в начале шеллкода).
Идея основана на рекурсивных вызовах функции exec*():
Мы сразу же замечаем, что адреса, выделяемые для arg и argv, больше не двигаются после второго вызова. Мы будем использовать это свойство в нашем эксплоите. Мы просто должны немного изменить нашу программу build, чтобы она вызывала себя перед вызовом vuln. Так мы получаем точный адрес argv и нашего шеллкода:
Хитрость в том, что мы знаем, что вызывать, исходя из количества аргументов, полученных программой. Чтобы запустить эксплоит, мы просто передаем build2 адрес, куда мы хотим писать, и смещение. Нам не надо больше передавать значение, так как оно вычисляется нашими последовательными вызовами.
Чтобы достичь цели, нам надо сохранять одинаковое распределение памяти между различными вызовами build2, а затем vuln (вот почему мы вызываем функцию build(), чтобы использовать одинаковый "отпечаток" памяти).:
Почему это не работает? Мы сказали, что должны построить точную копию памяти между двумя вызовами ... а сами не сделали этого! argv[0] (имя программы) поменялось. Наша программа вначале называлась build2 (6 байт), а затем vuln (4 байта). Различие в 2 байта, которое в точности равно значению, которое вы могли заметить в примере выше. Адрес шеллкода при втором вызове build2 равен sc=0xbffff88f, однако содержимое argv[2] в vuln дает нам 20xbffff891: наши 2 байта. Чтобы решить проблему, достаточно переименовать build2 в что-нибудь из 4 букв, например, bui2:
Опять выиграли: таким способом это работает намного лучше ;-) Программа запуска оболочки находится в стеке, мы изменили адрес, на который указывал ptrf, чтобы он указывал на наш шеллкод. Естественно, такое может произойти только если стек доступен для выполнения.
Но мы видели, что строки формата позволяют нам производить запись куда угодно: давайте добавим деструктор в нашу программу в секцию .dtors:
Опять, так как данное окружение находится в стеке, мы должны заботиться о том, чтобы не изменять память (т.е. изменять позицию переменных и аргументов). Имя выполняемого файла должно содержать такое же количество символов, как и имя уязвимой программы vuln.
Здесь, мы решили использовать глобальную переменную extern char **environ, для установки нужных нам значений:
environ[0]: содержит шеллкод;
environ[1]: содержит адрес, куда мы предполагаем произвести запись;
environ[2]: содержит смещение.
Мы покидаем вас, чтобы вы поиграли с этим ... эта (слишком) длинная статья уже наполнена слишком большим количеством исходного кода и тестирующих программ.