30 января 2013 г.

Inject методом import - Часть 1

В интернетах достаточно много инфы по поводу ижектов при помощи подмены пролога, но я считаю этот метод грязноватым и применяю его только в тех случаях, когда подменить при помощи импорта функцию не получается. На подмену пролога частенько ругаются антивирусы и приходиться изобретать что нибудь странное.
Итак, поговорим том, как сделать inject в чужой процесс своей DLLки и собственно перехват функции через импорт на ассемблере.


Для начала, что нам необходимо знать:
1. Формат PE файлов, так как придется искать таблицу импортов(Import Address Table, в народе IAT)
2. Небольшое знание устройства памяти и WinAPI
Порядок действий ижекта у нас получается следующий:
1. Ищем в памяти необходимый процесс.
2. Открываем этот процесс на запись.
3. Резервируем в процессе необходимое количество памяти
4. Копируем туда исполняемый код который при помощи loadlibrary загрузит нужную DLL
5. Вызываем удаленный тред с адреса как раз который прописали в 4ом пункте
6. Завершаем удаленый тред.
После внедрения дллки, теперь должна работать сама DLL, что она делает ниже:
1. При загрузке DLLки автоматически стартует LibMain.
2. Ищем IAT.
3. В IAT ищем ссылку ту функцию которую будем подменять.
4. Сохраняем адрес ссылки на старую функцию и записываем адрес ссылки на свою.
5. Что то делаем при вызове собственно перехваченной функции.

Подготовительный момент.
Создадим простенькое WinAPI приложение, с кнопкой ОК, при нажатии на которую будет вываливаться мессадж бокс с надписью "Message".
Выглядеть оно будет так:
Называется приложение victim.exe
Перехватывать будем собственно как раз функцию MessageBoxA, а подменять будем слово Message.
Эта программа в исходниках выглядит следующим образом, ничего сложного, все из шаблона, по этому комментировать особа нечего тут.

.386
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

include victim.inc

.code

start:
    invoke    GetModuleHandle,NULL
    mov    hInstance,eax
    invoke    InitCommonControls
    invoke    DialogBoxParam,hInstance,IDD_MAIN,NULL,addr DlgProc,NULL
    invoke    ExitProcess,0

;########################################################################

DlgProc    proc    hWin:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
    mov    eax,uMsg
    .if    eax==WM_INITDIALOG
        ;initialization here
    .elseif    eax==WM_COMMAND
        mov edx,wParam
        movzx eax,dx
        shr edx,16
        .if edx==BN_CLICKED
            .if eax==IDOK
                invoke    MessageBox, hWin, addr wMessage, addr wTitle, MB_OK
            .elseif eax==IDCANCEL
                invoke    SendMessage,hWin,WM_CLOSE,NULL,NULL
            .endif
        .endif
    .elseif    eax==WM_CLOSE
        invoke    EndDialog,hWin,0
    .else
        mov    eax,FALSE
        ret
    .endif
    mov    eax,TRUE
    ret
DlgProc endp

end start
Я только добавил сюда сам MessageBox чтобы он вызывался по нажатию на кнопку OK.
Посмотрим что в импортах появилось у этой простенькой программки при помощи CFF Explorer:

Функция MessageBoxA находится в системной дллке User32.dll, это мы запомним, так как будем дальше искать в таблице IAT этого приложения. Можно конечно жестко прописать где и как ее менять, но это не наш принцип, так как смещения могут поплыть в новых версиях программы.
По этому поиск будет осуществляться динамически по PE заголовку.

Итак, дальше готовим сам инжектор, фаил будет называться inject.exe. У него не будет никакого интерфейса и его задача заставить думать систему, что жертва загрузила DLL.
Кодъ достаточно простой, с пояснениями:
Для начала объявим для удобства свою структуру(можно конечно сразу байткод записать, но для наглядности - структура):
Опять же для удобства я посчитал сколько она занимает в памяти.
jcode    struct
    lpush    db ? ; 0 ; код команды push
    pname    dd ? ; 1 ; указатель на имя DLL
    acall    dw ? ; 5 ; код команды call
    llpoint    dd ? ; 7 ; ссылка на указатель системной функции LoadLibrary
    etpush    db ? ; 11 ; код команды push
            dd ? ; 12 ; ноль
    bcall    dw ? ; 16 ; код команды Call
    etpoint    dd ? ; 18 ; ссылка на указатель системной функции ExitThread
    lladdr    dd ? ; 22 ; указатель на LoadLibrary
    etaddr    dd ? ; 26 ; указатель на ExitThread
    lname    db 64h dup (?) ;30 ; имя dll
jcode ends
Посмотрим глазками на заполненую структуру:
HEX:
А теперь дизасемблированное:
Как видно, все достаточно просто и никакого гениального кода.

Теперь сам код инжектора:
.386
.model flat, stdcall  ;32 bit memory model
option casemap :none  ;case sensitive

include inject.inc ; подцепим необходимые данные и переменные

.code

start:
    
    mov        eax, sizeof lprocentry ; вычисляем размет структуры PROCESSENTRY32
    mov        lprocentry.dwSize, eax ; запихаем этот размер в структуру PROCESSENTRY32
    invoke    CreateToolhelp32Snapshot,TH32CS_SNAPPROCESS OR TH32CS_SNAPMODULE, NULL ; получим хэндл списка процессов
    .if    eax == NULL ; если нулевой
        jmp        errorExit ; что то пошло не так, выход
    .endif
    mov        hSnap, eax ; запишим хэндл в переменную hSnap
    invoke    Process32First, hSnap, offset lprocentry ; запишем данные первого процесса в структуру PROCESSENTRY32
    .if    eax == NULL ; если нулевой
        jmp        errorExit ; что то пошло не так, выход
    .endif

mainsearch:    ; так как касперский не люит lstrcmp изобретаем свой велосипед
    mov        ecx, sizeof processname ; запишем размер имени которое ищем в ecx
    mov        edi, offset lprocentry.szExeFile ; в edi запишем текущее имя
    mov        esi, offset processname ; в esi запишем то с чем сравниваем

nextsimb:    
    mov        al, BYTE PTR [esi] ; запишем байт из того что сравниваем в al
    .if        al != BYTE PTR [edi] ; сравним его с байтом в названии текущего процесса
        jmp        nextproc ; если не совпадает, прыгаем на получение имени следующего процесса
    .endif
    inc        esi ; если совпало, то смещаем указатель позиции в имени на 1
    inc        edi ; если совпало, то смещаем указатель позиции в имени на 1
    loop    nextsimb ; зацикливаем пока не сравним целиком имя
    mov        eax, lprocentry.th32ProcessID ; копируем Process ID в eax
    mov        pid, eax ; перемещаем его в переменную в pid
    jmp        processfound ; процесс найден, прыгаем на обработку его

nextproc:    
    invoke    Process32Next, hSnap, offset lprocentry ; это имя процесса не подходит, получаем следующее
    .if    eax == NULL ; если нулевой
        jmp        errorExit ; что то пошло не так, выход
    .endif
    jmp    mainsearch ; повторяем основной цикл

processfound: ; процесс найден
    invoke    OpenProcess, PROCESS_CREATE_THREAD OR PROCESS_VM_WRITE OR PROCESS_VM_OPERATION, NULL, pid ; открываем его на чтение
    mov        hProc, eax ; сохраняем хеадер, он нам понадобится еще
    invoke    VirtualAllocEx, hProc, NULL, sizeof jcode, MEM_COMMIT,PAGE_EXECUTE_READWRITE ; выделяем память причем в режиме записи
    mov        mAddr, eax ; записываем адрес с которого начинается выделенная память
    invoke    fillStruct, eax ; вызываем процесс который заполняет структуру
    invoke    WriteProcessMemory, hProc, mAddr, addr jjcode, sizeof jcode, NULL ; записываем структуру в выделенную память
    invoke    CreateRemoteThread, hProc, NULL, NULL, mAddr, NULL,NULL, NULL ; создаем тред от имени жертвы
    invoke    WaitForSingleObject, eax, INFINITE ; ждем когда он отработает
    invoke    VirtualFreeEx, hProc, mAddr, sizeof jcode, MEM_RELEASE ; корректно на всякий случай освобождаем память
    
errorExit:
    invoke    ExitProcess,0 ; завершаем работу
    

fillStruct proc memaddr:DWORD ; заполнение структуры
    mov        jjcode.lpush, 68h ; копируем код push, он равняется 68h
    mov        eax, memaddr ; копируем начальный адрес выделенной памяти
    add        eax, 1Eh ; прибавим к нему 1Eh чтобы он указывал на имя DLL
    mov        jjcode.pname, eax ; копируем в указатель на имя
    mov        jjcode.acall, 15ffh ; записываем код call он равняется 15ffh
    invoke    LoadLibrary, offset kendll ; получаем базу kernel32.dll
    mov        pkernel, eax; сохраним в переменной pkernel
    invoke    GetProcAddress, eax, offset llibr ; получаем адрес функции LoadLibrary
    mov        jjcode.lladdr, eax ; копируем в ячейку на которую будем ссылатся
    mov        eax, memaddr ; копируем начальный адрес выделенной памяти
    add        eax, 16h ; прибавим к нему 16h чтобы он ссылался на указатель функции LoadLibraryA
    mov        jjcode.llpoint, eax ; записываем его в ссылку
    mov        jjcode.etpush, 68h ; копируем код push, он равняется 68h 
    mov        jjcode.bcall, 15ffh ; записываем код call он равняется 15ffh
    invoke    GetProcAddress, pkernel, offset exth ; получаем адрес функции ExitThread
    mov        jjcode.etaddr, eax ; копируем в ячейку на которую будем ссылатся
    mov        eax, memaddr ; копируем начальный адрес выделенной памяти
    add        eax, 1Ah ; прибавим к нему 1Ah чтобы он ссылался на указатель функции ExitThread
    mov        jjcode.etpoint, eax ; записываем его в ссылку
    invoke    lstrcpy, addr jjcode.lname, addr libname ; копируем имя DLL в структуру
    
    ret
fillStruct endp


end start
Ну вот, на сегодня все, в ближайшее время я выложу вторую часть статьи, в которой будет описание как сделать DLL для инжекта и как она работает. Там же будут исходники и готовые бинарники для тестов.

Комментариев нет:

Отправить комментарий