Привет, есть своя виртуальная машина, инструкции одинаковой длины, реализован как полный стэк регистров общего назначения, так и некое подобие стековой машины. В общем, что-то похожее на x86, только с фиксированной длиной инструкций для простаты простоты. Есть для этой штуки интерпретатор. Все работает. Прекрасно. Но кодить малварь на своем виртуальном ассемблере - это издевательство... Подскажите, каким образом VMProtect и Themida преобразуют Native x86/x64 код в виртуальные опкоды? Как это происходит? Каким образом можно, например, написать софт, натравив который на бинарик, он его преобразует в виртуальные опкоды и навесит под свою виртуальную машину? Интересует сам алгоритм, так и разжевывание для тупого меня. Мне еще не понятен сам алгоритм чтения ассемблерных инструкций, поидее, придется эмулировать все переходы внутри бинаря, чтобы верно пройти весь код виртуализируемый. Не бежать же линейно по всей кодовой секции? Неверно сдизасмить можно... нужен некий code-analysis, как в IDA... бр, буэ.... Спасибо заранее за ответы. P.S. Надоели мемдетекты дефендера и езета P.P.S. Надоели резерчеры, кремезы и прочие наосеки. Пусть сначала мои vmhandlers под трешгеном пореверсят
zerodawn, Вначале нужно решить задачу по указателям https://wasm.in/threads/poluchit-spisok-procedur.31729/ Нужно как то получить все входа в модуль, аналогично как PE-CFG tables. Тогда от этих исходных точек весь код может быть выделен в граф и далее уже можно его обработать. Во вторых нужно получить размеры данных(массивы/строки етц). VMP вроде как сурки обрабатывает для этих целей. > Надоели мемдетекты дефендера и езета Есть куча подводных камней". К примеру нужно знать размер данных, передаваемых во вне вм, тоесть это вызов апи. Если передавать большие обьёмы данных, то на этом может сработать детект. Передавать данные порциями в случае вм не получится, для этого нужно системное решение(подмена данных при их выборке) https://wasm.in/blogs/softvernye-anklavy.548/ Модуль может быть вм и тогда будут происходить выборки данных на чтение, вместо исполнения(проход вм по байт коду). В таком случае данные так же должны лежать в памяти, иначе необходим анклав. В противном случае будет детект. Но даже в случае с анклавами есть проблемы, это выборка данных из км, к примеру доступ к аргументам кернел апи: для MessageBox() например строка выбирается ядром, а эта выборка идёт не в анклав, так как он юзермодный механизм и происходит фейл. Например может быть проблема с поточными данными. Так например передавая некоторый блок данных в апи его возможно будет нужно загрузить на исходный адрес, принимаемый апи. Если такие вызовы происходят во множестве потоков, то это соберёт в памяти много данных, построится часть модуля и будет детект. Вм для детектов в памяти не панацея. Она также будет сигнатурой. Положительных моментов лишь два - виртуализация затруднит реверс и возможно проще морфить байт код, чем нэйтивный.
Проанализил задачу, ничего нового не получается. Ситуация следующая. В некоторую функцию должен быть передан массив данных, это её параметры(аргументы). Этот аргумент - указатель на область данных, которая их содержит. В данном случае содержание данных в памяти не допустимо. Так же размер их не известен. Формулировка решения задачи: Данные не существуют до их выборки(R/W-data fetch). Они формируются при самой выборке данных. Это называется анклавом. В случае байт кода: каждая выборка данных должна быть кодирована в формате вм. При этом событии должен быть запущен декриптор данных. Он вернёт в среду минимальную часть данных соответственно машинной гранулярности. При этом данных в памяти может не быть вовсе - зависит от реализации. Так например метод DYPE использовал фикс MODR/M полей для направления выборки в буфер. Но это не решит не решаемую задачу - датафетч из км. Передача указателя на анклав в км приведёт к фейлу. В таком виде задача успешно решится локально, но не в пределах апи(в случае вм). Для неё апи это целостный обьект, в неё нужно передать декриптованный массив. Запуск под визором(DYE) данную задачу снимает, так как мод тот же(UM) и все выборки данных отслеживаются. Походу автор поднял не подьмную задачу. Не продумав изначально механизмы.
гугли "[FASM x86] Миниатюрня софтверная стековая виртуальная машина" "Как обнаружить FinFisher. Руководство ESET" "Компиляторы: принципы, технологии и инструменты" итд итп
возьми Radare2 или IDA Pro, дизассемблируй ими, получишь простые абстракции, как функция, команда и тд, получить их можно с помощью того же IDAPython или байндингов питона для Radare2... но имея виртуальную машину, я бы делал транслятор из какого-нить высокоуровневого языка в байткод (при этом можно заюзать готовый парсер, как например ast модуль питона), или же из LLVM IR в байткод, если так уж хочется кодить на сишечке...
Rel, Эти толстые инструменты не решают первую задачу(выделение входов) и вторую(размеры данных). Если это сделать вероятным путём, то и стабильность конечного приложения будет зависеть от фазы луны. Вторая проблема кстате никак не резолвится, кроме трека выборки, увы. Размеры данных не известны даже для исходного апп. Данные выбираются при событии выборки их. Это событие должно быть отслежено. Вариант с байт кодом я описал выше. Но это не покрывает вызов кода вне тела вм(апи). В этом случае вариант решения лишь один - запустить визор для трека выборок. Но это совершенно иная тема.
как из того чтобы потрогать x86obf (там есть много ограничений, но идея ясна будет) но тебе это не даст ничего. просто будут детектить твою вм и все равно что она скрывает и для работников вирлаба это будет даже проще. лучше запили для себя на базе ллвм+кланг, кпд на трафе будет лучше. имхо
спасибо всем за советы, поступлю как superakira советует. Все таки-да, зубодробильный морф лучше любой виртуалки. Хотя виртуалка под зубодробильным морфом еще лучше > но тебе это не даст ничего. просто будут детектить твою вм и все равно что она скрывает и для работников вирлаба это будет даже проще. P.S. Как по мне морфить вм опкоды и vmhandlers - ну хз. а по поводу того, что проще это как? Придется же восстановить всю логику хэндлеров.
Да там не трудно на самом деле. и т.д, на любое твое усмотрение, и делаешь инструкции, типа своего асма придумываешь. У меня есть транслятор на базе макросов FASM'а, просто берешь макросами команлы обозначаешь и через db их кодируешь с операнадами. Потом это все компилишь в байткод, ну и дальше дело за малым. Делаешь потом саму вм на си, Берешь например структуру обозначаешь с контекстом потока: там регистры и т.д, и просто потом бежишь парсишь команды. Обозначаешь как нибудь контекст, например Код (Text): struct ThreadContext { UINT register1, UINT register2, UINT register3; UINT StackPointer; UINT EIP; }; например, пусть buf - это вм опкоды, берешь: vm_handler(&context, decode(buf1[context.EIP])); - декодируешь команду на которую EIP указывает, пихаешь ее вместе с контекстом в хандлер, далее хандлер ее исполняет. Если это пересылка байт, то пересылает. Вся инфа находится в структуре context, если это вызовы или прыжки, то изменяет например регистр EIP