October 12

Теряем упаковщика среди сигнатур

Пока в отложенных висит ещё одна моя публикация с моими очередными мыслями для моего небольшого дневника, я подумал, почему бы не показать, как можно теряться среди сигнатур упаковщиков... Ну, а почему бы и нет? Для кого-то это будет очевидным, но мне всё равно нечего делать, поэтому ... Пусть просто будет.

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

Как многие уже должны понимать, всякие анализаторы файлов типа DetectItEasy привязаны к известным сигнатурам упаковщиков. Это их слабость (и слабость ревёрс-инженеров, которые не до конца понимая сути ревёрс-инжиниринга, делают выводы основываясь только на анализаторах файлов).

Забегая вперёд, сразу ударим по сигнатуре FASM-компилятора, чтобы было неповадно (на FASM нельзя вешать сигнатуры!). Всегда! Запомните, всегда к формату PE добавляйте волшебное слово on - эта директива позволит указать код DOS-версии программы. Единственная существующая сигнатура на FASM компилятор - привязана к его DOS-заглушке, ведь больше прикопаться не к чему. Поэтому ставим DOS-заглушку на какой-то пустой файл, либо если кто-то программирует в Linux как я (нет времени объяснять) - просто пишем on "/dev/null", и теперь анализаторы файлов понятия не имеют, на чём написана программа, и оставляют лишь немногословное "PE32".

Но это мелочи. Самое интересное будет тогда, когда я возьму UPX, и поставлю на него сигнатуру VMProtect, либо вообще уберу какие-либо сигнатуры с него. При этом программа будет работать, а для её распаковки придётся воспользоваться руками, либо переписывать упаковщик.

Неизвестный упаковщик

Пакуем данный код с помощью UPX и приступаем:

format PE GUI on "/dev/null" entry start include 'win32a.inc'

section '.code' code readable executable start: push 0 push text push text push 0 call dword[MessageBox] push 0 call dword[ExitProcess]

rd 0x1000 text: db 'FASMGENIUS', 0

section '.idata' import data readable writeable library kernel32,'kernel32.dll',\ user32,'user32.dll',\ user32,'user32.dll' include 'api\kernel32.inc' include 'api\user32.inc'

DetectItEasy определяет программу как упакованную с помощью UPX

Итак, нажимаем на буковку "S" и анализируем код сигнатуры.

Видим, что определение версии и дальнейшее определение метода сжатия у нас завязано на слове "UPX!". Патчим это слово в файле!

"UPX!"
Было "UPX!" стало ",,,,"

Отлично, теперь анализатор думает, что у нас модифицированный UPX, но при этом программа осталась рабочей!

Продолжаем, я должен окончательно затереть следы UPX.

Анализатор определяет UPX по начальному коду... Классика. Ищу, какие начальные байты на EP (Entry Point) он нашёл, и выхожу на сигнатуру:

Уничтожить сигнатуру!

Дальше одним HEX-редактором не обойтись, в ход идёт великий и могучий - ассемблер!

О великий ассемблер, видоизмени данные инструкции!

Включаем мозги, на что можно по одинаковому размеру подменить эти инструкции? Кстати, расслаблю на случай, если кто-то не придумал - можно просто затереть данные инструкции, создать ещё одну секцию, и в месте старых инструкций - вставить call на вызов кода секции, а уже в этой секции можно вставить старые инструкции, либо какие-то аналогичные, таким образом мы уже уничтожим сигнатуру.

Но я просто меняю вторую инструкцию на sub si, 0xF0. Почему? Да потому что загрузчик кладёт в ESI значение 0x4080F0, и так будет практически всегда, поверьте мне. Поэтому в таком варианте нет ничего страшного, таким образом мы получаем значение 0x408000, которое UPX и ждёт.

Программа работает, а что скажет анализатор? Старая версия DetectItEasy на Windows сломалась, говорит только "PE32":

Но новая версия на Linux с улучшенным эвристическим анализом всё ещё видит какие-то странные слова "UPX" у меня в файле, которые на что-то указывают... Но на что?

Шутка, к счастью анализатор сам выдал нам все свои секретики, а поэтому мы будем исправлять! Чтобы он совсем не волновался.

Уничтожаем следы UPX в секциях:

Было "UPX0", "UPX1", "UPX2"

После каждого изменения проверяем программу на работоспособность.

Правим импорты. Например, меняем ExitProcess на ExitThread:

Было "ExitProcess", стало "ExitThread"

И уже получаем неплохой результат. Программа работает, а анализатор думает, что это вообще какой-то eXPressor:

Какой ещё eXPressor?

Мне не нравится, что анализатор агрессирует на pushad в начале. Давайте исправим это...

Я попробовал просто нопнуть его, и это сработало. Смешно. Похоже, UPX оставляет этот "pushad" чисто чтобы сохранять значения регистров от загрузчика, но мой код на ассемблере не требует точности данных регистров, так что... Засоряй мне регистры, UPX! Я уже ничего не боюсь:

Осталось разобраться импортами! Сначала я думал что-то изменить, удалить или добавить. Но это тщетно. После добавления новой библиотеки или удаления какого-то импорта, анализатор всё равно говорил, что это "eXPressor". Посмотрев код сигнатуры, я понял, что он агрессирует тупо на тройку функций вроде LoadLibrary и GetProcAddress, как же это глупо...

Конечно, и это можно обойти, но для этого придётся писать перехват на ассемблере, который бы перед запуском основного кода, подменил в импорт-таблице адрес одной функции на другую. Например, так можно написать функцию "VirtualAlloc", но во время исполнения подменить её адрес на "CreateFile", и таким образом обойти сигнатуру. Но я показал способы, доступные всем, у кого есть HEX-редактор и отладчик. Остальное, возможно, покажу как-нибудь позже...

VMProtect? Нет, UPX! Вешаю сигнатуру

Итак, ну здесь довольно легко. Сигнатура анализатора состоит в том, что секции должны называться ".vmpN", а где N - номер. И при этом, секция с точкой входа - должна быть под последним номером.

Готово! Анализатор и на Linux и на Windows теперь всей своей душой верит, что это VMProtect с самым-самым максимальным уровнем защиты, бу!

Ну, а программа естественно после всех этих манипуляций прекрасно отрабатывает и показывает нам сообщение - "FASMGENIUS", это я!

Вот так мы получили из "UPX(версия)[NRV]" - "VMProtect(1.70)[Max protection]".