Тотальный разгром! Реверсим Total Commander и обходим защиту всех версий
Какое-то время назад мой друг Jupiter предложил вместе разобрать алгоритм лицензирования Total’а. Пораскинув мозгами, мы написали генератор лицензии — файлов-ключей. И все было бы замечательно, если бы не присутствие в основе алгоритма лицензирования криптосистемы с открытым ключом — LUC. И ключи, разумеется, для успешного прохождения лицензирования нужно знать.
LUC — это похожая на RSA криптосистема. Ее отличие от RSA заключается в использовании последовательностей Люка вместо возведения в степень. Как и для RSA, для генерации закрытого ключа необходимо знать множители (P и Q), которые можно получить через факторизацию модуля (N). Но в нашем случае длина модуля — 832 бита. Естественно, ни у меня, ни у Jupiter’а таких вычислительных мощностей нет. А на квантовый компьютер мы еще денег не накопили. 🙂
Для решения этой проблемы мы сами сгенерируем приватный и публичный ключ криптоалгоритма LUC. Приватным зашифруем лицензию, публичным программа будет расшифровывать лицензию. А чтобы публичный ключ проходил, мы пропатчим его в памяти.
Помимо LUC, в Total’е присутствуют механизмы самозащиты, защита от модификации исполняемого файла. Можно, конечно, хардкорно запатчить файл, но это как раз и есть «костыль», который лишает обход защиты универсальности.
WARNING
Что делаем?
Наша задача — заменить модуль (N) в исполняемом файле программы, не нарушая его целостности. Тогда наш сгенерированный файл ключа будет верно расшифрован и программа будет зарегистрирована.
Существует два варианта решения данной задачи:
- Написать загрузчики для х86- и х64-версий программы (Loader).
- Написать proxy DLL, которые будут выполнять ту же функцию, что и загрузчики.
Оба варианта позволяют беспрепятственно обновлять программу. Но я выбираю второй вариант, он более удобный. В этом варианте не нужно будет исправлять пути в свойствах ярлыков программы с исполняемого файла Total’а на наш лоадер. Достаточно просто скопировать DLL’ки и файл ключа в папку с установленной программой.
Инструменты
- x64dbg — отладчик;
- masm x32 — компилятор;
- masm x64 — компилятор;
- wincmd.key — ключ для программы, сгенерированный моим с Jupiter’ом кейгеном.
Процесс
Я скачал с официального сайта последнюю бета-версию, включающую в себя обе версии программы (х86 и х64). Установил в директорию, которую предложил инсталлятор ( C:\totalcmd ).
Xakep #231. МессенджерыТеперь запускаем на выбор TOTALCMD.EXE или TOTALCMD64.EXE , без разницы. Получаем вот такое окно.
Это было ожидаемо. 😉 Теперь запускаем Total под отладчиком и заходим в закладку Symbols.
В левой половине окна видим загруженные в память процесса модули (DLL). Из всех модулей нас интересуют только две динамические библиотеки — это version.dll и winspool.drv .
Пусть тебя не смущает, что у winspool.drv расширение не dll , на самом деле внутренняя структура у winspool.drv как у обычной динамической библиотеки. Эти два модуля и будут кандидатами для написания одноименных proxy DLL для Total’а.
Как работает механизм proxy DLLВ основе механизма proxy DLL лежит особенность загрузки модулей (DLL) в память процесса Windows загрузчиком (NTLDR или NT Loader).
В Windows-загрузчике этим занимается API LdrLoadDll , который находится в модуле ntdll.dll . Обертками этого API служат такие API, как LoadLibrary и LoadLibraryEx .
Один из этапов загрузки исполняемого файла (в нашем случае это модуль с расширением EXE) — это заполнение таблицы импорта исполняемого файла адресами API из DLL, необходимых для работы программы. В начале этого процесса LdrLoadDll начинает искать модуль (DLL) по имени файла, к примеру version.dll , который находится в таблице импорта в виде строки в кодировке ASCII. LdrLoadDll ищет DLL в текущей директории созданного процесса, в нашем случае это C:\totalcmd . Далее, если модуль не был найден, в зависимости от битности процесса (х86 или х64) LdrLoadDll продолжает поиск требуемой DLL в системной директории ( C:\Windows\System32 или C:\Windows\SysWOW64 ). Если и в системной директории модуль не будет найден, мы получим сообщение об ошибке.
LdrLoadDll позволяет загружать в адресное пространство созданного процесса модули, имеющие одно и то же название, из разных директорий. К примеру, в нашем случае proxy DLL загружает в память NTLDR из директории, где находится TOTALCMD.EXE , а оригинальную DLL (из системной директории) мы загружаем в память из proxy DLL с помощью API LoadLibrary , передавая ей в качестве параметра абсолютный путь к оригинальной DLL. Это еще один нюанс, который позволяет реализовать механизм proxy DLL. Далее из приведенного кода proxy DLL ты поймешь, как это работает. 🙂
Продолжаем. В процессах TOTALCMD.EXE и TOTALCMD64.EXE обе DLL присутствуют. Для TOTALCMD.EXE мы будем использовать version.dll , а для TOTALCMD64.EXE — winspool.drv .
В Total’е модуль (N), участвующий в расшифровке файла ключа ( wincmd.key ), имеет вид строковой константы в кодировке ASCII:
Его необходимо, для правильной расшифровки нашего ключа, заменить на наш модуль (N):
Теперь вновь по очереди запускаем под отладчиком обе версии программы, чтобы определить, где (в какой секции исполняемого файла) находится искомый модуль (N).
Итак, запускаем х86-версию и переходим в закладку Memory Map. Нажимаем сочетание клавиш Ctrl + B, откроется окно бинарного поиска в памяти процесса, копируем оригинальный модуль (N) и вставляем его в поле ASCII.
Нажимаем OK, и у нас откроется закладка References с результатами поиска.
Видим адрес, по которому был найден модуль (N), — 0x004E219C.
Переходим в окно дампа памяти по данному адресу и скроллом поднимаемся вверх.
Видим адрес 0x00401000. Это верхний адрес секции, в которой находится модуль (N).
Опять возвращаемся в закладку Memory Map и видим, что адрес 0x00401000 соответствует адресу первой секции исполняемого файла TOTALCMD.EXE — CODE .
Для х64-версии проделываем те же самые манипуляции с отладчиком.
В результате выясняем, что ASCII-строка модуля (N) для х86-версии находится в секции CODE ( 0x00401000 ), а для х64-версии — в секции .data ( 0x0000000000AD9000 ).
Ну что же, необходимую информацию для написания proxy DLL мы получили. Начинаем кодить. 🙂
Кодинг
Разберем код для х86-версии, а именно version.dll . Для х64-версии все аналогично. Точка входа proxy DLL ( EntryPoint ). Здесь все стандартно.
API DisableThreadLibraryCalls использовать не обязательно. Я пользовался им для отключения уведомлений DLL_THREAD_ATTACH и DLL_THREAD_DETACH , на всякий случай.
Далее переходим в MainProc.
Здесь я объявляю глобальные переменные, для сохранения адресов оригинальных API.
Резервирую область памяти для сохранения полученного пути к оригинальной version.dll.
Здесь я прокомментировал все шаги исполнения кода MainProc.
Чуть ниже MainProc я объявляю экспортируемые функции с безусловными переходами (JMP) из proxy DLL в оригинальную DLL. В файле version.def определены имена экспортируемых функций proxy DLL, которые аналогичны именам функций в оригинальной DLL.
И главная функция proxy DLL — это ReplaceModulus .
В секции данных proxy DLL у меня находятся оригинальный модуль (Original) и модуль (New), на который необходимо заменить оригинальный.
Здесь тоже все шаги выполнения кода прокомментированы.
Финал
Результатом всех описанных действий будет зарегистрированная версия программы. Причем в данном случае можно спокойно обновлять программу, не боясь того, что регистрация «слетит». 🙂
Копируем в папку с Total’ом три файла — это наш ключ wincmd.key и наши DLL’ки: version.dll и winspool.drv . Запускаем программу.
В диспетчере задач видим, что в процесс загружены обе DLL’ки.
Итоги
Как видишь, механизм proxy DLL — удобный и мощный инструмент. С его помощью ты можешь беспрепятственно эмулировать работу оригинальных функций и при этом комфортно модифицировать данные и код в памяти процесса.
Исходники и ключСкачать исходники программы и ключ для самостоятельной практики ты можешь по этой ссылке. Напоминаем, что все материалы выкладываются исключительно в образовательных целях. Редакция не несет ответственности за любой вред, причиненный материалами данной статьи.