Описание: Это тема представляет из себя сборник решений наиболее интерсных задачек(CrackMe). Думаю новичкам и не только будет полезно почитать решения таких людей как hidden, ProTeuS, Ra$cal. ИМХО не писать же из-за пары интересных приёмов статьи. Условие: Не выкладывать решения до прохождения, расчитанным автором crackme, определённого числа людей или истечения срока давности! Формат: Придерживайтесь следующего формата! Code: [b]CrackMe:[/b] ... [b]Цель:[/b] пропатчить/закейгенить/найти верный пароль/... [b]Сложность:[/b] легко/средне/сложно [b]Коментарий:[/b] Ваши комментарии. Если есть. [b]link:[/b] где искать [b]Решение:[/b] Само решение
..::[FaTmiKE 2oo5]::.. CrackMe #2 CrackMe: ..::[FaTmiKE 2oo5]::.. CrackMe #2 Цель: закейгенить Сложность: легко Коментарий: KeyGen писать не будем, только найдём способ генерации. link: http://www.crackmes.de/archive/ Решение: Обходим упаковщик, тк распаковка не воходит в наши планы. Так и не посмотрел какой но судя по pusha - popa - UPX. Два способа обхода этого препятствия: f8 > hr esp > run > hd esp или Ctrl-F > popa > f2 > f9 > f2 > f8 В первую очередь пореверим крякмис на возможность String Reference взлома. Другими словами пороверим наличие в открытом виде таких строк, как "Registred", "good","sucsessfull","Cool","Xaker","Cracker","password". Для этого жмём F10 (эквивалент нажатия на правую клавишу), Search for > All referenced text strings. Ничего полезного((! Поищем функции ввода, вывода, и проставим бряки. Для этого в command bar'е наберём "bpx MessageBoxA". Перед нами предстали все апи. Но MessageBoxA и GetWindowTextA среди них нет. Но где то они вызываться должны!! С этими словами, я полез в Executable modules(Alt-E). Выбираем user32.dll, тк именно там храняться интересующие нас функции, и Ctrl-N. Ищем MessageBoxA, enter (входим в функцию). Ставим бряк на retn 10. Почему? Вдруг автору крякми вздумается украсть пару байт или проверить первый байт на присутствие int 3 (это breackpoint F2, можно так сказать). F9. Вводим что-нибудь и жмём "Go!". Вылетает сообщение, о необходимости регистрации. Жмём Ok и брякаемся на retn 10. F8 и мы вновь в программе. Поднимимся немного выше. Что то маловато кода. Только функции зашифровки/расшифровки текста и MessageBoxA. Code: 004018E0 3E:8B0D 78924000 MOV ECX,DWORD PTR DS:[409278] ; Crackme2.004010F0 004018E7 FFD1 CALL ECX 004018E9 3E:8B0D 80924000 MOV ECX,DWORD PTR DS:[409280] ; Crackme2.00401290 004018F0 FFD1 CALL ECX 004018F2 A1 4C924000 MOV EAX,DWORD PTR DS:[40924C] 004018F7 6A 40 PUSH 40 004018F9 68 0C924000 PUSH Crackme2.0040920C ; ASCII "Information" 004018FE 68 10904000 PUSH Crackme2.00409010 ; ASCII "This function is only available in the registered version!" 00401903 50 PUSH EAX 00401904 3E:8B0D 68924000 MOV ECX,DWORD PTR DS:[409268] ; Crackme2.004080B8 0040190B 3E:FF11 CALL DWORD PTR DS:[ECX] 0040190E 3E:8B0D 78924000 MOV ECX,DWORD PTR DS:[409278] ; Crackme2.004010F0 00401915 FFD1 CALL ECX 00401917 3E:8B0D 80924000 MOV ECX,DWORD PTR DS:[409280] ; Crackme2.00401290 0040191E FFD1 CALL ECX 00401920 C3 RETN Чтож мы пойдём другим путём. Перезапустим программу и доберёмся до точки входа, вобщем опять обходим упаковщик. Проскроллируем ниже, поищем функцию GetModuleHandleA. Code: 00407006 FF15 14804000 CALL DWORD PTR DS:[408014] ; kernel32.GetModuleHandleA 0040700C 50 PUSH EAX 0040700D E8 8EA3FFFF CALL Crackme2.004013A0 После GetModuleHandleA ничего нет, зато идёт функция CALL Crackme2.004013A0 в которой наверняка всё и спрятанно. Заходим в неё. Вот и то, что мы искали: Code: 004013E9 6A 00 PUSH 0 004013EB 68 50144000 PUSH Crackme2.00401450 004013F0 6A 00 PUSH 0 004013F2 6A 65 PUSH 65 004013F4 50 PUSH EAX 004013F5 FF15 A8804000 CALL DWORD PTR DS:[4080A8] ; USER32.DialogBoxParamA push NULL push ADDR DlgProc push NULL push ADDR DlgName push hInstance call DialogBoxParam или более привычный для меня вариант invoke DialogBoxParam, hInstance, ADDR DlgName,NULL,addr DlgProc,NULL Нам нужен адрес функции диалогового окна, DlgProc равный, в нашем случае, 00401450. Отправляемся по нему, Ctrl-G > 00401450. Вот она, DlgProc, во всей красе. Code: 00401450 55 PUSH EBP ; DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 00401451 8BEC MOV EBP,ESP 00401453 83EC 68 SUB ESP,c68 00401456 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C] ; eax <- uMsg 00401459 53 PUSH EBX 0040145A 56 PUSH ESI 0040145B 83E8 10 SUB EAX,10 0040145E 57 PUSH EDI 0040145F 0F84 BC020000 JE Crackme2.00401721 ; WM_CLOSE 00401465 2D 00010000 SUB EAX,100 0040146A 0F84 22020000 JE Crackme2.00401692 ; WM_INITDIALOG 00401470 48 DEC EAX 00401471 74 0B JE SHORT Crackme2.0040147E ; WM_COMMAND 00401473 33C0 XOR EAX,EAX 00401475 5F POP EDI 00401476 5E POP ESI 00401477 5B POP EBX 00401478 8BE5 MOV ESP,EBP 0040147A 5D POP EBP 0040147B C2 1000 RETN 10 ; DlgProc endp Протрссировав функцию, я определил Code: 0040145F 0F84 BC020000 JE Crackme2.00401721 ; WM_CLOSE переходом в случаее, получения uMsg равного WM_CLOSE. Code: 0040146A 0F84 22020000 JE Crackme2.00401692 ; WM_INITDIALOG переходом в случаее, получения uMsg равного WM_INITDIALOG WM_*** - сообщения от Windows, которые должна обработать DlgProc. Code: WM_CLOSE equ 10h WM_INITDIALOG equ 110h WM_COMMAND equ 111h Нас интересует случай когда uMsg равен WM_COMMAND (обработка сообщений нажатия на кнопку, открытия меню и тп). mov eax,wParam wParam - ID кнопки, меню и тп. Code: 0040147E 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10] Равняем по 3e8h (1000d). Code: 00401481 05 18FCFFFF ADD EAX,-3E8 Делаем вывод, что ID кнопок от 1000 до 1009 тк дальше идёт проверка. Code: 00401486 83F8 09 CMP EAX,9 00401489 0F87 F8010000 JA Crackme2.00401687 Code: 0040148F 33C9 XOR ECX,ECX 00401491 8A88 58174000 MOV CL,BYTE PTR DS:[EAX+401758] 00401497 FF248D 44174000 JMP DWORD PTR DS:[ECX*4+401744] ecx*4+xxxxxxxx =)), видимо где то харнятся адреса меток по которым подаётся управление. Но нас это не интересует. Ставим бряк на jmp и жмём f9. Вводим вместо имени и пароля 01234, 56789 соответсвенно. Для удобства все символы разные. Посмотрим, что делается после нажатия "Go!". Мы попадаем на функцию. Code: 004014E3 FF15 58924000 CALL DWORD PTR DS:[409258] ; Crackme2.004018E0 Зайдем в неё, Enter. Чтож это мы уже видели. F9. Теперь попробуем нажать Registred. Брякаемся на всё том же прыжке и переходим по нему. Чтож трейсим)). Ниже встречаются вызовы GetDlgItemTextA. Введёные слова записываются в стек. После второй GetDlgItemTextA встречаем: Code: 0040154C 837D 10 13 CMP DWORD PTR SS:[EBP+10],13 00401550 0F85 31010000 JNZ Crackme2.00401687 Из чего можно сделать вывод, что символов в пароле должо быть 19, тк по ebp+10 хранится размер введёного пароля. Чтож жмём f9 и меняем пароль на 0123456789123456789. И идём дальше. Code: 00401556 8A0D 1C924000 MOV CL,BYTE PTR DS:[40921C] 0040155C B0 2D MOV AL,2D 0040155E 3AC8 CMP CL,AL 00401560 0F85 21010000 JNZ Crackme2.00401687 AL=2D ('-') CL=34 ('4') Из чего можно сделать вывод, что через каждые 4 символа должнен быть "-". Меняем пароль на 0123-4567-8912-3456. Проходим проверки, всё гуд)). Трейсим!! Дальше нам встречается цикл: Code: 00401592 0FBE4C05 98 MOVSX ECX,BYTE PTR SS:[EBP+EAX-68] 00401597 03F1 ADD ESI,ECX 00401599 40 INC EAX 0040159A 3BC2 CMP EAX,EDX 0040159C ^7C F4 JL SHORT Crackme2.00401592 Суммируются некоторые символы. И умноножение на некое DEADBEFF. Code: 0040159E 69F6 EFBEADDE IMUL ESI,ESI,DEADBEEF В esi остаётся своеобразный хэш. Числа будут совпадать не так часто, а число будет строго определённого формата, так что, можно назвать и хэшем. Code: 004015A4 8975 0C MOV DWORD PTR SS:[EBP+C],ESI 004015A7 8B15 44914000 MOV EDX,DWORD PTR DS:[409144] ; Crackme2.004091A4 004015AD FF75 0C PUSH DWORD PTR SS:[EBP+C] 004015B0 8D85 CCFFFFFF LEA EAX,DWORD PTR SS:[EBP-34] 004015B6 52 PUSH EDX 004015B7 50 PUSH EAX 004015B8 3E:8B0D 74924000 MOV ECX,DWORD PTR DS:[409274] ; Crackme2.004080C0 004015BF 3E:FF11 CALL DWORD PTR DS:[ECX] Можно распознать как: Code: push 75AC7566 ; - наш "хэш")) push "%X" ; Unsigned hexadecimal integer push 0012FA38 ; Буфеp для пpиема отфоpматиpованных символов CALL wsprintfA Вобщем эта функция переводит наше hex-число в ASUII строку. Code: 004015C5 8A55 CC MOV DL,BYTE PTR SS:[EBP-34] 004015C8 A0 19924000 MOV AL,BYTE PTR DS:[409219] 004015CD 3AD0 CMP DL,AL 004015CF 0F85 B2000000 JNZ Crackme2.00401687 ... ... 0040162B 8A45 D3 MOV AL,BYTE PTR SS:[EBP-2D] 0040162E 8A0D 29924000 MOV CL,BYTE PTR DS:[409229] 00401634 3AC1 CMP AL,CL 00401636 75 4F JNZ SHORT Crackme2.00401687 Судя по представленному выше коду в пароле нужно заменить 12 на 75, 56 на AC, 91 на 75, 45 на 66. Формируем новый пароль a75b-cACd-e75f-g66h. Ну ещё добавил ab cd ef gh для наглядности. Хотя вместо них можно поставить что угодно. Пробуем пароль. Попадаем в ольку, проходим все прверки. Дальше идут функции GetDlgItem, EnableWindow. Code: ControlID = 3EF (1007.) hWnd = 0048061C ('..::[FaTmiKE 2oo5]::..',class='#32770') CALL GetDlgItem Enable = FALSE hWnd = 00460798 (class='Edit',parent=0044061C) CALL EnableWindow Что значит дезактивация поля. Идём до конца. Нам несколько раз встречаются EnableWindow, что придаёт нам уверенности в правильном ответе)). Жмём F9. И переходим к нашей программе.Поля и кнопка регистрации, как я сказал больше не активны. Момент истины, барабанная дробь)). Жмём "Go!". Мдя... Только для зарегестрированных пользователей! Крякмис всётаки не хочет нас таковыми считать)))! Придётся перезапускаться. Ставим старые breackpoint'ы. Вводим старые имя и пароль. Проходим проверки. И вот, что мы проглядели: Code: 0040164D 890D 58924000 MOV DWORD PTR DS:[409258],ECX Ничего не напоминает? А как вам этот адресок? Code: 004014E3 FF15 58924000 CALL DWORD PTR DS:[409258] ; Crackme2.004018E0 По ходу автор меняет адрес функции вывода о необходимости регистрации новым адресом. Тогда всё понятно)). Пробуем опять нажать "Go!". Вновь брякаемся на jmp. Заходим. Code: 004014E3 FF15 58924000 CALL DWORD PTR DS:[409258] ; Crackme2.004012B0 Как мы и предполагали, содержимое 409258 поменялось на 004012B0. Code: 004012B0 55 PUSH EBP 004012B1 8BEC MOV EBP,ESP 004012B3 83EC 10 SUB ESP,10 004012B6 53 PUSH EBX 004012B7 56 PUSH ESI Что то новое)). Сотрим!! Code: 004012B9 0FBE05 18924000 MOVSX EAX,BYTE PTR DS:[409218] 004012C0 8945 FC MOV DWORD PTR SS:[EBP-4],EAX ... 004012F6 C1E0 08 SHL EAX,8 004012F9 8945 F8 MOV DWORD PTR SS:[EBP-8],EAX 004012FC 8B4D FC MOV ECX,DWORD PTR SS:[EBP-4] 004012FF 0B4D F4 OR ECX,DWORD PTR SS:[EBP-C] 00401302 0B4D F8 OR ECX,DWORD PTR SS:[EBP-8] 00401305 0B4D F0 OR ECX,DWORD PTR SS:[EBP-10] Программа раскладывает наши ab cd по регисистрам, кладёт в стек значения, сдвигает значения на 8, 10, 18 влево. В результате, получая что то вроде этого: xx000000 00yy0000 0000zz00 000000uu Затем с помощью or собирает всё в один регистр ecx xxyyzzuu в нашем случае: ECX 61626364 или abcd ECX сохраняется и тоже самое проделывается с efgh. Далее идёт некая функция зайдём в неё. Code: 00401115 8B15 48914000 MOV EDX,DWORD PTR DS:[409148] По 409148 судя по всему какаято функция, в ecx попадает те самые abcd Code: 00401124 33D1 XOR EDX,ECX РасXORивается значение некой функции и записывается наместо Code: 0040112C 8915 48914000 MOV DWORD PTR DS:[409148],EDX Следующий байт рахоривается по efgh и тд. Встаёт законный вопрос. Где тогда узнать abcdefgh. На это у меня один ответ. Если не устанавливается seh-обработчик, то скорее всего автор сам узнает о правильности прохореных команд. Ничего не остаётся как идти дальше. Выйдя из этой функции мы встречаем ещё одну. В которой расположены следующие строки. Code: 00401776 813D 98914000 E8>CMP DWORD PTR DS:[409198],97BE8 00401780 0F85 32010000 JNZ Crackme2.004018B8 00401786 813D 5C914000 FF>CMP DWORD PTR DS:[40915C],0D8BD1FF 00401790 0F85 22010000 JNZ Crackme2.004018B8 Мдя... Придётся опять перезапускать и проходить до того места где всё расхоривается. Улавиливаете чем хорить байты в 409198 и 40915c. Правильно тем чем сравнивется. итак до расхоривания у нас было: Code: 00409198 A4 MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI] 00409199 2C 4C SUB AL,4C 0040919B 4B DEC EBX 0040919C 54 PUSH ESP 0040915C AB STOS DWORD PTR ES:[EDI] 0040915D 98 CWDE 0040915E C3 RETN 0040915F 5E POP ESI Теперь хорим тем, что должно было быть. Code: 4b4c2ca4 xor 00097be8 ____________ 4b45574c (KEWL) 5ec398ab xor 0d8bd1ff ___________ 53484954 (SHIT) Вставляем в пасс новые буковки)) Password: K75E-WACL-S75H-I66T Проверяем!
hiddCrackMeImp CrackMe: hiddCrackMeImp Цель: найти последовательность действий для регистрации Сложность: средне Решение: Запускаем, видим функцию. Нужно зайти, тк лично у меня по f8 прога упала. Code: 00401BB7 > E8 16000000 CALL HidCreck.00401BD2 Трейсим, функция преобразовывается налицо. И получается следующий цикл который тоже что то расшифровывает. Code: 00401BE2 8D41 FF LEA EAX,DWORD PTR DS:[ECX-1] 00401BE5 83E0 0F AND EAX,0F 00401BE8 8A0428 MOV AL,BYTE PTR DS:[EAX+EBP] 00401BEB 30040E XOR BYTE PTR DS:[ESI+ECX],AL 00401BEE ^E2 F2 LOOPD SHORT HidCreck.00401BE2 Пройдя этот цикл и mov следующий за ним переж нами появится ret. Code: 00401BF3 C3 RETN Пройдя его, мы идём дальше. И после прохода следующей функции Code: 00401009 E8 02040000 CALL HidCreck.00401410 ,мы видим, что подгружаются новые библиотеки. Это даёт нам право предполагать, что внутри лежит загрузчик и импортёр апи. Перезапускаемся и заходим в функцию. Поанализировав её, мы замечаем следующие строки: Code: 0040143E 66:8338 8B CMP WORD PTR DS:[EAX],0FF8B 00401442 75 03 JNZ SHORT HidCreck.00401447 Если первые байты апи mov edi,edi (0FF8B), то они пропускается, что дезориентирует Олю. Просто перебиваем условный на безусловный переход. И ставим бряк на cmp. Жмём f9, в eax появляются загружаемые функции. Ищем что-нибудь подозрительное. И находим))). Code: EAX 77D48666 user32.RegisterHotKey Единственная функция получения данных)). И слава Богу)) Далее я полез в Executable Modules (Ctrl-E) > user32.dll > ctrl-N > RegisterHotKey. Ставим бряк на первый байт. F9. Сработало моментально: Code: 0006F848 00401BB5 /CALL to RegisterHotKey from HidCreck.00401BB0 0006F84C 003204CE |hWnd = 003204CE (class='HiddenClass') 0006F850 00000001 |HotKeyID = 1 0006F854 00000007 |Modifiers = MOD_ALT|MOD_CONTROL|MOD_SHIFT 0006F858 00000075 \Key = VK_F6 Нужно нажать Ctrl-Alt-Shift-F6. Жмём в Olly F9, и полученную комбинацию в окне крякмиса. И снова брякаемся на апи. Code: 0006FD80 00401BB5 /CALL to RegisterHotKey from HidCreck.00401BB0 0006FD84 003204CE |hWnd = 003204CE (class='HiddenClass') 0006FD88 00000001 |HotKeyID = 1 0006FD8C 00000007 |Modifiers = MOD_ALT|MOD_CONTROL|MOD_SHIFT 0006FD90 00000072 \Key = VK_F3 Нужно нажать Ctrl-Alt-Shift-F3. Code: 0006FD80 00401BB5 /CALL to RegisterHotKey from HidCreck.00401BB0 0006FD84 003204CE |hWnd = 003204CE (class='HiddenClass') 0006FD88 00000001 |HotKeyID = 1 0006FD8C 00000007 |Modifiers = MOD_ALT|MOD_CONTROL|MOD_SHIFT 0006FD90 00000077 \Key = VK_F8 Нужно нажать Ctrl-Alt-Shift-F8 Code: 0006FD80 00401BB5 /CALL to RegisterHotKey from HidCreck.00401BB0 0006FD84 003204CE |hWnd = 003204CE (class='HiddenClass') 0006FD88 00000001 |HotKeyID = 1 0006FD8C 00000007 |Modifiers = MOD_ALT|MOD_CONTROL|MOD_SHIFT 0006FD90 00000074 \Key = VK_F5 Нужно нажать Ctrl-Alt-Shift-F5 Ну вот!! registred)) Лично мой путь был более длинным(( Я ставил бряк на CreateWindowExA, искал функцию окна, там нашол сравнение при получении uMsg равного 111h и ток потом до меня допёрло, для чего нужна была эта функция. Я просто не ждал, что регистрация будет проходить так необычно))
First CrackMe by ProTeuS CrackMe: First CrackMe by ProTeuS Цель: найти верный пароль Сложность: средне link: http://www.forum.antichat.ru Решение: Запускаем, вводим чтонить! Брякаемся по 004578AE. Code: 004578AE 21 DB 21 ; CHAR '!' 004578AF A7 DB A7 004578B0 CD FE INT 0FE 004578B2 0D DB 0D Первый вывод - зашифрованная функция. Следовательно в стеке должен лежать адрес возврата (помимо SEH-обработчика). Смотрим в стек. Так оно и есть)). Code: 0012F920 004579C5 RETURN to CrackMe.004579C5 Идём по адресу!! Code: 004579B7 . 64:FF30 PUSH DWORD PTR FS:[EAX] 004579BA . 64:8920 MOV DWORD PTR FS:[EAX],ESP 004579BD . 8D05 AC784500 LEA EAX,DWORD PTR DS:[4578AC] 004579C3 . FFD0 CALL EAX ; <- Вот вызов криптованной функции 004579C5 . 33C0 XOR EAX,EAX 004579C7 . 5A POP EDX Поднимимся выше. Видим интересные функции. Code: 0045796C . E8 3FE5FAFF CALL <JMP.&kernel32.GetCurrentProcess> ; |[GetCurrentProcess 00457971 . 50 PUSH EAX ; |hProcess 00457972 . E8 51E6FAFF CALL <JMP.&kernel32.ReadProcessMemory> ; \ReadProcessMemory ... 004579A0 . E8 8BE6FAFF CALL <JMP.&kernel32.WriteProcessMemory> ; \WriteProcessMemory Начало функции в которую мы попали: Code: 0045793C . 55 PUSH EBP 0045793D . 8BEC MOV EBP,ESP 0045793F . 83C4 F0 ADD ESP,-10 00457942 . 53 PUSH EBX 00457943 . 56 PUSH ESI Ctrl-F2(перезапускаемся). Ctrl-G > 0045793C. Ставим бряк. F9. Вводим чтонить. И брякаемся. Дальше трейсим. Code: 0012F5F8 00457977 /CALL to ReadProcessMemory from CrackMe.00457972 0012F5FC FFFFFFFF |hProcess = FFFFFFFF 0012F600 004578AC |pBaseAddress = 4578AC 0012F604 0045AC03 |Buffer = CrackMe.0045AC03 0012F608 00000001 |BytesToRead = 1 0012F60C 0012F61C \pBytesRead = 0012F61C 4578AC - не далеко от адреса по которому упала прога. Истина гдето рядом)) Code: 0045798D . 3013 XOR BYTE PTR DS:[EBX],DL ____________________________________ DL=01 DS:[0045AC03]=99 Настоящее начало криптованной функции. В 90% случаях функции начинаются с push ebp. Алго криптования нам известен)). 99 xor 55 = 0CCh = 204d Пасс: 204
CrackMe: CrackMe by [Great] Цель: найти верный пароль Сложность: средне Решение: Выложу сам свое решение нормальное Во-первых прога запакована UPX'ом, поэтому быстро распаковываем. Открываем прогу в олли, ждем окончания анализа, запускаем по F5, и.... она закрывается. Причем вместе с олли. Аналогичная фишка и в других отладчиках, например, встроенном в MS Visual Studio. Что-то тут не так Вообще можно обойтись плагином IsDebuggerPresent hide для олли, но ввиду того, что он есть не у всех, мы пойдем влоб) Code: 004010B5 |. B8 01000000 MOV EAX, 1 004010BA |. 40 INC EAX 004010BB |. 68 8C114000 PUSH ucrackme.0040118C 004010C0 |. 64:8B40 FE MOV EAX, DWORD PTR FS:[EAX-2] 004010C4 |. 50 PUSH EAX 004010C5 |. 64:8925 00000>MOV DWORD PTR FS:[0], ESP Это, очевидно, установка SEH, только слегка измененная, чтобы Олли её не опознал, как таковую Дальше идет код, который всегда вызовет исключение нарушения доступа, слегка замаскированный. Поэтому просто ставим бряк на 0040118c и жмем F9(Run). Брякаемся на 40118c. Идет расшифровка строк kernel32.dll и IsDebuggerPresent и запуск этой функции: Code: 0040120F . FF55 FC CALL DWORD PTR [EBP-4] ; kernel32.IsDebuggerPresent Конечно же, без плагина, она возвратит 1. Смело ставим в EAX 0 и двигаемся дальше. Нопим call EnumWindows, т.к. она ищет окно Olly и грохает его. Подходим к DialogBoxParamA. DlgProc устроена так, что после нажатия ОК она возвращает указатель на строку и дропает окно. Поэтому вводим что-нибудь и нажимаем ОК. Видим: Code: 004012DA . 68 90304000 PUSH ucrackme.00403090 ; ASCII "ertwrwe" 004012DF . E8 DC010000 CALL ucrackme.004014C0 004012E4 . 83C4 04 ADD ESP, 4 004012E7 . 8BF0 MOV ESI, EAX 004012E9 . 68 64304000 PUSH ucrackme.00403064 ; ASCII "rqugvPo`ac[mehgvPrduqsnva" 004012EE . E8 CD010000 CALL ucrackme.004014C0 004012F3 . 83C4 04 ADD ESP, 4 004012F6 . 3BF0 CMP ESI, EAX ertwrwe - это введено в поле. Очевидно, это сравнене длин. Смело нопим, чтобы не мешало или просто ставим курсор на инструкцию JE, Ctrl-*, делаем флаг Z=1. Перескакиваем вывод ругательства из-за неправильной длины. Далее идет цикл проверки: Code: 00401365 . 0FBE8A 903040>MOVSX ECX, BYTE PTR [EDX+403090] 0040136C . 8B45 F8 MOV EAX, DWORD PTR [EBP-8] 0040136F . 33D2 XOR EDX, EDX 00401371 . BE 0B000000 MOV ESI, 0B 00401376 . F7F6 DIV ESI 00401378 . 0FBE92 803040>MOVSX EDX, BYTE PTR [EDX+403080] 0040137F . 33CA XOR ECX, EDX 00401381 . 0FBEC1 MOVSX EAX, CL 00401384 . 8B4D F8 MOV ECX, DWORD PTR [EBP-8] 00401387 . 0FBE91 643040>MOVSX EDX, BYTE PTR [ECX+403064] 0040138E . 3BC2 CMP EAX, EDX Самый обычный XOR циклически с ключем. Расшифровывая быстренько правильный пароль, получим super_mega_lamer_password. Перезапускаем, вводим super_mega_lamer_password и Registration completed!
CrackMe #3 by anorganix CrackMe: CrackMe #3 by anorganix Цель: найти верный пароль Сложность: средне link: http://arteam.accessroot.com/releases/ (Crackmes 1 To 8 By Anorganix) Решение: Грузим прогу в Olly! Code: 100A45BC > 9C PUSHFD 100A45BD 60 PUSHAD 100A45BE E8 00000000 CALL CrackMe3.100A45C3 100A45C3 5D POP EBP Это даёт нам понять, что прога чем то запакованна. F8 и ставим бряк на esp (в command bar ввести hr esp). F9 и брякаемся тут: Code: 100A482D 9D POPFD 100A482E -E9 15F3FAFF JMP CrackMe3.10053B48 Удаляем бряк (в command bar ввести hd esp-4). F8 и мы на oep! Мдя o e p.... Code: 10053B48 55 DB 55 ; CHAR 'U' 10053B49 8B DB 8B 10053B4A EC DB EC 10053B4B 83 DB 83 10053B4C C4 DB C4 10053B4D F0 DB F0 10053B4E B8 DB B8 10053B4F 60390510 DD CrackMe3.10053960 10053B53 E8 DB E8 10053B54 6C DB 6C ; CHAR 'l' 10053B55 22 DB 22 ; CHAR '"' 10053B56 FB DB FB 10053B57 FF DB FF 10053B58 E8 DB E8 Меня утешило то, что я знал: 55h - "push ebp", 8BECh - "mov ebp,esp" и тп. А следовательно можно попытаться объяснить это Olly, разуж Ctrl-A не всилах нам помочь. F10 > Analysis > During next analysis ... > command. После третьей команды я опять попробовал нажать Ctrl-A и, о чудо, Olly всё проанализировала как надо!! Code: 10053B48 . 55 PUSH EBP 10053B49 . 8BEC MOV EBP,ESP 10053B4B . 83C4 F0 ADD ESP,-10 10053B4E . B8 60390510 MOV EAX,CrackMe3.10053960 10053B53 . E8 6C22FBFF CALL CrackMe3.10005DC4 10053B58 . E8 73FDFFFF CALL CrackMe3.100538D0 10053B5D . 84C0 TEST AL,AL 10053B5F . 75 09 JNZ SHORT CrackMe3.10053B6A 10053B61 . E8 8EFDFFFF CALL CrackMe3.100538F4 10053B66 . 84C0 TEST AL,AL 10053B68 . 74 05 JE SHORT CrackMe3.10053B6F 10053B6A > E8 3903FBFF CALL CrackMe3.10003EA8 10053B6F > A1 3C500510 MOV EAX,DWORD PTR DS:[1005503C] 10053B74 . 8B00 MOV EAX,DWORD PTR DS:[EAX] 10053B76 . E8 D5D9FFFF CALL CrackMe3.10051550 10053B7B . A1 3C500510 MOV EAX,DWORD PTR DS:[1005503C] 10053B80 . 8B00 MOV EAX,DWORD PTR DS:[EAX] 10053B82 . BA C03B0510 MOV EDX,CrackMe3.10053BC0 ; ASCII "CrackMe #3" 10053B87 . E8 D4D5FFFF CALL CrackMe3.10051160 10053B8C . 8B0D 684F0510 MOV ECX,DWORD PTR DS:[10054F68] ; CrackMe3.10056C00 10053B92 . A1 3C500510 MOV EAX,DWORD PTR DS:[1005503C] 10053B97 . 8B00 MOV EAX,DWORD PTR DS:[EAX] 10053B99 . 8B15 C4320510 MOV EDX,DWORD PTR DS:[100532C4] ; CrackMe3.10053310 10053B9F . E8 C4D9FFFF CALL CrackMe3.10051568 10053BA4 . A1 3C500510 MOV EAX,DWORD PTR DS:[1005503C] Я начал трейсить!! И после Code: 10053B6A > E8 3903FBFF CALL CrackMe3.10003EA8 Прога вылетела! Пришлось проходить всё заново! Беглый анализ функции дал понять, что она проверяла под отладчиком мы или нет! Не долго думая, я решил обойти злощастную функцию! Дойдя до неё, я поменял eip (с помощью New origin here). F9 и прога запустилась и вылетела. Что удивило вылетела в отладчике и норамльно функционировала со мной! Единственное что пришло в голову, так это - пред тем как прога убивает себя она создаёт новый процесс! А с помощь чего можно создать процес? Правильно CreateProcessA! Поищем эту апи. И вот код: Code: 10053657 |. 6A 04 PUSH 4 10053659 |. 6A 00 PUSH 0 1005365B |. 6A 00 PUSH 0 1005365D |. 6A 00 PUSH 0 1005365F |. 8D95 C0FEFFFF LEA EDX,DWORD PTR SS:[EBP-140] 10053665 |. 33C0 XOR EAX,EAX 10053667 |. E8 78F3FAFF CALL crackme3.100029E4 1005366C |. 8B85 C0FEFFFF MOV EAX,DWORD PTR SS:[EBP-140] 10053672 |. E8 E10DFBFF CALL crackme3.10004458 10053677 |. 50 PUSH EAX ; |CommandLine 10053678 |. 6A 00 PUSH 0 ; |ModuleFileName = NULL 1005367A |. E8 3D29FBFF CALL <JMP.&kernel32.CreateProcessA> ; \CreateProcessA 1005367F |. C785 18FFFFFF >MOV DWORD PTR SS:[EBP-E8],10007 10053689 |. 8D85 18FFFFFF LEA EAX,DWORD PTR SS:[EBP-E8] 1005368F |. 50 PUSH EAX ; /pContext 10053690 |. 8B85 0CFFFFFF MOV EAX,DWORD PTR SS:[EBP-F4] ; | 10053696 |. 50 PUSH EAX ; |hThread 10053697 |. E8 F029FBFF CALL <JMP.&kernel32.GetThreadContext> ; \GetThreadContext 1005369C |. 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] 1005369F |. 50 PUSH EAX ; /pBytesRead 100536A0 |. 6A 04 PUSH 4 ; |BytesToRead = 4 100536A2 |. 8D45 F8 LEA EAX,DWORD PTR SS:[EBP-8] ; | 100536A5 |. 50 PUSH EAX ; |Buffer 100536A6 |. 8B45 BC MOV EAX,DWORD PTR SS:[EBP-44] ; | 100536A9 |. 83C0 08 ADD EAX,8 ; | 100536AC |. 50 PUSH EAX ; |pBaseAddress 100536AD |. 8B85 08FFFFFF MOV EAX,DWORD PTR SS:[EBP-F8] ; | 100536B3 |. 50 PUSH EAX ; |hProcess 100536B4 |. E8 7B2AFBFF CALL <JMP.&kernel32.ReadProcessMemory> ; \ReadProcessMemory 100536B9 |. 6A 40 PUSH 40 100536BB |. 68 00300000 PUSH 3000 100536C0 |. 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10] 100536C3 |. 50 PUSH EAX 100536C4 |. 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] 100536C7 |. 8B40 34 MOV EAX,DWORD PTR DS:[EAX+34] 100536CA |. 50 PUSH EAX 100536CB |. 8B85 08FFFFFF MOV EAX,DWORD PTR SS:[EBP-F8] 100536D1 |. 50 PUSH EAX 100536D2 |. E8 BD2AFBFF CALL <JMP.&kernel32.VirtualAllocEx> 100536D7 |. 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] 100536DA |. 50 PUSH EAX ; /pBytesWritten 100536DB |. 8B45 F0 MOV EAX,DWORD PTR SS:[EBP-10] ; | 100536DE |. 50 PUSH EAX ; |BytesToWrite 100536DF |. 8B45 E8 MOV EAX,DWORD PTR SS:[EBP-18] ; | 100536E2 |. 50 PUSH EAX ; |Buffer 100536E3 |. 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] ; | 100536E6 |. 8B40 34 MOV EAX,DWORD PTR DS:[EAX+34] ; | 100536E9 |. 50 PUSH EAX ; |Address 100536EA |. 8B85 08FFFFFF MOV EAX,DWORD PTR SS:[EBP-F8] ; | 100536F0 |. 50 PUSH EAX ; |hProcess 100536F1 |. E8 BE2AFBFF CALL <JMP.&kernel32.WriteProcessMemory> ; \WriteProcessMemory 100536F6 |. 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] 100536F9 |. 50 PUSH EAX ; /pBytesWritten 100536FA |. 6A 04 PUSH 4 ; |BytesToWrite = 4 100536FC |. 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] ; | 100536FF |. 83C0 34 ADD EAX,34 ; | 10053702 |. 50 PUSH EAX ; |Buffer 10053703 |. 8B45 BC MOV EAX,DWORD PTR SS:[EBP-44] ; | 10053706 |. 83C0 08 ADD EAX,8 ; | 10053709 |. 50 PUSH EAX ; |Address 1005370A |. 8B85 08FFFFFF MOV EAX,DWORD PTR SS:[EBP-F8] ; | 10053710 |. 50 PUSH EAX ; |hProcess 10053711 |. E8 9E2AFBFF CALL <JMP.&kernel32.WriteProcessMemory> ; \WriteProcessMemory 10053716 |. 8B45 E4 MOV EAX,DWORD PTR SS:[EBP-1C] 10053719 |. 8B40 34 MOV EAX,DWORD PTR DS:[EAX+34] 1005371C |. 8B55 E4 MOV EDX,DWORD PTR SS:[EBP-1C] 1005371F |. 0342 28 ADD EAX,DWORD PTR DS:[EDX+28] 10053722 |. 8945 C8 MOV DWORD PTR SS:[EBP-38],EAX 10053725 |. 8D85 18FFFFFF LEA EAX,DWORD PTR SS:[EBP-E8] 1005372B |. 50 PUSH EAX ; /pContext 1005372C |. 8B85 0CFFFFFF MOV EAX,DWORD PTR SS:[EBP-F4] ; | 10053732 |. 50 PUSH EAX ; |hThread 10053733 |. E8 342AFBFF CALL <JMP.&kernel32.SetThreadContext> ; \SetThreadContext 10053738 |. 8B85 0CFFFFFF MOV EAX,DWORD PTR SS:[EBP-F4] 1005373E |. 50 PUSH EAX ; /hThread 1005373F |. E8 002AFBFF CALL <JMP.&kernel32.ResumeThread> ; \ResumeThread 10053744 |. 33C0 XOR EAX,EAX 10053746 |. 5A POP EDX Поставив бряк на CreateProcessA, я нажал F9 (не забывайте обходить ту вредную функцию). Брякаемся и вот параметры функции: Code: 0012FC6C 1005367F /CALL to CreateProcessA from crackme3.1005367A 0012FC70 00000000 |ModuleFileName = NULL 0012FC74 00DB3A30 |CommandLine = "C:\crackme3u.exe" 0012FC78 00000000 |pProcessSecurity = NULL 0012FC7C 00000000 |pThreadSecurity = NULL 0012FC80 00000000 |InheritHandles = FALSE 0012FC84 00000004 |CreationFlags = CREATE_SUSPENDED 0012FC88 00000000 |pEnvironment = NULL 0012FC8C 00000000 |CurrentDir = NULL 0012FC90 0012FCC0 |pStartupInfo = 0012FCC0 0012FC94 0012FD04 \pProcessInfo = 0012FD04 Действительно создаёт новый процес и тормозит его! А как же нам попасть туда? Аттачится конечно! Но вот беда, Olly видит процес только после выполнения Code: 1005373F |. E8 002AFBFF CALL <JMP.&kernel32.ResumeThread> ; \ResumeThread А после выполнения этой функции программу на ep нам не застать! Лан жмём F9 в Olly прога вылетает, новый процесс остаётся! Открываем ещё раз Olly (ну чтоб было два процесса)! Аттачимся! Открываем карту памяти и вот что замечаем: Code: Memory map, item 15 Address=00400000 Size=000A0000 (655360.) Owner=CrackMe3 00400000 (itself) Section= Contains=PE header Type=Priv 00021040 Access=RWE Initial access=RWE Новый image base => новый ep, код которого записывается старым процесом. Посмотрим новый EP! Code: 00400068 BC950700 DD 000795BC ; AddressOfEntryPoint = 795BC Ладно всё что нужно мы узнали закрываем окно! Перезапускаем прогу и опять проходим до CreateProcessA. Идём до WriteProcessMemory. И пока не выполняем её! Вот параметры этой функции: Code: 0012FC80 100536F6 /CALL to WriteProcessMemory from crackme3.100536F1 0012FC84 000000A8 |hProcess = 000000A8 (window) 0012FC88 00400000 |Address = 400000 0012FC8C 00D13A24 |Buffer = 00D13A24 0012FC90 000A0000 |BytesToWrite = A0000 (655360.) 0012FC94 0012FDF0 \pBytesWritten = 0012FDF0 Отправляемся по адресу, с которого ведётся запись в новый процесс! 00D13A24 - это image base в новом процессе, а значит по 00D13A24+795BC будет код ep. Отправляемся по 00D8CFE0. Этот код вызвал у меня истерический смех: Code: $+795BC > 9C PUSHFD $+795BD > 60 PUSHAD $+795BE > E8 00000000 CALL 00D8CFE7 $+795C3 > 5D POP EBP $+795C4 > 83ED 07 SUB EBP,7 Судя по всему процесс будет ещё распаковываться =)) Спросите зачем я вас сюда завёл! Смотрите, если в каком-нибудь процессе возникнет исключение, то мы можем поймать его Olly, правильно сконфигурировав её. Итак перебиваем pushfd на int3. Именно int3; не int 3; разница в целый байт! Нам нужен int3 = 0CCh. Теперь конфигурируем Olly. В Options > Just-in-time debugging > Make OllyDbg just-in-time debugger. Теперь когда процесс вызовит исключение, а он вызовит первой же коммандой =), Olly среагирует на это и мы окажемся но ep! F9 и открывается новый процесс Olly! Ура мы на EP! Возвращаем на место pushf и обходим пакер, благо использовался старый! Мдя.. На первый взгляд ничего не изменилось... Code: 00454DFC 55 PUSH EBP 00454DFD 8BEC MOV EBP,ESP 00454DFF 83C4 F0 ADD ESP,-10 00454E02 B8 1C4C4500 MOV EAX,CrackMe3.00454C1C 00454E07 E8 540EFBFF CALL CrackMe3.00405C60 00454E0C E8 7BFDFFFF CALL CrackMe3.00454B8C 00454E11 84C0 TEST AL,AL 00454E13 75 09 JNZ SHORT CrackMe3.00454E1E 00454E15 E8 96FDFFFF CALL CrackMe3.00454BB0 00454E1A 84C0 TEST AL,AL 00454E1C 74 05 JE SHORT CrackMe3.00454E23 00454E1E E8 39EFFAFF CALL CrackMe3.00403D5C 00454E23 A1 98604500 MOV EAX,DWORD PTR DS:[456098] 00454E28 8B00 MOV EAX,DWORD PTR DS:[EAX] 00454E2A E8 71DFFFFF CALL CrackMe3.00452DA0 00454E2F A1 98604500 MOV EAX,DWORD PTR DS:[456098] И та функця проверки осталась. На этот раз мы в неё зайдём, только через enter. И поставим бряк на первый байт! Теперь если туда попадёт управление мы будем об этом знать! "-" и обходим с помощью New origin here! F9. Попадание на ep нам практически ничего не дало! Пытал прогу и на String reference и бряки на апи ставил, естественно на последние байты, но это ничего не дало. ппц! Но у меня в запасе был ещё один приём. Я ввёл dimok и вылетело сообщение о неправильности пароля. В ArtMoney нашёл адреса, в которых лежит это слово. Поставил на них бряки и ещё раз нажал ввод! Бряк сработал мгновенно я оказался внутри какойто функции! Посомотрим на стек, там несколько раз встречается слово dimok. Но мы ищем первый адрес вазврата в секцию кода и находим Code: 0012F45C |00437B10 RETURN to CrackMe3.00437B10 from CrackMe3.00406278 Идём по адресу! Code: 00437B03 50 PUSH EAX 00437B04 8B86 74010000 MOV EAX,DWORD PTR DS:[ESI+174] 00437B0A 50 PUSH EAX 00437B0B E8 68E7FCFF CALL CrackMe3.00406278 ; JMP to USER32.CallWindowProcA 00437B10 8943 0C MOV DWORD PTR DS:[EBX+C],EAX 00437B13 8B03 MOV EAX,DWORD PTR DS:[EBX] 00437B15 83F8 0C CMP EAX,0C 00437B18 75 1B JNZ SHORT CrackMe3.00437B35 00437B1A 8B53 08 MOV EDX,DWORD PTR DS:[EBX+8] 00437B1D 52 PUSH EDX Снимаем старые бряки и поставим новый на 437b1a. Нажмём на кнопочку, и бряк срабатывает! Code: 00437B1A 8B53 08 MOV EDX,DWORD PTR DS:[EBX+8] 00437B1D 52 PUSH EDX 00437B1E 8B4B 04 MOV ECX,DWORD PTR DS:[EBX+4] 00437B21 8BD0 MOV EDX,EAX 00437B23 8BC6 MOV EAX,ESI 00437B25 E8 56B7FFFF CALL CrackMe3.00433280 Один из параметров этой функции адрес Warning. Следовательно функция проверки гдето выше, а то куда мы попали это функции отрисовки MessageBoxA. Начинаем трассировать. В стеке должны храниться адреса строк о неправильности. Мыже находимся в вызываемой функции отрисовки. Короче жмём F8 пока в стеке нам не встретятся строчки о неправильности! Дальше нам встретится Code: 0012F93C 00DB2448 ASCII "dimok" 0012F940 00DB3B90 ASCII "351239814584495632156" 0012F944 00DB23F8 ASCII "651236594485418932153-XNA" 0012F948 00DB2420 ASCII "ANX-351239814584495632156" а после этого следует адрес возврата Code: 0012F950 |00434C7A RETURN to CrackMe3.00434C7A идём по нему и мы окажемся в этой функции, если подняться выше. Code: 004549BC 55 PUSH EBP 004549BD 68 CC4A4500 PUSH CrackMe3.00454ACC 004549C2 64:FF30 PUSH DWORD PTR FS:[EAX] 004549C5 64:8920 MOV DWORD PTR FS:[EAX],ESP 004549C8 8D45 F4 LEA EAX,DWORD PTR SS:[EBP-C] Кароче, если вы её проанализируете, то поймёте, что пароль ANX-351239814584495632156.
Ra$cal KeyGenMe 2 Написал как автор крякми. То есть на что я делал ставку. Ждём солюшена человека, взломавшего сей крякми... Итак. Основная фича этого творения - антиотладка используется с интересной целью. Не просто выдаёт месаджбокс и выходит. А данные получаемые от проверок изменяют внутренний ключ, который участвует в проверке. Таким образом если программа отлаживается и хоть одна проверка обнаружила отладчик, то найденный ключ не подойдёт при запуске программы без отладчика. Теперь про реализацию... Сначала была придумана константа - 0x7DA3EF10. Далее вспомнил какие проверки знаю. Всего решил сделать 5 штук. Соотв решил какие биты в константе должны быть изменены. 3 бита должны быть изменены, 2 должны остаться. 0x7923EF14. Далее для сложности обнаружения обращений к этому дворду (в олли очень просто найти всю работу с конкретным адресом в секции данных. то есть легко локализовать и изменения и использование этой константы) сделал так. Дворд был разбит на 2: ControlDW_L = 0x5ca3ef14 и ControlDW_H = 0xba617923. В сладших словах находятся части константы. А для собсно скрытия обращений сделал ещё две переменные. Итог - static DWORD DummyBefore = 0x547298f0, ControlDW_L = 0x5ca3ef14, ControlDW_H = 0xba617923, DummyAfter = 0xba45910f; Компилятор сделает так что эти переменные в памяти находятся последовательно. Следовательно чтоб обратиться к нужной достаточно получить адрес подставной переменной и и прибавить/удалить 4. Или 8. Вобщем как это работает: Code: _asm{ lea eax, DummyBefore; add eax, 8; mov eax, [eax]; получили ControlDW_H } Дальше... Для реализации идеи пришлось придумывать, как связать возвращаемое значение проверки (причём не просто cmp jnz, а именно то значение, которое возвращает апи(например) при наличии отладчика и при его отсутствии). Так для IsDebuggerPresent задумано, что при отсутствии отладчика он вернёт 0. То есть если вернёт 1 константа будет испорчена... Code: temp = IsDebuggerPresent(); _asm{ lea eax, DummyBefore; add eax, 8; mov ecx, temp; xor [eax], ecx; } Ещё из применённых проверок. Code: 004016D0 >/$ 55 PUSH EBP 004016D1 |. 8BEC MOV EBP,ESP 004016D3 |. 83EC 08 SUB ESP,8 004016D6 |. 8D45 FC LEA EAX,DWORD PTR SS:[EBP-4] 004016D9 |. 50 PUSH EAX ; /pThreadId 004016DA |. 6A 00 PUSH 0 ; |CreationFlags = 0 004016DC |. 6A 00 PUSH 0 ; |pThreadParm = NULL 004016DE |. 68 30164000 PUSH KeyGenMe.CheckMemoryCrc ; |ThreadFunction = KeyGenMe.CheckMemoryCrc 004016E3 |. 6A 00 PUSH 0 ; |StackSize = 0 004016E5 |. 6A 00 PUSH 0 ; |pSecurity = NULL 004016E7 |. FF15 1C104000 CALL NEAR DWORD PTR DS:[<&KERNEL32.CreateThread>] ; \CreateThread 004016ED |. C745 F8 00000000 MOV DWORD PTR SS:[EBP-8],0 004016F4 |. EB 09 JMP SHORT KeyGenMe.004016FF 004016F6 |> 8B4D F8 /MOV ECX,DWORD PTR SS:[EBP-8] 004016F9 |. 83C1 01 |ADD ECX,1 004016FC |. 894D F8 |MOV DWORD PTR SS:[EBP-8],ECX 004016FF |> 817D F8 88130000 CMP DWORD PTR SS:[EBP-8],1388 00401706 |. 7D 30 |JGE SHORT KeyGenMe.00401738 00401708 |. 817D F8 D0070000 |CMP DWORD PTR SS:[EBP-8],7D0 0040170F |. 7E 25 |JLE SHORT KeyGenMe.00401736 00401711 |. 817D F8 C4090000 |CMP DWORD PTR SS:[EBP-8],9C4 00401718 |. 7D 1C |JGE SHORT KeyGenMe.00401736 0040171A |. C705 18214000 01>|MOV DWORD PTR DS:[CanCheck],1 00401724 |. 6A 00 |PUSH 0 ; /Timeout = 0. ms 00401726 |. FF15 18104000 |CALL NEAR DWORD PTR DS:[<&KERNEL32.Sleep>] ; \Sleep 0040172C |. C705 18214000 00>|MOV DWORD PTR DS:[CanCheck],0 00401736 |>^ EB BE \JMP SHORT KeyGenMe.004016F6 00401738 |> 8BE5 MOV ESP,EBP 0040173A |. 5D POP EBP 0040173B \. C3 RETN Вот пример. Цикл в 5000 итераций. Причём с сомнительным содержимым. Тут надежда что поставят бряк на 3 последние команды цикла. А суть простая - Sleep (0) приводит к прекращению обработки текущего потока (его квант процессорного времени обнуляется) и начале работы другого потока процесса (если такой есть). А он, как видим немного выше, как раз создаётся. Как видно из инфы в pdb, функцию я назвал CheckMemoryCrc.Так вот Sleep (0) приведёт к тому, что второй поток будет сработывать при вызове Sleep(0)... Code: 00401630 >/. 55 PUSH EBP 00401631 |. 8BEC MOV EBP,ESP 00401633 |> 813D 14214000 C7>/CMP DWORD PTR DS:[ThreadCounter],0C7 0040163D |. 75 32 |JNZ SHORT KeyGenMe.00401671 0040163F |. 8B0D 28204000 |MOV ECX,DWORD PTR DS:[MemoryCrc] 00401645 |. BA 01000000 |MOV EDX,1 0040164A |. 22D1 |AND DL,CL 0040164C |. C1C9 10 |ROR ECX,10 0040164F |. 22D1 |AND DL,CL 00401651 |. C1C9 08 |ROR ECX,8 00401654 |. 22D1 |AND DL,CL 00401656 |. C1C2 06 |ROL EDX,6 00401659 |. 8D05 18204000 |LEA EAX,DWORD PTR DS:[DummyBefore] 0040165F |. 83C0 08 |ADD EAX,8 00401662 |. C1C2 04 |ROL EDX,4 00401665 |. 3110 |XOR DWORD PTR DS:[EAX],EDX 00401667 |. C705 28204000 00>|MOV DWORD PTR DS:[MemoryCrc],0 00401671 |> 813D 14214000 C8>|CMP DWORD PTR DS:[ThreadCounter],0C8 0040167B |. 7E 0B |JLE SHORT KeyGenMe.00401688 0040167D |. 833D 18214000 01 |CMP DWORD PTR DS:[CanCheck],1 00401684 |. 75 02 |JNZ SHORT KeyGenMe.00401688 00401686 |. EB 40 |JMP SHORT KeyGenMe.004016C8 00401688 |> A1 14214000 |MOV EAX,DWORD PTR DS:[ThreadCounter] 0040168D |. 83C0 01 |ADD EAX,1 00401690 |. A3 14214000 |MOV DWORD PTR DS:[ThreadCounter],EAX 00401695 |. C705 28204000 00>|MOV DWORD PTR DS:[MemoryCrc],0 0040169F |. 833D 14214000 0F |CMP DWORD PTR DS:[ThreadCounter],0F 004016A6 |. 75 05 |JNZ SHORT KeyGenMe.004016AD 004016A8 |. E8 03010000 |CALL KeyGenMe.HRChecker 004016AD |> B9 D0164000 |MOV ECX,KeyGenMe.MemoryCheckFaker 004016B2 |. 8B51 68 |MOV EDX,DWORD PTR DS:[ECX+68] 004016B5 |. 8915 28204000 |MOV DWORD PTR DS:[MemoryCrc],EDX 004016BB |. 6A 00 |PUSH 0 ; /Timeout = 0. ms 004016BD |. FF15 18104000 |CALL NEAR DWORD PTR DS:[<&KERNEL32.Sleep>] ; \Sleep 004016C3 |.^ E9 6BFFFFFF \JMP KeyGenMe.00401633 004016C8 |> 5D POP EBP 004016C9 \. C3 RETN Здесь тоже цикл и Sleep (0). То есть тут просто синхронизированы потоки. Далее. Установка бряка в олли осуществляется просто. В целевой процесс пишут байт 0xCC по нужному адресу. Поэтому я читаю 4 байта с конца функции (куда я ожидаю установки бряка). Code: 004016B2 |. 8B51 68 |MOV EDX,DWORD PTR DS:[ECX+68] 004016B5 |. 8915 28204000 |MOV DWORD PTR DS:[MemoryCrc],EDX Прикинув как выглядят байты 0x8B, 0x5D, 0xC3 и 0xCC я нашёл, что СС - нечётное число, остальные чётные. Соответственно первый бит равен единице, если бряк не установлен, и 0, если установлен. Эта проверка должна изменить константу. Если хоть один бряк установлен бит будет обнулён. Code: 0040163F |. 8B0D 28204000 |MOV ECX,DWORD PTR DS:[MemoryCrc] 00401645 |. BA 01000000 |MOV EDX,1 0040164A |. 22D1 |AND DL,CL 0040164C |. C1C9 10 |ROR ECX,10 0040164F |. 22D1 |AND DL,CL 00401651 |. C1C9 08 |ROR ECX,8 00401654 |. 22D1 |AND DL,CL 00401656 |. C1C2 06 |ROL EDX,6 00401659 |. 8D05 18204000 |LEA EAX,DWORD PTR DS:[DummyBefore] 0040165F |. 83C0 08 |ADD EAX,8 00401662 |. C1C2 04 |ROL EDX,4 00401665 |. 3110 |XOR DWORD PTR DS:[EAX],EDX Плюс ещё есть вызов 004016A8 |. E8 03010000 |CALL KeyGenMe.HRChecker Эта проверка проверят, установлен ли хардбряк. Для этого вызывает исключение и проверяет переданнвый контекст потока в функцию обработчик исключения, в котором было исключение. Отладчик для полноты картины ставит в контекст ВСЕХ потов хард бряки, заданные пользователем в каком-то одном. Чтобы мы не пропустили никаких интересующих нас событий. Следовательно, даже если бряк поставлен в другом потоке, в нашем созданном потоке он тоже будет. Поэтому в контексте можно проверить их наличие. Code: 00401748 |. 33DB XOR EBX,EBX 0040174A |. 0B59 04 OR EBX,DWORD PTR DS:[ECX+4] 0040174D |. 0B59 08 OR EBX,DWORD PTR DS:[ECX+8] 00401750 |. 0B59 0C OR EBX,DWORD PTR DS:[ECX+C] 00401753 |. 0B59 10 OR EBX,DWORD PTR DS:[ECX+10] 00401756 |. 0B59 18 OR EBX,DWORD PTR DS:[ECX+18] 00401759 |. 8D05 24204000 LEA EAX,DWORD PTR DS:[DummyAfter] 0040175F |. 83E8 04 SUB EAX,4 00401762 |. 3118 XOR DWORD PTR DS:[EAX],EBX Эта проверка тоже не должна изменить никакой бит, ибо используется значение регитсров dr, и если хоть 1 задан, то константа опять не будет валидной. Собсно есть ещё проверки, но логика их такая же. Перечислю только их суть - проверка отладочного порта и фича с обработкой CloseHandle с неправильным параметром. Теперь что с алгоритмом. Как я и говорил он простой. Напишу тока код моего генератора Code: DWORD Control = 0x7DA3EF10; void Generate(char* UserName, char* Password) { DWORD Hash = 0; long Summ = 0; DWORD Pass = 0; for (int i = strlen (UserName); i >= 0; i--){ Hash ^= UserName[i]; _asm{ rol Hash, 8; } Summ += UserName[i]; } Pass = (Summ + Hash) ^ Control; _itoa(Pass, Password, 16); int len = strlen (Password); if (len < 8){ int d; d = 8 - len; memcpy (Password + d, Password, len); memset (Password, '0', d); Password [8] = '\0'; } }
CrackME by _taha_ CrackMe: CrackME by _taha_(мой тобишь) Цель: найти верный пароль Сложность: легко link: искать в разделе крякмисы Поскольку на мой кякмис было только три ответа, я решил последовать примеру Rascal’a и написать небольшой солюшен. Сейчас я расскажу о неновых, зато довольно интересных, антиотладочных приёмах затронутых в этом крякмисе. Из множества приёмчиков я отобрал именно эти. Итак начнём... Открываем файл в OllyDbg. Первое, что бросается в глаза не стандартное начало. Это явно не стартап.. Первое что вы увидели это переход, войдя в него - инкремент [ESP], затем стандартное продолжение установки обработчика исключений. Code: 0040103C |. 64:FF35 00000>PUSH DWORD PTR FS:[0] ; | 00401043 |. 64:8925 00000>MOV DWORD PTR FS:[0],ESP ; | Из чего вы должны были сделать вывод, что следующий, после call, байт – это мусор, запутывающий дизассемблер Olly. Поэтому вместо этого Code: call opa db 68h mov eax, [esp+0Ch] inc dword ptr [eax+0B8h] xor edx,edx вы видели это Code: 00401000 >/$ E8 34000000 CALL _CrackME.00401039 00401005 |. 68 8B44240C PUSH 0C24448B 0040100A |. FF80 B8000000 INC DWORD PTR DS:[EAX+B8] Теперь понятно почему я выбрал байт 68h. Olly отказывается верить, что там нет push’а. Далее идёт исключительная ситуация, int3. В результате чего мы попадаем на адрес 401006. Здесь вы должны заметить, что программа проверяет хардваровые бряки. Code: 0040101E |. 66:3115 26304>XOR WORD PTR DS:[403026],DX Если breakpoint’ов нет, то в dx должен быть ноль. Соответсвенно константа [403026] не пострадает. А пока перенесёмся на следущую после int3 команду. Восстанавливается старый обработчик исключний. А уже ненужный кадр стека не затерается как обычно, а увеличивается на 24h, в результате чего в стеке останется указатель на строку “%s%s%s%s”, и используется в качестве параметра к функции OutputDebugStringA. Это exploit, так сказать. OutputDebugStringA отправляет Olly строку “%s%s%s%s”, которую та не может коректно обработать. В результате Olly просто вылетает. Поэтому нужно использовать либо пофикшеные модификации отладчика, либо выравнить стек, а функцию забить нопами. Далее вроде всё тихо, всё за исключением одной функции 0040106C |. E8 6A010000 CALL _CrackME.004011DB Code: mov eax, fs:[30h] ; lea eax, [eax+2] ; Это своеобразный вызов IsDebuggerPresent movzx ebx, byte ptr [eax] ; test ebx, ebx je ex_ xor [00403026],0EA43h ; херю константу ex_: mov byte ptr [eax],90h ret Дальше интересней.. Проверить я проверил, но в подлиности результата в 99% случаев можно сомневаться, тк редкий реверсИр не использует плагин для скрытия. И проблема большенства из них в том, что они патчат не PEB, а функцию IsDebuggerPresent. Естественно API IsDebuggerPresent будет возвращать ноль в любом случае. И это как раз проблема плагина. Я кладу в PEB число 90h и если при следующем вызове IsDebuggerPresent возвратит не 90h, то смело можно предполагать – нас отлаживают. Убедится в этом вы можете, найдя эти строки Code: 004010B9 . E8 4C010000 CALL <JMP.&kernel32.IsDebuggerPresent> ; [IsDebuggerPresent 004010BE . 3D 90000000 CMP EAX,90 004010C3 . 75 09 JNZ SHORT _CrackME.004010CE 004010C5 . 66:8135 28304>XOR WORD PTR DS:[403028],0DEAD Перейдём к GetDlgItemTextA и продолжим исследовать. Дальше идёт вот эта функция 00401145 . E8 3D000000 CALL _CrackME.00401187 Исследовав внутренности этой функции, вы должны были понять, что хэшируется. Но это не всё! Одна из главных “интересностей” крякмиса скрывается ИМЕНО ЗДЕСЬ. Code: 0040118E |. 8B1424 MOV EDX,DWORD PTR SS:[ESP] 00401191 |. 66:8B12 MOV DX,WORD PTR DS:[EDX] Этот код сохранит в dx следующие после CALL _CrackME.00401187 два байта, которые будут учавствовать в хэшировании. И что же тут интересного? – спросит читатель. А дело в том, что когда программа трассируется по F8, Olly ставит на следующий после call байт бряк – int3 и отпускает программу в “свободный полёт”. Когдаже программа “нагуляется” по внутреностям CALL _CrackME.00401187 и перейдёт на следующий байт, Olly всё вернёт на место. Соответсвено после прохождения функции по F7 и вызова её по F8, будут разные результаты. Что повлияет на результат. Далее идёт вызов Int1, что должно повлечь исключительную ситуацию. Фитча в том, что Olly не считает прирывание INT1 исключением если оно идёт с префиксом, а вот Windows считает. Code: 0040114A . F3:64: PREFIX REP: ; Superfluous prefix 0040114C . F1 INT1 Так что, без отладчика мы должны попасть на SEH-обработчик. Итак пойдём на действующий обработчик. Code: 004011B4 /$ 8B4424 0C MOV EAX,DWORD PTR SS:[ESP+C] ; Structured exception handler 004011B8 |. 8B1D 42354000 MOV EBX,DWORD PTR DS:[403542] ; _CrackME.0040114D 004011BE |. 8998 B8000000 MOV DWORD PTR DS:[EAX+B8],EBX 004011C4 |. 8B90 A8000000 MOV EDX,DWORD PTR DS:[EAX+A8] 004011CA |. 80FA CC CMP DL,0CC 004011CD |. 74 09 JE SHORT _CrackME.004011D8 004011CF |. 66:8135 26304>XOR WORD PTR DS:[403026],9696 004011D8 |> 33C0 XOR EAX,EAX 004011DA \. C3 RETN Как вы можете заметить, здесь устанавливается EIP и ещё раз проверяется была ли трассеровка по F8. С анти дебагом всё! Дальше идёт сравнение: Code: 0040114D . 3B05 26304000 CMP EAX,DWORD PTR DS:[403026] 00401153 . 75 13 JNZ SHORT _CrackME.00401168 Как вы могли заметить, [403026] складывается из тех двух word’ов, которые менялись в процессе отладки. Если вы проделали всё правильно, то у вас должно храниться по этому адресу число 2D5253F3h. Теперь о том как найти пароль. Ведь хэш – это необратимое преобразование, следовательно ничего кроме брута вам не остается. Как вы должны были подметить, пароль состоит из 4’х символов. К тому же, как я написал пароль состоит из русских букв. Заметьте это значительно снижает область брута. Приводить код брутера не буду, только дам вам вот эти линки: http://www.reversing.be/article.php?story=20050302175850923&query=bruteforcing http://www.reversing.be/article.php?story=20050414211341413&query=bruteforcing Замечу только, что если всё проделанно верно, то пароль брутится считанные секунды.
CrackMe: CrackMe by taha Цель: найти пару имя+ключ Сложность: легко link: в разделе реверсинг Решение: В этой статье я постараюсь максимально подробно описать взлом данного крекми, но если у вас есть замечания/дополнения/ прием не нравится какой-либо (или способ лучше есть), пишите в пм или icq-буду рада конструктивной критике. Приступим Итак, загружаем crackme в отладчик Entry point Code: 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 (хекс). Шифровщик простой - ксорный. Code: 004011A7 80340E 18 XOR BYTE PTR DS:[ESI+ECX],18 ; с каждым проходом значение уменьшается, двигаемся к началу Исходя из начальных значений можно посчитать с какого адреса начинается расшифровка кода. В esi в самом начале цикла 400FFF=>адрес с которого начинается расшифровка=00400FFF+185=401184. Так как ecx после каждого прохода цикла декрементируется => расшифровываться код начинает с конца. Самый конец цикла Code: 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... Начинаем трассировать. Вызов функций в уже раскриптованом коде имеет вид, подобный этому Code: 00401035 . /EB FF JMP SHORT CrackME.00401036 00401037 . 15 C4204000 ADC EAX,<&USER32.DialogBoxParamA> Интересно... Это значит, что срабатывает джамп JMP SHORT CrackME.00401036 и передает управление на инструкцию call [адрес] (опкод FF15 хххххххх). Но это еще не все. Довольно неприятна и такая конструкция Code: 00401090 . /74 01 JE SHORT CrackME.00401093 00401092 . |3B83 C404E800 CMP EAX,DWORD PTR DS:[EBX+E804C4] Из-за этого всего оля неправильно анализирует инструкции. Напишем скрипт, исправляющий это дело. Конечно, в данном конкретном случае не вижу особого смысла этого делать. Так как замусоривание не очень страшное и глобальное) Честно говоря, взломала без скрипта, - итак все было вполне понятно=) Если бы не статья- скрипт бы писать, конечно, не стала. Code: 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: Code: 00401035 . /EB FF JMP SHORT CrackME.00401036 00401037 . 15 C4204000 ADC EAX,<&USER32.DialogBoxParamA> Итак смотрим первые 4 байта eb,ff,15,0c4.. Посмотрим аналогичную инструкцию Code: 00401003 /EB FF JMP SHORT CrackME.00401004 00401005 15 82204000 ADC EAX,<&KERNEL32.GetModuleHandleA> Первые 4 байта eb,ff,15,82. Значит меняется только один байт. Аналогично поступаем с другой замусоренной инструкцией. Потом записываем нопы, декрементируем, возвращаясь на начало буфера и записываем содержимое на место старой команды. И так пока не проштудируем весь код. Остальной мусор (мелочи жизни) вычищается плагином analyse this!. Возможно, способ который я избрала довольно примитивен, так как эта последовательность может принадлежать, например другой инструкции... и тогда мы занопим совсем не то, что надо. Но опять же писала скрипт для этого крекми, объем кода в котором совсем не большой. И для данного конкретного случая он вполне подходит. После работы скрипта код выглядит так Code: 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, принимающая в качестве параметра указатель на строку, которую требуется перевести в число. В качестве параметра в данном случае-строка с паролем. Из этого можно сделать вывод, что пароль состоит из цифр. Потом берется имя юзера и с ним проводятся следующие манипуляции Code: 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 Code: 004010DE . 8B049D F4114000 MOV EAX,DWORD PTR DS:[EBX*4+4011F4] Если ebx будет 1 - значение в еах окажется 12744695, потом оно проксорится следующей инструкцией Code: 004010E5 . 35 78563412 XOR EAX,12345678 и получим адрес перехода... И прыгнем на него инструкцией jmp eax. В хорошем случае прыгаем на 004010ED. Вплохом случае ebx будет равен 0, в eax мы получим 12744721, а после ксора - совершенно другой адрес перехода - 00401159. И еще. Сначала я, как порядочная, ввела свой семисимвольный ник, но крекми наглым образом проигнорировал этот факт и урезал до 6-ти символов.... Хм( Присмотрелась к функции чтения имени Code: 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 в десятичную систему. Все элементарно=) Вот и все...
Crackmy2 by Xserg CrackMe: Цель: найти верный пароль Сложность: автором позиционируется как "сложно" Коментарий: предупреждаю - буду краток. link: http://ifolder.ru/5872875 Решение: Запускаем крякми... Имя, пароль... Вводим чён-ть... Need good password. Запускаем под Олей - debug detected. Ну что ж, будем лечить. Трассируем программу с начала. Узнаём начало DialogProc 401810. Оставляем бряк после DialogBoxIndirectParamA, так как там мы окажемся после ввода пароля, а так же ставим бряк на саму DialogProc. Пускаем программу (F9) брякаемся, идём - всё пролетает до RETN 10. Поэтому жмём ещё раз F9. Видим - по адресу 401836 создаётся таймер. Смотрим процедуру таймера - ничего интересного она не делает - что-то увеличивает, что-то уменьшает и тп. Значит ясно - нужен для антиотладки. Отключать его нельзя, ибо вероятна проверка на его запущенность. Соображаем... А давайте поставим огромный интервал? Доходим до самого вызова, а дальше в стеке правим в 12FC88 100ms на сколько влезет. Например 0FFFFFFF ms (не забываем писать байты в обратном порядке!). Теперь снимаем бряк с 401810 - ничего нового уже не происходит, происходит обыкновенная прорисовка окна. Посмотрим дальше. Видим взятие имени и пароля. Теперь либо ставим бряк на попадающиеся на глаза команды по адресам типа 40185F - GetDlgItem, то есть те команды, которые мы только что проскачили, трассируя программу до формы, либо ищем эти команды в прыжках на импортируемые функции, видим там Local call from xxxx и ставим бряк на xxxx. F9. Вводи чтон-ть в логин и пасс, нажимаем ок. Брякаемся на GetDlgItem. Трассируем... До EndDialog ничего интересного нет. После EndDialog трассировать бесполезно, поэтому нажимаем F9 и ждём пока сработает наш бряк в 401802. Сработал! Трассируем (заходя во все call'ы)... Видим создание потока. Наверняка тоже для антиотладки! Смотрим ThreadFunction... 40101D... Там что-то страшное... ладно, поставим туда бряк и попробуем запуститься по F9. Трассируя её, смотрим, что у нас там происходит. Проверяется содержимое dword [403063], если там ноль, то выводим сообщение про отладчик, иначе уменьшаем это значение на 1. Сейчас там 1F. А давайте поставим FFFFFFFF. Итак, у нас есть очень много времени, чтобы закончить это крякми. Снимем бряки и запустим его выполняться. (В опциях надо поставить, чтобы все ошибки обрабатывала программа). Ждём-с... Замечательно! Пишет, что пасс неверен, но ничего не говорит про отладчик! Посмотрим на код - в нём появились команды типа MessgeBox, появились слова "need good password" Запомним то место, где мы видим MessageBox(401567), перезапустим программу и, сделав те же манипуляции с таймером и с числом 1F, поставим бряк Memory Breakpoint on write на 401567. Смотрим после каждого бряка, что там. Однажды видим появился call! Смотрим назад - там "Congratulations", а перед этим проверка пароля. Нехитрый анализ показывает, что прыжок в случае неверного пароля идёт с 4014AD. Поставим бряк на предшествующий CMP. Жмём F9... Попадаем как раз туда. теперь осталось собрать пароль Побайтно смотрим буковки из AL... Для имени Ter я получил пароль X43Vr3U6qQ74Up9Q. Закрываем всё, запускаем крякми, вставляем буковки...
CrackMe: crackme_vm by _Great_ Цель: найти ключ попутно закейгенить Сложность: средне link: ссылка Давно я не писал на ачате, пора нарушить обет молчания. Да и есть хороший повод поболтать - крякми с вм. Кто не знает, почему так существенно упоминание о ВМ, послушайте кратенький эпос, в чем сложность взлома таких программ: када вы ломаете обычные программы, вы видите в отладчике команды процессора(мнемокод), видите панель регистров, окно дампа памяти, брекпоинты итп. Вы хорошо знаете набор команд данного процессора и можете легко понять алгоритм по диизасм листингу, отбрасывая кучи полиморфа и мусора. Да, вы профи, и противник знает это, поэтому он идет ва-банк. Единственный способ лишить вас преимущества - играть на другом поле (читай работать на другом процессоре). Чем это грозит - незнакомая структура команд, незнакомые сами команды. Никакой мнемоники - только машкоды виртуального процессора. Никаких доков об архитектуре процессора - только рабочий интерпритатор и кучка пикода. Это все равно что велогонщику дать велосипед с квадратными колесами. Чтобы взломать такую программу вам придется сначала разобраться со структурой команд вм, дальше декодировать опкоды вм, дальше декомпилировать пикод программу, чтобы получить последовательность команд вм в понятном для осознания виде, чтобы из них составить алгоритм работы пикода. А чтобы сделать это, вы должны не просто знать реальный процессор, вы должны еще хорошо соображать и быть самым что ни на есть реверсером. Поэтому защита программ с помощью вм - серьезное препятстсвие для многих крякеров. Кстати мой крякми с вм поломали только пара человек, и то не с ачата =) Поэтому мне эта тема близка, и я покажу, что вм - это не приговор, просто хороший повод понапрягать свой мозг. Ну а теперь к делу. У нас имеется ехе небольшого веса (всего 6кб). Не пожат. Видим создание диалога. Топаем на оконную процедуру - PUSH crackme_.00402534 ; |DlgProc = crackme_.00402534 Убираем анализ кода, т.к. криво вставленные байты сильно пугают анализатор олли. Code: 00402568 PUSH 13 Сюда бряк и поехали. Вводим name - Ra$cal , key - lalala. Заходим в пару колов и стоим тут Code: 0040259E CALL NEAR DWORD PTR DS:[<&NTDLL.sscanf>] ; ntdll.sscanf Это есть функция, которая рабирает строку на переменные по строке-формату. Стмотрим в стек Code: 0006FBB0 00401569 |s = "lalala" 0006FBB4 00402596 |format = "%x" 0006FBB8 0040157D OFFSET <crackme_.iKey> %x - значит будет попытка считать из строки s 16тиричное число и записать его по адресу iKey. Значит ключ - DWORD. Есть вариант его набрутить. Но наша цель - вм. Поэтому отпускаем программу по F9 и вводим нормальное число - BAADF00D. По адресу iKey видим это число. Продолжаем. Code: 004024C3 |. MOV DWORD PTR DS:[<vm_regs>], EAX ; <crackme_.name> 004024C8 |. PUSH EAX ; /String 004024C9 |. CALL NEAR DWORD PTR DS:[<&KERNEL32.lstrlenA>>; \lstrlenA 004024CF |. MOV DWORD PTR DS:[<uname_len>], EAX Здесь кладется в секцию данных указатель на имя. Видно мой лейбл. Но об этом позже. Дальше кладется длина имени. Code: 004024D4 |. MOV EAX, DWORD PTR SS:[EBP+C] 004024D7 |. MOV DWORD PTR DS:[<iKey2>], EAX Здесь сохраняется числовой ключ. Code: 004024DC |. MOV DWORD PTR DS:[401404], E98FB720 004024E6 |. MOV DWORD PTR DS:[401420], 21525253 И две странных константы. Code: 004024F0 |. PUSH crackme_.004014BB 004024F5 |. CALL crackme_.00402474 Спуск в вм. Code: 0040248D |. MOV DWORD PTR DS:[<vm_eip>], EAX ; p_code_addr 00402492 |. MOV DWORD PTR DS:[40142C], OFFSET <crackme_.vm_regs> 0040249C |> /CALL <crackme_.vm_proc> 004024A1 |. |TEST AL, AL 004024A3 |.^ \JE SHORT crackme_.0040249C Это цикл обработки пикода. Как видим, он продолжается, пока vm_proc не вернет число, отличное от 0. Ну а теперь самое веселое - погружение в интерпритатор вм. Поясню, как я пришел к выводу о назначении переменной vm_eip. Code: 004020F2 . MOV ESI, DWORD PTR DS:[<vm_eip>] ; crackme_.004014BB 004020F8 . LODS BYTE PTR DS:[ESI] ; первый байт - опкод Здесь идет чтение байта по адресу, взятому из переменной. Дальше будет считан следующий байт. Чисто логически понимаем, что тут нечего больше считывать, кроме как пикод. Посмотрим на дамп памяти. Code: 004014BB 06 44 00 00 00 00 00 00 004014C3 00 00 03 05 00 00 00 00 004014CB 00 00 00 00 04 25 00 00 004014D3 00 00 00 00 00 00 43 03 004014DB 00 00 00 00 00 00 00 00 004014E3 06 13 00 00 00 00 00 00 004014EB 00 00 04 34 00 00 00 00 004014F3 00 00 00 00 0A 00 00 00 Можете увидеть закономерности? Идут 2 ненулевых байта, затем 8 нулеых. Закономерность устойчива. Далее будут ненулевые байты на месте нулевых, это вполне логично, иначе бы зачем в командах держать пустые поля. Чисто на удачу можно сделать предположение о длине команды - 10 байт. А пока изучим команды, которые обрабатывают считанный пикод: Code: 004020F8 . LODS BYTE PTR DS:[ESI] ; первый байт - опкод. esi += 1 004020F9 . MOV BL, AL 004020FB . MOV CL, AL 004020FD . AND AL, 3F ; сохраняются 6 млабших бит 004020FF . MOV BYTE PTR SS:[EBP-14], AL 00402102 . SHR BL, 7 ; сохраняется старший бит номер 8 00402105 . MOV BYTE PTR SS:[EBP-13], BL ; 8ой бит 00402108 . SHR CL, 6 ; сохраняется бит номер 7 0040210B . AND CL, 1 0040210E . MOV BYTE PTR SS:[EBP-F], CL ; 7ой бит 00402111 . LODS BYTE PTR DS:[ESI] ; второй байт. esi += 1 00402112 . MOV BL, AL 00402114 . AND BL, 0F ; осталяем 4 бита младших 00402117 . SHR AL, 4 ; переходим к следующим 4м битам 0040211A . MOV BYTE PTR SS:[EBP-B], BL ; byte2_4_bits_low 0040211D . MOV BYTE PTR SS:[EBP-A], AL ; byte2_4_bits_high 00402120 . MOV ECX, DWORD PTR DS:[ESI+4] ; следующие 4 байта пропускаются (vm_command+6) 00402123 . PUSH DWORD PTR SS:[EBP-F] 00402126 . CALL <crackme_.decode_cmd_argument> 0040212B . MOV DWORD PTR SS:[EBP-5], EAX ; загрузить в ebp-5 содержимое регистра вм high 0040212E . MOV AL, BYTE PTR SS:[EBP-B] 00402131 . MOV ECX, DWORD PTR DS:[ESI] ; считать дворд по адресу vm_command+2 00402133 . PUSH DWORD PTR SS:[EBP-13] 00402136 . CALL <crackme_.decode_cmd_argument> 0040213B . MOV DWORD PTR SS:[EBP-9], EAX ; загрузить в ebp-9 содержимое регистра вм low BYTE opcode = (*(BYTE*)vm_eip) & 0x3F; // 0x3F = 111111b - сохраняем младшие 6 бит, старшие обнуляем BYTE bit_13 = (*(BYTE*)vm_eip) >> 7; // остался только восьмой бит BYTE bit_f = ((*(BYTE*)vm_eip) >> 6) & 1; // сохранили седьмой бит и сбросили восьмой Визуально первый байт команды раскладывается так: X X XXXXXX | | |_ opcode | |___ bit_f |_____ bit_13 Смотрим обработку второго считанного байта BYTE field_b = (*(BYTE*)vm_eip+1) & 0x0F; // 0x0F = 1111b - сохраняем младшие 4 бита BYTE field_a = (*(BYTE*)vm_eip+1) >> 4; // сохраняем старшие 4 бита итого второй байт делится на биты так: XXXX XXXX | |_ field_b |______ field_a Причем если смотреть на байт, то здесь выполняется отделение первого и второго символов в байте. Например если байт будет равен 0xF5, то разложение даст 0x0F и 0x05. Это упростит нам наблюдение. Так же видим чтение двух двордов здесь 00402120 и здесь 00402131. DWORD dw_1 = *(DWORD*)((BYTE*)vm_eip+6); DWORD dw_2 = *(DWORD*)((BYTE*)vm_eip+2); Теперь к функции decode_cmd_argument Code: 0040203A |. CMP AL, 9 0040203C |. JLE SHORT crackme_.00402055 0040203E |. CMP AL, 0A 00402040 |. JE SHORT crackme_.00402065 00402042 |. CMP AL, 0B 00402044 |. JE SHORT crackme_.0040206C 00402046 |. CMP AL, 0C 00402048 |. JE SHORT crackme_.00402073 0040204A |. PUSH 1 0040204C |. CALL crackme_.00402000 00402051 |. LEAVE 00402052 |. RETN 4 00402055 |> PUSH ECX 00402056 |. MOVZX ECX, AL 00402059 |. MOV EAX, DWORD PTR DS:[ECX*4+401400] 00402060 |. POP ECX 00402061 |. ADD EAX, ECX 00402063 |. JMP SHORT crackme_.00402075 00402065 |> MOV EAX, DWORD PTR DS:[<vm_eip>] 0040206A |. JMP SHORT crackme_.00402075 0040206C |> MOV EAX, DWORD PTR DS:[40142C] 00402071 |. JMP SHORT crackme_.00402075 00402073 |> MOV EAX, ECX 00402075 |> CMP BYTE PTR SS:[EBP+8], 1 00402079 |. JNZ SHORT crackme_.0040207D 0040207B |. MOV EAX, DWORD PTR DS:[EAX] 0040207D |> LEAVE 0040207E \. RETN 4 В al лежит field_a. Проверяется на 9. Если меньше либо равно - идем к получению адреса, используя al как индекс. Итого имеем границу в 10 элементов. На другие джампы пока не смотрим. Дальше к считанному адресу прибавляется содержимое ecx. Ecx задается перед колом - это как раз считанный дворд из команды - dw_1. Code: 00402075 |> CMP BYTE PTR SS:[EBP+8], 1 00402079 |. JNZ SHORT crackme_.0040207D 0040207B |. MOV EAX, DWORD PTR DS:[EAX] Здесь идет проверка байта, который так же передается функции, но через стек (bit_f). Если он = 1 - то идет разыменовывание eax. Иначе eax остается без изменения. Таким образом эта функция использует field_a для вычисления адреса элемента в массиве, и бит bit_f для чтения содержимого этого адреса. Вполне похоже на работу с регистрами вм. Пока мы можем только предполагать - в этом сложность исследований вм. Чтобы выявить закономерность нужно выполнить несколько пикод команд. На выходе из функции имеем следующий момент Code: 0040212B . MOV DWORD PTR SS:[EBP-5], EAX Результат декодирования кладется в переменную. Дальше все то же самое, но для второй половины пикод команды. Результат декодирования пишется сюда Code: 0040213B . MOV DWORD PTR SS:[EBP-9], EAX Для понятливости назовем ebp-5 как r1, а ebp-9 как r2. Дальше начинается кутерьма с проверкой байта ebp-14, который есть 6 бит первого байта пикод команды. Это нам говорит о назначении этих битов - выбор операции. Значит это - код операции. Это можно утверждать достаточно уверенно. У нас код равен шести. Доходим до обработчика Code: 00402231 > CMP BYTE PTR SS:[EBP-14], 6 00402235 . JNZ SHORT crackme_.0040225F 00402237 . MOV EDX, DWORD PTR SS:[EBP-9] 0040223A . XOR EDX, DWORD PTR SS:[EBP-5] 0040223D . MOV ECX, DWORD PTR DS:[ESI] 0040223F . MOV AL, BYTE PTR SS:[EBP-B] 00402242 . PUSH DWORD PTR SS:[EBP-13] 00402245 . CALL <crackme_.save_to_vm_register> Что мы здесь видим. Берется r2, и ксорится с r1. В ecx кладется dw_2(используется при вычислении r2), в al field_b(индекс регистра, адрес которого возвращается в r2), в стек bit_13, то поле, которое используется при вычислении r2 из индекса регистра. Пока все сходится - все поля относятся к одному логическому полю. Code: 00402084 |. PUSH EDI 00402085 |. CMP AL, 9 00402087 |. JLE SHORT crackme_.004020A1 00402089 |. CMP AL, 0A 0040208B |. JE SHORT crackme_.004020B7 0040208D |. CMP AL, 0B 0040208F |. JE SHORT crackme_.004020BF 00402091 |. CMP AL, 0C 00402093 |. JE SHORT crackme_.004020C7 004020A1 |> PUSH ECX 004020A2 |. MOVZX ECX, AL 004020A5 |. LEA EDI, DWORD PTR DS:[ECX*4+401400] 004020AC |. POP ECX 004020AD |. CMP BYTE PTR SS:[EBP+8], 1 004020B1 |. JNZ SHORT crackme_.004020B5 004020B3 |. ADD EDI, ECX 004020B5 |> JMP SHORT crackme_.004020DD 004020B7 |> LEA EDI, DWORD PTR DS:[<vm_eip>] 004020BD |. JMP SHORT crackme_.004020DD 004020BF |> LEA EDI, DWORD PTR DS:[40142C] 004020C5 |. JMP SHORT crackme_.004020DD 004020C7 |> CMP BYTE PTR SS:[EBP+8], 1 004020CB |. JE SHORT crackme_.004020D9 004020D9 |> MOV EDI, ECX 004020DB |. JMP SHORT crackme_.004020E5 004020DD |> CMP BYTE PTR SS:[EBP+8], 1 004020E1 |. JNZ SHORT crackme_.004020E5 004020E3 |. MOV EDI, DWORD PTR DS:[EDI] 004020E5 |> MOV DWORD PTR DS:[EDI], EDX 004020E7 |. POP EDI 004020E8 |. LEAVE 004020E9 \. RETN 4 код похож на предыдующую функцию. так же выборка адреса регистра по индексу. только добавление к адресу поля dw_2 происходит по биту bit_13, так же через этот бит опять идет управление разыменовыванием. Ну а главная строка - 004020E5 MOV DWORD PTR DS:[EDI], EDX. Здесь значение регистра edx пишется по адресу, полученному из индекса и базового адреса. Вобщем, это запись в регистр вм. Все. Выходим, проходим, и вот мы вышли из обработчика. Одна команда выполнена. Теперь надо бы закрепить наши предположения. Начинаем рассматривать вторую команду. код следующий - 03 05. Если наша теория верна, то имеем следующее: 03 - opcode = 3. Биты обнулены. Индекс первого регистра - 0, второй индекс - 5. Это считывание первого регистра. Там лежит наше имя. DS:[00401400]=00401555 (<crackme_.name>), ASCII "Ra$cal" EAX=00000000 Теперь должно будет считывать значение регистра 5. Так ли? Code: DS:[00401414]=0040155B (crackme_.0040155B) EAX=00401505 (crackme_.00401505) 00401400 - базовый адрес регистров. 5*4 - 20 -> 14h. DS:[00401414] - да, наши предположения оправдываются. Теперь ищем обработчик для опкода 3. Code: 004021B0 . MOV EDX, DWORD PTR SS:[EBP-5] 004021B3 . MOV ECX, DWORD PTR DS:[ESI] 004021B5 . MOV AL, BYTE PTR SS:[EBP-B] 004021B8 . PUSH DWORD PTR SS:[EBP-13] 004021BB . CALL <crackme_.save_to_vm_register> В edx кладется r1. А в r1 мы считывали имя. Дальше в al кладется индекс регистра из второго поля - 5. Проверим, запишется ли туда указатель на имя. 004020E5 |> MOV DWORD PTR DS:[EDI], EDX EDX=00401555 (<crackme_.name>), ASCII "Ra$cal" DS:[00401414]=0040155B (crackme_.0040155B) Все верно. Итак, теперь можно заключить о структуре команд Code: 1 23 4 5 6 7 8 XXXXXXXX XX XXXXXX XXXX XXXX XXXXXXXX XXXXXXXX Вот структура команды. Теперь объединим поля логически 1 - пока нам не известно, так что пропускаем 2 - бит, отвечающий за разыменовывание поля 6. типа mod r/m в архитектуре ia32 - modrm2 3 - бит, отвечающий за разыменовывание поля 5. типа mod r/m в архитектуре ia32 - modrm1 4 - код опеации - opcode 5 - индекс регистра, который будет помещен в r1 - rivm1 (register index vm 1) 6 - индекс регистра, который будет помещен в r2 - rivm2 7 - поле, используемое c битом 3 и полем 6 - data2 8 - поле, используемое с битом 2 и полем 5 - data1 Вот мы разобрали команды. Теперь нада разобрать все используемые опкоды. Пока картина следующая 06 - xor 03 - mov Еще важный момент. Результат всегда кладется в r2, т.е. поле rivm2 является указанием как источника операнда, так и указанием получателя результата. Итого мы можем преобразовать пикод уже в уме 004014BB: 06 44 -> xor rvm4, rvm4 ;// обнуляем rvm4 004014C5: 03 05 -> mov rvm5, rvm0 ;// rvm5 теперь содержит указатель на имя Теперь нада упомянуть, что часть из регистров проинициализированны вне интерпритатора вм. Например rvm0 содержит изначально имя. Пока продолжим. Дальше я буду давать дизасм и останавливаться лишь на тех моментах, которые еще не были продебажины (использование modrm1 и modrm2, и использование data1 и data2). Остальные просто приводятся листинг обработчика опкода и декомпилированная команда 004014CF: 04 25 -> add rvm5, rvm2 ;// в rvm2 лежит длина имени. таким образом мы переходим к концу имени - видимо будет использована как проверка конца цикла обработки имени Code: 004021DB . MOV EDX, DWORD PTR SS:[EBP-9] ;edx = r2 004021DE . ADD EDX, DWORD PTR SS:[EBP-5] ;r1 + r2 004014D9: 43 03 -> mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени Вот здесь modrm1 = 1. Смотрим, что изменится при выполнении декодирования rivm1. Code: 00402075 |> CMP BYTE PTR SS:[EBP+8], 1 00402079 |. JNZ SHORT crackme_.0040207D 0040207B |. MOV EAX, DWORD PTR DS:[EAX] DS:[00401555]=63246152 EAX=00401555 (<crackme_.name>), ASCII "Ra$cal" Угу, считываются символы из имени, а не адрес имени. Так что все так, как мы и предполагали. Опкод - 3, то есть mov mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени Code: 004021C0 . CMP BYTE PTR SS:[EBP-13], 1 004021C4 . JE SHORT crackme_.004021D0 004021C6 . CMP BYTE PTR SS:[EBP-B], 0A 004021CA . JE crackme_.00402470 Обсудим эту проверку. Если modrm1 = 0, т.е. мы изменили регистр вм, а не память, на которую указывал регистр, то проверяется регистр. Если индекс регистра = 0x0A, то мы выходим не трогая vm_eip, иначе выполняется переход к следующей команде. Тут следует упомянуть, что у данной вм использованн естественный порядок следования команд, как и у ia32, адрес следующей команды получается добавлением к vm_eip длины команды. Но для реализации условных переходов приходится нарушать это следование, или указывая процессору адрес, на который надо перейти (ia32 не позволяет писать в регистр eip), в данной же вм запись в регистр указатель команды очевидно возможна, и чтобы не испортить адрес перехода, естественное следование предотвращается при записи в vm_eip. Кароче, это просто проверка на случай команд вида jmp или mov vm_eip, чтобы не убить правильный адрес суммированием - пропускается вот этот код 00402467 > > ADD ESI, 8 0040246A . MOV DWORD PTR DS:[<vm_eip>], ESI 004014E3: 06 13 -> xor rvm3, rvm1 В rvm1 лежит константа - E98FB720 004024DC |. MOV DWORD PTR DS:[401404], E98FB720 004014ED: 04 34 -> add rvm4, rvm3 ;// rvm4 обнулили в самом начале, добавляем результат хэша 004014F7: 0A 00 -> inc rvm0 ;// к следующей букве в имени Code: 0040230D . MOV EDX, DWORD PTR SS:[EBP-9] 00402310 . INC EDX Просто инкремент 00401501: 0F 50 cmp rvm0, rvm5 ;// сравнить текущий указатель в имени и указатель на конец имени Code: 00402365 . MOV EAX, DWORD PTR SS:[EBP-9] 00402368 . MOV EBX, DWORD PTR SS:[EBP-5] 0040236B . CMP EAX, EBX А это cmp 0040150B: 14 00 -> jg vm_eip+20 Code: 004023C5 > CMP BYTE PTR SS:[EBP-14], 14 004023C9 . JNZ SHORT crackme_.004023DC 004023CB . CMP BYTE PTR DS:[<LE_F>], 1 004023D2 . JE SHORT crackme_.004023D7 004023D4 . ADD ESI, 0A 004023D7 > JMP <crackme_.lbl_end_command> Jump is taken 004023D7=crackme_.004023D7 Тут проверяется флаг LE. Если он установлен, значит текущий адрес в имени меньше либо равен конца имени. Поэтому выполнится следующая команда. Когда адрес в имени уйдет за конец, выполнится изменение esi, проскочив следующую команду. Т.е. в этой вм условный переход возможен только на 1 команду вперед. Посмотрим, что произойдет дальше. 00401515: 03 CA 00000000 004014D9 mov vm_eip, 004014D9 А вот и использование поля data1. Посмотрим, что произойдет при декодировании первого rivm1 Code: 00402046 |. CMP AL, 0C 00402048 |. JE SHORT crackme_.00402073 00402073 |> MOV EAX, ECX Получается следующая картина. Когда rivm1 = 0x0C, на выход поступит содержимое ecx. А ecx - это есть data1. Итого на eax будет 004014D9 Поле 2 декдируется так тоже не по индексу Code: 0040203E |. CMP AL, 0A 00402040 |. JE SHORT crackme_.00402065 00402065 |> MOV EAX, DWORD PTR DS:[<vm_eip>]
При rivm, равном 0x0A мы получим содержимое vm_eip. Но не это главное. Код операции - 3. А это запись по декодированному rvim2 декодированного rvim1. Т.е. то, что обсуждалось выше - это принудетльное задание адреса следующей команды, эмуляция безусловного перехода с абсолютным адресом. Прокрутите окно выше и вы увидите, куда идет прыжок - 004014D9: 43 03 -> mov rvm3, [rvm0] ;// в rvm3 лежит 4 байта - букв из имени. Т.е. повторение чтения байтов, смещенных на один символ, ксор на константу, и прибавление к rvm4 результата ксора. И так, пока не уйдем за конец строки. Как мы проскочим цикл? Нада поставить бряк на обработчике опкода 0x14, и ждать, когда LE_F будет = 0. Встали тут Code: 004023D4 . ADD ESI, 0A 0040151F: 0F 74 -> cmp rvm4, rvm7 ;// сравнить регистр, в котором сумма хэшей с регистром, в котором введенный номер. rvm4 = 568AA687 00401529: 10 00 jnz vm_eip+20 Code: 004023DC > CMP BYTE PTR SS:[EBP-14], 10 004023E0 . JNZ SHORT crackme_.004023F0 004023E2 . CMP BYTE PTR DS:[<Z_F>], 1 004023E9 . JE SHORT crackme_.004023EE 004023EB . ADD ESI, 0A 004023EE > JMP SHORT <crackme_.lbl_end_command> Проверка флага нуля. У нас не ноль, поэтому проходит через одну команду. Запоминаем это место. 0040153D: 03 C4 00000000 00000000 -> mov rvm4, 0 00401547: 3F 00 -> ret Code: 00402440 > \807D EC 3F CMP BYTE PTR SS:[EBP-14], 3F 00402444 . 75 11 JNZ SHORT crackme_.00402457 00402446 . 83C6 08 ADD ESI, 8 00402449 . 8935 28144000 MOV DWORD PTR DS:[<vm_eip>], ESI 0040244F . 30C0 XOR AL, AL 00402451 . FEC0 INC AL 00402453 . C9 LEAVE 00402454 . C3 RETN При 3F vm_eip правится, и в al кладется 1, что вызовет прекращение работы цикла, выполняющего пикод. Значит это ret. Теперь отпускаем по F9, ставим хард бряк на доступ к байту 00401529 (10), но вводим в поле ключа 568AA687. Считываение проходим, идем до проверки флага. Флаг = 1, и перепрыгивания команды не происходит. Смотрим, какую пикод команду выполнит интерпритатор 00401533: 03 C8 00000000 87148712 -> mov rvm8, 87148712 Дальше опять выполняется обнуление rvm4 и выход. Теперь выходим и топаем медленно, высматривая сравнения Code: 00402501 |. CMP DWORD PTR DS:[401420], 87148712 0040250B |. JNZ SHORT crackme_.0040250E 0040250D |. INC EAX Вот и все. Последний момент - таблица опкодов и алгоритм. Code: 6 - xor r2, r1 3 - mov r2, r1 4 - add r2, r1 A - inc r1; mov r2, r1 F - cmp r2, r1 14 - jg $+0x0A 10 - jnz $+0x0A 3F - ret Code: mov r0, name mov r1, E98FB720 mov r7, key xor r4, r4 mov r5, name add r5, name_len lbl_NEXT_1: mov r3, dword_ptr [r0] xor r3, r1 add r4, r3 inc r0 ;// к следующей букве имени cmp r0, r5 jle lbl_NEXT_1 cmp r4, r7 jnz lbl_EXIT mov r8, 87148712 lbl_EXIT: mov r4, 0 ret Заключение: эта вм не представляет никаких сложностей, т.к. написана прямо и без мусора. Работает она скорее как эмулятор, так как у нее нет специфичных своих команд, мы с легкостью перевели каждую команду вм в команду процессора x86. Нету ни банальной смены логического базиса, ни разложения команд на мелкие, мы смогли на глаз декомпилировать пикод команды, так что это очень хорошая мишень для начинающих. Кстати структура команд очень напоминает мне микрокоманды процессора, для которого в этом семестре в качестве курсового проекта я кодировал микропрограммы в машкодах =) ЗЫ: объемно получилось, ибо разжевывал подробно, чтобы могли понять общий алгоритм и ход действия при отломе вм.