Blog. Just Blog

M/o/Vfuscator. Ночной кошмар реверсера

Версия для печати Добавить в Избранное Отправить на E-Mail | Категория: Образ мышления: Assembler | Автор: ManHunter
M/o/Vfuscator. Ночной кошмар реверсера
M/o/Vfuscator. Ночной кошмар реверсера

Когда-то давно наткнулся на интересный проект - M/o/Vfuscator от Chris Domas. Его необычность заключается в том, что все ассемблерные команды в исходнике преобразуются в набор команд MOV (и только их!!!), которые в результате выполняют то же самое действие, что и заменяемая команда. Никаких проверок, никаких условных переходов, никаких ветвлений алгоритма, вообще ничего, кроме сотен и тысяч последовательных инструкций MOV. Понятное дело, что размеры исходника и готовой программы распухают на порядки, но кого это в наше время волнует. Похоже на магию?

Сразу успокою горячие головы с обеих сторон баррикады. Нельзя, например, просто взять C-шный исходник программы и перегнать его в стопицотмильёнов команд MOV. Требуется серьезная предварительная подготовка кода, да и с вызовами API все не так радужно. Мне просто понравился сам подход к реализации, некоторые моменты действительно интересные.

Вот простейшие примеры, как можно заменить команды инкремента и декремента командами MOV. Для начала нам понадобятся две таблицы, в целях оптимизации я совместил их пересекающиеся данные. Здесь и далее подразумевается, что мы работаем с байтом.
  1. dec_table:
  2. db 255,  0
  3.  
  4. inc_table:
  5. db   1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15, 16
  6. db  17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
  7. db  33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48
  8. db  49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64
  9. db  65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80
  10. db  81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96
  11. db  97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,112
  12. db 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128
  13. db 129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144
  14. db 145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160
  15. db 161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176
  16. db 177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192
  17. db 193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208
  18. db 209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224
  19. db 225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240
  20. db 241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,  0
А вот реализация INC и DEC классическим способом и через M/o/Vfuscator:
  1.         ;---------------------------
  2.         ; y = x+1
  3.         ;---------------------------
  4.         mov     ecx,X_VAL
  5.  
  6.         ; Classic
  7.         mov     al,cl
  8.         inc     al
  9.  
  10.         ; M/o/Vfuscator
  11.         mov     eax,ecx
  12.         mov     al,[inc_table+eax]
  13.  
  14.         ;---------------------------
  15.         ; y = x-1
  16.         ;---------------------------
  17.         mov     ecx,X_VAL
  18.  
  19.         ; Classic
  20.         mov     al,cl
  21.         dec     al
  22.  
  23.         ; M/o/Vfuscator
  24.         mov     eax,ecx
  25.         mov     al,[dec_table+eax]
Для работы с WORD и DWORD, а также для реализации сложения и вычитания произвольных значений байт надо в два раза расширить таблицу результатов и завести таблицу состояний флага переноса для каждого случая. Затем поочередно выполнить операцию к каждому байту.

Пример немного посложнее - сравнение байт. Для этого понадобится вспомогательный массив нулевых байт размером в 256 элементов.
  1. scratch rb 256
Реализация команд SETE AL и SETNE AL по результатам сравнения двух байт:
  1.         ;---------------------------
  2.         ; if (x==y) { AL=1 }
  3.         ;---------------------------
  4.         mov     ecx,X_VAL
  5.  
  6.         ; Classic
  7.         xor     eax,eax
  8.         cmp     ecx,Y_VAL
  9.         sete    al
  10.  
  11.         ; M/o/Vfuscator
  12.         mov     [scratch+ecx],0
  13.         mov     eax,Y_VAL
  14.         mov     [scratch+eax],1
  15.         mov     al,[scratch+ecx]
  16.  
  17.         ;---------------------------
  18.         ; if (x!=y) { AL=1 }
  19.         ;---------------------------
  20.         mov     ecx,X_VAL
  21.  
  22.         ; Classic
  23.         xor     eax,eax
  24.         cmp     ecx,Y_VAL
  25.         setne   al
  26.  
  27.         ; M/o/Vfuscator
  28.         mov     [scratch+ecx],1
  29.         mov     eax,Y_VAL
  30.         mov     [scratch+eax],0
  31.         mov     al,[scratch+ecx]
Реализация условного перехода с помощью команд MOV. В сегменте данных нам потребуется табличка с двумя адресами переходов на случай выполнения и невыполнения условия.
  1. jmptable dd loc_je,loc_jne
После сравнения получаем адрес перехода:
  1.         ;---------------------------
  2.         ; if (x==y) { goto loc_1 } else { goto loc_2 }
  3.         ;---------------------------
  4.         mov     ecx,X_VAL
  5.  
  6.         ; M/o/Vfuscator
  7.         mov     [scratch+ecx],4
  8.         mov     eax,Y_VAL
  9.         mov     [scratch+eax],0
  10.         mov     al,[scratch+ecx]
  11.         mov     eax,[jmptable+eax]
  12.         ; EAX -> адрес перехода
  13.         ...
  14.         ...
  15. loc_je:
  16.         ; JE
  17.         ...
  18. loc_jne:
  19.         ; JNE
  20.         ...
Как выполнить переход? Для этого лучше всего использовать SEH со срабатыванием на запись в несуществующий адрес памяти. Но SEH требует настройки через стек. Когда будет реализовано сложение и вычитание DWORD, можно будет сделать аналоги команд PUSH и POP через модификацию регистра ESP и записи значений напрямую в стек.

Таким образом у нас получился абсолютно линейный алгоритм, в котором реализована проверка и условный переход. При этом задействованы только команды MOV. Я нарисовал простенькую программу для демонстрации, она есть в аттаче, но там WinAPI вызываются в чистом виде, весь остальной алгоритм реализован через команды MOV. Как спрятать вызовы WinAPI? Тут, к сожалению, красивого решения нет, придется точно так же через стек настраивать параметры, а через SEH выполнять вызовы на функции-переходники. Что, кстати, читаемости программе тоже особо не добавляет. Но, как вы понимаете, разработке и отладке стиль M/o/Vfuscator тоже не способствует, поэтому его хорошо использовать точечно, в каких-нибудь неожиданных или критических участках кода.

Презентацию M/o/Vfuscator можно скачать с офсайта или по ссылке ниже. Там больше готовых примеров и объяснений, как все работает.

Презентация M/o/Vfuscator (ENG)Презентация M/o/Vfuscator (ENG)

MoVfuscator.Presentation.zip (3,385,117 bytes)

Хорошее теоретическое обоснование о тьюринг-полноте команды mov от Stephen Dolan, на котором базируется M/o/Vfuscator.

Stephen Dolan - mov is Turing-complete (ENG)Stephen Dolan - mov is Turing-complete (ENG)

Stephen.Dolan.mov.is.Turing.complete.zip (167,648 bytes)

В приложении пример программы с исходным текстом, в котором реализованы проверки и переходы по принципу M/o/Vfuscator.

Пример программы с исходным текстом (FASM)Пример программы с исходным текстом (FASM)

The.MoVfuscator.Demo.zip (3,460 bytes)


Поделиться ссылкой ВКонтакте
Просмотров: 1277 | Комментариев: 19

Внимание! Статья опубликована больше года назад, информация могла устареть!

Комментарии

Отзывы посетителей сайта о статье
Petya (08.08.2022 в 19:49):
ЦитатаКак вариант, можно попробовать раздробить операцию на действия над 4-битными блоками.

Будем посмотреть, конечно, но как в отсутствие AND выделить из числа 4 бита?
Или иметь три таблицы, одну 256*1 слов для разборки, вторую 16*16 байт для AND  и третью 16*16 байт для сборки?
Уже лучше, но больно на извращение похоже.
ManHunter (05.08.2022 в 15:58):
Не обязательно же таскать таблицы сразу в программе, их можно генерить по мере надобности. Ослабления основного алгоритма это не вызовет, т.к. к логике программы не относится.

Как вариант, можно попробовать раздробить операцию на действия над 4-битными блоками.
Petya (05.08.2022 в 15:08):
Копался с добавлением макросов для логики. Есть ли более умный способ делать AND, чем через таблицу 255*255 байт?
ManHunter (10.05.2022 в 17:42):
Хорошо, посмотрю на досуге.
Petya (10.05.2022 в 15:42):
Примеры, по оригинальному коду:
Запустить в отладчике
Поставить бряк на 402019 (mov [dummy],esp), выполнить (нажав что угодно в MessageBox'e)
Занулить ESP
Поставить бряк на 40208E (mov esp,eax), выполнить
Сделать ещё шаг
В ESP окажется FF00FFFC - не совсем -4.
По аналогии, 000DF300 - 4 = FF0CF2FC вместо 000DF2FC.
ManHunter (03.05.2022 в 18:14):
Цитатаmov_sub, mov_add криво делают перенос

И конечно же есть примеры?
Petya (02.05.2022 в 13:51):
Терпение вернулось, макросы появились. Вопиюще неполные, но хватает для переписывания демки в более понятный вид. Попутно припрятаны получше API - от них остались только JMP по штуке на вызов, остальное спрятано.
https://www.upload.ee/files/14...tor.zip.html
Замеченные недостатки:
- mov_push, mov_pop не совсем идентичны натуральным, если адрес дан относительно ESP
- mov_sub, mov_add криво делают перенос. Явно глюк из кода ManHunter'a, сам разобраться не смог.
Petya (20.04.2022 в 16:55):
Сбываются древние мечты Юрия Нестеренко...
Цитатакомпания Velorola также пpедставила свой новый пpоцессоp. Основанный на PISC - аpхитектуpе, он имеет 65535 pегистpов и одну команду (ее мнемоника и фоpмат являются коммеpческой тайной компании).

А если серьёзно, то ещё бы набор макросов из этого сделать, как автор в презентации рекомендовал. Чего-нибудь типа mov_jmp, mov_add, mov_push, mov_call. Я пытался, но терпение быстро кончилось.
Джо (11.04.2022 в 14:12):
Напоминает учебную задачу про сортировку без условий
ManHunter (11.04.2022 в 09:53):
А в динамике процесс чем облегчится? На которой тысяче F7 рука онемеет? Место для бряка теоретически найти можно, а практически затруднительно, т.к. код линейный. Читать трассу в сотни тысяч mov тоже такое себе занятие. И это, заметь, при условии, что код не будет разбавляться мусором, а блоки не будут мутировать и искусственно усложняться.
User (11.04.2022 в 05:25):
... ночным кошмаром реверсера в статике.
ManHunter (10.04.2022 в 10:45):
Добавил в статью архив с Stephen Dolan - mov is Turing-complete
0101 (10.04.2022 в 09:38):
Gauri, видно же, что x32dbg
Gauri (10.04.2022 в 09:25):
0101, на всехскринах IDA?
Знаток (09.04.2022 в 23:00):
По поводу переходов. Если бы x86+ процессоры позволяли делать mov EIP, то всё было бы куда проще: как в Brainfuck )))
Знаток (09.04.2022 в 22:38):
Прекрасная иллюстрация того факта, что команда mov - тьюринг-полная.
Исследование по теме: http://web.archive.org/web/201...pers/mov.pdf
0101 (09.04.2022 в 19:57):
согласен)Увидел переходы и заскринил.
ManHunter (09.04.2022 в 19:38):
Обфускация != Антиотладка. Строки тоже никто специально не прятал, а последний скрин вообще к логике программы никаким местом не относится.
0101 (09.04.2022 в 19:22):

Добавить комментарий

Заполните форму для добавления комментария
Имя*:
Текст комментария (не более 2000 символов)*:

*Все поля обязательны для заполнения.
Комментарии, содержащие рекламу, ненормативную лексику, оскорбления и т.п., а также флуд и сообщения не по теме, будут удаляться. Нарушителям может быть заблокирован доступ к сайту.
Наверх
Powered by PCL's Speckled Band Engine 0.2 RC3
© ManHunter / PCL, 2008-2024
При использовании материалов ссылка на сайт обязательна
Время генерации: 0.15 сек. / MySQL: 2 (0.0051 сек.) / Память: 4.5 Mb
Наверх