CrackMe - это небольшая программа, разработанная для проверки навыков реверс-инженеринга программистов. © Википедия
В этой статье я опишу процесс взлома CrackMe #1 от Cruehead’а.
Описание программы
Скачать архив с нужными файлами можно по ссылке
В архиве, помимо рассматриваемого в этой статье CrackMe, содержится еще несколько подобных примеров.
Итак, при запуске мы видим основное окно программы:
Меню содержит 3 основных пункта:
File - Exit - выйти из программы
Help - Register - показать окно регистрации программы
Help - About - показать окно информации о программе
При выборе пункта Help - Register появляется окно ввода имени и серийного номера. Любому допустимому имени соответствует серийный номер. В случае ввода имени и серийника, соответствующих друг другу появляется окно:
В противном случае окно будет таким:
Итак, наша задача заключается в следующем: необходимо определить, каким образом проверяется соответствие введенных имени и пароля.
Логика работы программы
Открываем отладчик. Первое, что мы должны найти - адрес процедуры, рисующей окно “Good work!”. Судя по коду, это 0040134D. Из этого делаем вывод, что вызов JMP или CALL на этот адрес приводит нас к успеху
Теперь рассмотрим код, выполняющийся при нажатии кнопки OK в меню Help - Register:
00401228 | 68 8E214000 PUSH OFFSET Crackme1.0040218E 0040122D | E8 4C010000 CALL 0040137E 00401232 | 50 PUSH EAX 00401233 | 68 7E214000 PUSH OFFSET Crackme1.0040217E 00401238 | E8 9B010000 CALL 004013D8 0040123D | 83C4 04 ADD ESP,4 00401240 | 58 POP EAX 00401241 | 3BC3 CMP EAX,EBX 00401243 | 74 07 JE SHORT 0040124C 00401245 | E8 18010000 CALL 00401362 0040124A | EB 9A JMP SHORT 004011E6 0040124C | E8 FC000000 CALL 0040134D 00401251 | EB 93 JMP SHORT 004011E6
В этом коде нужно обратить внимание на инструкции 0040122D и 00401238. Первая получает на вход введенное имя и считает по нему некоторую характеристическую величину. Вторая считает характеристическую величину по серийному номеру. Алгоритмы подсчета разные, но результат должен получиться один. В зависимости эквивалентности этих величин осуществляется переход на вызов соответствующей функции, рисующей окно “Good luck” или “No luck”.
Рассмотрим процедуры 0040137E (вычисление характеристической величины от имени) и 004013D8 (от серийника).
0040137E: 0040137E | 8B7424 04 MOV ESI,DWORD PTR SS:[ARG.1] 00401382 | 56 PUSH ESI 00401383 | 8A06 MOV AL,BYTE PTR DS:[ESI] 00401385 | 84C0 TEST AL,AL 00401387 | 74 13 JE SHORT 0040139C 00401389 | 3C 41 CMP AL,41 0040138B | 72 1F JB SHORT 004013AC 0040138D | 3C 5A CMP AL,5A 0040138F | 73 03 JNB SHORT 00401394 00401391 | 46 INC ESI 00401392 | EB EF JMP SHORT 00401383 00401394 | E8 39000000 CALL 004013D2 00401399 | 46 INC ESI 0040139A | EB E7 JMP SHORT 00401383 0040139C | 5E POP ESI 0040139D | E8 20000000 CALL 004013C2 004013A2 | 81F7 78560000 XOR EDI,00005678 004013A8 | 8BC7 MOV EAX,EDI 004013AA | EB 15 JMP SHORT 004013C1 004013AC | 5E POP ESI 004013AD | 6A 30 PUSH 30 004013AF | 68 60214000 PUSH OFFSET Crackme1.00402160 004013B4 | 68 69214000 PUSH OFFSET Crackme1.00402169 004013B9 | FF75 08 PUSH DWORD PTR SS:[EBP+8] 004013BC | E8 79000000 CALL 004013C1 | C3 RETN
Эта процедура делает следующее:
- Если код какого-либо символа меньше, чем код символа ‘A’, то программа выводит окно “No luck” (даже не проверяя при этом серийный номер).
- Если код символа больше или равен коду символа ‘Z’, то из кода символа вычитается 20h.
- Находится сумма кодов всех символов, к которой применяется операция XOR с числом 5678h. Результат помещается в EAX.
004013D8: 004013D8 | 33C0 XOR EAX,EAX 004013DA | 33FF XOR EDI,EDI 004013DC | 33DB XOR EBX,EBX 004013DE | 8B7424 04 MOV ESI,DWORD PTR SS:[ARG.1] 004013E2 | B0 0A MOV AL,0A 004013E4 | 8A1E MOV BL,BYTE PTR DS:[ESI] 004013E6 | 84DB TEST BL,BL 004013E8 | 74 0B JE SHORT 004013F5 004013EA | 80EB 30 SUB BL,30 004013ED | 0FAFF8 IMUL EDI,EAX 004013F0 | 03FB ADD EDI,EBX 004013F2 | 46 INC ESI 004013F3 | EB ED JMP SHORT 004013E2 004013F5 | 81F7 34120000 XOR EDI,00001234 004013FB | 8BDF MOV EBX,EDI 004013FD | C3 RETN
Эта процедура реализует схему Горнера для перевода десятичного числа, сохраненного в виде строки в шестнадцатеричное, выполняет операцию XOR с числом 1234h и помещает ответ в регистр EBX.
Пишем KeyGen
Итак, зная как работает программа попробуем разработать программу, которая по введенному имени выдаст серийный номер, который подходит для “разблокировки” нашего CrackMe.
Пока упустим некоторые формальности вроде ограничений на введенное имя и сосредоточимся на процессе генерации ключа.
Пусть у нас есть 2 функции: f(s) и g(s), которые считают характеристические величины для строки s соответственно, по алгоритмам приведенным выше, но без применения операции XOR. Следовательно для успешного взлома необходимо, чтобы выполнялось условие
f(name) XOR 5678h = g(key) XOR 1234h
выражаем g(key):
g(key) = f(name) XOR 5678h XOR 1234h
т.к. функция g(s) - функция перевода десятичного числа s, записанного в виде строки в шестнадцатеричное, то можно ввести обратную функцию g’(x), результатом которой будет являться строка, содержащая десятичную запись шестнадцатеричного числа x. Тогда
key = g’(f(name) XOR 5678h XOR 1234h)
это и будет результатом работы программы.
Проверим.
Пусть name = ”AA”.
Тогда f(name) = 82h
f(name) XOR 5678h XOR 1234h = 44CEh
g’(f(name) XOR 5678h XOR 1234h) = “17614”
Попробовав ввести в окно регистрации имя AA и пароль 17614 увидим окно “Good luck”, поздравления и предложение попробовать взломать следующий CrackMe.