Пошук і аналіз "троянських коней" під UNIX

Mixter, Security papers
Переклад: Василь Кондрашов, www.citforum.ru

Цей документ – спроба дати уявлення про методи аналізу виконуваних файлів ОС UNIX для передбачення дій, які вони можуть зробити в системі. Ці методи застосовні для дослідження виявлених "Троянських коней" та іншого шкідливого програмного забезпечення. Вони також будуть корисні для аналізу прекомпільованних програмного забезпечення з метою переконатися в його надійності.

При використанні програмного забезпечення з відкритим вихідним кодом користувач може отримати вихідний код програми і відкомпілювати свою, надійну версію. Однак, вихідний код може містити можливості троянського коня, які нелегко помітити. Деякі з витончених способів виробництва прихованих дій використовують системні виклики system() або exec для передачі команд, що викликають навмисні переповнення або небезпечні ситуації, інтерпретатору shell. Використовується також безпосереднє виконання інструкцій асемблера шляхом створення покажчика (“void (*fp)()”) на двійкову рядок з наступним його виконанням. Проте, буває, що програми прекомпільованних, наприклад, як частина rpm або подібного двійкового пакета, або частини комерційного програмного забезпечення, або виконавчі файли скомпільовані з неперевіреного вихідного коду, до того ж віддаленого після компіляції.

На щастя, більшість UNIX-систем пропонують безліч інструментів для розробки та налагодження, які полегшують аналіз двійкових файлів. Перш за все, все має робитися в "чистій", тобто, надійної середовищі, де виявлений двійковий файл досліджується, але не був ще виконаний. Естетственно, потрібно використовувати непривілейованих обліковий запис (account). Якщо Вам дійсно потрібно шукати і аналізувати можливих "Троянців" з ненадійною системі, то потрібно використовувати автономний інтерпретатор shell (sash), який повинен бути поєднаний статично (statically linked). У такому випадку єдиною програмою, що грає роль "троянця" може бути модуль ядра, відповідальний за упаковку системних викликів open і read, Але такі "троянці" досить рідкісні. При використанні автономного інтерпретатора shell найбільш значущими є команди ls, more і ed.

Перше, що повинно бути зроблено для пошуку "троянця" – пошук явних кодограмм в двійковому файлі. Це може бути зроблено з використанням strings або переглядом за допомогою less. Автор першоджерела воліє редактор joe, Який дозволяє переглядати та редагувати майже всі не-ascii символи. Кодограмм зазвичай включають жорстко запрограмований імена використовуваних файлів, ascii-рядки, які програма записує в інші файли або статичні рядки, які вона може шукати, або імена використовуваних бібліотечних функцій, якщо з файлу не видалені символи. Вони можуть також містити імена незвичайних бібліотек або бібліотек, котрі вони не мають наміру використовувати будучи "троянцем". Краще за все перевірити це, використовуючи ldd для визначення залежностей від бібліотек і file для визначення чи були видалені символи, чи була програма статично пов'язана і інших спеціальних форматів.

На наступному етапі аналізу програми потрібно простежити виклики функцій, що виконуються програмою і порівняти їх з функціями, які програма, за припущенням, повинна виконувати. Системні виклики можуть бути простежено в більшості систем за допомогою strace, ktrace в BSD, або truss в Solaris. Слід звернути увагу на всі спроби доступу до файлів (open/stat/access/read/write), Виклику гнізд (socket calls), особливо, виклики listen() і fork(). Породжені процеси можуть бути простежені за допомогою опції -f в усіх цих програмах.

Заслуговує інтересу подібний інструмент для Linux – ltrace, Який розпізнає всі бібліотечні виклики, вироблені програмою в обхід системних програм і дозволяє створити дуже докладний список параметрів програми.

Нарешті, важливо те, що програма може і повинна бути дизасемблювати, переважно, з використанням gdb. Дизасемблювання, в основному, означає виявлення функцій у двійковому файлі і переклад двійкового коду назад у команди асемблера. Спочатку має бути визначено точку входу в програму. Це – адреса початку функції, яка буде виконана при запуску програми і яка, якщо з програми не була модифікована або з неї не були видалені символи, завжди називається main або _start. Слідуючи за цією функцією можна простежити процес виконання програми і побачити, що може, а чого не може зробити програма. Особливо цікаві виклики інструкцій (function
calls
) Та інструкції jmp/int, Якщо вони використовують небібліотечние виклики ядра або скомпільовані статично. Типові точки входу для двійкових файлів архітектури x86 виглядають приблизно так:

0x8048f97 <_start+7>:   call   0x8048eac <atexit>
0x8048f9d <_start+13>: call 0x8048dcc <__libc_init_first>
0x69662 <__open+18>:    int    $0x80

Останньою деталлю розгляду є рядки, що знаходяться в певних частинах програми і аргументи, що передаються функціям. Рядки (що містяться в символьному чи іншому буферах) згадуються в програмі певними загальними способами, наприклад:

void do_something (char *y) {};
char *h = "hello world";
int main() {
char *text = h;
int x = getchar();
do_something(text);
return 0;
}

Це відповідає наступним командам асемблера:

0x804847e <main+6>:     movl   0x804950c,%eax
0x8048483 <main+11>:    movl   %eax,0xfffffffc(%ebp)

Зберегти покажчик за відносною адресою. Покажчик посилається на статичну, жорстко запрограмовану рядок “hello world” в коді.

0x8048486 <main+14>:    call   0x80483cc <getchar>
0x804848b <main+19>:    movl   %eax,%eax
0x804848d <main+21>:    movl   %eax,0xfffffff8(%ebp)

Викликати функцію getchar і помістити результат (мабуть, ціле. зберігається в регістрі EAX) в стек. EBP – базовий покажчик, використовуваний для посилання на адреси щодо текукщей функції в стек.

0x8048490 <main+24>:    movl   0xfffffffc(%ebp),%eax
0x8048493 <main+27>:    pushl  %eax
0x8048494 <main+28>: call 0x8048470 <do_something>

Тут отриманий назад покажчик на рядок відправляється як перший і єдиний аргумент функції do_something в стеку. Отже, очевидно, рядок, на яку посилається вказівник, передається функції.

Тепер ми можемо вручну разименовать покажчик за допомогою команди x
:

(gdb) x/a 0x804950c
/* x option /a displays the memory content as an address, to
   see which address a pointer actually points to */
0x804950c <h>:  0x80484fc <_fini+28>
(gdb) x/s 0x80484fc
/* x option /s displays the memory content as string up to the
   point where a terminating \0 is found */
0x80484fc <_fini+28>:    "hello world"

У великих і складних програмах (автор допускає, що це може зажадати часу на пошук всіх можливих дій) це досить повний метод визначення, що насправді робить програма, безвідносно до того, чи була вона прекомпільованних, статично пов'язана, позбавлена символів або що-небудь ще.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*