В сегодняшней статье поговорим о навороченном протекторе Themida. Рассмотрим простой способ обхода Themida. Я покажу, как сбросить триал программы защищенной Themida.
Еще по теме: Обход защиты StarForce
Статья в образовательных целях и не призывает к каким-либо незаконным действиям. Ни редакция spy-soft.net, ни автор не несут ответственность за ваши действия.
Обход защиты Themida
В статье не будем рассматривать «традиционные» способы обхода триала Themida, вместо этого рассмотрим самый простой и понятный метод взлома и патча защищенной программы.
Итак, есть приложение, которое использует третью версию Themida. Как обычно, нам понадобиться минимальный инструментарий (x64dbg и его плагины).
Следует отметить, что для обнаружения Themida не стоит полагаться на DetectItEasy. С данной защитой лучше работают ExeInfo и Nauz File Detector.
На присутствие защиты Themida в файле как бы намекает наличие между секциями .idata и .pdata двух секций с непроизносимыми названиями из 8 случайных символов. Но, в третьей версии разрабы уже не стесняясь прямо называют секции .themida и .boot. Код точно зашифрован, упакован и не поддается статическому анализу и реверсу.
Для начала попробуем загрузить защищенную протектором программу в популярный отладчик x64dbg. Конечно, все плохо: при старте отладчик проваливается в виртуальную машину, такую темную на вид, что пропадает желание с ней разбираться. Виртуальная программа сразу же палит отладчик и кроме этого она отслеживает тайминг похождения отдельных участков своего кода.
Сразу приаттачиться к уже активному процессу тоже не получается, разрабы Фемиды предусмотрели и это. Поступим чуть умнее — возможно, по прошлым статьям вы помните прекрасный анти‑антиотладочный плагин ScyllaHide для x64dbg, в котором специально для таких ленивых как я уже есть готовые профили под все популярные защиты. Разумеется, подобный профиль там есть и для Фемиды, правда, он нам не очень и поможет: во время загрузки приложения он не спасает от антиотладчика, однако приаттачиться к запущенному приложению уже позволяет.
Толку от этого мало: после этого бряка трассировка валит приложение наповал. Но это уже какой-то прогресс — далее по нашей стандартной схеме, опробованной ранее на Obsidium, Enigma и прочих протекторах, мы пробуем сдампить прерванный процесс при помощи другого специально предназначенного для этого плагина — Scylla.
Приложение успешно дампится. Будем искать точку входа и во многих случаях этого вполне хватает, но, наш случай сложный. После загрузки сдампленного файла в IDA мы обнаруживаем, что наша программа хорошенько обфусцирована: на большинстве вызовов импортируемых функций (в частности, на библиотечных вызовах QT, на котором написана анализируемая нами программа), стоят заглушки, ведущие на изрядной длины цепочки безумного код подобного вида:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 спалит наш отладчик при загрузке даже так).
Однако к запущеной столь хитрым образом программе можно приаттачиться, после чего спокойно ставить бряки и отлаживать код, не боясь антиотладчика, что нам, собственно и требовалось. Теперь, не испытывая ни малейших препятствий, обнаруживаем в необфусцированной части кода развилку обхода проверки лицензии:
1 2 3 4 5 6 7 8 9 |
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.
1 2 3 4 5 |
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, ее код мы и будем использовать для автопатча основной программы:
1 2 3 4 5 |
mov rax,qword ptr ds:[rcx+28] mov eax,dword ptr ds:[rax+8] shr eax,F and al,1 ret |
Следующая сложность заключается в том, что мы не можем так просто взять и поправить код исполняемого процесса из него же. Значит, надо искать переменные в секции данных, правка которых дала бы тот же эффект. Итак, нам надо добиться, чтобы вызов метода
1 |
00007FF630D5C11A call qword ptr [rdx+8] |
возвращал в AL ненулевое значение.
Еще немного покопавшись в отладчике, превращаем данный метод вот в такую последовательность действий:
1 2 3 |
mov rcx,qword ptr ds:[7FF6F47F6EF8] mov rcx,qword ptr ds:[rcx+10] movzx eax,byte ptr ds:[rcx+A1E] |
Это означает, что для того, чтобы программа почувствовала себя лицензионной, нужно установить по адресу:
1 |
[[[7FF6F47F6EF8]+10]+A1E] |
любое ненулевое значение байта (например, 1).
Алгоритм наших действий таков:
Выполняем исходный код метода bool __cdecl QWidget::isVisible(void), результат в регистре AL, регистр RCX программе уже не интересен, его можно использовать в своих целях, что мы и сделаем.
Проверим, из нужного ли места был вызван метод isVisible, поскольку секция кода садится каждый раз на разные адреса, самое простое и более‑менее надежное — проверять последние несколько байт адреса, например 16 бит, искомый адрес вызова должен быть:
1 |
????????????BDB7 |
Адрес вызова мы также используем для относительной адресации переменной:
1 |
[7FF6F47F6EF8] |
Несложно посчитать, что смещение между ее адресом и адресом вызова равно 0x5AAB44C.
На всякий случай проверяем значение этой переменной (на момент вызова нашего isVisible она запросто может быть еще не инициализирована) и устанавливаем значение [[[7FF6F47F6EF8]+10]+A1E] в 1.
Теперь, когда алгоритм понятен, ищем в коде библиотеки qt5widgets.dll ближайший пустой кусок достаточной длины и устанавливаем обработчик isVisible на него. Поправленный и дополненный код isVisible выглядит так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
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, но и любой другой серьезной защиты, шифрующей код и проверяющей его целостность.
Еще по теме: Отладка программ с помощью WinDbg