Пришла в голову идея заменить тело одного PE на тело другого PE в памяти, только не классическим замещением типа ProcessHollowing из чужого процесса, а выделить виртуальную память и поместить в нее шеллкод который вызовет NtUnmapViewOfSection для текущего PE, а затем создаст новую секцию и положит новое тело на место старого и вызовет точку входа, то есть замещение "самого себя", это будет работать в теории?
Да, этой технике сто лет в обед и да, это будет работать, но только не понятно зачем? Я пока не вижу никаких преимуществ в этом. Разве что это позволит модифицировать оригинальный исполняемый файл, если делать какой-то метаморфизм.
В общем идея такова, сначала стартует код который вручную мапит DLL с движком (о котором чуть далее), затем делает эти махинации с заменой PE в памяти на другой PE секция кода у которого зашифрована и остается шифрованной все время, затем вызывается движок, он устанавливает обработчик VEH и будет ловить ошибки доступа к страницам, вся секция кода помечается как защищенная и при попытке доступа к ней генерится исключение, одна страница кода расшифровывается и ей даются нормальные права на исполнение, расшифрованный код исполняется, затем когда исполнение попадает в секцию где стоит гуард снова ловится исключение, старая страница шифруется обратно а новая расшифровывается, в общем таким методом в памяти на исполнение есть одна открытая страничка, все остальное зашифровано, и вот так на эксепшенах это все расшифровывается и шифруется обратно в динамике, вроде бы похожая техника была в протекторе армадилло, правда там была уязвимость, которая позволяла обратившись по порядку ко всем страницам по очереди получить все их в расшифрованном виде, из-за того, что там было условие, что код шифрующий страницы не вызывался до тех пор пока выполнение кода находилось в пределах двух соседних страниц Изначально была концепция расшифровки по одной инструкции и исполнение их под SINGLE_STEP, но после реализации оказалось что это очень медленно, если накрыть весь код такой защитой, то он по скорости выполняется на уровне вирт машин типа VMP и др, большие циклы заставляют подождать код пару секунд, если применять такой метод к отдельным функциям, то это еще будет нормально работать в плане скорости, но весь код исполнять пошагово расшифровывая каждую инструкцию отдельно оказалось очень медленно для защиты крупных приложений, какой-то дроппер или даунлоадер на 100 строк кода исполнится без задержек, но если защитить что-то из коммерс нагруженного софта, то это сильно убивает производительность
А, ну да, ну да, софтварные анклавы преобретают свою заслуженную популярность. Да, так можно сделать, но все равно держать целую страницу в памяти в расшифрованном виде палевно. И потом, хорошо бы знать, что страница не нужна в определенное время, когда поток пошел код dll исполнять или еще чего, а не только когда код перепрыгнул на другую залоченную страницу. Ну а всё, что обрабатывается по одной инструкции, там через INT3 и VEH (как в статье на дамаге), или через исполнение одной инструкции в буфере (как местный спец делал), так или иначе будет существенно медленнее обычного исполнения. Ну и по поводу VEH, не стоит забывать, что сам защищаемый код может захотеть обрабатывать исключения, тут может некоторая коллизия возникнуть. Про многопоточность тоже надо подумать.
По поводу хотелки использования приложением VEH, эта функция установки обработчика хукается, и все обработчики устанавливаемые приложением ставятся ниже по уровню чем мой обработчик, так что это не проблема, вопрос многозадачности тоже решен, через глобальную таблицу занятости конкретных адресов тем или иным потоком, поэтому два потока не полезут никак в одно и то же место исполняться, если один уже занял инструкцию, то второй ждет пока другой исполнит ее и зашифрует обратно. Переходы в соседние модули\dll отслеживаются, потому что на борту не тупо дизассемблер длин инструкций, а полноценный дизасм, который отслеживает поток выполнения кода, вычисляет адреса куда будут идти прыжки\колы и будут ли они идти исходя из флагов, так же хукается создание потоков, опять же для решения проблемы попытки одновременной расшифровки двумя потоками одного и того же места. int3 для пошагового выполнения не очень, потому что постоянно нужно лишний раз втыкать его и еще байт бэкапить, проще ставить бряки в регистры DR0-7 Единственная проблема остается в том, что приложение может пытаться читать свою же память, которая может быть зашифрована, но проблема решаемая касательно чтения из сторонних dll вызываемых кодом, опять же дизасм инструкции, которая пытается что-то считать, определение размерности считываемых данных и расшифровка этого кусочка, после отработки инструкции шифровка обратно. Это отслеживается очень легко путем установки защиты на все страницы зашифрованного кода прямо перед переходом в код сторонней библиотеки, которая потенциально может что-то считать, когда управление возвращается - возвращаем опять секции нормальные права удобные для исполнения и расшифровки - RWE Я бы кстати даже не называл это анклавами, по сравнению с нормальными хардварными анклавами данная технология очень примитивна
Говоря такие вещи, ты удаленно предаёшь сильный вращательный импульс телу 20-летнего, лежащему под действиями грибов и молочки на диване в хрущевке в могилёвской области РБ. Я бы был с такими фразами поосторожнее. Ну, допустим, если ты все проблемы с этой темой решил, то вопрос то в чем? И я не понял, зачем тебе во всем этом нужно анмапить PE файлы?
Ну а что если по факту там строчек не более 1к не считая дизасм, это не тянет даже на "средний" проект, поэтому и примитив А по поводу вопроса, так ты ж на него ответил уже выше, что анмап будет работать с последующим мапом другого тела Анмап нужен что бы заменить "загрузчик" всей этой бабуйни на PE с зашифрованной секцией кода, что бы загрузить его по стандартной базе, иначе адреса будут заняты и выделить память не получится, можно конечно просто втупую перезаписать тело вручную выдав права на запись, но это сработает, если тело заменяемого PE больше чем то, на которое происходит замена, а так лучше бы сначала всю память освободить и потом выделить нужное количество под новый PE
Ну в софтварном анклаве или же полноценном антидампе можно не только лишь малварь держать. Может, он хочет свой Армадилдо сделать. Для малвари такие вещи не то чтобы очень эффективны, если авер не сможет палить саму малварь, то он будет палить софтварный анклав, какая проблема?
Делал пдобное только на VB6, заменял изнутри свой образ через шеллкод https://wasm.in/threads/zagruzchik-shellkod-bez-rantajma.31744/
Как уже написал выше Rel не обязательно скрывать таким методом малварь, но если уж на то пошло, то это поможет избавиться только от сигнатур, и более приемлемое решение в данном случае именно для малвари - морфинг кода, не тук уж сложно дизассемблировать рекурсивно функции, заменить все возможные инструкции на аналоги по случайному выбору и перенастроить все ссылки, можно вообще по пирсу раскладывать, там вообще нет ни конца ни края, с одной инструкции можно 10000 сделать, в результате сигнутары будут убиты, а скорость исполнения кода не пострадает, в отличие от шифрования кода на лету, которое было названо как софтверный анклав, но я считаю больно громкое это название для простого самомодифицирующегося кода. А так на самом деле в юзермоде это все баловство если честно, захотят сдампить - сдампят, даже если из ядра драйвером защищаешь, из ядра и сдампят, даже если расшифровка идет по одной инструкции, можно это все автоматизировать Но по поводу палева движка в случае использования с малварой - его можно морфить как минимум
Ну, если тело 20-летнего успеет отойти от молочки и грибов и появится здесь до того, как это обсуждение станет неактуальным, то оно тебе расскажет, что все не так то просто. Это просто для заранее известного и сравнительно небольшого набора инструкций, в общем случае, чтобы отрабатывать все возможные инструкции и для всех возможных компиляторов всего возможного софта, это будет сложно. Это все баловство, в идеале нужен протектор, который может защитить от сканирования памяти в том числе и себя самого. Как это нормально по скорости и по покрытию инструкций реализовать - надо думать. В случае с софтварным акнлавом через VEH, как минимум VEH обработчик должен торчать в памяти все время. Можно было бы, наверное, его на ROP-цепочку разложить, но не знаю.
Начнем с того, что ничего нового данное тело (может быть начнем называть ники, а то я не пойму о ком речь идет) мне не расскажет в этом плане, потому что у меня есть собственная реализация такого морфера и я бы не говорил с такой уверенностью если бы данные вещи не были мною проверены на практике, он обрабатывает все существующие на данный момент инструкции которые может дизассемблировать Zydis и ассемблировать Keystone, потому что они были взяты за основку как дизассемблер и ассемблер. В идеале его нужно кормить файликом map, что бы было проще, но если такой возможности нет, то он сам рекурсивно забуривается во все функции определяя их границы и дизассемблирует весь код, если код не какой-то самомодифицирующийся, да и вообще просто не имеет ничего, что помешало бы нормально его дизассемблировать, то он прекрасно будет обработан. И именно такой код мы можем наблюдать во всех стандартных компиляторах. И опять же повторюсь, ничего сложного в этом нет, другое дело что это долго и нудно, и может надоесть. Главная нуднота заключается в том, что меняются размеры команд и приходится корректировать абсолютно все ссылки в коде. Я бы сказал это очень нудный алгоритм. Нудный в плане написания. Если в крации, то все дизассемблированные команды переводятся во внутренний формат со своими ссылками, после морфа каждая команда может сильно раздуться и стать размером в 5-10 команд, а то и больше, но во внутреннем формате этот набор остается одной командой, затем на основе получившихся внутренних смещений в одной команде корректируются все ссылки, рассчет идет от смещения внутри раздутой команды до начала другой раздутой команды, (у них у всех есть свое положение в "пространстве", начало, конец, дополнительно соханяется еще оригинальный RVA, локальный RVA в каждой функции и много другой служебной инфы) между которыми ранее была ссылка. Это требует некоторого понимания. В целом алгоритм позволяет заморфить и полностью пересобрать код в который он сможет зайти рекурсивно. Во всякие call rax ествественно не заходит, потому что заранее неизвестно без трассировки куда ведет кол. Побудил меня на написание этого алго модуль морфинга VMP, он достаточно хорош, но в нем достаточно мало уделено именно качественному морфингу, там попросту код разбивается на блоки, которые слегка меняются местами и соединяются джампами, причем банальными jmp или же связкой jz+jnz что равносильно jmp. Никаких скрытых переходов с вычислением адреса от текущего положения и даже попыток замусорить код левыми байтами, что бы инструкции поплыли. Я такое писал когда-то, только на уровне исходных кодов, даже без "качественного" и логического морфинга инструкции, одного только перемешивания команд и соединения их переходниками вперемешку с мусором отщипывающим при дизассемблировании такого кода часть реальных инструкций делало невозможным его анализ в IDA и Ghidra, после обработки код дизассемблируется так, что почти все переходы идут в середины инструкций, они конечно же дизассемблированы неверно, из-за того что мусор специально генерируется так, что бы "отщипнуть" от соседних инструкций несколько байт. Такой код невозможно анализировать в статике, мне удалось достичь результатов при которых IDA вообще отказывалась показывать что либо, гидра сдалась чуть позже, но в итоге была так же повержена. Если есть интерес позже запишу видос как эта штуковина работает. --- Сообщение объединено, 19 апр 2022 --- Да, согласен что это баловство, практической пользы от расшифровки по одной инструкции мало, то что я сейчас пишу - просто развлечение
Имя или ник таки больше ассоциируется с сознанием, а поскольку под молочкой и грибами сознание где-то далеко, а не в нашем бренном мире, то "тело" вполне себе описывает это состояние. Ну кстати у "тела" тоже есть свой убер проект, который никто не видел, так как это жуткие приваты. Конечно пиши.
Вот видос, не обращай внимание в начале кручу много мышкой в очевидном месте, изначально видос записывался человеку который мог не понять откуда берется фраза HelloWorld, начинающий программист Грубо алго такой - режет исходный код на указанное количество блоков в случайных местах, блоки перемешивает и соединяет переходниками, которые случайно генерируются по определенным правилам, всего там штук 20 разновидностей переходников между блоками, плюс если возможность простейшего шифрования адреса перехода для каждого переходника, который будет вычисляться динамически в процессе исполнения. Ну и бинарный мусор в местах куда поток выполнения не зайдет никогда + левые джампы в середины инструкций, которые так же никогда не выполнятся https://ru.files.fm/f/4puq6vx45 А на втором видео просто функционал общий, вместо строк 1,2,3,4... соответственно должен быть асм код, все что выдает тулза совместимо с FASM\MASM и инлайн ассемблером в C++, все приколы из секции Advanced рассчитываются в компиль тайме компилятором, MASM поддерживает только смещения на +-, FASM умеет еще и XOR адреса https://ru.files.fm/f/7jpf3e3p2 --- Сообщение объединено, 19 апр 2022 --- Кстати код получившийся на выходе можно снова копировать и обрабатывать повторно, это очень полезно если на первой итерации сделать обработку со всеми опциями, а вторым проходом только с обычным jmp, который побьет сигнатуры переходников сгенерированных на предыдущем проходе. К слову такая штука прибивает иду уже на второй итерации, а иногда и на первой, в режиме графа посмотреть невозможно, ида плюется ошабками, декомпилировать соответствено так же невозможно в псевдокод --- Сообщение объединено, 19 апр 2022 --- Что за убер проект кстати ? --- Сообщение объединено, 19 апр 2022 --- Смотри вот по порядку, исходник, то как он выглядит в IDA, как одна итерация выглядит в исходном виде, реакция IDA на одну эту итерацию Последний скрин показывает ТРИ итерации обработки в представлении графа Ghidra Во всех скринах используются ТОЛЬКО прямые переходы которые дизассемблер может распознать, тоесть jmp и варианты jxx, без динамичных вычислений адреса перехода, что бы видно было насколько оно запутано --- Сообщение объединено, 19 апр 2022 --- . --- Сообщение объединено, 19 апр 2022 --- Подумай что будет когда будут включены все возможные переходы в том числе с динамичным вычислением адреса следования, ответ прост - статичный анализ обрывается на первом же таком переходе, а динамичный анализ в отладчике становится приближен по сложности к анализу виртуальной машины --- Сообщение объединено, 19 апр 2022 --- Вот для полного счастья тебе видео с записью трассировки того кода который был на графе Ghidra https://ru.files.fm/u/xj2ffxc2q
Без каких то претензий и тд, это вполне может нормально работать, но имхо это не то чтобы прям морфинг в каком-то классическом понимании, это скорее обфускация control flow и генератор мусорного кода. Под морфингом я понимаю непосредственную замену одной инструкции на рандомную цепочку инструкций, которая сохраняет семантику (поведение) исходной инструкции, грубо говоря замену инструкции ее аналогом. В твоем случае, да, это не очень сложно должно быть, нужен дизассемблер и ассемблер, плюс правильно пересчитать переходы, которые были в исходном коде. Не совсем понял как можно обработать ситуацию, когда компилятор сгенерировал mov <reg>, <адрес_внутри_твоего_кода> + n-других инструкций + call <reg>?
Да ты все правильно подметил, тулза которая была показана на видео это не полноценный классический морфер, он не заменяет исходные инструкции аналогами (хотя в будущем планирую добавить и этот вид морфинга туда), а просто разрывает поток выполнения и разбивает весь исходник на мелкие кусочки раскидывая их в случайные места и соединяя переходниками в нужном порядке. По сути какова задача морфинга ? - запутать реверсера сделав код нечитаемым и сбить сигнатуры. Эти же самые задачи прекрасно решаются в моей утилитке только слегка другим подходом, это можно сказать замена классического морфинга, потому что разбить весь код можно будет хоть так, что переходы будут буквально между каждой инструкцией, слишком мало для составления сигнатуры, понятное дело практически это бесполезно и есть смысл разбивать на более крупные блоки, просто в несколько проходов, по итогу будут разбиты сигнатуры и переходников каждого предыдущего прохода и сигнатуры самого исходника. По поводу конструкции mov <reg>, <адрес_внутри_твоего_кода> + n-других инструкций + call <reg> Да, такие вещи требуют отдельного рассмотрения, часто такие приколы появляются в результате switch case, и их нужно обрабатывать отдельно, отслеживая такие конструкции по общим правилам, корректируя эти смещения после морфинга, но это уже больше вопрос распознавания высокоуровневых конструкций в коде ассемблера по определенным правилам и шаблонам