Хакер - Фемида дремлет. Как работает обход защиты Themida
hacker_frei
МВК
Themida или «Фемида» — одна из самых навороченных защит для софта. Вместе с WMProtect ее относят к настолько суровым протекторам, что некоторые программисты, которым жаль денег на честную покупку этих инструментов (а стоят они недешево), просто имитируют их наличие, что отпугивает ленивых и неопытных хакеров. Сегодня мы поговорим о том, как сбросить триал приложения, защищенного настоящей Themida.
WARNING
Статья написана в исследовательских целях, имеет ознакомительный характер и предназначена для специалистов по безопасности. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Использование или распространение ПО без лицензии производителя может преследоваться по закону.
Несмотря на широкую популярность Themida, высокую стоимость взлома и, соответственно, большое количество мануалов и видеоуроков в сети, они почти всегда бесполезны. Они в основном описывают снятие старых версий защиты 1 и 2. Уже третья версия устроена настолько сложнее, что в паблике нет универсальных инструментов и скриптов для ее взлома или обхода. И даже на старых версиях всегда находятся какие‑то нюансы, исключающие возможность универсального однокнопочного решения.
Поэтому я не буду останавливаться сейчас на «традиционных» способах обхода «Фемиды», а вместо этого опишу наиболее простой и понятный метод исследования, взлома и патча одного из защищенных графических приложений. Эта программа использует Themida третьей версии, а мы, как обычно, вооружимся лишь минимальным набором доступных инструментов (x64dbg и его плагины).
Нужно заметить, что для обнаружения Themida не следует сильно полагаться на DetectItEasy. С этим протектором гораздо лучше работают Nauz File Detector и ExeInfo.


Визуально же на присутствие «Фемиды» в файле как бы намекает наличие между секциями .idata и .pdata двух секций с непроизносимыми названиями из 8 случайных символов. Впрочем, в третьей версии разработчики уже не стесняясь прямо называют секции .themida и .boot. Код гарантированно зашифрован, упакован и статическому анализу и реверсу не поддается.
Поэтому попробуем загрузить накрытую Themida программу в наш любимый отладчик x64dbg. Разумеется, все плохо: при старте отладчик проваливается в виртуальную машину, столь страшную на вид, что отбивается всякая охота с ней разбираться. Хотя в отдельных случаях этого не избежать, скажем, при привязке конкретной программы к компьютеру или донглу. В любом случае этот путь непрост и тернист: виртуальная программа мгновенно палит отладчик, вдобавок она отслеживает тайминг похождения отдельных участков своего кода.
Сходу приаттачиться к уже активному процессу тоже нельзя, разработчики и это предусмотрели. Попробуем сделать чуть хитрее — возможно, по прошлым статьям ты помнишь замечательный анти‑антиотладочный плагин ScyllaHide для x64dbg, в котором специально для ленивых уже подготовлены готовые профили под каждую популярную защиту. Конечно же, подобный профиль там есть и для Themida, правда, не шибко сильно он нам поможет: при загрузке программы он не спасает от антиотладчика, однако приаттачиться к запущенному приложению уже позволяет.
Толку, правда, с этого немного: после такого бряка трассировка валит программу наповал. Но это уже прогресс — далее по стандартной схеме, опробованной нами ранее на Enigma, Obsidium и прочих, пробуем сдампить прерванный процесс при помощи другого специально предназначенного для этого плагина — Scylla.
Приложение дампится успешно. Как обычно, надо искать точку входа и во многих случаях этого вполне достаточно, однако, наш случай непростой. Загрузив сдампленный файл в IDA мы обнаруживаем, что наше приложение неплохо обфусцировано: на большинстве вызовов импортируемых функций (в частности, на библиотечных вызовах QT, на котором написана анализируемая нами программа), стоят заглушки, ведущие на изрядной длины цепочки безумного код подобного вида:
00007FF6F4FA521D | E9 DB2F5F00 | jmp 7FF6F55981FD
00007FF6F4FA5222 | E9 E5E31D00 | jmp 7FF6F518360C
00007FF6F4FA5227 | E9 25064500 | jmp 7FF6F53F5851
00007FF6F4FA522C | E9 375E5900 | jmp 7FF6F553B068
00007FF6F4FA5231 | 73 D5 | jae 7FF6F4FA5208
00007FF6F4FA5233 | CF | iretd
00007FF6F4FA5234 | 0026 | add byte ptr ds:[rsi],ah
00007FF6F4FA5236 | 49:89ED | mov r13,rbp
...
00007FF6F55981FD | 48:83EC 08 | sub rsp,8
00007FF6F5598201 | E9 E6DB0200 | jmp 7FF6F55C5DEC
...
00007FF6F55C5DEC | 48:83EC 08 | sub rsp,8
00007FF6F55C5DF0 | 48:83EC 08 | sub rsp,8
00007FF6F55C5DF4 | 48:81EC 08000000 | sub rsp,8
00007FF6F55C5DFB | 48:891C24 | mov qword ptr ss:[rsp],rbx
00007FF6F55C5DFF | 8F0424 | pop qword ptr ss:[rsp]
00007FF6F55C5E02 | 8F0424 | pop qword ptr ss:[rsp]
00007FF6F55C5E05 | E9 83000100 | jmp 7FF6F55D5E8D
...
По‑хорошему было бы неплохо отфильтровать все адреса подобного импорта и написать деобфускатор, но, учитывая их количество, задача выглядит муторной, а я обещал относительно простой и комфортный путь (насколько это возможно вообще в случае столь серьезной защиты). Поэтому самое время приступить к анализу логики программы в процессе ее работы, то есть, изыскать возможность для ее трассировки.
По счастью, умные люди уже подсуетились и создали для нас чудесный плагин Themidie, который позволяет беспроблемно трассировать приаттаченный процесс под Themida. Для его использования необходимы последние версии отладчика x64dbg и плагина ScyllaHide, про которые я писал выше. Загрузи с GitHub последнюю версию Themidie и извлеки Themidie.dll и Themidie.dp64 в папку плагинов x64dbg. В итоге там должны обязательно присутствовать четыре файла: Themidie.dll, Themidie.dp64, HookLibraryx64.dll и ScyllaHideX64DBGPlugin.dp64.
Загружаем x64dbg и с чувством полного удовлетворения обнаруживаем в подменю Plugins (Модули) дополнительный пункт Themidie. В опциях ScyllaHide отключаем все, кроме чекбокса Kill Anti-Attach.

Запускаем исследуемую программу из подменю Plugins-Themidie-Start. Если мы все сделали правильно, то должно появиться вот такое окно.

Как следует из текста в этом окошке, программа при запуске не загружается сразу в отладчик (более того, ее и при всем желании принудительно загрузить не получится — Themida спалит наш отладчик при загрузке даже так). Однако к запущеной столь хитрым образом программе можно приаттачиться, после чего спокойно ставить бряки и отлаживать код, не боясь антиотладчика, что нам, собственно и требовалось. Теперь, не испытывая ни малейших препятствий, обнаруживаем в необфусцированной части кода развилку обхода проверки лицензии:
00007FF630D5C10F call sub_7FF630D5D970
00007FF630D5C114 mov rdx, [rax]
00007FF630D5C117 mov rcx, rax
00007FF630D5C11A call qword ptr [rdx+8]
00007FF630D5C11D test al, al
00007FF630D5C11F jz loc_7FF630D5CEE4
00007FF630D5C125 lea rcx, [rsp+0EA8h+var_810]
00007FF630D5C12D call sub_7FF631797E20
00007FF630D5C132 xor bl, bl
Если бы на приложении не было навешено «Фемиды», можно было бы пить шампанское: делов то — поправить пару байтиков условного перехода je. Но тут начинается самое интересное. Исходный код у нас зашифрован и упакован, реверсить виртуальную машину Themida, как мы уже успели убедиться, — вещь достаточно трудоемкая. Причесать и деобфусцировать сдампленный чуть раньше код, конечно, чуть попроще, но все равно задача выглядит весьма непростой.
Самым легким вариантом кажется использование лоадера или инлайн патча. Чтобы не кодить лоадер, попробуем второй вариант. Суть в том, что если нельзя пропатчить код самого защищенного приложения, то можно попробовать сделать это из какой‑нибудь незащищенной внешней библиотеки, благо, QT-шных либ рядом с программой валяется в изобилии, и контроля целостности на них нет. Слегка поковыряв код, обнаруживаем ближайший к нашей развилке обфусцированный вызов функции bool __cdecl QWidget::isVisible(void) библиотеки qt5widgets.dll.
00007FF6EE96BDAE | 49:8BCF | mov rcx,r15
00007FF6EE96BDB1 | FF15 B1B75401 | call qword ptr ds:[7FF6EFEB7568]
00007FF6EE96BDB7 | 84C0 | test al,al
00007FF6EE96BDB9 | 0F85 EB020000 | jne 7FF6EE96C0AA
00007FF6EE96BDBF | 48:8D8C24 20090000 | lea rcx,qword ptr ss:[rsp+920]
После прохождения всей последовательности обфусцированного кода вызов упирается в коротенькую реализацию данной функции внутри qt5widgets.dll, ее код мы и будем использовать для автопатча основной программы:
mov rax,qword ptr ds:[rcx+28]
mov eax,dword ptr ds:[rax+8]
shr eax,F
and al,1
ret
Следующая сложность заключается в том, что мы не можем так просто взять и поправить код исполняемого процесса из него же. Значит, надо искать переменные в секции данных, правка которых дала бы тот же эффект. Итак, нам надо добиться, чтобы вызов метода
00007FF630D5C11A call qword ptr [rdx+8]
возвращал в AL ненулевое значение. Еще немного покопавшись в отладчике, превращаем данный метод вот в такую последовательность действий:
mov rcx,qword ptr ds:[7FF6F47F6EF8]
mov rcx,qword ptr ds:[rcx+10]
movzx eax,byte ptr ds:[rcx+A1E]
Это означает, что для того, чтобы программа почувствовала себя лицензионной, нужно установить по адресу [[[7FF6F47F6EF8]+10]+A1E] любое ненулевое значение байта (например, 1). Алгоритм наших действий таков:
- Выполняем исходный код метода
bool __cdecl QWidget::isVisible(void), результат в регистреAL, регистрRCXпрограмме уже не интересен, его можно использовать в своих целях, что мы и сделаем. - Проверим, из нужного ли места был вызван метод
isVisible, поскольку секция кода садится каждый раз на разные адреса, самое простое и более‑менее надежное — проверять последние несколько байт адреса, например 16 бит, искомый адрес вызова должен быть????????????BDB7. - Адрес вызова мы также используем для относительной адресации переменной
[7FF6F47F6EF8]. Несложно посчитать, что смещение между ее адресом и адресом вызова равно0x5AAB44C. - На всякий случай проверяем значение этой переменной (на момент вызова нашего
isVisibleона запросто может быть еще не инициализирована) и устанавливаем значение[[[7FF6F47F6EF8]+10]+A1E]в 1.
Теперь, когда алгоритм понятен, ищем в коде библиотеки qt5widgets.dll ближайший пустой кусок достаточной длины и устанавливаем обработчик isVisible на него. Поправленный и дополненный код isVisible выглядит так:
00007FFDB8EFF04F | 48:8B41 28 | mov rax,qword ptr ds:[rcx+28]
00007FFDB8EFF053 | 8B40 08 | mov eax,dword ptr ds:[rax+8]
00007FFDB8EFF056 | C1E8 0F | shr eax,F
00007FFDB8EFF059 | 24 01 | and al,1 <- В AL результат isVisible
00007FFDB8EFF05B | 48:8B0C24 | mov rcx,qword ptr ss:[rsp] <- Адрес вызова, точнее, возврата
00007FFDB8EFF05F | 81E1 FFFF0000 | and ecx,FFFF <- Берем от него младшие 16 бит...
00007FFDB8EFF066 | 48:81F9 B7BD0000 | cmp rcx,BDB7 <- И проверяем их на ????????????BDB7
00007FFDB8EFF06D | 74 01 | je qt5widgets.7FFDB8EFF070
00007FFDB8EFF06F | C3 | ret <- Если вызов не оттуда то ничего не делаем и просто возвращаем AL
00007FFDB8EFF070 | 48:8B0C24 | mov rcx,qword ptr ss:[rsp] <- Адрес вызова, точнее, возврата
00007FFDB8EFF074 | 48:81C1 41B1E805 | add rcx,5E8B141 <- В rcx адрес [7FF6F47F6EF8]
00007FFDB8EFF07B | 48:8B09 | mov rcx,qword ptr ds:[rcx]
00007FFDB8EFF07E | 48:85C9 | test rcx,rcx
00007FFDB8EFF081 | 74 EC | je qt5widgets.7FFDB8EFF06F <- Если [7FF6F47F6EF8] не инициализированна то тоженичего не делаем
00007FFDB8EFF083 | 48:8B49 10 | mov rcx,qword ptr ds:[rcx+10]
00007FFDB8EFF087 | C681 1E0A0000 01 | mov byte ptr ds:[rcx+A1E],1 <- Устанавливаем признак лицензированности и возвращаем AL
00007FFDB8EFF08E | C3 | ret
Все это кажется каким‑то извращением, но это работает: внешняя стандартная библиотека QT при обращении к ней из нужного места делает накрытую Themida программу «лицензионной», даже не модифицируя ее код.
Я, конечно, не претендую на чистоту, правильность и надежность приведенного выше кода, но, на мой взгляд, это самый простой и быстрый принцип автопатча, пригодный не только для обхода Themida, но и любой другой серьезной защиты, шифрующей код и проверяющей его целостность.
Читайте ещё больше платных статей бесплатно: https://t.me/hacker_frei