VIKA JIT DUMPER 1.0 BETA
До этого проекта все никак не доходили руки. Моей целью было не просто создание джит дампера, которых наплодили уже 100 штук галимых или нерабочих, а чего-то действительно уникального. То, что заберётся в самые дебри компилятора и сдампит токены, локали, обработчики исключений и даже строки непосредственно из джит.
Мой дампер работает с последним днгуард.
Подгрузка дебаг символов позволяет дампить .net цели на любой машине. Поддерживаются как x86, так и х64 сборки.
На текущий момент .netcore не поддерживается . Но для него будет создана отдельная версия VIKA JIT DUMPER CORE. Там есть принципиальные отличия некоторые, и поэтому я не хочу добавлять неткор в дотнет .
Во время разработки дампер претерпел много изменений в своей архитекуте. Изначально задумывалась нативная либа с detours в которой жёстко привязаны rva под мою виртуалку.
Но чтобы не делать трудностей с обходом виртуалок на некотором софте, я сначала отказался от жёстко прописанных рва заменив их на подгрузку отладочных символов, а затем и вовсе отказался от натива, так как детоурс ведёт себя непредсказуемо с указателями на .net методы, пусть даже и скомпилированные в памяти, и даже правильно согласованных. Detours был заменён на классический трамплин, этого пока хватает.
Если такое научатся детектить обфу, добавлю другие варианты хуков.
Патч виртуальной таблицы не предлагать сразу, его уже детектит все. Или как молодежь говорит shadow table.
Ну а теперь к делу.
Как работает jit вкратце и что нужно знать для анпака?
JIT - Just in time, если переводить с вражеского на человеческий будет компиляция во время выполнения.
Функции не компилируются сразу, скажу уже в сотый раз банальные вещи, а компилируют нативное тело по мере необходимости этой функции.
Также следует отметить, что существуют так называемые inline, встраиваемые методы. Это то, чего нам нужно избегать, она же оптимизация.
К счастью обфускаторы сами не могут без их отключения =).
Это немного изменяет компиляцию метода. Например тот же днгуард сам выключает инлайн, но если вы попытаетесь подменить результат canInline будет орать. Спрашивается зачем)))
Работая с JIT, мы должны понимать его язык. Язык JIT - это хендлы. Хендлы методов, хендлы типов, модулей, и даже хендлы компилятора clr)
Все взаимодействие в Джит происходит именно по хендлам. До того как метод скомпилирован - его тело это заглушка, которая посылает его на компиляцию.
За это отвечает метод doPreStub
Наша задача искусственно отправить каждый метод на компиляцию.
Здесь существуют два способа, оба я реализовал.
1)вызов doPreStub(), Нужно пропатчить его внутренние проверки некоторые.Если метод valueType нужно перезагрузить стаб через getUnboxingStub()
Не знаю насколько оправдано это использовать, но я сделал
2)jitAndCollectTrace, часть AppDomain. Отправляет метод на компиляцию внутри определенного аппдомена. Намного удобнее и не так требовательна. Тоже реализовано у меня, главный минус, исключение внутри него обработать невозможно.
Метод отправлен на компиляцию, что дальше?
Казалось бы хукать функции компиляции и все, но их целых 4 штуки.
compileMethod, jitNativeCode, compCompile fastcall, compCompileHelper, compCompile thiscall
Все они по сути просто прокси друг друга.
Хук глубже compCompile fastcall не оправдан, чтобы подменить структуры там нужны оффсеты приватных полей компилятора, которых больше нет ни у кого, так как Майкрософт не поставляют их больше .
Для хука функции как я уже сказал, использую трамплин. Он не срет сильно в виртуальные таблицы, которые проверить легче всего.
Метод хукнут, далее нам нужно получить настоящий код.
Здесь начинается первые сложности.
IL код и его размер получается легко из параметра methodInfo, также как и максимальный размер стека.
Но что делать с локалями и ешками?
Начну с ешек. Ситуация безвыходная на первый взгляд. Существует только один метод в параметрах которого есть структура EH_CLAUSE, это getEHInfo()
Это очень удобный метод, отличный, все использовали его. Но его можно отменить в прямом смысле патчем таблицы и сделать свой, в котором инвок уже будет невозможен. Это настоящая трагедия, которой пользуется днгуард.
Что придумал я?
После компиляции все обработчики исключений сохраняются в особой таблице, EHblckDsc, поля которой содержат всю необходимую информацию о ешках вместо EH_CLAUSE
Но в чем беда, эту таблицу ещё нужно получить. Пендосы закрыли нам отладочные символы, и казалось бы можно развести руками.
Но любители красивого кода мальчики программисты из пендостана заботливо обернули нам функцию аллока этой самой таблицы, берём дизассемблер и получаем оффсет смело.
Первая сложнейшая проблема была решена.
Тут я хотел все бросить, скажу честно. Да, по умолчанию локали есть в метод инфо.
Но кто бы их нам там оставил....
Днгуард использует самый сложный на сегодняшний момент хук из цепочки в 3 метода.
1)getArgType - подмена elementType
2)getArgNext - подмена размера
3)getArgClass - подмена type для не элементарных типов данных
Я мог бы хукнуть эти функции но не ныть, но днгуард пошли дальше. Они вырезали кусок компилятора вместе с lvaInitVarDsc, и стали заполнять все вручную. Как итог - локали перемешаны и хуки не помогут.
У меня возник план инвокать сами функции обфускатора.
Для этого нужна была таблица всех методов ICorStaticInfo, доступ к которой закрыли уроды майки через оффсеты.
Видим compileMethod
Хукаем и один раз воруем ICorStaticInfo, затем сопоставляем с таблицей после патча
Все, слоты получены, дальше дело техники
Обфускатор все еще может подменить их в двух функциях, resolveToken и impResolveToken.
Vika JIT dumper хукает оба этих метода, сопоставляет их трейсы и создает таблицу токенов.
Все, на этом проблема была решена полностью с дампом на x64.
Но остались то x86, и у нас шарп, который не умеет работать с fastcall.
Пришлось писать 2 обертки для хука compCompile
Шеллкоды вносят аргументы в регистры как этого требует fastcall
В дампере также предусмотрена защита от "некомпилируемых" методов
Парс идет напрямую через таблицу метадаты.
Рекомпиляция классическая, на движке dnlib с кастомным резолвером.
Вот такой получился дампер, на текущий момент он лучший из существующих.
Я смог сдампить днгуард без единого нативного патча его рантайма, и это безусловно успех.
Жду ваших тестов.