Источник: https://github.com/AnaktaCTF/CTF/blob/main — Reverse/program_patching.md
Обход лицензирования ПО: патчинг и другие техники
Введение
Обход лицензирования программного обеспечения (ПО) представляет собой процесс модификации программы с целью устранения или обхода механизма проверки лицензии. Данный процесс часто используется при реверс-инжиниринге для анализа работы программы, создания патчей или устранения ограничений пробных версий, но так же может использовать в CTF соревнованиях, где нужно пропатчить бинарный файл для получения флага.
В данной статье мы рассмотрим одну из популярных техник обхода — патчинг с использованием команды NOP. Также опишем другие методы обхода лицензирования и возможные трудности, с которыми можно столкнуться в процессе.
Логика проверки ключа
Механизм проверки лицензионного ключа часто основан на сравнении введённого пользователем ключа с эталонным значением. Это значение может храниться в программе в следующих формах:
- Жёстко прописанный ключ — строка или числовое значение зашито непосредственно в бинарном файле.
- Вычисляемый ключ — программа генерирует корректный ключ на основе внутреннего алгоритма (например, хэширование введённых данных).
- Серверная проверка — программа отправляет введённый ключ на сервер и получает ответ о его корректности.
Как происходит проверка
- Пользователь вводит ключ (например, через форму или командную строку).
- Программа проверяет ключ одним из следующих способов:
- Сравнение с жёстко прописанным значением.
- Генерация эталонного ключа и сравнение с введённым.
- Отправка данных на сервер и ожидание ответа.
- В случае успешной проверки программа продолжает выполнение (например, разблокирует функции), иначе — завершает с ошибкой.
Важные аспекты
- Некоторые программы защищены сложными алгоритмами генерации ключей, включающими шифрование.
- Использование серверной проверки делает обход на уровне клиента практически невозможным без имитации сервера.
Пример патчинга с использованием NOP
Исходный код (до патчинга)
На входе программа выводит случайный серийный номер:
Serial: 359-220
Input key:
Откроем программу через дисассемблер IDA и перейдём к фрагменту кода, который отвечает за проверку введённого ключа. Для понимания работы программы нужно обладать знаниями Ассемблера.
.text:0000000140001300 lea rcx, aInputKey ; "Input key: "
.text:0000000140001307 call display_result
.text:000000014000130C mov r8d, ebx
.text:000000014000130F lea rdx, [rsp+188h+var_118]
.text:0000000140001314 lea rcx, aS ; инструкция запроса данных
.text:000000014000131B call read_x ; функция, включающая инструкцию чтения потока данных
.text:0000000140001320 lea rdx, [rsp+188h+var_118]
.text:0000000140001325 lea rcx, [rsp+188h+Destination]
.text:000000014000132A call check_key_serial ; функция валидации введённых данных
.text:000000014000132F test al, al
.text:0000000140001331 lea rdx, aWrongKey ; Ключ введён неверно
.text:0000000140001338 lea rcx, aRightKey ; Ключ введён верно
.text:000000014000133F cmovz rcx, rdx
.text:0000000140001343 call display_result ; printf
Как мы видим, программа работает по следующему алгоритму:
- Вводится серийный номер, который затем проверяется функцией check_key_serial.
- Результат проверки сохраняется в регистре AL.
- Если AL равен нулю, программа переходит на вывод сообщения об ошибке (Wrong Key). Иначе выводится сообщение о правильном ключе (Right Key).
Патчинг
Для обхода проверки можно использовать команду NOP (No Operation), чтобы исключить блок проверки путём «затирания» машинных команд производящих валидацию. Так можно заставить программу пропустить эти шаги и сразу перейти к «положительному» исходу.
Разбор патча
Для патчинга мы должны определить какие команды отвечают за валидацию и заменить их на nop’ы.
Мы затираем:
.text:000000014000132F test al, al — проверку результата работы функции check_key_serial
.text:0000000140001331 lea rdx, aWrongKey — исход при неверно введённом ключе
.text:000000014000133F cmovz rcx, rdx — инструкция переноса данных между регистрами при выполнении условий
Рассмотрим патченный код:
.text:000000014000132A call sub_140001944
.text:000000014000132F nop
.text:0000000140001330 nop
.text:0000000140001331 nop
.text:0000000140001332 nop
.text:0000000140001333 nop
.text:0000000140001334 nop
.text:0000000140001335 nop
.text:0000000140001336 nop
.text:0000000140001337 nop
.text:0000000140001338 lea rcx, aRightKey ; "Right key\n"
.text:000000014000133F nop
.text:0000000140001340 nop
.text:0000000140001341 nop
.text:0000000140001342 nop
.text:0000000140001343 call sub_140001010
.text:0000000140001348 call sub_14000186C
Что делают команды после патчинга:
- call sub_140001944 - вызов функции проверки ключа. Несмотря на вызов функции, результат её выполнения больше не учитывается из-за последующего патчинга.
- nop - команда, не выполняющая никаких операций. В оригинальном коде на месте NOP была в том числе проверка регистра AL, в котором функция check_key_serial возвращает результат (0 или 1). Заменяя эти инструкции на NOP, мы исключаем проверку правильности ключа.
- lea rcx, aRightKey - загрузка адреса строки "Right key" в регистр RCX. Эта строка будет использована в качестве аргумента при вызове функции display_result.
- call sub_140001010 - вызов функции вывода результата (printf). В результате всегда отображается сообщение "Right key", так как проверка была пропущена.
Как команды работают в связке
- После вызова функции проверки ключа (sub_140001944), результат сохраняется в регистре AL. Однако из-за патчинга команда test al, al, которая проверяла результат на равенство нулю, заменена на NOP.
- Вместо условного перехода к выводу "Wrong key" (при AL=0), который мы так же «затёрли», программа сразу переходит к команде загрузки "Right key" в RCX.
- Таким образом, программа всегда считает ключ правильным и выводит соответствующее сообщение, поскольку проверка была полностью устранена.
В результате после патчинга программа всегда выводит сообщение об успешной проверке ключа, независимо от введённых данных.
Почему это работает
Команда NOP заменяет проверку на отсутствие операций, поэтому проверка результата (регистра AL) всегда пропускается. Это позволяет всегда переходить к ветке вывода сообщения о правильном ключе, независимо от вводимых данных и последующей валидации. Мы просто пропускаем этот шаг. При этом мы не нарушаем структуру файла, т.к. не просто удаляем байты данных, а заменяем их на легитимные.
Альтернативные методы удаления логики программы
- Удаление проверочных блоков: Вместо замены инструкций на NOP можно просто удалить часть кода, но это рискованно, так как может нарушить структуру секций PE-файла.
- Перепрыгивание проверок: Использование инструкций перехода (например, jmp) сразу к блоку успешного выполнения.
- Изменение логического оператора: Заменить оператор проверки на противоположный, например, jne на je.
- Изменение возвращаемого значения: В случае вызова функции проверки можно изменить её возвращаемое значение на успешное (например, заменив команду возврата на mov eax, 1; ret).
- Инверсия условий: В некоторых случаях можно инвертировать условие, чтобы ветка ошибки всегда считалась успешной.
Другие методы обхода лицензирования
1. Inline-патчинг
Изменение байтов кода непосредственно в бинарном файле с целью модификации логики.
2. Кейген
Создание утилиты, которая генерирует валидные серийные номера на основе анализа алгоритма генерации ключей. В данном случае кейген основывается на сейрином номере. Выглядит он вот так:
3. Подмена API
Перехват вызовов функций проверки лицензии с помощью поддельных библиотек или хук-кода.
4. Дебаггер
Использование отладчика для изменения кода на этапе выполнения. Одним из популярных инструментов является OllyDbg — отладчик для Windows, который позволяет выполнять пооперационное выполнение кода, изменять значения регистров и патчить инструкции прямо в процессе выполнения.
Пример использования:
• Загрузка защищённого приложения в OllyDbg.
• Поиск точки проверки лицензионного ключа.
• Изменение команды проверки на инструкцию перехода к валидной ветке (например, с использованием команды jmp).
• Сохранение изменений и тестирование работы приложения без повторного запуска.
5. Подмена файла лицензии
Замена оригинального файла лицензии на поддельный с корректными данными.
6. Использование трейсеров
Отслеживание всех операций программы для определения, где и как происходит проверка лицензии. Основная цель трейсеров — найти последовательность вызовов функций и определить, какие из них отвечают за проверку лицензионного ключа.
Пример использования:
• Применение таких инструментов, как x64dbg или API Monitor.
• Запуск программы с активированным трейсингом всех системных вызовов.
• Фиксация момента проверки ключа и анализ стека вызовов.
• Изменение возвращаемого значения функции проверки с помощью отладчика.
Заключение
Обход лицензирования ПО — сложная задача, требующая знаний в области реверс-инжиниринга и понимания внутренней структуры бинарного кода. Использование NOP — лишь один из множества методов.