Welcome to The Passion Of Code Laboratory!!!Статьи

"Win32-вирусы"
2. Находим адрес необходимой нам функции

Автор:_follower / TPOC

Задача: Найти адреса всех остальных функций из библиотеки kernel32.dll которые нам могут понадобиться.

Адрес библиотеки kernel.dll: ...

Итак в прошлой главе мы нашли адрес kernel32.dll. Теперь нам нужно найти адрес необходимой нам функции Пусть нам нужно найти адрес функции ExitProcess( ), и потом , пользуясь этим адресом, вызвать ExitProcess( ) к выполнению. Вот этим вопросом мы и займемся.
.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib



MIN_KERNEL_SEARCH_BASE EQU 070000000h
MAX_API_STRING_LENGTH EQU 150


.CONST

szExitProcess DB "ExitProcess",0
szError DB "ОШИБКА",0
szGetBaseErr DB "Ошибка в получении ImageBase ",0
szGetApiErr DB "Ошибка в полученииApi адреса ",0
szCap DB ":)",0


.DATA

szFormat DB "Адрес библиотеки kernel.dll: 0x%08lX",13,10
DB "Адрес функции ExitProcess() : 0x%08lX",0
cBuff DB 120 DUP (0)
_ExitProcess DD 0

.code

main:
PUSH [ESP]
CALL GetKernelBase
OR EAX, EAX
JZ GetBaseErr

PUSH EAX

PUSH OFFSET szExitProcess
PUSH EAX
CALL GetProcAddr
OR EAX, EAX
JZ GetApiErr
MOV _ExitProcess, EAX

POP EDX

INVOKE wsprintf,OFFSET cBuff,OFFSET szFormat,EDX,EAX
INVOKE MessageBox,0,OFFSET cBuff,OFFSET szCap,MB_ICONINFORMATION OR MB_SYSTEMMODAL
CALL _ExitProcess ;)
;------ Сообщения об ошибках ------
GetBaseErr:
MOV EAX, OFFSET szGetBaseErr
JMP ShowErr

GetApiErr:
MOV EAX, OFFSET szGetApiErr
JMP ShowErr

ShowErr:
INVOKE MessageBox,0,EAX,OFFSET szError,MB_ICONINFORMATION OR MB_SYSTEMMODAL
RET

; --------------------------------------------------------------------------------
; --------------------------------------------------------------------------------
GetKernelBase PROC USES EDI ESI, dwTopStack : DWORD

MOV EDI, dwTopStack
AND EDI, 0FFFF0000h
.WHILE TRUE

.IF WORD PTR [EDI] == IMAGE_DOS_SIGNATURE

MOV ESI, EDI
ADD ESI, [ESI+03Ch]
.IF DWORD PTR [ESI] == IMAGE_NT_SIGNATURE

.BREAK

.ENDIF

.ENDIF

ExceptCont:

SUB EDI, 010000h
.IF EDI < MIN_KERNEL_SEARCH_BASE

MOV EDI, 0BFF70000h
.BREAK

.ENDIF

.ENDW
XCHG EAX, EDI
RET

GetKernelBase ENDP

; --------------------------------------------------------------------------------
; --------------------------------------------------------------------------------
; dwDllBase - адрес загруженной библиотеки kernel32.dll
; szApi - адрес строки с названием искомой Api-функции

GetProcAddr PROC USES ESI EDI ECX EBX EDX, dwDllBase : DWORD, szApi : LPSTR

; Проверим PE сигнатуру
MOV ESI, dwDllBase
CMP WORD PTR [ESI], IMAGE_DOS_SIGNATURE
JNZ @@BadExit
ADD ESI, [ESI+03Ch]
CMP DWORD PTR [ESI], IMAGE_NT_SIGNATURE
JNZ @@BadExit

; Определим длину строки - названия искомой функции API
MOV EDI, szApi
MOV ECX, MAX_API_STRING_LENGTH
XOR AL, AL
REPNZ SCASB
MOV ECX, EDI
SUB ECX, szApi ; теперь в ECX -> длина строки названия Api-функции

; Переходим к таблице экспортов (Export table)
MOV EDX, [ESI+078h] ; в EDX -> указатель на начало таблицы
; экспортов (Export table) в PE - заголовке
ADD EDX, dwDllBase

ASSUME EDX : PTR IMAGE_EXPORT_DIRECTORY

MOV EBX, [EDX].AddressOfNames; в EBX указатель на RVA массива
; указателей на имена экспортируемых функций
ADD EBX, dwDllBase
XOR EAX, EAX ; в EAX будим хранить номер
; искомой функции в таблице AddressOfNames
.REPEAT

MOV EDI, [EBX]
ADD EDI, dwDllBase
MOV ESI, szApi
PUSH ECX ; сохраним длину имени искомой Api-функции
REPZ CMPSB
.IF ZERO?

.BREAK

.ENDIF
POP ECX
ADD EBX, 4
INC EAX

.UNTIL EAX == [EDX].NumberOfNames


; Находим Ординал функции
MOV ESI, [EDX].AddressOfNameOrdinals
ADD ESI, dwDllBase
PUSH EDX ; сохраним указатель на Таблицу Экспорта
MOV EBX, 2
XOR EDX, EDX
MUL EBX
;POP EDX
ADD EAX, ESI
XOR ECX, ECX
MOV WORD PTR CX, [EAX] ; в ECX ординал Api-функции


POP EDX


; Получим адрес искомой Api-функции
MOV EDI, [EDX].AddressOfFunctions
XOR EDX, EDX
MOV EBX, 4
MOV EAX, ECX
MUL EBX
ADD EAX, dwDllBase
ADD EAX, EDI
MOV EAX, [EAX]
ADD EAX, dwDllBase
JMP @@ExitProc

ASSUME EDX : NOTHING

@@BadExit:
XOR EAX, EAX
@@ExitProc:
RET

GetProcAddr ENDP
; ---------------------------------------------------------------

end main

Из исходника видно что в основном новой для нас тут является в основном функция GetProcAddr() , поэтому ее и разберем.
1)
; Определим длину строки - названия искомой функции API
MOV EDI, szApi
MOV ECX, MAX_API_STRING_LENGTH
XOR AL, AL
REPNZ SCASB
MOV ECX, EDI
SUB ECX, szApi ; теперь в ECX -> длина строки названия Api-функции

В данном участке кода мы определяем длину искомой Api-функции. [1.1.стр. 64] REPNZ -повторять пока "0" (т.е. пока флаг ZF не равен 1). REPNZ SCASB запускается на число повторений равное MAX_API_STRING_LENGTH. При каждом выполнении команды SCASB адрес в EDI увеличивается на 1. Далее из увеличенного адреса в EDI вычитаем адрес первого символа szApi и получаем в ECX длину строки искомой Api - функции.

 

2)
...
MOV ESI, dwDllBase
CMP WORD PTR [ESI], IMAGE_DOS_SIGNATURE
JNZ @@BadExit
ADD ESI, [ESI+03Ch]
CMP DWORD PTR [ESI], IMAGE_NT_SIGNATURE
JNZ @@BadExit
...

 

ESI( начало kernell.dll ) +03Ch = ESI указывает на сигнатуру PE
ESI( начало kernell.dll ) + 03Ch + 078h = ESI указывает на RVA адрес таблицы экспорта
 

; Переходим к таблице экспортов (Export table)
MOV EDX, [ESI+078h] ; в EDX -> указатель на начало таблицы
; экспортов (Export table) в PE - заголовке
ADD EDX, dwDllBase

Во всех dll ( и некоторых exe ) файлах в их заголовке есть таблица где перечисленны навания тех функций которые эта dll "предоставляет" другим PE -файлам. Это правило относится и к нашей kernel32.dll. Согластно [1.2] эта таблица имеет следующую структуру:

Таблица 2.1
СмещениеОписание
+00Зарезерврованно обычно равно 0
+04время и дата создания экспортных данных
+08Старший номер версии таблицы экспорта
+0AМладший номер версии таблицы экспорта
+0CRVA строки указывающей на имя нашей библиотеки
+10начальный номер экспорта, для функций нашей библиотеки, обычно установлено в 1(начальный номер первой экспортируемой функции)
+14количество функций экспортируемых нашим модулем
+18число указателей на имена, обычно равно числу функций экспортируемых по имени
+1CRVA массива адресов экспортируемых функций
+20RVA массива указателей на имена экспортируемых функций
+24RVA массива номеров экспорта всех экспортируемы функций

Что такое RVA - это смещение от начала адреса, по которому в памяти загружена наша программа. Учитавая это адрес RVA приходится постоянно переводить в реальный адрес в памяти путем добавления к нему того же адреса, по которому в памяти загружена наша программа в памяти. Обычно exe в память грузят по адресу 00400000h, поэтому рельный адрес объектов, из exe - фалов, из таблицы выше равен RVA + 00400000h. В нашем же случае адрес загрузки kernel32.dll известен и лежит в переменной dwDllBase. Поэтому реальный адрес всех объектов в kernel32.dll будет равен RVA + dwDllBase. Это мы и сделали командой:

ADD EDX, dwDllBase


3)

ASSUME EDX : PTR IMAGE_EXPORT_DIRECTORY

Тут мы говорим ассемлеру, что он может допустить, что EDX указывает на структуру IMAGE_EXPORT_DIRECTORY.
 

4)

MOV EBX, [EDX].AddressOfNames ; в EBX указатель на RVA массива
ADD EBX, dwDllBase


Теперь давайте снова откроем файл \masm32\include\windows.inc и найдем там описание структуры IMAGE_EXPORT_DIRECTORY:

Структура 2.1
IMAGE_EXPORT_DIRECTORY STRUCT
 CharacteristicsDWORD?
 TimeDateStampDWORD?
 MajorVersionWORD?
 MinorVersionWORD?
 nNameDWORD?
 nBaseDWORD?
 NumberOfFunctionsDWORD?
 NumberOfNames DWORD?
 AddressOfFunctionsDWORD?
 AddressOfNamesDWORD?
 AddressOfNameOrdinalsDWORD?
IMAGE_EXPORT_DIRECTORY ENDS

 

Сопоставляя таблицу выше и эту структуру можно увидеть реальные имена полей в IMAGE_EXPORT_DIRECTORY. Итак AddressOfNames это указатель на RVA массива указателей на имена экспортируемых функций. Ну и следующей коммандой сложения мы получим реальный адес в памяти.
 

5)

Для того чтобы успешно расмотреть и понять смысл следующей операции, мне кажется, удобно:

1. Пользоваться программами - PE studio v 3.3 и WinHex 10.0 SR-7 в паре.
2. Скопируйте к себе на сторону файл kernel32.dll для дальнейших исследований.

Давайте посмотрим как выглядит таблица AddressOfNames ( +20 RVA массива указателей на имена экспортируемых функций ) из kernel32.dll в программе [2.3].

Рис 2.2

PE studio v 3.3

 В верхнем правом углу мы видим адресс 50950-смещение относительно начала нашего исследуемого файла. Полезно найти его в среде программы [2.4] , вот как это выглядит:

Рис 2.3

 WinHex 10.0 SR-7

Теперь видно как "в живую" выглядит Таблица 2.1. Если это комуто интересно. Давайте теперь посмотрим как в [2.3] выглядит таблица AddressOfNames:

Рис 2.4

PE studio v 3.3

И посмотрим что по смещению 5287F находится в файле :
Для этого выбирите из меню Position пункт Go To Offset… и поместим в открытый диалог искомый адрес.

Рис 2.5

WinHex 10.0 SR-7

… вот здесь теперь видно перечень экспортируемых файлом kernel32.dll функций .
Но самое важное приимущество, на мой взгляд, у программы [2.4] это то, что в нем можно наглядно отобразить таблицы. Посмотрим на по как в программе [2.3] выглядит структура AddressOfNameOrdinals…

Рис 2.6

PE studio v 3.3

… видим что ординалы выстроены в таблицу по 2 байта в строке. Теперь пользуясь кнопками изминения формата выводимой таблицы в программе [2.4] получим эту таблицу:

Рис 2.7

WinHex 10.0 SR-7

Мне кажется, что это отступление, посвященное возможностям этих программ кому-то пригодится (мне бы, например, в свое время, пригодилась). Вернемся к участку нашей программы.

 

6)

XOR EAX, EAX ; в EAX будим хранить номер
; искомой функции в таблице AddressOfNames
.REPEAT

MOV EDI, [EBX]
ADD EDI, dwDllBase
MOV ESI, szApi
PUSH ECX ; сохраним длину имени искомой Api-функции
REPZ CMPSB
.IF ZERO?

.BREAK ; выход из цикла если достигнут конец

.ENDIF
POP ECX ; востановим длину имени искомой Api-функции
ADD EBX, 4 ; переход к следующей записи в таблице AddressOfNames
; строка в этой таблице занимает 4 байта
INC EAX ; увеличим счетчик строк

.UNTIL EAX == [EDX].NumberOfNames

В EAX мы будем хранить номер строки искомой функции в таблице (смотри рис. 2.4). В EBX у нас хранится указатель на таблицу AddressOfNames (RVA массива указателей на имена экспортируемых функций). Так как это RVA адрес, его нужно превратить в реальный адрес. Этого мы добиваемся в строке ADD. В ESI поместили адрес строки с именем искомой функции. [1.1.стр. 64-65] REPZ CMPSB проводит сравнение строк байт до тех пор, пока не "0" (т.е. пока флаг ZF не равен 0). В этом сравнении содержимое EDI сравнивается с ESI. Если в текущей строке таблицы имя функции не совпало с искомым переходим на следующую строку (ADD EBX, 4). Докажем что строка рассматриваемой таблицы занимает 4 байта.

Рис 2.8

PE studio v 3.3

51700h - 516FCh = 4. Увеличиваем счетчик проверенных строк (INC EAX ), и проверим не равен ли EAX суммарному числу всех экспортируемых kernel32.dll функций. Кстати их в моем файле 745:

Рис 2.9

PE studio v 3.3

Команда EAX == [EDX].NumberOfNames определяет момент равенства числа обработанных в цикле строк и общего количества строк в таблице.

 

7)

; Находим Ординал функции
MOV ESI, [EDX].AddressOfNameOrdinals
ADD ESI, dwDllBase
PUSH EDX ; сохраним указатель на Таблицу Экспорта
MOV EBX, 2
XOR EDX, EDX ; EDX должен быть равен 0 т.к. результат
; от умножения ложится в регистровую пару EDX:EAX
MUL EBX ; EAX=EAX*EBX
ADD EAX, ESI ; нормализуем адрес в EAX
XOR ECX, ECX
MOV WORD PTR CX, [EAX]; в ECX ординал искомой Api-функции
POP EDX

В EDX у нас все также хранится указатель на структуру IMAGE_EXPORT_DIRECTORY.
В ESI получаем адрес RVA массива номеров экспорта всех экспортируемых функций AddressOfNameOrdinals (см. рис. 2.6).
Вся игра в этом построена на том, что N-ной функции таблицы AddressOfNames строго соответствует N-ный ординал таблицы AddressOfNameOrdinals.
Зная номер строки искомой функции в таблице AddressOfNames можно найти соотвенствующий ей ординал из таблицы AddressOfNameOrdinals по формуле:

Правило 2.1. Нахождение ординала искомой Api-функции

[Номер строки в AddressOfNames]*2
+
([IMAGE_EXPORT_DIRECTORY]->AddressOfNameOrdinals)
+
dwDllBase
_______________________________________________
= Ординал искомой Api-функции

В данной формуле :
1. [Номер строки в AddressOfNames]*2 - N-ная строка, значит она начинается с N*2-байта от начала таблицы. Таблица AddressOfNameOrdinals состоит из строк в 2 байта (см. рис. 2.7).
2. ([IMAGE_EXPORT_DIRECTORY]->AddressOfNameOrdinals)+dwDllBase = Полный адрес начала таблицы AddressOfNameOrdinals;

 

8)

; Получим адрес искомой Api-функции
; В EDX все также адрес структуры IMAGE_EXPORT_DIRECTORY
; Поместим в EDI адрес таблицы AddressOfFunctions
MOV EDI, [EDX].AddressOfFunctions
XOR EDX, EDX ; EDX должен быть равен 0 т.к. результат
; от умножения ложится в регистровую пару EDX:EAX
MOV EBX, 4
MOV EAX, ECX ; в ECX ординал Api-функции
MUL EBX ; EAX=EAX*EBX
ADD EAX, dwDllBase ; нормализуем адрес в EAX
ADD EAX, EDI ; добавим адресс начала таблицы AddressOfFunctions
MOV EAX, [EAX] ; в EAX поместим адрес Api-функции
; из таблицы AddressOfFunctions
ADD EAX, dwDllBase ; нормализуем адрес в EAX
JMP @@ExitProc
ASSUME EDX : NOTHING ; освободим EDX

Переходим к следующей таблице AddressOfFunctions ( +1Сh RVA массива адресов экспортируемых функций). Эта таблица состоит из строк в 4 байта ( т.к. там лежат адреса ):

Рис 2.10

PE studio v 3.3

5097Ch - 50978h = 4. Воспользовавшись [2.4] получим:

Рис 2.11

WinHex 10.0 SR-7

Здесь также суть в том , что N-ному ординалу таблицы AddressOfNameOrdinals строго соответствует N-ный строка с адресом таблицы AddressOfFunctions .
Зная ординал в таблице AddressOfNameOrdinals можно найти соотвенствующую ей строку с адресом из таблицы AddressOfFunctions по формуле:

 

Правило 2.2. Нахождение адреса Api-функции
 

[Ординал из AddressOfNameOrdinals]*4
+
([IMAGE_EXPORT_DIRECTORY]-> AddressOfFunctions)
+
dwDllBase
___________________________________________
= Адрес искомой Api-функции
 

 

В данной формуле :
3. [Ординал из AddressOfNameOrdinals]*4 - N-ная строка, значит она начинается с N*4-байта от начала таблицы. Таблица AddressOfFunctions состоит из строк в 4 байта (см. рис. 2.11).
4. ([IMAGE_EXPORT_DIRECTORY]-> AddressOfFunctions)+dwDllBase = Полный адрес начала таблицы AddressOfFunctions

9)

PUSH OFFSET szExitProcess ; строка с именем функции
PUSH EAX ; адресс загрузки kernel32.dll
CALL GetProcAddr ;
OR EAX, EAX
JZ GetApiErr
MOV _ExitProcess, EAX ; сохраним полученный адрес
...
CALL _ExitProcess ;) ; Вызовем функцию ExitProcess() ; по найденному нами самостоятельно адресу функции


Теперь самый важный пункт в этом разделе. Тут мы использовали найденный нами адрес в своей программе. Воспользовавшись программой PE view v3.0, видим что наша программа не импортирует функцию ExitProcess(), но мы ее использовали!

Рис 2.12

PEview v3.0

Исходник

Перечень используемой литературы.
[1.1] - "Assembler - язык неограниченных возможностей". Зубков.С.В. Москва 1999.
[1.2] - Описание формата файлов РЕ от Hard Wisdom
[1.3] - Файл \masm32\include\windows.inc

Перечень используемого программного обеспечения :
[2.1] - ASM Editor for Windows Version 2.2a ( www.avt.newmail.ru )
[2.2] - OllyDbg 1.09d by Oleh Yuschuk ( http://www.wasm.ru )
[2.3] - PE studio (http://www.pestudio.com/)
[2.4] - WinHex 10.0 SR-7 (http://www.winhex.com/)
[2.5] - PEview v3.0 (jamk@mail.ru)
 

В следующей стать мы получим все адреса нужных нам функций. :)

[C] _follower / TPOC

Наши новости

Новые события из жизни нашей лаборатории

Статьи

Статьи и переводы лаборатории TPOC

Программы

Программы лаборатории TPOC

Релизы

Здесь мы сообщаем Вам, какие творения скоро появятся

Ссылки

Ссылки на сайты, где можно найти больше информации

Наша лаборатория

История нашей лаборатории и ее члены

Дата последнего обновления: 4 августа 2005 года
У вас есть предложения по нашему сайту?
Напишите сюда
Любимые сайты вирмейкеров:
(WASM)   (RSDN)