CrackMe: CrackMe by taha
Цель: найти пару имя+ключ
Сложность: легко
link: в разделе реверсинг
Решение:
В этой статье я постараюсь максимально подробно описать взлом данного крекми, но если у вас есть замечания/дополнения/ прием не нравится какой-либо (или способ лучше есть), пишите в пм или icq-буду рада конструктивной критике.
Приступим
Итак, загружаем crackme в отладчик
Entry point
Код:
00401185 > B9 C7010000 MOV ECX,1C7
0040118A BB 78104000 MOV EBX,CrackME.00401078
0040118F 21CB AND EBX,ECX
00401191 81F1 FF000000 XOR ECX,0FF
00401197 09F2 OR EDX,ESI
00401199 83C1 4D ADD ECX,4D
0040119C BE 220F4000 MOV ESI,CrackME.00400F22
Видно,что на обычную точку входа это мало похоже. Значит, это начало расшифровки кода. А если быть точнее- выполнение вспомогательных операций. В частности в по окончанию этого фрагмента в ecx окажется количество зашифрованных байт а именно 185h (хекс).
Шифровщик простой - ксорный.
Код:
004011A7 80340E 18 XOR BYTE PTR DS:[ESI+ECX],18 ; с каждым проходом значение уменьшается, двигаемся к началу
Исходя из начальных значений можно посчитать с какого адреса начинается расшифровка кода.
В esi в самом начале цикла 400FFF=>адрес с которого начинается расшифровка=00400FFF+185=401184.
Так как ecx после каждого прохода цикла декрементируется => расшифровываться код начинает с конца.
Самый конец цикла
Код:
004011E2 83EC 01 SUB ESP,1
004011E5 83C4 05 ADD ESP,5
004011E8 ^ FF6424 FC JMP DWORD PTR SS:[ESP-4] ; CrackME.004011A7
Ставим точку останова на этот джамп, в дампе идем по адресу 401000 и наблюдаем за тем,
как расшифровывается код. Пока идет расшифровка кода - управление передается на адрес 004011A7.
В ecx в этот момент считается количество оставшихся байт... Когда код расшифруется- джамп передаст управление на адрес 00401001... Начинаем трассировать.
Вызов функций в уже раскриптованом коде имеет вид, подобный этому
Код:
00401035 . /EB FF JMP SHORT CrackME.00401036
00401037 . 15 C4204000 ADC EAX,<&USER32.DialogBoxParamA>
Интересно... Это значит, что срабатывает джамп JMP SHORT CrackME.00401036 и передает управление на
инструкцию call [адрес] (опкод FF15 хххххххх).
Но это еще не все. Довольно неприятна и такая конструкция
Код:
00401090 . /74 01 JE SHORT CrackME.00401093
00401092 . |3B83 C404E800 CMP EAX,DWORD PTR DS:[EBX+E804C4]
Из-за этого всего оля неправильно анализирует инструкции. Напишем скрипт, исправляющий это дело. Конечно, в данном конкретном случае не вижу особого смысла этого делать. Так как замусоривание не очень страшное и глобальное) Честно говоря, взломала без скрипта, - итак все было вполне понятно=) Если бы не статья- скрипт бы писать, конечно, не стала.
Код:
var nachalo ; все, что начинается с var - объявление переменных
var conez
var str_2
var str_3
mov str_3,00402000 ; куда данные инструкции впихнуть должно быть 5 байт свободно! указать можно и свой адрес
mov nachalo,00401001 ; начало исправляемого кода == entry point
mov conez,004011e8 ; конец исправляемого кода
start:
cmp nachalo,conez ; мы в конце?
je final ; тогда на выход
mov [str_3],[nachalo]
mov str_2,str_3
inc str_2 ; первый байт в последовательности меняющийся, его пропускаем
cmp [str_2],15ffeb ; если последовательность совпадает-идем исправлять
je ispr_call ;если равно- замусоренная инструкция call
cmp [str_2],3b0174 ; если последовательность совпадает-идем исправлять
je ispt_inst
inc nachalo
jmp start ; на начало
final:
ret
ispr_call:
mov [str_2],15ff90 ; нопим злой негодяйский байт
dec str_2
mov [nachalo],[str_2]
inc nachalo ; идем вперед
jmp start ; на начало
ispt_inst:
mov [str_2],909090 ; нопим три паразитных байта
dec str_2
mov [nachalo],[str_2]
inc nachalo ; идем вперед
jmp start ; на начало
Суть в том, что я пробегаюсь по коду и ищу опкоды замусоренных команд. Нехорошо, то, что обе постоянные последовательности имеют размер 3 байта- было бы двойное слово-было б гораздо удобнее. Поэтому приходится хитрить. Недаром буфер должен быть 5 байт. То есть мы читаем очередной дворд, пишем его в буфер, инкрементируем, пропускаем переменный (изменяющийся от инструкции к инструкции) байт и тогда уже сравниваем. Чтобы было понятней, о чем я говорю, рассмотрим все ту же обработанную инструкцию call:
Код:
00401035 . /EB FF JMP SHORT CrackME.00401036
00401037 . 15 C4204000 ADC EAX,<&USER32.DialogBoxParamA>
Итак смотрим первые 4 байта eb,ff,15,0c4.. Посмотрим аналогичную инструкцию
Код:
00401003 /EB FF JMP SHORT CrackME.00401004
00401005 15 82204000 ADC EAX,<&KERNEL32.GetModuleHandleA>
Первые 4 байта eb,ff,15,82. Значит меняется только один байт. Аналогично поступаем с другой замусоренной инструкцией. Потом записываем нопы, декрементируем, возвращаясь на начало буфера и записываем содержимое на место старой команды. И так пока не проштудируем весь код. Остальной мусор (мелочи жизни) вычищается плагином analyse this!. Возможно, способ который я избрала довольно примитивен, так как эта последовательность может принадлежать, например другой инструкции... и тогда мы занопим совсем не то, что надо. Но опять же писала скрипт для этого крекми, объем кода в котором совсем не большой. И для данного
конкретного случая он вполне подходит.
После работы скрипта код выглядит так
Код:
00401023 |> \830424 09 ADD DWORD PTR SS:[ESP],9
00401027 \. C3 RETN
00401028 15 DB 15
00401029 . 6A 00 PUSH 0 ; /lParam = NULL
0040102B . 68 45104000 PUSH CrackME.00401045 ; |DlgProc = CrackME.00401045
00401030 . 6A 00 PUSH 0 ; |hOwner = NULL
00401032 . 6A 25 PUSH 25 ; |pTemplate = 25
00401034 . 50 PUSH EAX ; |hInst
00401035 . 90 NOP ; |
00401036 . FF15 C4204000 CALL DWORD PTR DS:[<&USER32.DialogBoxParamA>] ; \DialogBoxParamA
0040103C . 6A 00 PUSH 0 ; /ExitCode = 0
0040103E . 90 NOP ; |
0040103F . FF15 86204000 CALL DWORD PTR DS:[<&KERNEL32.ExitProcess>] ; \ExitProcess
00401045 . 55 PUSH EBP
Красотища)))))
Теперь поиск всех вызовов имортируемых функций (Search for -> All intermodular call's).
Отлично. Найдены все функции. А их всего ничего
USER32.DialogBoxParamA
kernel32.ExitProcess
USER32.GetDlgItemTextA
USER32.GetDlgItemTextA
SHLWAPI.StrToIntA
USER32.MessageBoxA
USER32.EndDialog
И проанализировано верно)
Что ж. Пора запускать программу. Жмем f9. Высвечивается диалог для ввода имени и пароля. Теперь назначение по крайне мере двух вызовов функции GetDlgItemTextA нам ясно) Первый читает имя, второй-код. Это я узнала, загрузив файл в Restorator и посмотрев идентификаторы эдитов для ввода имени 101 (в хексе -65h), а пароля-102 (66 в хексе). Теперь смотрим на параметры функции GetDlgItemTextA. В первом случае как раз в качестве ид-а идет 65h, а во втором - 66h. После прочтения имени и пароля вызывается функция StrToIntA, принимающая в качестве параметра указатель на строку, которую требуется перевести в число.
В качестве параметра в данном случае-строка с паролем. Из этого можно сделать вывод, что пароль состоит из цифр. Потом берется имя юзера и с ним проводятся следующие манипуляции
Код:
004010B9 . 52 PUSH EDX ;строка с паролем
004010BA . 90 NOP
004010BB . FF15 1E214000 CALL DWORD PTR DS:[<&SHLWAPI.StrToIntA>] ;Великая функция
004010C1 . 31C9 XOR ECX,ECX
004010C3 . 21CB AND EBX,ECX
004010C5 . 8D75 E0 LEA ESI,DWORD PTR SS:[EBP-20] ;строка с именем
004010C8 > 8A1E MOV BL,BYTE PTR DS:[ESI] ;дальше манипуляции с именем
004010CA . 84DB TEST BL,BL
004010CC . 74 08 JE SHORT CrackME.004010D6
004010CE . 31D9 XOR ECX,EBX
004010D0 . C1C1 04 ROL ECX,4
004010D3 . 46 INC ESI
004010D4 .^ EB F2 JMP SHORT CrackME.004010C8
004010D6 > 31DB XOR EBX,EBX
004010D8 . 66:39C1 CMP CX,AX ; ключевое сравнение
004010DB . 0F94C3 SETE BL ; false-true
004010DE . 8B049D F4114000 MOV EAX,DWORD PTR DS:[EBX*4+4011F4]
004010E5 . 35 78563412 XOR EAX,12345678
004010EA . FFE0 JMP EAX
Особое внимание надо уделить инструкции SETE. Данная инструкция работает следующим образом. Сначала перед ней идет сравнение,она анализирует флаги и устанавливает значение своего операнда (bl) логическим значением 0 или 1 (false или true). То есть, если cx будет равен ax- после выполнения инструкции sete в bl окажется "истина", то есть 1. Ну и следующая инструкция напрямую зависит от значения ebx
Код:
004010DE . 8B049D F4114000 MOV EAX,DWORD PTR DS:[EBX*4+4011F4]
Если ebx будет 1 - значение в еах окажется 12744695, потом оно проксорится следующей инструкцией
Код:
004010E5 . 35 78563412 XOR EAX,12345678
и получим адрес перехода... И прыгнем на него инструкцией jmp eax. В хорошем случае прыгаем на 004010ED. Вплохом случае ebx будет равен 0, в eax мы получим 12744721, а после ксора - совершенно другой адрес перехода - 00401159.
И еще. Сначала я, как порядочная, ввела свой семисимвольный ник, но крекми наглым образом проигнорировал этот факт и урезал до 6-ти символов.... Хм( Присмотрелась к функции чтения имени
Код:
00401073 . 6A 07 PUSH 7 ; /Count = 7
00401075 . 8D55 E0 LEA EDX,DWORD PTR SS:[EBP-20] ; |
00401078 . 52 PUSH EDX ; |Buffer
00401079 . 6A 65 PUSH 65 ; |ControlID = 65 (101.)
0040107B . FF75 08 PUSH DWORD PTR SS:[EBP+8] ; |hWnd
0040107E . 90 NOP ; |
0040107F . FF15 C8204000 CALL DWORD PTR DS:[<&USER32.GetDlgItemTextA>] ; \GetDlgItemTextA
00401085 . 6A 00 PUSH 0
Нда. Если бы в count было 8- ник бы прочитался нормально)
В общем далее вникать в суть происходящего дальше не имеет смысла, так как у нас есть функция StrToIntA. Как она работает-известно.
Что должно получится-нам тоже известно - это содержимое регистра cx.
Возьмем, например, число 1111 и переведем его в хекс. Получим 457. Введем 1111 в поле пароль и посмотрим что вернет функция
StrToIntA. А вернет она..... то самое 457... Это означает, что в поле пароля мы должны ввести число, полученое путем
перевода регистра cx в десятичную систему. Все элементарно=)
Вот и все...