1 Востаннє редагувалося d4rkc10ud (19.10.2012 15:30:48)

Тема: Отримання адреси функцій без використання IAT та WinAPI (ч.1)

Сьогодні, з метою пропаганди низькорівневе програмування, я хочу розповісти про одну задачу, яка постає майже перед кожним shareware-писакою, що хоче захистити свою прогу від кракерів(а точніше від їх дурного різновида, що звуться "какерами"), або хулхацкером, що пише шеллкоди для використання вразливості переповнення буферу, або ж ви пишете демку, яка за розміром не повинна перевищувати 16кб (умова деяких демопаті).
Цією задачею є отримання хендлу(адреси) бібліотеки kernel32.dll та отримання адреси функцій без використання таблиці імпорту(IAT) \ WinAPI (LoadLibrary & GetProcAddress). Одразу попереджаю, що ловлевел гури для себе нічого нового не відкриють. Всі приклади розроблені для х86 (IА32) архітектури та чудово працюють в емуляторі WoW (WindowsOnWindows технологія, збоченці). Як я вже казав, одним із застосувань цієї технології є збивання з пантелику недовчених кракерів, що звуться какерами. Кракер завантажить вашу прогу в редактор ресурсів \ дизасемблер \ hex-редактор \ блокнот і перше, на що він погляне, буде таблиця імпорту (IAT), щоб знати які функції необхідно перехоплювати. Але наша таблиця імпорту (IAT) не буде відповідати дійсності, тому він піде хибним шляхом.
Спершу розглянемо функцію отримання хендлу(адреси) бібліотеки kernel32.dll.
Ця процедура написана за stdcall декларацією виклику. Тобто вертає результат в регістр eax.
Код різними мовами:

AT&T Assembler Syntax (GAS)

GetKernelHandle_x86:
     mov    %fs:0x30,%eax
     mov    0xc(%eax),%eax
     mov    0x1c(%eax),%eax
     mov    (%eax),%eax
     mov    0x8(%eax),%eax
     ret 

Intel Assembler Syntax (FASM, NASM)

GetKernelHandle_x86:
    mov    eax, [fs:030h]
    mov    eax, [eax+0ch]
    mov    eax, [eax+01ch]
    mov    eax, [eax]
    mov    eax, [eax+08h]
    ret

Якщо використовувати Intel Assembler Syntaxis (MASM), то треба ще додати рядок, наведений нижче, щоб асемблювалося без помилок.

ASSUME FS:NOTHING

Отриманий машинний код:

064h, 0A1h, 030h, 000h, 000h, 000h, 08Bh, 040h, 00Ch, 08Bh, 040h, 01Ch, 08Bh, 000h, 08Bh, 040h, 008h, 0C3h

Через велику кількість несумісних мов програмування леше одного разу асемблювати, а машинний код прямо в файл і вставляти, наприклад:

Intel Assembler Syntax (FASM, NASM, TASM, MASM)

GetKernelHandle_x86:
db 064h, 0A1h, 030h, 000h, 000h, 000h, 08Bh, 040h, 00Ch, 08Bh, 040h, 01Ch, 08Bh, 00h, 08Bh, 040h, 08h, 0C3h

AT&T Assembler Syntax (GAS)

GetKernelHandle_x86:
db 0x64, 0xA1, 0x30, 0x0, 0x0, 0x0, 0x8B, 0x40, 0x0C, 0x8B, 0x40, 0x1C, 0x8B, 0x0, 0x8B, 0x40, 0x8, 0xC3

Оскільки небагато людей пишуть софт на чистому асемблері, а якщо хто і пише, то це йому читати зайве :) Тому поговоримо про інтеграцію асемблера та машинного кода у високорівневі мови програмування.
С/С++ є однією з найпопулярніших мов системного та прикладного програмування. Розглянуті нижче можливості наявні в VisualC++ й PellesC.

__declspec(naked) - наказує компілятору не створювати пролог\епілог для функції.

__asm{} - використання вбудованого асемблеру.

_emit - директива для прямого включення машинного коду. На х86 платформі, з естетичних міркувань, краще використовувати директиву асемблера(вбудованого) db, бо директива _emit додає один байт (тобто один рядок додає один байт), а db одразу декілька.

Приклад використання цих директив у проекі:

#include <stdlib.h>
#include <stdio.h>
#include <windows.h>

__declspec(naked) HANDLE __stdcall GetKernel32_x86(){
    __asm{
    db 064h, 0A1h, 030h, 000h, 000h, 000h, 08Bh, 040h, 00Ch, 08Bh, 040h, 01Ch, 08Bh, 000h, 08Bh, 040h, 008h, 0C3h 
    }
}

int main(){
    SetConsoleTitle("Matrix is Anywere!");
    printf("\nAddress of Kernel32 = 0x%X\n", GetKernel32_x86());
    _getch();
    return(0);
} 

Виконаємо програму:

+=[Matrix is Anywere!]=================================================[_][O][X]=+
|                                                                                |
|Address of Kernel32 = 0x7C800000                                                |
|                                                                                |
|                                                                                |
|                                                                                |
|                                                                                |
|                                                                                |
|                                                                                |
|                                                                                |
|                                                                                |
+================================================================================+

Число може відрізнятися(на моїй вінді ХР SP3), але завжди воно буде вказувати на місце розташування kernel32.dll у віртуальній памяті процеса.

За допомогою директиви _emit ми б досягнули однакового результату, але...
Розглянемо процедуру на директиві _emit

  
__declspec(naked) HANDLE __stdcall GetKernel32_x86(){
    __asm{
    _emit 0x64
    _emit 0xA1
    _emit 0x30
    _emit 0x0
    _emit 0x0
    _emit 0x0
    _emit 0x8b
    _emit 0x40
    _emit 0x0C
    _emit 0x8B
    _emit 0x40
    _emit 0x1C
    _emit 0x8B
    _emit 0x00
    _emit 0x8B
    _emit 0x40
    _emit 0x08
    _emit 0xC3
    }
}

Як кажуть на міжнародних форум "Ur code not sexy!". Памятайте, що гламурний код полегшить роботу з проектом та це запорука вашої репутації в секті Open Source.

Ви можете сказати "Поєднати асемблер та машинний код з С\С++ кожен зможе, а от спробуй-но додати низькорівневі фічі до мов програмування, що компілюються в байт-код або ж взагалі інтерпретуються".
Це зробити не так вже й важко за допомогою однієї фічі в WinAPI функції CallWindowProc. Ця функція викликає процедуру вікна з заданими параметрами і повертає результат, що повернула процедура вікна. Так би мовити, оболонка Першим параметром функції є посилання на процедуру вікна. Але ми можемо замість процедури вікна підставити свою процедуру і функція CallWindowProc верне результат, що верне наша функція. Єдина умова, щоб наша функція не більше 4х разів клала щось в стек. За допомогою цих танців із бубном ми маємо змогу викликати свої функції, написані мовою асемблера, і попередньо скомпільовані, в таких мовах: VB6, AutoIt, C# (і инших його родичів за .NET).
AutoIt

#include <winapi.au3>

$strucGetKernelHandle_x86 = DllStructCreate("char[64]");
$GetKernelHandle_x86 = Chr(0x64)&Chr(0xA1)&Chr(0x30)&Chr(0x0)&Chr(0x0)&Chr(0x0)&Chr(0x8B)&Chr(0x40)&Chr(0x0C)&Chr(0x8B)
$GetKernelHandle_x86 &= Chr(0x40)&Chr(0x1c)&Chr(0x8B)&Chr(0x0)&Chr(0x8B)&Chr(0x40)&Chr(0x08)&Chr(0xC3)
DllStructSetData($strucGetKernelHandle_x86, 1, $GetKernelHandle_x86)
$hKernel32 = _WinAPI_CallWindowProc(DllStructGetPtr($strucGetKernelHandle_x86), 0, 0, 0, 0)
MsgBox(0, "Address of Kernel32.dll", Hex($hKernel32))

Виконаємо програму:

+=[Address of Kernel32.dll]====[_][O][X]=+
|  7C800000                              |
|                   [OK]                 |
+========================================+

Корисні посилання:
Онлайн дизасемблер: http://www.onlinedisassembler.com/

Спільнота wasm.ru (найкраща на пострадянському просторі): http://www.wasm.ru/

Міжнародна(англомовна) спільнота imsecure (невеличка, проте одна з найкращих): http://www.imsecure.org/


На днях допишу

Подякували: Replace, funivan, bunyk, Chemist-i, 0x9111A, leofun017