Пропатчил стаб Themida 3.1.8.0 без снятия и повреждений, показываю
Прежде всего, поясню, что я сделал - я не просто снял защиту или распаковал какой-то файл, я крякнул стаб Themida, то есть в демо-версии есть окно при запуске программы, которое информирует о том, что это демо-версия защищённая Themida, так вот я его убрал! То есть, сделал практически платную защищённую версию программы, не повредив (почти) защиту.
Подготовительная работа
Я скачал демо-версию Themida с официального сайта - https://themida.com/download.php, самую последнюю версию, и защитил следующий код:
section '' readable writeable executable
data 1
library kernel, 'kernel32.dll',\
user, 'user32.dll'
import kernel,\
ExitProcess, 'ExitProcess'
import user,\
MessageBox, 'MessageBoxA'
start:
push 0
push 0
push 0
push 0
call dword[MessageBox]
push 0
call dword[ExitProcess]
Код выше при запуске показывает сообщение с текстом "Ошибка" (потому что текст не указан), это минимальный демонстрационный код только для подтверждения того, что оригинальный код будет отрабатывать.
После его защиты Themida размер вырос из 2 килобайт в 2,64 мегабайта.
При запуске, появляется следующее окно:
Данное окно мы и будем убирать!
Принцип создания окна демо-версии Themida
Прежде чем что-то делать, нужно понять, как работает шаблон создания окна демо-версии Themida, поэтому посидев 30 минут в отладчике, я нашёл ответ - окно создаётся с помощью функции CreateWindowExA
, а соответственно сначала вызывается RegisterClassExA
, где и передаётся структура класса, в которой и хранится обработчик окна Themida, в которой в последнюю очередь хранится обработчик нажатия по окну, после которого запускается оригинальный код.
Итак, после полученной информации, нашей задачей становится:
- Хукнуть RegisterClassExA и получить адрес обработчика
- Хукнуть обработчик окна, и передать ему сообщение WM_LBUTTONDOWN
Задача слишком простая, а поэтому приступаем к написанию шеллкода на ассемблере.
Исходный код будет в моём канале - https://t.me/fasmgenius
Для краткости, я пропущу часть с инициализацией шеллкода, а приступим сразу к основной и главной части:
- Снимаем защиту на запись с функции:
push esp push PAGE_EXECUTE_READWRITE push 10 push dword[addr RegisterClassExA] call dword[addr VirtualProtect] add esp, 4
- Ставим хук на функцию:
mov eax, dword[addr RegisterClassExA] mov byte[eax], 0xB8 ; mov eax, hookRegClass lea ecx, dword[addr hookRegClass] mov dword[eax+1], ecx mov word[eax+5], 0xD0FF ; call eax mov byte[eax+7], 0x90 ; nop lea ecx, dword[addr hookWndProc] mov dword[addr hookRegClass.wndproc+1], ecx
Чтобы перехватить обработчик, сначала нам нужно получить этот обработчик. Обработчик хранится в выделенной расшифрованной области памяти, и ДО инициализации Themida его не существует! А значит нам нужно получить его через саму функцию. Как я это называю - динамический патч.
И уже внутри перехваченной функции, мы перезапишем адрес на обработчик, и выполним оригинальную функцию - RegisterClassExA
. Внутри нашего кода обработчика - мы будем подменять ВСЕ сообщения на WM_LBUTTONDOWN, тем самым заставим Themida немедленно выполнить оригинальный код.
- Внутри перехвата RegisterClassExA, мы перезаписываем адрес на обработчик окна - он хранится в структуре WNDCLASSEX по смещению +8:
hookRegClass: mov eax, dword[esp+8] .wndproc: mov ecx, 0 mov dword[eax+8], ecx mov eax, dword[esp] add esp, 4 push ebp ; Original code mov ebp, esp mov ecx, dword[ebp+8] jmp eax
В конце, мы должны исполнить оригинальные инструкции, на которые мы перезаписали вызов нашего перехвата. Размер нашего перехвата - 7 байт, а эти 3 инструкции в конце - на 8 байт, поэтому мы во втором пункте добавили NOP, и соответственно теперь мы исполняем эти инструкции для правильной работы функции.
Готово. Теперь при вызове RegisterClassExA, мы подменим адрес обработчика на наш обработчик - hookWndProc
, в котором мы подменим сообщение.
А теперь код подмены сообщения, он очень прост:
hookWndProc:
mov dword[esp+8], 0x201
mov eax, 0x6C1A22
jmp eax
0x201 - это и есть наше сообщение WM_LBUTTONDOWN, а хранится сообщения согласно соглашению о вызовах на смещении +8. Потому что на смещении +4 хранится дескриптор нашего окна, подробнее тут - https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc
Отлично, вся работа сделана, теперь с помощью CFF Explorer мы создаём новую секцию, встраваем в неё наш бинарный файл с шеллкодом, и устанавливаем точку входа (в OptionalHeader) на нашу секцию.
В самом шеллкоде не забываем вычислить смещение на оригинальную точку входа Themida, и прыгнуть на неё, в самом конце.
В отладчике вычисляем EP-OEP, и получаем необходимое смещение.
После чего, в коде отнимаем данное смещение от call $
в самом начале, при необходимости значение поправить, всё проверить в отладчике.
Не забываем выдать необходимые права - на запись, на чтение, на исполнение.
Кое-что забыли
Возможно, некоторые читатели с самого начала подозревали, что всё должно быть не настолько просто. И конечно же, Вы правы, у Themida есть функция проверки хеша файла!
Способ проверки хеша давно слит на форумах типа UnknownCheats, поэтому не буду врать, что я его искал сам. Способ состоит в том, что Themida сама себя маппит с помощью функций MapViewOfFile
, а после чего собирает хеш CRC, и затем проверяет с заранее вычисленным хешем.
Обход ещё проще, чем пропатчить окно!
Мы просто хукнем функцию MapViewOfFile
, подсунем ему адрес на оригинальный файл, который мы встроим внутрь шеллкода, и обязательно хукнем UnmapViewOfFile
, чтобы она ничего не освобождала.
Патч на обход проверки контрольной суммы Themida
Снимаем защиту с функций, которые будут пропатчены:
sub esp, 4
push esp
push PAGE_EXECUTE_READWRITE
push 10
push dword[addr MapViewOfFile]
call dword[addr VirtualProtect]
push esp
push PAGE_EXECUTE_READWRITE
push 10
push dword[addr UnmapViewOfFile]
call dword[addr VirtualProtect]
После чего, патчим UnmapViewOfFile
, подставляя туда просто mov eax, 1
/ ret
, после этого патча данная функция не будет работать. Если Ваша программа использует данную функцию - она должна обязательно снять этот патч, перезагрузив kernel32.dll
, либо можно дописать снятие хука. Но мой код не использует данные функции, поэтому я их ломаю без каких-либо сожалений.
mov eax, dword[addr UnmapViewOfFile]
mov byte[eax], 0xB8
mov dword[eax+1], 1 ; mov eax, 1
mov byte[eax+5], 0xC2
mov word[eax+6], 4 ; retn 4
Хук на MapViewOfFile
будет намного отличаться от хука на RegisterClassExA
, поскольку код MapViewOfFile
состоит из 5-6 инструкций, одна из которых - это просто jmp на альтернативную в области kernelbase.dll
А поэтому мы будем использовать это в свою пользу, мы просто хукнем адрес внутри инструкции jmp dword[MapViewOfFile]
так, чтобы она указывала на другой адрес, где будет хранится адрес на наш код хука.
lea eax, dword[addr mapShell]
mov dword[addr addrMapShell], eax
Мы создали переменную, в которую положили адрес на наш хук.
mov eax, dword[addr MapViewOfFile]
lea ecx, dword[addr addrMapShell]
mov dword[eax+8], ecx
После чего подменили адрес внутри jmp
на нашу переменную так, чтобы jmp
выглядел следующим образом: jmp dword[addrMapShell]
Дальше всё похоже на предыдущие хуки. Внутри кода хуков у нас есть структурки с адресами, которые надо заполнить. В данном случае - адрес на оригинальный файл, который будет возвращён, и адрес на kernelbase.MapViewOfFile
на случай, если требуется исполнение оригинальной функции.
lea eax, dword[addr orig_file]
mov dword[addr mapShell.origfile+1], eax
mov eax, dword[addr bMapViewOfFile]
mov dword[addr mapShell.caddr+2], eax
mapShell:
.origfile:mov eax, 0
mov ecx, dword[esp]
cmp byte[ecx], 0x9C ; pushfd
je .exit
.caddr: jmp dword[0]
.exit:
retn 4*5
В этом хуке я проверяю инструкции на адресе возврата. Если они равны pushfd
(с данной инструкции начинается код, защищённый виртуальной машиной Themida) - тогда мы подменяем возвращённое значение, и выходим из функции, не исполняя оригинальный MapViewOfFile
.
Иначе - исполняем оригинальный MapViewOfFile
, который уже обратно изменит значение EAX, тем самым без последствий исполнит оригинальную функцию.
Демонстрация / PoC
Демонстрация работы оригинальной демо-защиты Themida 3.1.8.0, и пропатченной защиты Themida 3.1.8.0: