28 августа 2012 г.

Пишем пакер, ну или основу криптора. (Часть 4)

И наконец последняя часть креатива про пакер/анпакер. Предыдущие Глава1, Глава2, Глава3
У нас остается 2 вещи которые необходимо сделать, это сохранить данные для востановления и загрузки оригинального файла и сам алгоритм распаковки.

Данные для распаковски потребуются следующие:

DWORD - Base Import Address - адрес импорта
DWORD - Base Image - базовый образ
DWORD - Decript Key - ключ для распаковки, если он нужен
DWORD - Original Base Image - оригинальный базовый образ
DWORD - Entery Point - точка входа
DWORD - Import Table Address - собственно адрес IAT
WORD - Sections Count - количество секций
DWORD - Size Data - размер данных
DWORD - Virtual Address Data - виртуальный адрес
Эти данные будем записывать после кода распаковщика, чтобы точно знать где они лежат, так как мы точно знаем длинну распаковщика.
А для удобства создадим следующие константы:
UBIMPADDR                EQU 0                ; 0h
UBASEIMG                EQU UBIMPADDR+4h    ; 4h
UDECRKEY                EQU UBASEIMG+4h        ; 8h
UOBASEPOINT                EQU UDECRKEY+4h        ; 0Ch
UENTRYPOINT            EQU UOBASEPOINT+4h    ; 10h
UIMPTABLE                EQU UENTRYPOINT+4h    ; 14h 
USECCOUNT                EQU    UIMPTABLE+4h    ; 18h
USIZEDATA                EQU USECCOUNT+2h    ; 1Ah
UVADDRDATA                EQU USIZEDATA+4h    ; 1Eh
TABLESIZE                EQU 22h

Итак, сам код:
;Write service data    
    mov        eax, DWORD PTR [ebx+IMAGE_OPTIONAL_HEADER.ImageBase] ; в eax заносим базу образа
    add        eax, DWORD PTR [ebx+IMAGE_OPTIONAL_HEADER.BaseOfData] ; прибавляем к нему размер даты и получаем указатель на импорт
    mov        DWORD PTR [edi], eax ; записываем в табличку Base Import Address
    add        edi, 4h ; смещаем указатель на 4 байта
    
    mov        eax, DWORD PTR [ebx+IMAGE_OPTIONAL_HEADER.BaseOfCode] ; в eax заносим откуда начинается код
    add        eax, DWORD PTR [ebx+IMAGE_OPTIONAL_HEADER.ImageBase] ; прибавляем к базовому образу
    mov        DWORD PTR [edi], eax ; результат записываем в Base Image
    add        edi,4h ; смещаем указатель на 4 байта
    
    mov        DWORD PTR [edi], -1h ; ключ для декриптора можно разместить например здесь, я его сделал -1
    
    add        edi, 4h ; смещаем указатель на 4 байта
    mov        eax, hinfile ; в eax заносим адрес замапленного файла который упаковываем
    add        eax, dosHSize ; прибавляем размер DOS заголовка
    add        eax, FULL_IMAGE_FILE_HEADER ; прибавляем полный размер заголовка IMAGE_FILE_HEADER
    mov        edx, DWORD PTR [eax+IMAGE_OPTIONAL_HEADER.ImageBase] ; получаем в edx ImageBase оригинального файла
    mov        DWORD PTR [edi], edx ; заносим в Original Base Image
    
    add        edi, 4h ; смещаем указатель на 4 байта
    mov        edx, DWORD PTR [eax+IMAGE_OPTIONAL_HEADER.AddressOfEntryPoint] ; получаем точку входа оригинального файла
    add        edx, DWORD PTR [eax+IMAGE_OPTIONAL_HEADER.ImageBase] ; прибавляем к базе 
    mov        DWORD PTR [edi], edx ; заносим в Entery Point
    
    add        edi, 4h ; смещаем указатель на 4 байта
    mov        edx, DWORD PTR [eax+IMAGE_OPTIONAL_HEADER.ImageBase] ; получаем базу
    add        edx, DWORD PTR [eax+IMAGE_OPTIONAL_HEADER.DataDirectory+8h] ; прибавляем к адресу IAT
    mov        DWORD PTR [edi], edx ; заносим в Import Table Address
    
    mov        edx, ebx ; копируем ebx в edx
    add        edi, 4h ; смещаем указатель на 4 байта
    mov        ebx, hinfile ; в eax заносим адрес замапленного файла который упаковываем
    add        ebx, pehSize ; прибавляем размер заголовка PE чтобы получить указатель на секции
    mov        ecx, secCount ; в ecx заносим количество секий
    mov        WORD PTR [edi], cx ; сохраняем количество секций в Sections Count
    add        edi, 2h ; смещаем указатель на 2 байта 
writeservinfo:    ; извлекаем необходимые данные из секций
    mov        eax, DWORD PTR [ebx+08h] ; копируем в eax виртуальный размер
    mov        DWORD PTR [edi], eax ; сохраняем его в  Size Data 
    add        edi, 4h ; смещаем указатель в табличке на 4 байта 
    mov        eax, edx ; эта команда не нужна, видать я о чемто задумался :)
    mov        eax, DWORD PTR [edx+IMAGE_OPTIONAL_HEADER.ImageBase] ; в eax заносим базу
    add        eax, DWORD PTR [ebx+0Ch] ; прибавляем ее к виртуальному адресу
    mov        DWORD PTR [edi], eax ; точный адрес секции заносим в Virtual Address Data
    add        edi, 4h ; смещаем указатель в табличке на 4 байта 
    add        ebx, 28h ; смещаем указатель в табличке секций на 28h байта 
    loop    writeservinfo ; повторяем пока не кончились секции
    
    invoke    UnmapViewOfFile, houtfile ; размапливаем
    invoke    CloseHandle, hOFMap ; закрываем хэндл
    invoke    CloseHandle, hOpenfile ; закрываем хэндл
    ret
В принципе ничего сложного, просто готовим табличку для востановления оригинального файла в памяти.

А теперь сам код распаковщика, там будут великие колдунства :)
Самое главное в распаковке это держать выравнивание стека, если выравнивание стека съезжает, то последствия могут быть ужасные, например kernel32.LoadLibraryA перестает загружать все библиотеки кроме kernel32. Да и сама оригинальная программа может работать некорректно.
unpack: ; метка начала анпакера
    push    edx ; сохраним точку входа, она нам еще понадобиться
    pusha ; сохраняем все регистры ибо это полезно для начала работы оригинальной программы


;move data
    xor        ecx, ecx ; обнуляем ecx
    xor        eax, eax ; обнуляем eax
    mov        cx, WORD PTR [edx+UNPACKSIZE+USECCOUNT] ; в cx копируем количество секций
    mov        esi, DWORD PTR [edx+UNPACKSIZE+UBASEIMG] ; в esi копируем базу образа
movecode:    ; метка на копирование
    push    ecx ; сохраним ecx так как нам нужен счетчик секций
    mov        edi, DWORD PTR [edx+UNPACKSIZE+UVADDRDATA+eax] ; в edi помещаем указатель на начало секций
    mov        ecx, DWORD PTR [edx+UNPACKSIZE+USIZEDATA+eax] ; в ecx помещаем оригинальную длинну секции
    rep        movsb ; копируем
    add        eax, 8h ; увеличиваем eax на 8 позиции, чтобы перейти к следующей секции
    pop        ecx ; востанавливаем счетчик секции
    loop    movecode ; повторяем пока не кончатся секции

;load library + func    
    mov        ecx, DWORD PTR [edx+UNPACKSIZE+UIMPTABLE] ; копируем в ecx адрес таблицы импторта(IAT)
nextlib:
    push    ecx ; сохраним ecx
    mov        ebx, DWORD PTR [ecx+0Ch] ; в ebx скопируем смещение на адрес dll
    add        ebx, DWORD PTR [edx+UNPACKSIZE+UOBASEPOINT] ; прибавим к нему базу и получим адрес первой dll
    push    edx ; сохраним в стеке неоригинальную точку входа которая находится в edx
    push    ebx ; сохраним в стеке указатель на первую dll чтобы выполнить LoadLibraryA
    mov        esi, DWORD PTR [edx+UNPACKSIZE] ; в esi поместим указатель на функцию LoadLibraryA
    call    DWORD PTR [esi+SLOADL] ; выполняем LoadLibraryA и загружаем первую dllку
    pop        edx ; востанавливаем неоригинальную точку входа
    mov        ecx, DWORD PTR [esp] ; копируем в ecx указатель на оригинальный IAT
    mov        ebx, DWORD PTR [ecx+10h] ; получаем указатель на первую функцию в IAT
    add        ebx, DWORD PTR [edx+UNPACKSIZE+UOBASEPOINT] ; прибавляем к ней базу
    
nextProc: ; цикл загрузки функций
    push    eax ; сохраняем в стеке eax адрес kernel32
    push    edx ; сохраняем в стеке edx фейковую точку входа
    push    ebx ; сохраняем в стеке ebx адрес 1 DLL в IAT
    mov        ebx, DWORD PTR [ebx] ; получаем указатель на 1 функцию
    add        ebx, DWORD PTR [edx+UNPACKSIZE+UOBASEPOINT] ; прибавляем к ней базу и получаем название функции
    add        ebx, 2h ; прибавляем 2 байта, чтобы перепрыгнуть hint
    push    ebx ; помещаем в стек указатель на имя функции
    push    eax ; помещаем в в стек указатель на kernel32.LoadLibraryA
    mov        esi, DWORD PTR [edx+UNPACKSIZE] ; копируем в esi адрес функции GetProcAddress
    call    DWORD PTR [esi+SGPROCA] ; вызываем GetProcAddress c параметрами чтобы загрузить необходимую функцию
    pop        ebx ; востанавливаем указатель на первую запись dll в IAT
    mov        DWORD PTR [ebx], eax ; записываем реальный адрес функции
    pop        edx ; востанавливаем фейковую точку входа
    pop        eax ; востанавливаем адрес функции kernel32.LoadLibraryA
    add        ebx, 4h ; прибавим к ebx 4 байта
    .if    DWORD PTR [ebx] != 0 ; если там есть еще что то, то значит надо искать следующую функцию
        jmp        nextProc ; прыгаем на следующую функцию
    .endif
    
    pop        ecx ; востанавливаем указатель на текущую секцию IAT
    add        ecx, 14h ; сдвигаем на 1 секцию дальше, чтобы получить указатель на следующую dllку
    .if    DWORD PTR[ecx+0ch] != 0 ; если там есть указатель на dllку прыгаем на метку загрузки следующей
        jmp        nextlib
    .endif
    
    
    popa ; востанавливаем все регисты
    pop        edx ; востанавливаем фейковую точку входа
    push    DWORD PTR [edx+UNPACKSIZE+UENTRYPOINT] ; запихиваем в стек оригинальную точку входа
    ret ; делаем прыжек на нее
unpakend: ; конец кода распаковки
UNPACKSIZE EQU unpakend-unpack ; вычислим размер кода распаковки

Ну вот и все, это простенькая основа рассказывающая как сделать упаковщик/криптор. Можно наворотить всякую работу с SEH, шифровальщиком, упаковщиком, полиморфом, украденными байтами и т.п. чтобы запутать реверсера, но в принципе идеология скелета остается таже.
Я подумал и решил всетаки не выкладывать исходники ибо много там говнокода, да и не универсальный он. Но если кому нибудь понадобятся, то можно попросить меня в коментах.

7 комментариев: