October 15

Пропатчил стаб Themida 3.1.8.0 без снятия и повреждений, показываю

Прежде всего, поясню, что я сделал - я не просто снял защиту или распаковал какой-то файл, я крякнул стаб Themida, то есть в демо-версии есть окно при запуске программы, которое информирует о том, что это демо-версия защищённая Themida, так вот я его убрал! То есть, сделал практически платную защищённую версию программы, не повредив (почти) защиту.

Подготовительная работа


Я скачал демо-версию Themida с официального сайта - https://themida.com/download.php, самую последнюю версию, и защитил следующий код:

format PE GUI entry start

section '' readable writeable executable data 1 library kernel, 'kernel32.dll',\ user, 'user32.dll'

import kernel,\ ExitProcess, 'ExitProcess'

import user,\ MessageBox, 'MessageBoxA'

end data

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, в которой в последнюю очередь хранится обработчик нажатия по окну, после которого запускается оригинальный код.

Итак, после полученной информации, нашей задачей становится:

  1. Хукнуть RegisterClassExA и получить адрес обработчика
  2. Хукнуть обработчик окна, и передать ему сообщение WM_LBUTTONDOWN

Задача слишком простая, а поэтому приступаем к написанию шеллкода на ассемблере.

Исходный код будет в моём канале - https://t.me/fasmgenius

Для краткости, я пропущу часть с инициализацией шеллкода, а приступим сразу к основной и главной части:

  1. Снимаем защиту на запись с функции:
    push esp push PAGE_EXECUTE_READWRITE push 10 push dword[addr RegisterClassExA] call dword[addr VirtualProtect] add esp, 4
  2. Ставим хук на функцию:
    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 немедленно выполнить оригинальный код.

Продолжаем:

  1. Внутри перехвата 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, либо можно дописать снятие хука. Но мой код не использует данные функции, поэтому я их ломаю без каких-либо сожалений.

Хук на UnmapViewOfFile:

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: