M/o/Vfuscator. Ночной кошмар реверсера
M/o/Vfuscator. Ночной кошмар реверсера
Когда-то давно наткнулся на интересный проект - M/o/Vfuscator от Chris Domas. Его необычность заключается в том, что все ассемблерные команды в исходнике преобразуются в набор команд MOV (и только их!!!), которые в результате выполняют то же самое действие, что и заменяемая команда. Никаких проверок, никаких условных переходов, никаких ветвлений алгоритма, вообще ничего, кроме сотен и тысяч последовательных инструкций MOV. Понятное дело, что размеры исходника и готовой программы распухают на порядки, но кого это в наше время волнует. Похоже на магию?
Сразу успокою горячие головы с обеих сторон баррикады. Нельзя, например, просто взять C-шный исходник программы и перегнать его в стопицотмильёнов команд MOV. Требуется серьезная предварительная подготовка кода, да и с вызовами API все не так радужно. Мне просто понравился сам подход к реализации, некоторые моменты действительно интересные.
Вот простейшие примеры, как можно заменить команды инкремента и декремента командами MOV. Для начала нам понадобятся две таблицы, в целях оптимизации я совместил их пересекающиеся данные. Здесь и далее подразумевается, что мы работаем с байтом.
Code (Assembler) : Убрать нумерацию
- dec_table:
- db 255, 0
- inc_table:
- db 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16
- db 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32
- db 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48
- db 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64
- db 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80
- db 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96
- db 97, 98, 99,100,101,102,103,104,105,106,107,108,109,110,111,112
- db 113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128
- db 129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144
- db 145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160
- db 161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176
- db 177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192
- db 193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208
- db 209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224
- db 225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240
- db 241,242,243,244,245,246,247,248,249,250,251,252,253,254,255, 0
Code (Assembler) : Убрать нумерацию
- ;---------------------------
- ; y = x+1
- ;---------------------------
- mov ecx,X_VAL
- ; Classic
- mov al,cl
- inc al
- ; M/o/Vfuscator
- mov eax,ecx
- mov al,[inc_table+eax]
- ;---------------------------
- ; y = x-1
- ;---------------------------
- mov ecx,X_VAL
- ; Classic
- mov al,cl
- dec al
- ; M/o/Vfuscator
- mov eax,ecx
- mov al,[dec_table+eax]
Пример немного посложнее - сравнение байт. Для этого понадобится вспомогательный массив нулевых байт размером в 256 элементов.
Code (Assembler) : Убрать нумерацию
- scratch rb 256
Code (Assembler) : Убрать нумерацию
- ;---------------------------
- ; if (x==y) { AL=1 }
- ;---------------------------
- mov ecx,X_VAL
- ; Classic
- xor eax,eax
- cmp ecx,Y_VAL
- sete al
- ; M/o/Vfuscator
- mov [scratch+ecx],0
- mov eax,Y_VAL
- mov [scratch+eax],1
- mov al,[scratch+ecx]
- ;---------------------------
- ; if (x!=y) { AL=1 }
- ;---------------------------
- mov ecx,X_VAL
- ; Classic
- xor eax,eax
- cmp ecx,Y_VAL
- setne al
- ; M/o/Vfuscator
- mov [scratch+ecx],1
- mov eax,Y_VAL
- mov [scratch+eax],0
- mov al,[scratch+ecx]
Code (Assembler) : Убрать нумерацию
- jmptable dd loc_je,loc_jne
Code (Assembler) : Убрать нумерацию
- ;---------------------------
- ; if (x==y) { goto loc_1 } else { goto loc_2 }
- ;---------------------------
- mov ecx,X_VAL
- ; M/o/Vfuscator
- mov [scratch+ecx],4
- mov eax,Y_VAL
- mov [scratch+eax],0
- mov al,[scratch+ecx]
- mov eax,[jmptable+eax]
- ; EAX -> адрес перехода
- ...
- ...
- loc_je:
- ; JE
- ...
- loc_jne:
- ; JNE
- ...
Таким образом у нас получился абсолютно линейный алгоритм, в котором реализована проверка и условный переход. При этом задействованы только команды MOV. Я нарисовал простенькую программу для демонстрации, она есть в аттаче, но там WinAPI вызываются в чистом виде, весь остальной алгоритм реализован через команды MOV. Как спрятать вызовы WinAPI? Тут, к сожалению, красивого решения нет, придется точно так же через стек настраивать параметры, а через SEH выполнять вызовы на функции-переходники. Что, кстати, читаемости программе тоже особо не добавляет. Но, как вы понимаете, разработке и отладке стиль M/o/Vfuscator тоже не способствует, поэтому его хорошо использовать точечно, в каких-нибудь неожиданных или критических участках кода.
Презентацию M/o/Vfuscator можно скачать с офсайта или по ссылке ниже. Там больше готовых примеров и объяснений, как все работает.
Хорошее теоретическое обоснование о тьюринг-полноте команды mov от Stephen Dolan, на котором базируется M/o/Vfuscator.
Stephen Dolan - mov is Turing-complete (ENG)
Stephen.Dolan.mov.is.Turing.complete.zip (167,648 bytes)
Stephen.Dolan.mov.is.Turing.complete.zip (167,648 bytes)
В приложении пример программы с исходным текстом, в котором реализованы проверки и переходы по принципу M/o/Vfuscator.
Просмотров: 1363 | Комментариев: 19
Внимание! Статья опубликована больше года назад, информация могла устареть!
Комментарии
Отзывы посетителей сайта о статье
ManHunter
(05.08.2022 в 15:58):
Не обязательно же таскать таблицы сразу в программе, их можно генерить по мере надобности. Ослабления основного алгоритма это не вызовет, т.к. к логике программы не относится.
Как вариант, можно попробовать раздробить операцию на действия над 4-битными блоками.
Как вариант, можно попробовать раздробить операцию на действия над 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.
Запустить в отладчике
Поставить бряк на 402019 (mov [dummy],esp), выполнить (нажав что угодно в MessageBox'e)
Занулить ESP
Поставить бряк на 40208E (mov esp,eax), выполнить
Сделать ещё шаг
В ESP окажется FF00FFFC - не совсем -4.
По аналогии, 000DF300 - 4 = FF0CF2FC вместо 000DF2FC.
ManHunter
(03.05.2022 в 18:14):
И конечно же есть примеры?
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, сам разобраться не смог.
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):
Сбываются древние мечты Юрия Нестеренко...
А если серьёзно, то ещё бы набор макросов из этого сделать, как автор в презентации рекомендовал. Чего-нибудь типа mov_jmp, mov_add, mov_push, mov_call. Я пытался, но терпение быстро кончилось.
А если серьёзно, то ещё бы набор макросов из этого сделать, как автор в презентации рекомендовал. Чего-нибудь типа 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
Исследование по теме: 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):
имхо, современным средствам отладки противостоит плохо:
https://img001.prntscr.com/fil...NNg1ztwA.png
https://img001.prntscr.com/fil...Bso0TvEw.png
https://img001.prntscr.com/fil...2AssTSyQ.png
https://img001.prntscr.com/fil...TcXSCOWA.png
https://img001.prntscr.com/fil...NNg1ztwA.png
https://img001.prntscr.com/fil...Bso0TvEw.png
https://img001.prntscr.com/fil...2AssTSyQ.png
https://img001.prntscr.com/fil...TcXSCOWA.png
Добавить комментарий
Заполните форму для добавления комментария
Будем посмотреть, конечно, но как в отсутствие AND выделить из числа 4 бита?
Или иметь три таблицы, одну 256*1 слов для разборки, вторую 16*16 байт для AND и третью 16*16 байт для сборки?
Уже лучше, но больно на извращение похоже.