СТАНИСЛАВ ГОШКО
Анализ защиты программ и рекомендации
по её усилению
Для того чтобы защитить программы от взлома, необходимо знать, против чего мы боремся. Будем считать, что мы являемся разработчиком программного обеспечения и нам противостоит взломщик среднего уровня. Почему среднего? Да потому, что если взломщик выше среднего уровня, то будет очень сложно создать защиту для программы, которую он бы не взломал (если не невозможно).
В большинстве своём крэкеры используют следующий инструментарий:
- отладчики (SoftICE, TRW, Turbo Debugger, ...)
- дизассемберы (IDA, W32 Dasm, Sourcer, ... )
- шестнадцатиричные редакторы (Hiew, Hex Workshop, ...)
- мониторы (FileMon, RegMon, ...)
Теперь рассмотрим, как в основном взламываются обычные программы на конкретных примерах.
Winzip 8.0
Загрузить SoftICE, поставить точку прерывания на GetDlgitemTexta, затем заполнить поля формы Winzip (name, serial_number; заполнить чем угодно) и нажать OK. Появится окно SoftICE, там поставить точку прерывания на 0167:00407AAB и посмотреть данные.
- bpx GetDlgItemTextA
- bpx 0167:00407AAB
- d eax
- В окне данных будет серийный номер, сгенерированный по вашему имени.
Hexworkshop 2.54
Ставим точку прерывания на GetWindowTexta, затем ставим точку прерывания по адресу 0167:004262AF. Трассируем без вхождения (F10) до второго перехода jnz. Этот второй переход выполняться не хочет, изменяем флаг нуля (в окне регистров наводим на Z и нажимаем INS).
Теперь убираем все точки прерывания:
bc *
и закрываем SoftICE – F5.
Вводим имя и фирму, на которую хотим зарегистрироваться. Вот и всё.
Winamp 2.xx
- bpx hmemcpy
- Вводим serial.
- Всплываем в SoftICE.
- Видим что-то вроде:
cmp eax, esi
два раза клацаем на эту строку, жмём F5.
- ? eax (там и хранится регистрационный код).
- Убираем точки прерывания, выключаем SoftICE.
Irfanview 2.0
Данная программа была препарирована за 5 минут, а делалось это так:
- Устанавливаем точку прерывания в SoftICE на MessageBoxA:
bpx MessageBoxA
- Всплывя в Irfan, мы видим кучу разных сравнений и несколько переходов. Моё внимание привлекла функция Kernel32, заполнив форму, я нашёл на неё переход и увидел, что переход находится по адресу:
0167:00440347 jnz 00440375
- Исправляем флаг нуля в окне регистров и жмём F5, вуаля – мы зарегистрированы.
P.S. Можно было в HIEW заменить этот переход на jz.
mIRC v. 6.01
Загрузить SoftICE, поставить точку прерывания на MessageBoxA.
bpx MessageBoxA
Затем заполнить поля формы и нажать «ОК». Появится окно SoftICE. Выйдем из User32.dll и затем проанализируем код, предшествующий сообщению об ошибке.
Выше вызова MessageBoxA я натолкнулся на следующий вызов функции:
push 0056d737 ; Помещается в стек смещение серийного номера
push 0056d350 ; Помещается в стек смещение имени
call 004c37b1 ; Вызов функции, которая проверяет корректность
test eax,eax ; Проверка результата функции
jz 004c3ce1 ; !!!!!!!!!!!!!
Команду условного перехода необходимо либо обратить (jnz 004c3ce1), либо забить nop.
Адрес инструкции условного перехода следующий: 004с3с24.
Customiser
Данная программа работает определённое количество времени и потом сообщает о том, что время её использования кончилось. И больше работать не хочет – это выражается в том, что пропадает кнопка «Continue».
Начнём наше исследование с того, что взглянём, какие функции данная программа импортирует. Нас особо будут интересовать функции из USER32.dll, так как диалог, на котором была кнопка «Continue», импортируется скорее всего оттуда. Мое внимание привлекла функция – DialogBoxParamA.
Загружаем SoftICE и ставим брейкпоинт на эту функцию:
bpx DialogBoxParamA
После этого запускаем программу и сразу же всплывёт SoftICE в недрах USER32.dll, и мы поднимемся из этой функции (нажатием F12).
Протрассируем до адреса 41f8c4, и там мы заметим подозрительный условный переход, следом за которым идёт ещё один. Второй переход указывает на вызов функции EnableWindow. Становится ясно, что нам нужно перейти по этому переходу. Для этого мы инвертируем флаг нуля в SoftICE и нажимаем F5, что возвращает управление программе, и мы видим, что программа заработала.
Теперь мы должны пропатчить её. Запускаем HIEW, переходим по адресу 41f8c4 и меняем байты «75 03» на «EB 18».
После этого снимем брейкпоинт в SoftICE.
bc *
Запустим программу, нажмём кнопку «Exit», и программа не завершится, а, наоборот, запустится. Но по-прежнему будет постоянно появляться это мерзкое окно с сообщением, что время твое прошло. Поэтому мы попытаемся избавиться от него. Найдём вызов функции рисования данного окна. Получится адрес 41f89c, и там будет вызов подпрограммы из 3 байт, которые перезапишем 3 nop («90»).
Перезаписывать будем при помощи HIEW.
Вот полная информация о проведённых изменениях:
41f89c: 90 90 90
41f8c4: EB 18
После этого программа будет запускаться без всяких окон.
Существует ещё один способ продления лицензии без изменения программы. Нужно перед её установкой установить дату в системе лет на 20 вперёд, а после установки вернуть старую дату.
Shadow Security Scanner 5.37
Эта программа – один из лучших сетевых сканеров безопасности. Она имеет ограничение по времени на использование (trial).
Метод, который применялся при взломе Customiser, не работает потому, что окно создаётся функцией CretaeWindowExA, это всё несколько усложняет.
Но можно пойти другим путём. Трассировать программу без вхождения в подпрограммы, пока не появится окно, уведомляющее, что время программы закончилось.
Нужно запомнить адрес подпрограммы, в которой выводится данное сообщение, и заглянуть туда. Там мы можем заметить какое-то странное сравнение:
cmp [ebx+14],0
После этого переход, если не равно нулю. Поиграем с флагом нуля, и что мы видим – программа заработала, осталось только её пропатчить:
593598: EB
Вот и всё, защита с программы снята.
Как мы можем видеть, взлом программы чаще всего отталкивается от перехвата WIN API-функций. Как можно этому противостоять?
Существует несколько методов:
Написание и использование своих функций с переходом куда-нибудь в середину WIN API-функции
Рассмотрим написание своих функций на примере функции GetWindowTextA.
Как устанавливается прерывание отладчика на имя функции? Вставляется байт «CC» перед первой инструкцией функции. Но мы ведь можем сделать так, что на этот первый байт инструкции управление никогда и не будет передано. Вы спросите, как это сделать?
Давайте рассмотрим, как начинается данная функция:
77d5c13a: push 0c
77d5c13c: push 77d6e498
77d5c141: call 77d439c0
Так начало функции выглядит у меня. Каждому необходимо посмотреть на её начало под отладчиком самому:
- Загрузить SoftICE
- Ctrl + D ( Всплываем в SoftICE)
- u GetWindowTextA
Теперь давайте разберёмся, как выглядит вызов нашей функции в программе в общем виде:
push xxxxxxxx
................
push xxxxxxxx
call GetWindowTextA
Причём при вызове GetWindowTextA идёт переход по адресу 77d5c13a. Мы же можем сделать следующим образом:
push xxxxxxxx
................
push xxxxxxxx
push return_address
push 0c
jmp (GetWindowTextA+2)
Данный метод позволяет защититься от точки прерывания в SoftICE такого вида:
bpx GetWindowTextA
Так же можно сделать следующим образом:
push xxxxxxxx
................
push xxxxxxxx
push return_address
push 0c
push 77d6e498
jmp (GetWindowTextA+7)
И таких вариантов существует достаточно много.
В оптимальном варианте применения данной техники нужно написать дизассемблер длин и подключить его к программе таким образом, чтобы он формировал вызовы функций, копируя некоторые инструкции из них в тело программы.
Это позволит ещё более обезопасить программу от перехвата WIN API-функций.
Подсчёт контрольной суммы WIN API-функций
Данный метод мне ещё нигде не встречался, и хочется верить, что его идея принадлежит мне.
Он заключается в том, что перед вызовом необходимой WIN API-функции подсчитывается её контрольная сумма и сверяется с той суммой, которая хранится в программе. Как вы помните, при установленной точке прерывания на место первой инструкции записывается байт «СС» (int 3), который и изменяет контрольную сумму данной функции. При проверке контрольной суммы данной функции и определении отладки можно выйти из программы.
Теперь хочется сказать об эффективном применении данного метода. Я думаю, что если сверять контрольные суммы, то это может быть замечено взломщиком и результат сравнений подменён. Я предлагаю при помощи оригинальных контрольных сумм шифровать какие-нибудь критические данные, так, чтобы программа после декриптования данных неверной контрольной суммой работала некорректно.
Ну естественно, что это ограничит круг операционных систем и их версий, но между двух зол выбирают меньшую.
Обнаружение отладчика и завершение программы
Все отладчики подсистемы семейства Windows делятся по уровням:
- ring 3 (уровень приложения)
- ring 0 (уровень ядра)
В основном все отладчики, кроме SoftICE, относятся к отладчикам уровня приложения и против них существует множество методов, которые мы сейчас и рассмотрим:
Использование WIN API-функции IsDebuggerPresent
Использовать её довольно легко:
if (!IsDebuggerPresent()) goto no_debugger
//........................................
no_debugger:
Или вот её полный код:
mov eax,fs:[018h]
mov eax,[eax+30h]
movzx eax,byte ptr [eax+02]
ret
Погибель отладчику несут операции с SEH
Нужно установить свой обработчик ошибки, а после вызвать ошибку попыткой записи в ядро.
Вначале нам необходимо сохранить оригинальный обработчик SEH:
push dword ptr fs:[0]
Теперь нам необходимо заменить оригинальный обработчик на свой:
push offset SEH_Handler
mov fs:[0],esp
Восстановление оригинального обработчика в нашем случае будет производиться следующей инструкцией:
pop dword ptr fs:[0]
Данная инструкция восстанавливает из стэка оригинальный обработчик SEH.
А теперь рассмотрим программу, которая обрушивает отладчик, а без отладчика работает:
.386p
.model flat
extrn ExitProcess:PROC
.data
Hi dd 0
.code
;--------------------------------------------------------;
start:
pop ebx ; Адрес для вызова исключения
call setupSEH
Ex_Handler:
mov esp,[esp+8] ; Ошибка дает нам старый ESP в [ESP+8]
exit:
push 0 ; Кладём в стэк 0
call ExitProcess ; И завершаем программу
;--------------------------------------------------------;
setupSEH:
push dword ptr fs:[0] ; Push оригинальный обработчик SEH
mov fs:[0],esp ; И помещаем новый (который находится после первого call)
mov eax,012345678h ; Пытаемся писать в ядро (что вызовет
xchg eax,[ebx] ; исключение)
end start
После того, как мы научились противодействовать отладчикам уровня приложения, не мешало бы научиться бороться с SoftICE.
Победить правильно настроенный SoftICE практически невозможно, но дело в том, что в 90% он не настроен должным образом.
1) Стандартным методом является использование следующего кода:
push ss
pop ss
int 3
При попадании на инструкцию int 3 взломщик попадёт внутрь обработчика и будет там вдали от защитного механизма программы.
2) Следующий код эффективно использовать для модификации ключа расшифровки:
mov ebp,"BCHK"
push ss
pop ss
int 3
db blabla ; Опкод модифицирующий ключ декриптования
3) Разработчики SoftICE оставили возможности для определения его присутствия, которыми мы и воспользуемся для защиты своей программы от SoftICE.
Необходимо всего лишь открыть следующие файлы, если они открываются, значит, SoftICE присутствует на данной машине. В обратном быть уверенным нельзя.
Список файлов:
- «\\.\NTICE»
- «\\.\SIWVIDSTART»
- «\\.\SICE»
- «\\.\SIWVID»
Ну вот с отладчиками и закончили.
Дизассемблеры
Перейдём к дизассемблерам, которые позволяют анализировать код без его исполнения. Чтобы им противостоять, оптимальным вариантом считается шифрование критических участков программы. Также определённое распространение получила техника под названием «перекрывающийся код». Рассмотрим листинг:
mov eax,04ebh
jmp $-4
next:
Что же делает выделенная восклицательными знаками часть кода?
- В eax помещается значение 04ebh(это опкод команды jmp $+4)
- jmp $-4, переходит на значение 04ebh
- jmp $+4, переходит на метку next
Таким образом, можно строить сколь угодно сложный перекрывающийся код. Ещё в перекрывающемся коде есть один большой плюс, с его помощью можно прятать неприятные для кодоанализаторов инструкции.
Защита от изменения кода
Многие программисты видели так называемые патчи (заплатки) для программ, которые сводят суть защиты программы на нет. Как же с этим бороться?
Существуют два метода защиты:
- Шифровать при помощи контрольной суммы критического места программы важные данные.
При этом если даже защита будет снята, программа будет работать некорректно. И это сведёт попытки взломщика на нет. А для полноценного взлома такого вида защиты нужно будет полностью разобраться в алгоритме, что доступно далеко не каждому взломщику.
- Использование помехозащищённого программирования.
Смысл данной техники заключается в том, что программа сама восстанавливает изменённые в своём коде байты. Существует множество методов реализации данной схемы.
Мониторы
Мониторам противодействовать практически невозможно, поэтому при построении защиты для программы необходимо свести обращения к файлам и реестру либо к минимуму, либо к максимуму.
Первый способ скрывает защитный механизм внутри программы. А второй запутывает своими обращениями к различным файлам.
Надеюсь, данный материал поможет вам достойно ответить на вызов крэкеров.