| Welcome to The Passion Of Code Laboratory!!! | Статьи |
"Win32-вирусы" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .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] эта таблица имеет следующую структуру:
| Смещение | Описание |
| +00 | Зарезерврованно обычно равно 0 |
| +04 | время и дата создания экспортных данных |
| +08 | Старший номер версии таблицы экспорта |
| +0A | Младший номер версии таблицы экспорта |
| +0C | RVA строки указывающей на имя нашей библиотеки |
| +10 | начальный номер экспорта, для функций нашей библиотеки, обычно установлено в 1(начальный номер первой экспортируемой функции) |
| +14 | количество функций экспортируемых нашим модулем |
| +18 | число указателей на имена, обычно равно числу функций экспортируемых по имени |
| +1C | RVA массива адресов экспортируемых функций |
| +20 | RVA массива указателей на имена экспортируемых функций |
| +24 | RVA массива номеров экспорта всех экспортируемы функций |
Что такое 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:
| IMAGE_EXPORT_DIRECTORY STRUCT | |||
|---|---|---|---|
| Characteristics | DWORD | ? | |
| TimeDateStamp | DWORD | ? | |
| MajorVersion | WORD | ? | |
| MinorVersion | WORD | ? | |
| nName | DWORD | ? | |
| nBase | DWORD | ? | |
| NumberOfFunctions | DWORD | ? | |
| NumberOfNames | DWORD | ? | |
| AddressOfFunctions | DWORD | ? | |
| AddressOfNames | DWORD | ? | |
| AddressOfNameOrdinals | DWORD | ? | |
| 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

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

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

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

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

… видим что ординалы выстроены в таблицу по 2 байта в строке. Теперь пользуясь кнопками изминения формата выводимой таблицы в программе [2.4] получим эту таблицу:
Рис 2.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

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

Команда 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

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

Здесь также суть в том , что 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

В следующей стать мы получим все адреса нужных нам функций. :)
[C] _follower / TPOC
Новые события из жизни нашей лаборатории
Статьи и переводы лаборатории TPOC
Программы лаборатории TPOC
Здесь мы сообщаем Вам, какие творения скоро появятся
Ссылки на сайты, где можно найти больше информации
История нашей лаборатории и ее члены
| У вас есть предложения по нашему сайту? Напишите сюда | Любимые сайты вирмейкеров: (WASM) (RSDN) |