Пропатчил стаб Themida 3.1.8.0 без снятия и повреждений, показываю
Прежде всего, поясню, что я сделал - я не просто снял защиту или распаковал какой-то файл, я крякнул стаб Themida, то есть в демо-версии есть окно при запуске программы, которое информирует о том, что это демо-версия защищённая Themida, так вот я его убрал! То есть, сделал практически платную защищённую версию программы, не повредив (почти) защиту.
Не удалось прогрузить медиа
Подготовительная работа
Я скачал демо-версию Themida с официального сайта - [URL='https://themida.com/download.php%5B/URL']https://themida.com/download.php[/URL], самую последнюю версию, и защитил следующий код:
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, в которой в последнюю очередь хранится обработчик нажатия по окну, после которого запускается оригинальный код.
Итак, после полученной информации, нашей задачей становится:
[]Хукнуть RegisterClassExA и получить адрес обработчика
[]Хукнуть обработчик окна, и передать ему сообщение WM_LBUTTONDOWN
Исходный код будет ниже.
Для краткости, я пропущу часть с инициализацией шеллкода, а приступим сразу к основной и главной части:
Снимаем защиту на запись с функции:
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 хранится дескриптор нашего окна, подробнее тут - [URL='https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc%5B/URL']https://learn.microsoft.com/en-us/windows/win32/api/winuser/nc-winuser-wndproc[/URL]
Отлично, вся работа сделана, теперь с помощью 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
.exit:
retn 4*5
В этом хуке я проверяю инструкции на адресе возврата. Если они равны pushfd (с данной инструкции начинается код, защищённый виртуальной машиной Themida) - тогда мы подменяем возвращённое значение, и выходим из функции, не исполняя оригинальный MapViewOfFile.
Иначе - исполняем оригинальный MapViewOfFile, который уже обратно изменит значение EAX, тем самым без последствий исполнит оригинальную функцию.
Готово!
В этой статье попробовал BB codes, извиняюсь за испорченный код но суть понятна.
👍41
🔥8
👏11
🤔11
💯6
LIMB