✊Rot Front✊
-
Автор темы
- #1
Вступление
Основная проблема обхода функций протекторов – ручной обход т,е обход без автоматизации. Это замедляед анализ и приходится повторять многие моменты. Автор решил из-за этого написать небольшую статью,чтобы напомнить, что некоторые моменты можно легко обойти + автора забавляет, что для обхода anti-debug для Rin-3 используют Ring-0 т.е пытаются задавить таракана зачем-то танком. Сегодня мы заглянем под капот VMP и напишем обход для SDK функций и функции лоадера VMP(anti-debug,anti-vm, list import,spoof syscall_id and e.t.c).
Анализу будет больше про x64 кода(больше 3.7.x-3.8.x, но большинство моментов аналогичны для более ранних версии(например, обход SDK CRC)) т.к аналогичные действия нужно будет сделать для x32 + не вижу смысла делать раздутый код. Будет показана только логика,а остальное можете посмотреть на Github т.к уже был один раз, когда впихнул много и получилась неудачная статья(из-за ограничений на количество символов).
Важный момент!Это больше является PoC,нежели готовым к вставки для обхода на мой взгляд, но я не буду спорить с читателем.
Чем раздражителен VMP?
Во-первых, автор зачастую хитрит и использует WinApi/CRT оболочки, но об этом поговорим во 2 главе и из-за этого используется минимум NtApi/WinApi,а попытка построить anti-debug на manual syscall раздражает,но иногда вдохновляет + использование BugCheck функций для обнаружение хуков от anti-anti-debug tool может заставить вас потратить время.
Во-вторых, своей виртуализацией.
Не словом, а делом мы докажем эффективность отсутствия нужды анализа этого бреда. Это заставит докопаться до всего того, где отсутствует виртуализация + покажет разные способы к решению проблемы.
Автор уже затрагивал эту проблему в статье об обходе CRC протекторов.
VMP начиная ~ с версии 3.5.1 начинает использовать single step для обнаружения HWBP текущего потока:
![VMP SDK anti-debug single step.png VMP SDK anti-debug single step.png](https://yougame.biz/data/attachments/269/269387-b0a95eb13b79a0ab5a8193c4ccc16822.jpg)
Основная проблема в расшифровке ret_address т.к адрес к коду программы, после выполнения SDK функции:
![VMP SDK anti-debug decrypt exit SDK.png VMP SDK anti-debug decrypt exit SDK.png](https://yougame.biz/data/attachments/269/269388-e646b426a09e0f58f7ae1fe6a3b6feec.jpg)
Благодаря этому, можно написать простой хук:
Тут есть огрех из-за того, что не хочу пихать Unicore Engine т.к Dll сильно раздувается, но рекомендую её впихнуть и написать проверку на ret_address т.к в x32 из-за особенности обработки исключения VMP excepthion смещение(ret_address) будет на > 0x4xx т.е будет найден не тот адрес и эмуляция решает проблему, но в пункте 2.2 показывается другой способ обхода этого чуда.
P.S В качестве примера используется Veh хук,но можно уйти на более ранний и менее заметный перехват в обработки исключения (напримре,Wow64PrepareForException).
1.2 CRC SDKАналогичная ситуация с обходом SDK anti-debug т.к автор использует NtQueryVirtualMemory и
в более ранних версиях VirtualQuery(добавьте хук,если понадобится).
Тут появляется неприятный момент т.к если функция будет просто вызываться из виртуализированного кода и это будет не из SDK VMP вызов,то мы вернём слегка иной статус,хотя для NtApi это не будет влиять, но напишем проверку для практики.
Забавный момент: можно это сроку поменять на другой NtApi и VMP будет вызывать другой NtApi.
Изменение строки:
Вызов NtApi,который VMP не использует:
Ну и вишенка на торте!
Муха в котлете, заноза в заднице, ложка дёгтя в бочке дёгтя:
Протектор проверяет целостность кода до того,как это сделает SDK, даже если не включена проверка целостности в лоадере т.е неприятный подарок.
Но есть и приятный момент т.к это значение имеет 2 значения только т.е hash_detect_patch и hash_no_detect_patch т.е можете сделать наглый хук,если потребуется.
Довольно странное решение у автора,но пусть будет так. На самом деле эти значения есть в том же anti-debug, но у меня большие сомнения , что у вас их получится затригирить, если вы не творите полный бред.
Единственный вариант, который придумал – ручное нахождение из-за обращении(PG hook) RW памяти(обычно > PAGE_SIZE * 4) и это именно hash,который будет разный. Короче, лучше патчить после подсчёта CRC в лоадере, если вам лень делать лишние и бесполезные действия(или просто использовать HWBP).
1.3 Anti-vm SDKНа самом деле обход anti-debug обходит и anti-vm SDK,но есть пару проблем, которые автор скажет,но не будет решать т.к
Чтобы не было скучно,автор упомянет сканирование vm-строк,которые вызываются NtQuerySystemInformation с SystemFirmwareTableInformation (RSMB). План надёжный, как швейцарские часы т.е можно заставить возвращать, что их попросту нет:
Глава 2 Ломаем лоадер
Вот тут по большей части у всех начинаются проблемы т.к довольно неприятно ковырять это творение.
Погнали постепенно разбираться что к чему!
2.1 Импорт
Недавно colby57 обновил и выпустил тулзу для фикса импорта на основе эмуляции. Правда,тут есть неприятная проблема, которую указал автор.
Импорт будет расшифрован импорт только в .text секции,а это значит что вирта будет вертеть данную тулзу на все 360 градусов,если обфусцированный вызов под ней. Мы пойдём слегка по иному пути,но сначала слова.
Тут автор затронет халтуру разработчика т.к давайте изучим обфускацию импортов у VMP:
3.0.9:
3.8.1:
Не вижу смысл дальше показывать т.к мы можем заметить 2 вещи:
Как же они её находят, если я накинул обфускации слегка и это КПД стремится к NULL?
Вопрос, конечно, риторический.
Итак,что же делать? Автор идёт старинным способом т.е пишем хук для получения списка импорта + пару идей для хука даже для текущей версии.
Поскольку это тригерит CRC от лоадера,то просто перепишите на HWBP хук,чтобы не париться,но меньше слова и больше дел!
~Включая до версии 3.8.1 можно было вообще кайфовать т.к можно было перехватывать адрес импорта и вернуть адрес на своей функции => $$$бесплатно$$$ хукать импорт из-за просто мутации оболочки получения адреса(я упоминал об этом в 1 чате).
Её можно легко найти, если поставить на PE ntdll PG хук обращение:
Однако, начиная с версии 3.8.2 из-за слива автор решил кинуть эту функцию под вирту,чем усложнил задачу,но всё-равно это не мешает.
Если проанализировать, то используется VMP_STR_CMP т.е сравнивается 2 строки и возвращается статус сравнения. Она только под мутацией, поэтому халява
План действий:
Это не идеальное решение, но у нас есть список импорта.
Если говорить про правильное решение и эмуляцию,то:
Поиск lea reg64,qword ptr ds:[imm](48 8D) для версий ниже ~3.6 и call rip+offset(E8)
Собрать этот список в .vmp0 секции и можно наплевать на .text секцию т.к автор продолжает получать rip через call в обфусцированном вызове(не при переходе из .text в vmp0,а во время выхода обфусцированного чуда),поэтому он облегчает задачу.
Эмуляция и проверка, что указатель указывает на импорт(можно эмулировать до инструкции xchg) и проверка на ret,что мы на импорте. Но мне лень делать + я уже и без этого тащу 3 либы, поэтому это всё на усмотрение читателя,хотя логику сказал.
Так же можно добавить экспорт в свою библиотеку с тем же названием или хукать strcmp, хукнуть GetModuleHandleA и вернуть свою библиотеку,если там нас интересующий импорт. Это так, для шутки…
2.2 Драка за уничтожение syscall_id
Фирма «VMProtect» очень не хотела, чтобы реверсеры использовали для обхода anti-debug
но открытие,что у VMP идёт смешная борьба за manual syscall, повлекло бы за собой совершенно забавное представление о ней.
Стоп. Тут нужно остановиться. Как большинство читателей знаю, VMP начал использовать ~ с версии
В качестве примера, приведу дополнения код SharpOD,который патчит ProductVersion && FileVersion и берёт при загрузке ntdll(хук orig_NtMapViewOfSection) подделанные версии ProductVersion && FileVersion.
Простыми словами, все используют подделку OSBuildNumber в PEB, подделку в ресурсах ntdll ProductVersion, FileVersion. И чтобы жизнь малиной не казалось, VMP начал получать NtBuildNumber из PKUSER_SHARED_DATAи удивила всех подружек:
Это можно обойти и здесь будет показано,но с костылём, поскольку автор лентяй. Да и почему я должен быть честным в обходе, когда все хитрят?
Во-первых, потребуется подменить выше перечисленное( OSBuildNumber в PEB и ProductVersion/FileVersion),но и ещё подделать OSMajorVersion и до полного счастья OSMinorVersion для обхода NtBuildNumber.
Теперь происходит map ntdll.dll с помощью NtOpenSecthion и NtMapViewOfSection,а если это не получится то старый набор из NtOpenFile, NtCreateSection, NtMapViewOfSection. Если зафейлить и это,то автор кидает MessageBox через NtRaiseException и это вообще офигенно, поскольку так делать не надо, но разработчику виднее.
Вот на этом моменте автор застрял т.к пытался уничтожить, что только попалось и вот результат,что можо сделать при плохом настроении, будучи выпившим т.к обращение было только к PE мапнутой ntdll и секции .rdata.
Поэтому переходим к плану B, хотя это первоначальный способ обхода anti-debug в лоадере,но тут гарантировано будут уничтожены syscall_id. В чём шутка? Можно просто пройтись по стеку и уничтожить syscall_id,после unmap ntdll и как бы так делать нельзя. У всё работало, хотя это вообще варварский метод(но кто будет судить за такой метод честной игры?).
Просто повторите в пункте 1.2,но теперь просто уничтожайте syscall_id,но проблема в том,что в x64 они находится в верхушке стека т.к нужно вверх и вниз стека пройтись для полного счасть,как бы ни казалось странным.
Теперь можно пить медовуху у курить бамбук!
2.3 Ani-debug loader или Ivan...You are drunk
Что помогает реверсерам уже имевших с анализом VMP,обходить anti-debug и что VMP делает первым действием, как я упомянул в одной своей статье?
Верно!VMP проверяет всегда NtCurrentPeb()->BeingDebugged и тут просто устанавливаем HWBP hook на обращение.
Возвращаясь к прошлому пункту(2.2),первоначально использовался этот метод для уничтожения syscall_id NtQueryInformationProcess и NtSetInformationThread.
План действий аналогично такой же, но пишем 1 хук на сохранение HWBP и 3 хука(можно и для
Состояние anti-anti-debug tool на данный момент ~
Относительно легко и статья подходит постепенно к концу.
Тут должен быть обход CRC calc от лоадера.
Есть 2 типа CRC calc:критические о обычные т.е
1 вариант - делаем не валидный указатель и программа вызывает ошибку
2 вариант - кидаем MessageBox,если crc плохое
Я написал их поиск и подделку на чтение копии, но есть проблемы:
3 Подарок от китайских друзей.
04.01.2024 на
Я попросил разработчика anti-anti-debug плагина
Он накрыт VMP и советую просто использовать под VM,но он работает и автор это проверял.
P.S спасибо
4 Заключение
"Побеждённые сегодня будут победителями завтра, ибо поражение является для них наукой." Карл Либкнехт.
Проблема при публикации любой статьи по протекторам – постепенный фикс от разработчиков (рано или поздно, но вы придёте к такому выводу), поэтому многие не охотно делятся информацией или передают её между собой/в определённой группе.
Самый простой пример фиксов - догонялки с уничтожение build_windows в
Надеюсь, статья и PoC поможет некоторым людям и ждём от Ивана VMP 4.0!
Сам
I Liked It...
P.S если в чём-то ошибся при анализе, то поправьте меня и спасибо за чтение статьи!
В версии 3.8.7 команда VMProtect догадалась исправить недочёты показанные в статье(я не анализировал всё)
Основная проблема обхода функций протекторов – ручной обход т,е обход без автоматизации. Это замедляед анализ и приходится повторять многие моменты. Автор решил из-за этого написать небольшую статью,чтобы напомнить, что некоторые моменты можно легко обойти + автора забавляет, что для обхода anti-debug для Rin-3 используют Ring-0 т.е пытаются задавить таракана зачем-то танком. Сегодня мы заглянем под капот VMP и напишем обход для SDK функций и функции лоадера VMP(anti-debug,anti-vm, list import,spoof syscall_id and e.t.c).
Анализу будет больше про x64 кода(больше 3.7.x-3.8.x, но большинство моментов аналогичны для более ранних версии(например, обход SDK CRC)) т.к аналогичные действия нужно будет сделать для x32 + не вижу смысла делать раздутый код. Будет показана только логика,а остальное можете посмотреть на Github т.к уже был один раз, когда впихнул много и получилась неудачная статья(из-за ограничений на количество символов).
Важный момент!Это больше является PoC,нежели готовым к вставки для обхода на мой взгляд, но я не буду спорить с читателем.
Чем раздражителен VMP?
Во-первых, автор зачастую хитрит и использует WinApi/CRT оболочки, но об этом поговорим во 2 главе и из-за этого используется минимум NtApi/WinApi,а попытка построить anti-debug на manual syscall раздражает,но иногда вдохновляет + использование BugCheck функций для обнаружение хуков от anti-anti-debug tool может заставить вас потратить время.
Во-вторых, своей виртуализацией.
Не словом, а делом мы докажем эффективность отсутствия нужды анализа этого бреда. Это заставит докопаться до всего того, где отсутствует виртуализация + покажет разные способы к решению проблемы.
Автор уже затрагивал эту проблему в статье об обходе CRC протекторов.
VMP начиная ~ с версии 3.5.1 начинает использовать single step для обнаружения HWBP текущего потока:
![VMP SDK anti-debug single step.png VMP SDK anti-debug single step.png](https://yougame.biz/data/attachments/269/269387-b0a95eb13b79a0ab5a8193c4ccc16822.jpg)
Основная проблема в расшифровке ret_address т.к адрес к коду программы, после выполнения SDK функции:
![VMP SDK anti-debug decrypt exit SDK.png VMP SDK anti-debug decrypt exit SDK.png](https://yougame.biz/data/attachments/269/269388-e646b426a09e0f58f7ae1fe6a3b6feec.jpg)
Благодаря этому, можно написать простой хук:
- Устанавливаем Veh хук
- Проверка ExceptionCode == STATUS_SINGLE_STEP
- Проверяем,что перед nop инструкция cpuid,rdtsc(на самом деле нужно проверить на адрес из какой секции вызвано,но это на усмотрения читателя)
- Сканируем стек, на ret_address к коду юзера
- Устанавливаем bp(0xCC) хук(скопируйте инструкцию) или можете заменить на свой хук(рекомендуется вызывать исключение т.е скопировать,а потом переписать ret_address на не валидный => хук обработки на уничтожение указателей[1] => не тригерим CRC
- Обработчик для исключения(у меня:ExceptionCode == STATUS_BREAKPOINT)
- Возвращаем в al = 0, прыгаем к следующему коду и победа?(сюрпри-сюприз,но это будет обходить anti-vm ещё т.к anti-debug использует только rdtsc, но будет уточнение чуть позже)
Тут есть огрех из-за того, что не хочу пихать Unicore Engine т.к Dll сильно раздувается, но рекомендую её впихнуть и написать проверку на ret_address т.к в x32 из-за особенности обработки исключения VMP excepthion смещение(ret_address) будет на > 0x4xx т.е будет найден не тот адрес и эмуляция решает проблему, но в пункте 2.2 показывается другой способ обхода этого чуда.
P.S В качестве примера используется Veh хук,но можно уйти на более ранний и менее заметный перехват в обработки исключения (напримре,Wow64PrepareForException).
1.2 CRC SDK
в более ранних версиях VirtualQuery(добавьте хук,если понадобится).
Тут появляется неприятный момент т.к если функция будет просто вызываться из виртуализированного кода и это будет не из SDK VMP вызов,то мы вернём слегка иной статус,хотя для NtApi это не будет влиять, но напишем проверку для практики.
- Проверка откуда вызвалось(из нашего модуля,но лучше написать для секции VMP)
- ProcessHandle == NtCurrentProcess && MemoryInformationClass == MemoryBasicInformation
- Вот тут главный момент т.к у нас нет прямого доступа к rsp,но как бы и есть,но мы хотим просканировать строчку NtQueryVirtualMemory.
- Берём &MemoryInformationLength и отнимаем смещение 0x30 т.к 5 аргумент в x64 rsp + 0x28 и смещение текущего указателя на rsp.
- Если строка существует, то сканируем стек и ползучем ret_address и возвращаем al = 1.
Забавный момент: можно это сроку поменять на другой NtApi и VMP будет вызывать другой NtApi.
Изменение строки:
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2FvrNKsN0.png&hash=660bb4e92f3a1830462b21f36ba9c74c)
Вызов NtApi,который VMP не использует:
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2F0g6tIme.png&hash=ce4a5c6709c4544c7965e18c93d47c9f)
Ну и вишенка на торте!
Муха в котлете, заноза в заднице, ложка дёгтя в бочке дёгтя:
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2FbdsZ7So.png&hash=3527f4066f66257ca4570ae482ebc73a)
Протектор проверяет целостность кода до того,как это сделает SDK, даже если не включена проверка целостности в лоадере т.е неприятный подарок.
Но есть и приятный момент т.к это значение имеет 2 значения только т.е hash_detect_patch и hash_no_detect_patch т.е можете сделать наглый хук,если потребуется.
Довольно странное решение у автора,но пусть будет так. На самом деле эти значения есть в том же anti-debug, но у меня большие сомнения , что у вас их получится затригирить, если вы не творите полный бред.
Единственный вариант, который придумал – ручное нахождение из-за обращении(PG hook) RW памяти(обычно > PAGE_SIZE * 4) и это именно hash,который будет разный. Короче, лучше патчить после подсчёта CRC в лоадере, если вам лень делать лишние и бесполезные действия(или просто использовать HWBP).
1.3 Anti-vm SDK
- проблема требует сканировать vm_cpuid и хукать их,но в том же VMWare решается проблема настройкой конфига,как и VirtualBox(речь про вызов cupid с eax = 1 и проверка 31 бита в ecx).
- проблему требует подделки исправление для TF exception_info->ExceptionRecord->ExceptionAddress на адрес с опкодом nop для тогоже VirtualBox, но я им не пользуюсь + эта ошибка гипервизора и довольно странно, что её не исправляют.
Чтобы не было скучно,автор упомянет сканирование vm-строк,которые вызываются NtQuerySystemInformation с SystemFirmwareTableInformation (RSMB). План надёжный, как швейцарские часы т.е можно заставить возвращать, что их попросту нет:
- Сканирование инструкции mov rbx,qword ptr ss:[rsp+8](48 8B 5C 24)
- Дизассемблируем и проверяем,что это наша функция
- Находим mov al,1 и переписываем на mov al,0
- Победа?
Глава 2 Ломаем лоадер
Вот тут по большей части у всех начинаются проблемы т.к довольно неприятно ковырять это творение.
Погнали постепенно разбираться что к чему!
2.1 Импорт
Недавно colby57 обновил и выпустил тулзу для фикса импорта на основе эмуляции. Правда,тут есть неприятная проблема, которую указал автор.
Импорт будет расшифрован импорт только в .text секции,а это значит что вирта будет вертеть данную тулзу на все 360 градусов,если обфусцированный вызов под ней. Мы пойдём слегка по иному пути,но сначала слова.
Тут автор затронет халтуру разработчика т.к давайте изучим обфускацию импортов у VMP:
3.0.9:
import_obf_3.0.9_and_3_5:
00 | 00007FF63F25109E | E8 DD5A1900 | call bin_64.0.9.vmp.7FF63F3E6B80
01 | 00007FF63F3E6B80 | 90 | nop
02 | 00007FF63F3E6B81 | 41:0FBFCF | movsx ecx,r15w
03 | 00007FF63F3E6B85 | 41:8AC8 | mov cl,r8b
04 | 00007FF63F3E6B88 | 41:0F47C8 | cmova ecx,r8d
05 | 00007FF63F3E6B8C | 59 | pop rcx
06 | 00007FF63F3E6B8D | E9 CF82F3FF | jmp bin_64.0.9.vmp.7FF63F31EE61
07 | 00007FF63F31EE61 | 48:870C24 | xchg qword ptr ss:[rsp],rcx
08 | 00007FF63F31EE65 | E9 D99D0D00 | jmp bin_64.0.9.vmp.7FF63F3F8C43
09 | 00007FF63F3F8C43 | 51 | push rcx
0A | 00007FF63F3F8C44 | 0F9FC1 | setg cl
0B | 00007FF63F3F8C47 | 48:8D0D 7D8FE5FF | lea rcx,qword ptr ds:[7FF63F251BCB]
0C | 00007FF63F3F8C4E | E9 3DC8EDFF | jmp bin_64.0.9.vmp.7FF63F2D5490
0D | 00007FF63F2D5490 | 48:8B89 96FA1B00 | mov rcx,qword ptr ds:[rcx+1BFA96]
0E | 00007FF63F2D5497 | E9 B06D0300 | jmp bin_64.0.9.vmp.7FF63F30C24C
0F | 00007FF63F30C24C | 48:8D89 D821AE71 | lea rcx,qword ptr ds:[rcx+71AE21D8]
10 | 00007FF63F30C253 | 48:870C24 | xchg qword ptr ss:[rsp],rcx
11 | 00007FF63F30C257 | E9 24D7FFFF | jmp bin_64.0.9.vmp.7FF63F309980
12 | 00007FF63F309980 | C3 | ret
3.5
01 | 00007FF7A57710AC | E8 5FFFFFFF | call bin_64_3.5.0.vmp.7FF7A5771010
02 | 00007FF7A5771010 | 48:894C24 08 | mov qword ptr ss:[rsp+8],rcx
03 | 00007FF7A5771015 | 48:895424 10 | mov qword ptr ss:[rsp+10],rdx
04 | 00007FF7A577101A | 4C:894424 18 | mov qword ptr ss:[rsp+18],r8
05 | 00007FF7A577101F | 4C:894C24 20 | mov qword ptr ss:[rsp+20],r9
06 | 00007FF7A5771024 | 53 | push rbx
07 | 00007FF7A5771025 | 57 | push rdi
08 | 00007FF7A5771026 | 48:83EC 38 | sub rsp,38
09 | 00007FF7A577102A | B9 01000000 | mov ecx,1
0A | 00007FF7A577102F | 48:8D7C24 58 | lea rdi,qword ptr ss:[rsp+58]
0B | 00007FF7A5771034 | E8 72350500 | call bin_64_3.5.0.vmp.7FF7A57C45AB
0C | 00007FF7A57C45AB | 90 | nop
0D | 00007FF7A57C45AC | 50 | push rax
0E | 00007FF7A57C45AD | 41:0FB7C0 | movzx eax,r8w
0F | 00007FF7A57C45B1 | 40:8AC6 | mov al,sil
10 | 00007FF7A57C45B4 | 98 | cwde
11 | 00007FF7A57C45B5 | 48:8B4424 08 | mov rax,qword ptr ss:[rsp+8]
12 | 00007FF7A57C45BA | 48:8D40 01 | lea rax,qword ptr ds:[rax+1]
13 | 00007FF7A57C45BE | E9 C57C0500 | jmp bin_64_3.5.0.vmp.7FF7A581C288
14 | 00007FF7A581C288 | 48:894424 08 | mov qword ptr ss:[rsp+8],rax
15 | 00007FF7A581C28D | 48:8D05 EB4FF5FF | lea rax,qword ptr ds:[7FF7A577127F
16 | 00007FF7A581C294 | E9 200BFEFF | jmp bin_64_3.5.0.vmp.7FF7A57FCDB9
17 | 00007FF7A57FCDB9 | 48:8B80 AB060400 | mov rax,qword ptr ds:[rax+406AB]
18 | 00007FF7A57FCDC0 | E9 C2CA0300 | jmp bin_64_3.5.0.vmp.7FF7A5839887
19 | 00007FF7A5839887 | 48:8D80 690A0074 | lea rax,qword ptr ds:[rax+74000A69
1A | 00007FF7A583988E | E9 04B10600 | jmp bin_64_3.5.0.vmp.7FF7A58A4997
1B | 00007FF7A58A4997 | 48:870424 | xchg qword ptr ss:[rsp],rax
1C | 00007FF7A58A499B | E9 00E6FAFF | jmp bin_64_3.5.0.vmp.7FF7A5852FA0
1D | 00007FF7A5852FA0 | C3 | ret
import_obf_3.8.1:
00 | 00007FF7B7811079 | E8 20470000 | call bin_64_3_8_1.vmp.7FF7B781579E
01 | 00007FF7B781579E | 50 | push rax
02 | 00007FF7B781579F | 41:56 | push r14
03 | 00007FF7B78157A1 | E8 72C10000 | call bin_64_3_8_1.vmp.7FF7B7821918
04 | 00007FF7B7821918 | 9C | pushfq
05 | 00007FF7B7821919 | 49:BE B28D379E99702E23 | mov r14,232E70999E378DB2
06 | 00007FF7B7821923 | 57 | push rdi
07 | 00007FF7B7821924 | 4D:8DB426 A3E322EA | lea r14,qword ptr ds:[r14-15DD1C5D]
08 | 00007FF7B782192C | 48:BF 27442ED038039DBA | mov rdi,BA9D0338D02E4427
09 | 00007FF7B7821936 | 48:81FF 9B278BFA | cmp rdi,FFFFFFFFFA8B279B
0A | 00007FF7B782193D | 48:8B4424 28 | mov rax,qword ptr ss:[rsp+28]
0B | 00007FF7B7821942 | E8 E9F51A00 | call bin_64_3_8_1.vmp.7FF7B79D0F30
0C | 00007FF7B79D0F30 | 41:50 | push r8
0D | 00007FF7B79D0F32 | 4D:8BC6 | mov r8,r14
0E | 00007FF7B79D0F35 | E8 E37E0000 | call bin_64_3_8_1.vmp.7FF7B79D8E1D
0F | 00007FF7B79D8E1D | 55 | push rbp
10 | 00007FF7B79D8E1E | 49:8BEE | mov rbp,r14
11 | 00007FF7B79D8E21 | 48:8D40 01 | lea rax,qword ptr ds:[rax+1]
12 | 00007FF7B79D8E25 | 41:0F9EC0 | setle r8b
13 | 00007FF7B79D8E29 | 6645:2BC6 | sub r8w,r14w
14 | 00007FF7B79D8E2D | 41:50 | push r8
15 | 00007FF7B79D8E2F | E8 3174E4FF | call bin_64_3_8_1.vmp.7FF7B7820265
16 | 00007FF7B7820265 | 66:C1E5 B8 | shl bp,B8
17 | 00007FF7B7820269 | E8 DD541A00 | call bin_64_3_8_1.vmp.7FF7B79C574B
18 | 00007FF7B79C574B | 4C:23C7 | and r8,rdi
19 | 00007FF7B79C574E | 48:894424 60 | mov qword ptr ss:[rsp+60],rax
1A | 00007FF7B79C5753 | 40:F6D5 | not bpl
1B | 00007FF7B79C5756 | C1ED FC | shr ebp,FC
1C | 00007FF7B79C5759 | 66:C1E7 A1 | shl di,A1
1D | 00007FF7B79C575D | 48:81A4EC D0FFFFFF 3957B | and qword ptr ss:[rsp+rbp*8-30],79B7573
1E | 00007FF7B79C5769 | 41:55 | push r13
1F | 00007FF7B79C576B | 41:50 | push r8
20 | 00007FF7B79C576D | 48:814424 10 F4DFFFFF | add qword ptr ss:[rsp+10],FFFFFFFFFFFFD
21 | 00007FF7B79C5776 | 56 | push rsi
22 | 00007FF7B79C5777 | E8 08D00200 | call bin_64_3_8_1.vmp.7FF7B79F2784
23 | 00007FF7B79F2784 | 44:0B4424 11 | or r8d,dword ptr ss:[rsp+11]
24 | 00007FF7B79F2789 | 0F8D 1F56E2FF | jge bin_64_3_8_1.vmp.7FF7B7817DAE
25 | 00007FF7B79F278F | 48:BE 0351299631AC12E9 | mov rsi,E912AC3196295103
26 | 00007FF7B79F2799 | 48:8B84AC 00000000 | mov rax,qword ptr ss:[rsp+rbp*4]
27 | 00007FF7B79F27A1 | 48:8B8468 F0FFFFFF | mov rax,qword ptr ds:[rax+rbp*2-10]
28 | 00007FF7B79F27A9 | 0F82 9C100600 | jb bin_64_3_8_1.vmp.7FF7B7A5384B
29 | 00007FF7B79F27AF | 49:BD 9B961DD20F50BBD6 | mov r13,D6BB500FD21D969B
2A | 00007FF7B79F27B9 | 6644:214424 10 | and word ptr ss:[rsp+10],r8w
2B | 00007FF7B79F27BF | 856C24 30 | test dword ptr ss:[rsp+30],ebp
2C | 00007FF7B79F27C3 | 4A:8D8400 D6E8229E | lea rax,qword ptr ds:[rax+r8-61DD172A]
2D | 00007FF7B79F27CB | 48:0FCF | bswap rdi
2E | 00007FF7B79F27CE | 6644:8BC5 | mov r8w,bp
2F | 00007FF7B79F27D2 | E8 D999E3FF | call bin_64_3_8_1.vmp.7FF7B782C1B0
30 | 00007FF7B782C1B0 | 0F86 757D1900 | jbe bin_64_3_8_1.vmp.7FF7B79C3F2B
31 | 00007FF7B782C1B6 | F65424 38 | not byte ptr ss:[rsp+38]
32 | 00007FF7B782C1BA | 48:878424 80000000 | xchg qword ptr ss:[rsp+80],rax
33 | 00007FF7B782C1C2 | 48:8B7C24 60 | mov rdi,qword ptr ss:[rsp+60]
34 | 00007FF7B782C1C7 | 49:81C6 85A8A925 | add r14,25A9A885
35 | 00007FF7B782C1CE | F7D6 | not esi
36 | 00007FF7B782C1D0 | 66:F7DE | neg si
37 | 00007FF7B782C1D3 | 6641:C1EE 42 | shr r14w,42
38 | 00007FF7B782C1D8 | 4C:8BACAC 00000000 | mov r13,qword ptr ss:[rsp+rbp*4]
39 | 00007FF7B782C1E0 | 4C:037424 38 | add r14,qword ptr ss:[rsp+38]
3A | 00007FF7B782C1E5 | C74424 38 A2A6BBED | mov dword ptr ss:[rsp+38],EDBBA6A2
3B | 00007FF7B782C1ED | 41:C0E6 C7 | shl r14b,C7
3C | 00007FF7B782C1F1 | 814C24 38 B9A2BB03 | or dword ptr ss:[rsp+38],3BBA2B9
3D | 00007FF7B782C1F9 | 66:33F6 | xor si,si
3E | 00007FF7B782C1FC | C1AC34 38002A96 E3 | shr dword ptr ss:[rsp+rsi-69D5FFC8],E3
3F | 00007FF7B782C204 | 4B:8DACF6 0AF4036F | lea rbp,qword ptr ds:[r14+r14*8+6F03F40
40 | 00007FF7B782C20C | 0F87 08731A00 | ja bin_64_3_8_1.vmp.7FF7B79D351A
41 | 00007FF7B79D351A | 48:8B7424 10 | mov rsi,qword ptr ss:[rsp+10]
42 | 00007FF7B79D351F | 49:F7D8 | neg r8
43 | 00007FF7B79D3522 | 66:0FBE6C24 1D | movsx bp,byte ptr ss:[rsp+1D]
44 | 00007FF7B79D3528 | 45:84C6 | test r14b,r8b
45 | 00007FF7B79D352B | 0FCD | bswap ebp
46 | 00007FF7B79D352D | 44:0F497424 3B | cmovns r14d,dword ptr ss:[rsp+3B]
47 | 00007FF7B79D3533 | 0F9E846C 6E64FEFF | setle byte ptr ss:[rsp+rbp*2-19B92]
48 | 00007FF7B79D353B | 49:85EE | test r14,rbp
49 | 00007FF7B79D353E | E8 28A20500 | call bin_64_3_8_1.vmp.7FF7B7A2D76B
4A | 00007FF7B7A2D76B | 48:8B6C24 48 | mov rbp,qword ptr ss:[rsp+48]
4B | 00007FF7B7A2D770 | 0F85 A901F7FF | jne bin_64_3_8_1.vmp.7FF7B799D91F
4C | 00007FF7B799D91F | 0F86 83E90000 | jbe bin_64_3_8_1.vmp.7FF7B79AC2A8
4D | 00007FF7B799D925 | 6644:854424 43 | test word ptr ss:[rsp+43],r8w
4E | 00007FF7B799D92B | E8 FD000000 | call bin_64_3_8_1.vmp.7FF7B799DA2D
4F | 00007FF7B799DA2D | 41:0F91C0 | setno r8b
50 | 00007FF7B799DA31 | 4C:8BB424 88000000 | mov r14,qword ptr ss:[rsp+88]
51 | 00007FF7B799DA39 | 814C24 28 84C59D60 | or dword ptr ss:[rsp+28],609DC584
52 | 00007FF7B799DA41 | 0F934424 28 | setae byte ptr ss:[rsp+28]
53 | 00007FF7B799DA46 | 44:334424 4A | xor r8d,dword ptr ss:[rsp+4A]
56 | 00007FF7B799DA63 | 4E:8B8404 6A1D8A98 | mov r8,qword ptr ss:[rsp+r8-6775E296]
57 | 00007FF7B799DA6B | F75424 28 | not dword ptr ss:[rsp+28]
58 | 00007FF7B799DA6F | E8 21570000 | call bin_64_3_8_1.vmp.7FF7B79A3195
59 | 00007FF7B79A3195 | 48:8D6424 48 | lea rsp,qword ptr ss:[rsp+48]
5A | 00007FF7B79A319A | C2 1800 | ret 18
5B | 00007FF7B79914B4 | FF7424 18 | push qword ptr ss:[rsp+18]
5C | 00007FF7B79914B8 | 9D | popfq
5D | 00007FF7B79914B9 | 48:8D6424 30 | lea rsp,qword ptr ss:[rsp+30]
5E | 00007FF7B79914BE | C3 | ret
- Вызов всегда через call
- Всегда присутствует xchg
Как же они её находят, если я накинул обфускации слегка и это КПД стремится к NULL?
Вопрос, конечно, риторический.
Итак,что же делать? Автор идёт старинным способом т.е пишем хук для получения списка импорта + пару идей для хука даже для текущей версии.
Поскольку это тригерит CRC от лоадера,то просто перепишите на HWBP хук,чтобы не париться,но меньше слова и больше дел!
~Включая до версии 3.8.1 можно было вообще кайфовать т.к можно было перехватывать адрес импорта и вернуть адрес на своей функции => $$$бесплатно$$$ хукать импорт из-за просто мутации оболочки получения адреса(я упоминал об этом в 1 чате).
Её можно легко найти, если поставить на PE ntdll PG хук обращение:
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2F4n7oFuh.png&hash=6ad10c7957b83cdd22539418e8750fbd)
Однако, начиная с версии 3.8.2 из-за слива автор решил кинуть эту функцию под вирту,чем усложнил задачу,но всё-равно это не мешает.
Если проанализировать, то используется VMP_STR_CMP т.е сравнивается 2 строки и возвращается статус сравнения. Она только под мутацией, поэтому халява
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2FT7xKTnD.png&hash=a322420373423704057ce313a30a76d2)
План действий:
- Поиск по паттерну mov qword ptr ss:[rsp+8],rbx(48 89 5C 24 08)
- Дизассемблируем и проверяем,что это нужна функция
- Устанавливаем хук
Это не идеальное решение, но у нас есть список импорта.
Если говорить про правильное решение и эмуляцию,то:
Поиск lea reg64,qword ptr ds:[imm](48 8D) для версий ниже ~3.6 и call rip+offset(E8)
Собрать этот список в .vmp0 секции и можно наплевать на .text секцию т.к автор продолжает получать rip через call в обфусцированном вызове(не при переходе из .text в vmp0,а во время выхода обфусцированного чуда),поэтому он облегчает задачу.
Эмуляция и проверка, что указатель указывает на импорт(можно эмулировать до инструкции xchg) и проверка на ret,что мы на импорте. Но мне лень делать + я уже и без этого тащу 3 либы, поэтому это всё на усмотрение читателя,хотя логику сказал.
Так же можно добавить экспорт в свою библиотеку с тем же названием или хукать strcmp, хукнуть GetModuleHandleA и вернуть свою библиотеку,если там нас интересующий импорт. Это так, для шутки…
2.2 Драка за уничтожение syscall_id
Фирма «VMProtect» очень не хотела, чтобы реверсеры использовали для обхода anti-debug
Пожалуйста, авторизуйтесь для просмотра ссылки.
и плагины по типу
Пожалуйста, авторизуйтесь для просмотра ссылки.
,но открытие,что у VMP идёт смешная борьба за manual syscall, повлекло бы за собой совершенно забавное представление о ней.
Стоп. Тут нужно остановиться. Как большинство читателей знаю, VMP начал использовать ~ с версии
Пожалуйста, авторизуйтесь для просмотра ссылки.
. За этим забавно можно проследить в обновлениях
Пожалуйста, авторизуйтесь для просмотра ссылки.
.В качестве примера, приведу дополнения код SharpOD,который патчит ProductVersion && FileVersion и берёт при загрузке ntdll(хук orig_NtMapViewOfSection) подделанные версии ProductVersion && FileVersion.
Простыми словами, все используют подделку OSBuildNumber в PEB, подделку в ресурсах ntdll ProductVersion, FileVersion. И чтобы жизнь малиной не казалось, VMP начал получать NtBuildNumber из PKUSER_SHARED_DATA
![](/proxy.php?image=https%3A%2F%2Fi.imgur.com%2FgHXMl2g.png&hash=f0e98ad2c022a1ec36331e261b37413b)
Это можно обойти и здесь будет показано,но с костылём, поскольку автор лентяй. Да и почему я должен быть честным в обходе, когда все хитрят?
Во-первых, потребуется подменить выше перечисленное( OSBuildNumber в PEB и ProductVersion/FileVersion),но и ещё подделать OSMajorVersion и до полного счастья OSMinorVersion для обхода NtBuildNumber.
Теперь происходит map ntdll.dll с помощью NtOpenSecthion и NtMapViewOfSection,а если это не получится то старый набор из NtOpenFile, NtCreateSection, NtMapViewOfSection. Если зафейлить и это,то автор кидает MessageBox через NtRaiseException и это вообще офигенно, поскольку так делать не надо, но разработчику виднее.
Вот на этом моменте автор застрял т.к пытался уничтожить, что только попалось и вот результат,что можо сделать при плохом настроении, будучи выпившим т.к обращение было только к PE мапнутой ntdll и секции .rdata.
Поэтому переходим к плану B, хотя это первоначальный способ обхода anti-debug в лоадере,но тут гарантировано будут уничтожены syscall_id. В чём шутка? Можно просто пройтись по стеку и уничтожить syscall_id,после unmap ntdll и как бы так делать нельзя. У всё работало, хотя это вообще варварский метод(но кто будет судить за такой метод честной игры?).
Просто повторите в пункте 1.2,но теперь просто уничтожайте syscall_id,но проблема в том,что в x64 они находится в верхушке стека т.к нужно вверх и вниз стека пройтись для полного счасть,как бы ни казалось странным.
Теперь можно пить медовуху у курить бамбук!
2.3 Ani-debug loader или Ivan...You are drunk
Что помогает реверсерам уже имевших с анализом VMP,обходить anti-debug и что VMP делает первым действием, как я упомянул в одной своей статье?
Верно!VMP проверяет всегда NtCurrentPeb()->BeingDebugged и тут просто устанавливаем HWBP hook на обращение.
Возвращаясь к прошлому пункту(2.2),первоначально использовался этот метод для уничтожения syscall_id NtQueryInformationProcess и NtSetInformationThread.
План действий аналогично такой же, но пишем 1 хук на сохранение HWBP и 3 хука(можно и для
Пожалуйста, авторизуйтесь для просмотра ссылки.
- NtContinue,если вызов из LdrInitializeThunk т.к если будет загружена DLL до RIP = EP,то будет вызвана данная функция => Dr регистры будут NULL
- NtQueryInformationProcess для ProcessDebugPort, ProcessDebugObjectHandle
- NtSetInformationThread для ThreadHideFromDebugger
- NtClose для правильной обработки STATUS_INVALID_HANDLE
Состояние anti-anti-debug tool на данный момент ~
Пожалуйста, авторизуйтесь для просмотра ссылки.
,если кому-то интересно.Относительно легко и статья подходит постепенно к концу.
Тут должен быть обход CRC calc от лоадера.
Есть 2 типа CRC calc:критические о обычные т.е
1 вариант - делаем не валидный указатель и программа вызывает ошибку
2 вариант - кидаем MessageBox,если crc плохое
Я написал их поиск и подделку на чтение копии, но есть проблемы:
- Иногда происходит чтение байтов калькулятора не из CRC calc(vm-read_addr) => мы не может просто установить bp и отдавать выделенную память.
- Из-за пункта 1 можно только вручную добавить их для обхода + часть CRC calc не используется
3 Подарок от китайских друзей.
04.01.2024 на
Пожалуйста, авторизуйтесь для просмотра ссылки.
был опубликован специальный
Пожалуйста, авторизуйтесь для просмотра ссылки.
для отладочных исследований VMProtect v3.8.1.Я попросил разработчика anti-anti-debug плагина
Пожалуйста, авторизуйтесь для просмотра ссылки.
отправить его, поэтому он идёт в качестве бонуса.Он накрыт VMP и советую просто использовать под VM,но он работает и автор это проверял.
P.S спасибо
Пожалуйста, авторизуйтесь для просмотра ссылки.
за это.4 Заключение
"Побеждённые сегодня будут победителями завтра, ибо поражение является для них наукой." Карл Либкнехт.
Проблема при публикации любой статьи по протекторам – постепенный фикс от разработчиков (рано или поздно, но вы придёте к такому выводу), поэтому многие не охотно делятся информацией или передают её между собой/в определённой группе.
Самый простой пример фиксов - догонялки с уничтожение build_windows в
Пожалуйста, авторизуйтесь для просмотра ссылки.
.Надеюсь, статья и PoC поможет некоторым людям и ждём от Ивана VMP 4.0!
Пожалуйста, авторизуйтесь для просмотра ссылки.
:
Пожалуйста, авторизуйтесь для просмотра ссылки.
Сам
Пожалуйста, авторизуйтесь для просмотра ссылки.
Пожалуйста, авторизуйтесь для просмотра ссылки.
I Liked It...
P.S если в чём-то ошибся при анализе, то поправьте меня и спасибо за чтение статьи!
В версии 3.8.7 команда VMProtect догадалась исправить недочёты показанные в статье(я не анализировал всё)
Последнее редактирование: