Данный документ — вступление к методам анализа исполняемых файлов для Unix с целью определения типа операций, которые они производят с системой. Эта методика предназначена, в первую очередь, для обнаружения «троянов» и прочего вредного софта, так же это может пригодиться для анализа прекомпилирванного программного обеспечения.

Одно из преимуществ программного обеспечения, распространяемого вместе с исходным кодом заключается в том, что пользователь может сам пересмотреть исходник программы, добавить туда, к примеру, свои процедуры и скомпилировать свою, «проверенную» версию софта. Но в программе могут быть «скрытые» бэкдоры, которые не так просто заметить даже в исходном тексте. Некоторые из навороченных бэкдоров включают в себя использование вызовов system() или же exec для того, чтобы передавать команды шеллу, создавание мест, в которых может произвойти переполнени буффера, лёгкое для использования или же прямое использование указателя («void (*fp)()») на бинарные строки, интерпретирующиеся как скомпилированный пригодный для исполнения код. Так же бывают случаи, когда программа попадает к вам в скомпилированом виде, например, как часть rpm или же часть коммерческого приложения.

Но сейчас практически любые UNIX-клоны предлагают богатый выбор различных дебаггеров, которые облегчают анализ скомпилированного кода. Во-первых, анализ должен проводиться с заведомо чистого аккоунта, и, естественно, анализируемый исходник не должен запусткаться перед анализом.

Первый шаг, который вы должны проделать — это просмотреть скомпилированый текст на типичные включения. Это можно проделать, используя strings или при просмотре файла командой less. Любимый редактор автора — joe, который подходит длля просмотра практически всех скомпилированных файлов, в формате, отличающемся от ASCII. Как правило, такие включения есть вкомпилированные в программу имена файлов, к которым она обращается (незачем, к примеру, Вашему любимому named лазить в файлы /etc/master.passwd) или же различные строки, которые она может где-либо искать. Так же в трояненом файле могут содержаться имена баблиотек, можно использовать ldd для поиска библиотек, которые использует программа, и file для того, чтоб определить — обрезана ли программа, какие привязки имеются.

Следующий шаг — все вызовы функций, которые делает программа, могут быть отслежены, и сравнены с вызовами, которые вы видели в исходном коде. Системные вызовы могут быть отслежены практически на всех операционных системах при помощи strace, ktrace (FreeBSD), truss (Solaris). Интересные для вас функции — функции работы с файлами (open/stat/access/read/write), работа с сокетами (listen(), fork()). Дочерние процессы могут быть отслежены опцией -f.

Так же программа может быть дизасссемблирована, это лучше проделать с помощью gdb. Дизассемблироване сводится к тому, что мы отслеживаем функции внутри скомпилированного файла и заменяем их соответствующими ассемблерными инструкциями. Чтобы начать, надо обнаружить входную точку программы. Это есть адрес функции, которая исполняется после старта программы, как правило она называется _start или main. Следуя с этой функции, можно отследить весь процесс исполнения программы, и посмотреть,что эта программа может делать, а что — нет. Функции, на которые стоит обратить внимание — инструкцииcall,jmp(функции вызова процедур и безусловные переходы). Как правило, типичное содержимое скомпилированных программ для x86 выглядит так:0x8048f97 <_start+7 >: call 0x8048eac<atexit>0x8048f9d <_start+13>: call 0x8048dcc <__libc_init_first><br /> 0x69662 <__open+18>: int $0x80Последняя деталь, на которую стоит обратить внимание — строки, которые содержатся в программе в определённых местах, и аргументы, которые передаются функциям. Ссылки на строки (содержащиеся в символьных или других буферах) выглядят в программе совершенно типичным образом. К примеру:void do_something (char *y) {};<br /> char *h = &#171;hello world&#187;;<br /> int main()<br /> {<br /> char *text = h;<br /> int x = getchar();<br /> do_something(text);<br /> return 0;<br /> }В ассемблерном виде это будет выглядеть так:0x804847e<main+6>: movl 0x804950c,%eax<br /> 0x8048483<main+11>: movl %eax,0xfffffffc(%ebp)Сохранить указатель по относительному адресу. Указатель ссылается на адрес статической вкомилрованной в код строки «Hello World».0x8048486<main+14>: call 0x80483cc<getchar>0x804848b<main+19>: movl %eax,%eax<br /> 0x804848d<main+21>: movl %eax,0xfffffff8(%ebp)Вызвать функци getchar, и затем поместить результат (как правило, результат операции сохранятеся в АХ) в стек.0x8048490<main+24>: movl 0xfffffffc(%ebp),%eax<br /> 0x8048493<main+27>: pushl %eax<br /> 0x8048494<main+28>: call 0x8048470<do_something>Здесь указатель на строку помещён в стек командой push как первый и единственный аргумент функции do_something. После вызова do_something будет использовать первый элемент стека как ссылку на аргумент.

Теперь мы можем просмотреть содержимое памяти, куда ссылается указатель:

(gdb) x/a 0x804950c/* опция х, ключ /a показывает содержимое памяти
как адрес, т.е. мы сммотрим, куа показывает указатель */

(gdb) x/s 0x80484fc
/* опция x, ключ /s показывает содержимое памяти
как строку, кончающуюся нулём . */
0x80484fc <_fini+28>: «hello world»

Для больших, комплексных программ такой анализ может занять много времени до того, как все операции, осуществляемые программой будут найдены. Всё же это есть некий метод «защиты от дурака», который не позволит вам быть обманутыми, как в случае свежего «root-эксплоита для Apache», который вместо обещанного просто исполнял локально «шеллкод». Естественно, «шеллкод» рутил не удалённую, а локальную машину, открывая рутшелл на некотором порту.

2000

 

Оставит комментарий