Издательский дом ООО "Гейм Лэнд"СПЕЦВЫПУСК ЖУРНАЛА ХАКЕР #48, НОЯБРЬ 2004 г.

Like a Virus

Дмитрий Коваленко aka Ingrem

Спецвыпуск: Хакер, номер #048, стр. 048-064-2


Находим API по имени

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

1. Прочитать первые два байта kernel32.dll. Убедиться, что там 'MZ'. Прочитать RVA заголовка PE. Прочитать первые четыре байта заголовка PE. Убедиться, что там 'PE',0,0.

2. По смещению 78h от сигнатуры PE прочитать RVA таблицы экспорта.

3. Найти в таблице экспорта RVA массива имен, ординалов и адресов.

4. Перебрать массив имен, найти имя нужной API и запомнить ее индекс (номер по порядку).

5. Посмотреть, какой ординал лежит по этому же индексу в массиве ординалов. Вычесть из этого ординала базу ординалов - dword, находящийся в таблице экспорта по смещению 10h.

6. Используя число (полученное в предыдущем пункте) в качестве индекса, прочитать из массива адресов RVA нужной API. Вот зачем нам была нужна таблица ординалов!

Находим kernel32.dll в памяти

Но надо сначала добраться до kernel32.dll. Kernel32.dll при запуске программы отображается в ее адресном пространстве. И надо сделать вот что:

1. Найти точку внутри образа kernel32.dll в памяти. Для этого нужно пройтись по цепочке SEH до ее последнего элемента.

Код, реализующий нахождение точки внутри kernel32.dll, выглядит так:

mov eax, fs:[0] ; eax = указатель на первый элемент SEH

GetApi2k_10:

mov ebx, [eax] ; адрес следующего элемента

cmp ebx, -1 ; равен -1?

je GetApi2k_20 ; да - это последний элемент цепочки

mov eax, ebx ; нет - занесем в eax адрес следующего элемента

jmp short GetApi2k_10 ; новый виток цикла

GetApi2k_20:

mov eax, [eax+4] ; eax = точка внутри kernel32.dll

2. Выравнять найденный адрес точки внутри kernel32.dll на 64K. Память в Windows организована таким образом, что образы исполняемых модулей (и не только: любое резервирование памяти в виртуальном адресном пространстве процесса просходит по адресу, кратному 64K - это т.н. allocation granularity - прим. AvaLANche'а) загружаются по адресам, кратным 64K. Если по выравненному адресу не лежит 'MZ', уменьшим его на 64К и повторим проверку. И так до тех пор, пока не найдем "голову" kernel32.dll.

xor ax, ax ; выравняем адрес внутри kernel32 на 64К

GetApi2k_1:

mov ebx, [eax] ; читать 4 байта по адресу [eax]

cmp bx, 5A4Dh ; голова стаба? ('MZ' навыворот)

je GetApi2k_2 ; да!

sub eax, 010000h ; уменьшаем еще на 64К

jmp short GetApi2k_1 ; новая итерация

GetApi2k_2: ; теперь в eax addr 'MZ' k32

Потом находим RVA PE, а дальше все по плану :).

Несколько слов о внедрении в "чужую" программу

Допустим, удалось написать адресно-независимый код. Как теперь заразить им чужой EXE? Вирмейкеры знают кучу разных техник. Те, что попроще, легко секутся антивирусами. Сложные техники - труднее программировать.

К сложным техникам традиционно относят сжатие секции кода. Секция кода - это, грубо говоря, та часть PE-файла, в которой находится программный код. Суть техники в следующем. Находим RVA и размер секции кода - они лежат по смещениям 2Сh и 1Ch от начала PE-заголовка. Также нас интересует RVA точки входа - лежит в PE-заголовке по смещению 28h. Именно в точку входа передается управление сразу после старта EXE (если это EXE-файл) или загрузки DLL (если это DLL).

Назад на стр. 048-064-1  Содержание  Вперед на стр. 048-064-3