Solutions

Discussion in 'Реверсинг' started by taha, 9 Jan 2007.

  1. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251

    Описание: Это тема представляет из себя сборник решений наиболее интерсных задачек(CrackMe). Думаю новичкам и не только будет полезно почитать решения таких людей как hidden, ProTeuS, Ra$cal. ИМХО не писать же из-за пары интересных приёмов статьи.

    Условие: Не выкладывать решения до прохождения, расчитанным автором crackme, определённого числа людей или истечения срока давности!

    Формат: Придерживайтесь следующего формата!

    Code:
    [b]CrackMe:[/b] ...
    [b]Цель:[/b] пропатчить/закейгенить/найти верный пароль/...
    [b]Сложность:[/b] легко/средне/сложно
    [b]Коментарий:[/b] Ваши комментарии. Если есть.
    [b]link:[/b] где искать
    [b]Решение:[/b] Само решение
     
    6 people like this.
  2. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    ..::[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

    Проверяем!
     
    2 people like this.
  3. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    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 и ток потом до меня допёрло, для чего нужна была эта функция. Я просто не ждал, что регистрация будет проходить так необычно))
     
    1 person likes this.
  4. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    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
     
    1 person likes this.
  5. _Great_

    _Great_ Elder - Старейшина

    Joined:
    27 Dec 2005
    Messages:
    2,032
    Likes Received:
    1,119
    Reputations:
    1,139
    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!
     
    #5 _Great_, 10 Jan 2007
    Last edited: 10 Jan 2007
    4 people like this.
  6. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    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.
     
  7. Ra$cal

    Ra$cal Elder - Старейшина

    Joined:
    16 Aug 2006
    Messages:
    670
    Likes Received:
    185
    Reputations:
    78
    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';
    	}
    }
     
    4 people like this.
  8. taha

    taha Elder - Старейшина

    Joined:
    20 Aug 2006
    Messages:
    399
    Likes Received:
    330
    Reputations:
    251
    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

    Замечу только, что если всё проделанно верно, то пароль брутится считанные секунды.
     
    2 people like this.
  9. 0x0c0de

    0x0c0de Elder - Старейшина

    Joined:
    25 May 2007
    Messages:
    441
    Likes Received:
    396
    Reputations:
    297
    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 в десятичную систему. Все элементарно=)

    Вот и все...
     
    #9 0x0c0de, 3 Aug 2007
    Last edited: 3 Aug 2007
    5 people like this.
  10. desTiny

    desTiny Elder - Старейшина

    Joined:
    4 Feb 2007
    Messages:
    1,006
    Likes Received:
    444
    Reputations:
    94
    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. Закрываем всё, запускаем крякми, вставляем буковки...
     
    #10 desTiny, 25 Mar 2008
    Last edited: 25 Mar 2008
    2 people like this.
  11. Ra$cal

    Ra$cal Elder - Старейшина

    Joined:
    16 Aug 2006
    Messages:
    670
    Likes Received:
    185
    Reputations:
    78
    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>]
     
    2 people like this.
  12. Ra$cal

    Ra$cal Elder - Старейшина

    Joined:
    16 Aug 2006
    Messages:
    670
    Likes Received:
    185
    Reputations:
    78
    При 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. Нету ни банальной смены логического базиса, ни разложения команд на мелкие, мы смогли на глаз декомпилировать пикод команды, так что это очень хорошая мишень для начинающих. Кстати структура команд очень напоминает мне микрокоманды процессора, для которого в этом семестре в качестве курсового проекта я кодировал микропрограммы в машкодах =)
    ЗЫ: объемно получилось, ибо разжевывал подробно, чтобы могли понять общий алгоритм и ход действия при отломе вм.
     
    #12 Ra$cal, 9 Jun 2008
    Last edited: 14 Jun 2008