Что вы обычно делаете, чтобы взломать шароварную прогу? Правильно,
лезете в интернет за кряком. Но если программа редкая, то кряка
может не быть 🙁 Вот бы сделать универсальный кряк, подходящий к
любой программе… 🙂

Еще шесть лет назад, когда виндовз 95 еще считался непонятным извратом,
мной была создана автоматическая ломалка, за несколько секунд
вскрывающая защиту примерно трети подсунутых программ без вмешательства
человека. Но по молодости ваш покорный слуга спутал городской конкурс
прикладных программ и DefCon и имел неосторожность продемонстрировать
свое детище (демонстрировалось, как шароварный RAR становится
зарегистрированным). Естественно, что идею мягко говоря не оценили,
и программа была заброшена.

Недавно, столкнувшись с защитой электронной версии журнала,
я понял, что идея автоломалки все еще актуальна. Эксплуатируется
тот факт, что программисты, не знакомые со способами взлома софта,
из года в год делают одну и ту же глупость: сравнивают правильный
пароль с введенным.

Я не буду выкладывать готовую автоломалку, потому что этим отобью
всю охоту изучать этот вопрос 🙂 Гораздо полезнее будет описание,
как ее создать.

Языки программирования высокого уровня (Delphi, Cи, VB) сравнивают
строки при помощи стандартных процедур, которые можно пропатчить
с целью расширения их возможностей 🙂 Например, все сравниваемые
строки можно кидать в файл, таким образом можно выудить правильный
пароль. Или можно заставить программу считать любые две строки
равными, тогда любой введенный пароль будет считаться верным. 

На самом деле эта процедура сравнения вызывается много раз в
«служебных» целях, так что патчить ее надо осторожно. Я использую
такую модификацию алгоритма: если одна из сравниваемых строк
начинается на BUGZ, то результат сравнения — «строки равны».
Иначе запускается стандартный алгоритм сравнения. Это позволяет
ввести вместо серийника/пароля слово BUGZY 😉 А если серийник
должен быть длиннее, то остаток можно «забить» любым другим текстом.

Патчить код в исполняемом модуле (exe, dll) или прямо в памяти — дело
хозяйское, автоломалка в обоих случая выглядит абсолютно
одинаково, за исключением используемых функций чтения и записи.

Собственно о том, что и как патчить.

Шаг первый: найти функцию сравнения.
Искать будем по сигнатуре, т.е. куску кода, присутствующему в функции.
Каждый компилятор имеет свою сигнатуру, однако самих компилаторов
не так уж много, к тому же, нет особой нужды делать автоломалку
для компилятора фортрана и watcom c: ты попробуй сначала найди
проги, которые на них сделаны 🙂 Хватило бы джентельменсткого
набора: Delphi, MSVC и BC.

Чтобы определить сигнатуру конкретного компилятора, я не придумал ничего
лучше, чем поставить его себе, затем вызвать функцию сравнения и
посмотреть на нее из дизассемблирующего отладчика.

Шаг второй: найти место для дополнительного кода.
так как возможности функции сравнения расширяются, пропатчить код «на месте»
не получается, приходится размещать дополнительный код в неиспользуемой
части памяти. Из основной процедуры делается прыжок на вставку, а потом
управление возвращается стандартной процедуре проверки.

В качестве «свободного места» я использую первую попавшуюся область,
забитую большим количеством нолей. Вроде работает, хотя не обязано 🙂

Шаг третий: подготовка патча.
Патч содержит условные и безусловные переходы от куска кода на месте
«нормальной» процедуры сравнения к вставке и обратно. Так как «расстояние»
между ними заранее неизвестно, придется рассчитывать длинну прыжков
исходя из расположения частей патча.

Шаг четвертый: запись патча.
данные просто внаглую пишутся в память процесса или в файл, в зависимости от
выбранного метода. Начинать записывать лучше с вставки, мало ли чего 🙂 

Наконец, практический пример патча (для Delphi).

Для сравнения строк компилятор использует функцию LStrCmp. В явном виде из
кода высокого уровня эта функция не вызывается, но при дизассемблировнии
ее видно. Вот как выглядит ее ассемблерный листинг:

push ebx
push esi
push edi
mov esi,eax
mov edi,edx
cmp eax,edx
jz StringsEqual — проверка на сравнение строчки с самой собой
test esi,esi
jz NotEqual — проверка на пустую строку
test edi,edi
jz NotEqual — проверка на пустую строку

а дальше идет то, что я использую как сигнатуру (8b 46 fc 8b 57 fc 29 d0):

mov eax,[esi-4] — это место
mov edx,[edi-4] — замещается
_subptr: — сюда происходит возврат из патча
sub eax,edx

Там, где находится этот код, будет располагаться первая часть патча —
переход на вторую. Патч — 5 байт, начинающихся с E9.

Вторая часть патча:
mov eax,[esi-4] — то, что мы
mov edx,[edi-4] — затерли прыжком
cmp [esi], $5a475542 — строчка ‘BUGZ’
_se:
(*) jz StringsEqual — переход в часть процедуры, говорящей, что строки равны 
cmp [edi], $5a475542 — то же для второго аргумента
jz _se — чтобы не возиться лишний раз с перерасчетом длины прыжка 
(*) jmp _subptr — переход к «нормальной» процедуре сравнения

код строчек, помеченных (*), рассчитывается исходя из расстояния между
кусками патчей.

Остается только добавить, что для правки программы прямо в памяти (после
того, как она загрузилась и распаковала сама себя, если была запакована
asprotect etc), производится с помощью функций OpenProcess или
CreateProcess, ReadProcessMemory и WriteProcessMemory, описание которых есть в win32.hlp. Автоломалка успешно испытана на тестовых
программах (которые просто сравнивают введенную строчку с эталоном)
и на электрохакере #34 (последний в качестве кода активации принимал
слово ‘BUGZY’).

Ожидается, что автоломалка будет вскрывать каждую третью программу. Эта
ошибка начинающих защитников вечна…

Оставить мнение

Check Also

Антиотладка. Теория и практика защиты приложений от дебага

К методам детектирования отладки прибегают многие программисты: одни хотели бы уберечь сво…