Как заточить exploit под себя

       

Чем компилировать?


Чаще всего exploit'ы пишутся на Си/Си++, Perl, Python и PHP, реже на всякой экзотике типа Ruby, причем тип языка указывается далеко не всегда, а о версии транслятора и ключах компиляции остается только догадываться. Вот такая культура программирования, с которой нам приходится жить.

Ладно, Perl узнается с первого взгляда по строке "#!/usr/bin/perl", идущей впереди листинга. Если же ее нет, смотрим на следующее:

q       присутствуют директивы в стиле "use IO::Socket;"

q       точка с запятой ставится в конце каждой строки;

q       тело функций и многострочечных циклов/операторов if заключено в фигурные скобки;

q       отступ внутри тела роли не играет и часто отсутствует;

q       мнострочечные строковые константы соединяются через точку;

Выполнение всех этих условий свидетельствует о том, что перед нами Perl. Язык Python внешне похож на него, но содержит ряд принципиальных отличий (и обычно предваряется строкой "#!/usr/bin/python", которой, впрочем, может и не быть):

q       присутствуют директивы в стиле "import socket", "import sys";

q       точка с запятой в конце строки _не_ ставится;

q       тело функций и многострочечных циклов, операторов if _не_ берется в скобки;

q       отступ внутри тела функций, оператор if и циклов строго обязателен;

q       мнострочечные строковые константы соединяются как в Си ("<ENTER>");



Выполнение всех этих условиях — верный признак Питона, который как и Perl портирован на множество платформ и распространяется на бесплатной основе.

Наибольшие проблемы вызывает комплект поставки. Достаточно часто хакеры выкладывают не весь exploit, а только его часть и транслятор начинает материться на отсутствующие включаемые файлы/библиотеки.
Такие exploit'ы следует сразу отправлять в топку, хотя при наличии большого количества свободного времени и некоторого опыта работы с языком недостающие файлы можно (теоретически) воссоздать и самостоятельно. Но… зачем?!

Исключение составляют листинги, содержащие в себе строку "This file is part of the Metasploit Framework" и являющиеся модулями Framework'а, без которого они, естественно, не запускаются. Присутствие такой строки необязательно, но сама структура модуля настолько характерна, что увидев такую штуку один-единственный раз, будешь распознавать ее всегда, вот, например: http://milw0rm.com/exploits/1788.

С диалектами Си/Си++ все намного сложнее. Компиляторов просто море, а разногласиями в понимании Стандарта можно было бы утопить целый океан, поэтому, очень часто случается так, что программу, написанную под один компилятор не удается (без переделок) откомпилировать ничем другим. Последняя версия компилятора далеко не всегда оказывается самой лучшей (в особенности это касается gcc, в ядро которого вносится большое количество изменений, зачастую не без ущерба для обратной совместимости).

Первым делом необходимо определить: приплюснутый это си или классический? Вот характерные черты приплюснутого:

q       объявление переменных по месту использования, а не в начале функции;

q       наличие ключевых слов класс и двух двоеточий "::";

q       использование new для выделения памяти или явное преобразование типа после malloc;

q       отсутствует printf, а весь ввод/вывод осуществляется операторами "<<" и ">>"

Если хоть одно из этих условий выполняется, программа явно написана на приплюснутом си, в противном случае используется классический. Кстати говоря, си/си++ отличается от perl/python своими директивами "#include" и еще тем, что символ "#" в нем никогда не используется для оформления комментариев.



В отличии от интерпретируемых языков, библиотеки которых более или менее стандартизированы, си-компиляторы включают в себя большое количество системно-зависимых библиотек, в результате чего, программа может вызывать функции, отсутствующие в нашем трансляторе или использовать специфические особенности конкретной версии языка. В первую очередь это касается сырых сокетов (по разному реализованных в LINUX и *BSD) и прочих системно-зависимых фич. Некоторые exploit'ы пишутся в расчете на Windows и вместо "общепринятых" функций типа fopen/fclose используют громоздкие API-вызовы CreateFile/CloseHandle. Откомпилировать такой exploit под UNIX'ом вполне возможно, но для этого придется заменять API-вызовы на соответствующие им Си-функции или syscall'ы. Самое неприятное в том, что у Microsoft имеется свой собственный, особый взгляд на интерфейс сокетов и для переноса Windows-кода, работающего с сокетами, под UNIX приходится осуществлять большие телодвижения или… искать альтернативный UNIX-exploit. Формальным признаком форточной природы кода является наличие функции WSAStartup, которая в UNIX-подобных системах и не ночевала. Но классический си — это только цветочки. Самое страшное как всегда впереди.

Приплюснутый си — это настоящий кошмар. Компиляторы (и поставляемые вместе с ними библиотеки) различаются просто колоссально! Приходится иметь в своем распоряжении целую артиллерию GCC различных версий, а в рукавах держать всякую экзотику типа INTEL C++, но и тогда будут встречаться программы, которые упорно не хотят компилироваться!



Рисунок 5 попытка компиляции файла beta.cpp и ее результат

Яркий тому пример — http://milw0rm.com/shellcode/656

(прилагается к статье под именем beta.cpp). Пропускаем его через gcc и получаем следующий список ошибок (не считая варнигов):

beta.cpp:34:21: windows.h: No such file or directory

beta.cpp: In function `int main(int, char**, char**)':

beta.cpp:165: error: `stricmp' undeclared (first use this function)



beta.cpp:185: error: `strnicmp' undeclared (first use this function)

beta.cpp:245: error: `isalnum' undeclared (first use this function)

beta.cpp:250: error: `isprint' undeclared (first use this function)

beta.cpp:339: error: invalid conversion from `void*' to `char*'

beta.cpp:356: error: `O_BINARY' undeclared (first use this function)

beta.cpp:361: error: `lseek' undeclared (first use this function)

beta.cpp:377: error: invalid conversion from `void*' to `char*'

beta.cpp:384: error: `read' undeclared (first use this function)

beta.cpp:398: error: `close' undeclared (first use this function)

Листинг 1  список ошибок, выдаваемый компилятором gcc при попытке трансляции файла beta.cpp

Ну, с ошибкой 34 все понятно — программа усиленно косит под форточки, не понятно за каким хреном таща за собой файл <windows.h>, но тут же использует стандартные POSIX- вызовы: open (со странным флагом O_BINARY), lseek, read и close, которых ни в самом windows, ни в одном из win32-компиляторов никогда не существовало (см. ошибки 356, 361, 384 и 398). Убираем строку "#include <windows.h>", заменяя ее на "#include <unistd.h>" и удаляем глупый флаг O_BINARY, поскольку по умолчанию файл уже является двоичным (узнать какие заголовочные файлы соответствуют данной функции можно из man'а, например, "man 2 open"). Для избавления от ругательства "this file include <malloc.h> witch is deprecated, use <stdlib.h> instead" удаляем и "#include <malloc.h>".

Ошибки 245 и 250 устраняются подключением их "родного" заголовочного файла, в котором они были объявлены "#include <ctype.h>" (см. "man isalnum"). А вот функций stricmp и strnicmp в gcc действительно нет, однако, они могут быть заменены на аналогичные им strcmp и strncmp

даже без коррекции аргументов!

Ошибки 339 и 377 исправляется еще проще: достаточно взять строку "buffer=malloc(MAX_BUFFER_SIZE)" и добавить явное преобразование типов, так же называемое ксатингом: "buffer=(char*)malloc(MAX_BUFFER_SIZE)".

Исправленный вариант лежит в файле beta-fixed.cpp и компилируется безо всяких нареканий.

Короче, будем считать, что с идентификацией транслятора мы разобрались и exploit откомпилировался нормально, но... это еще не конец, а только начало. Ведь программный листинг это только оболочка (образна говоря "тетива"), а разящие острие — загадочный и таинственный shell-код, засунутый в строковой "иероглифический" массив "\x29\xc9\x83…\xe9\xb0\xd9" или типа того. Что делать, если он не работает, или работает не так, как нам этого хочется?


Содержание раздела