.. :: ____ _ :: _ ____ _ ____ _ ____ ____\ (________) _/________) _ |_____) \ _____ \\__ _______/ _| /__ | _ \/ /___ / | _ | _ | \ | __// … \\___| \___| \____| \_____| \ … +-------|______//--|_______//---|______//pO!i|_______//-----+ .. ____ _ ____ _ <<:::::::::::::::::::: :: … tEAM-fT … ____| (___\ (_______ ___ ___ ___ ___ ___ ___ ___\ _ _______/__ \\__\\__\\__\\__\\__\\__\ \\___ ___/ | ___// .. … / |__/\___| \ … :: +-----\\______|-------|_______//--+ :: :: TEAM FIFTY THREE TUTORiALS #12 :: _ _ _ _ _ __ _ _ _ | || |___| | | |/ /_ _ (_)__ _| |_| |_ ___ | __ / -_) | | ' <| ' \| / _` | ' \ _(_-< |_||_\___|_|_|_|\_\_||_|_\__, |_||_\__/__/ hellknights.void.ru |___/ .0x48k. . . . . . . . . . . . . . . . . . . . . . . . . . . . . [ Исследование CrackMe#6 by lord_Phoenix - разбор ВМ ] Автор: sotona [1] Что к чему. Привед! Сегодня мы разберем CrackmMe#6 от лорда_феникса. Этот крякми примечателен тем, что в нём для проверки правильности кода присутствует небольшая самодельная виртуальная машина - эмулировано выполнение некоторых важных инструкций, что само собой затрудняет дизассемблирование/отладку. Для удобства анализа используем дизассемблер IDA Pro и отладчик OllyDbg. Начать рекомендую вот с такого места: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401D64 push 1Eh .text:00401D66 push offset reg_name .text:00401D6B push 0Ah .text:00401D6D push dword ptr [ebp+8] .text:00401D70 call GetDlgItemTextA .text:00401D75 cmp eax, 4 .text:00401D78 jnb short loc_401D8D .text:00401D7A push offset aEnter4OrMoreCh ; "Enter 4 or more chars..." .text:00401D7F push 0Bh .text:00401D81 push dword ptr [ebp+8] .text:00401D84 call SetDlgItemTextA .text:00401D89 leave .text:00401D8A retn 10h .text:00401D8D ; --------------------------------------------------------------------------- .text:00401D8D .text:00401D8D loc_401D8D: ; CODE XREF: .text:00401D78j .text:00401D8D cmp eax, 1Eh .text:00401D90 jbe short loc_401DA5 .text:00401D92 push offset aNameTooLong ; "Name too long!" .text:00401D97 push 0Bh .text:00401D99 push dword ptr [ebp+8] .text:00401D9C call SetDlgItemTextA .text:00401DA1 leave .text:00401DA2 retn 10h .text:00401DA5 ; --------------------------------------------------------------------------- .text:00401DA5 .text:00401DA5 loc_401DA5: ; CODE XREF: .text:00401D90j .text:00401DA5 push offset sub_401FA3 .text:00401DAA push large dword ptr fs:0 .text:00401DB1 mov large fs:0, esp .text:00401DB8 pusha .text:00401DB9 push 1Fh .text:00401DBB push offset reg_key .text:00401DC0 push 0Bh .text:00401DC2 push dword ptr [ebp+8] .text:00401DC5 call GetDlgItemTextA .text:00401DCA int 3 ; Trap to Debugger .text:00401DCA ; --------------------------------------------------------------------------- ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Обратите внимание, что метки unk_00406424 и unk_00406438(или как они там у вас зовутся) я переименовал в reg_name и reg_key соответственно(press 'N'). Что здесь происходит? Для начала скажу, что GetDlgItemTextA возвращает в EAX количество прочитанных символов(длину строки reg_name). Видно, что 4 < EAX < 1Fh -- иначе дальше дело не пойдёт. по адресам 00401DA5,00401DAA,00401DB1 начинаются обыкновенные действия по добавлению своего обработчика SEH(в начало однонаправленного списка, которое мы можем наблюдать в [FS:0]). Далее забирается текст из Edit'а в reg_key и... самое интересное. int 3! что само собой вызовет исключение, которое и будет обрабатывать наш новоиспеченный SEH - sub_401FA3, посмотрим же на него... ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401FA3 sub_401FA3 proc near ; DATA XREF: .text:loc_401DA5o .text:00401FA3 .text:00401FA3 var_4 = dword ptr -4 .text:00401FA3 exception_record_= dword ptr 8 .text:00401FA3 frame_ = dword ptr 0Ch .text:00401FA3 context_ = dword ptr 10h .text:00401FA3 dispatch_ = dword ptr 14h .text:00401FA3 .text:00401FA3 push ebp .text:00401FA4 mov ebp, esp .text:00401FA6 pusha .text:00401FA7 mov edx, [ebp+exception_record_] .text:00401FAA mov eax, 1 .text:00401FAF cmp [edx+EXCEPTION_RECORD.ExceptionCode], 80000003h .text:00401FB5 jnz loc_40233A .text:00401FBB mov edi, [ebp+context_] .text:00401FBE mov ebx, [edi+_CONTEXT.ContextFlags] .text:00401FC0 mov esi, [edi+_CONTEXT.Eip] .text:00401FC6 inc esi .text:00401FC7 xor eax, eax .text:00401FC9 mov [edi+_CONTEXT.Dr0], eax .text:00401FCC mov [edi+_CONTEXT.Dr1], eax .text:00401FCF mov [edi+_CONTEXT.Dr2], eax .text:00401FD2 mov [edi+_CONTEXT.Dr3], eax .text:00401FD5 mov [edi+_CONTEXT.Dr6], eax .text:00401FD8 mov [edi+_CONTEXT.Dr7], 155h .text:00401FDF lodsb ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Прежде чем объяснять дальше, я бы попросил переключиться на subview 'Structures' в IDA, нажать там Ins, потом кнопку "Add standart structure" и выбрать там структуры _CONTEXT и EXCEPTION_RECORD - они нам нужны, потому как используются в SEH. Теперь везде, где идёт обращение к подобным данным, мы будем нажимать 'T' и выбирать нужную структуру, как я это сделал, например, по адресам 00401FAF, 00401FBE и 00401FC0. 80000003h - это константа, которую с успехом вы можете посмотреть в хедерах winbase.h и winnt.h, она есть EXCEPTION_BREAKPOINT - исключение именно с таким кодом будет создано, когда прога до этого попала на int3. вот.. далее, если ExceptionCode == EXCEPTION_BREAKPOINT, то обработчик обнуляет Dr-регистры, удаляя тем самым ваши hardware-бряки.. кладёт в ESI текущий EIP(а он после прерывания на 00401DCA конечно же и равен 00401DCA), увеличивает его и ... по адресу 00401FDF он кладёт в al байт, лежащий в byte[ESI], и увеличивает ESI на 1 (команда lodsb )). Забегая вперёд, скажу, что таких прерываний int3 по коду много и после каждого лежит некая структура данных, которая несёт в себе информацию о какой-то одной спижженой инструкции(а в кое-каком случае даже о двух)). Вот первый байт, получаемый первым lodsb - определяет тип команды. вот что идёт сразу после первого int3: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401DCB db 3 ; наверняка у вас на этом месте гора мусора.. .text:00401DCC db 4 ; вооружитесь клавишами 'U' и 'D', чтобы привести в порядок .text:00401DCD db 0 .text:00401DCE db 1 .text:00401DCF dd 0B0h ; а вот почему здесь и ниже DWORD - смотрите обработчик .text:00401DD3 dd 1Eh .text:00401DD7 db 4 .text:00401DD8 dd 0DEAD1219h ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Первая эмулируемая инструкция идёт с типом 3... попробуем угадать, что это такое. Сразу покажу код со своими именами переменных, чтоб легче было понимаь. Вот здесь обрабатываются инструкции с типом 3: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00402071 loc_402071: ; CODE XREF: sub_401FA3+AEj .text:00402071 cmp eax, 3 .text:00402074 jnz loc_40219A ; eax == 3 .text:0040207A lodsb .text:0040207B mov t_cmp, eax .text:00402080 lodsb .text:00402081 mov o1_t, eax .text:00402086 lodsb .text:00402087 mov o2_t, eax .text:0040208C lodsd .text:0040208D mov o1_v, eax .text:00402092 lodsd .text:00402093 mov o2_v, eax .text:00402098 lodsb .text:00402099 mov t_jcc, eax .text:0040209E lodsd .text:0040209F xor eax, 0DEAD1337h .text:004020A4 add eax, [edi+_CONTEXT.Eip] .text:004020AA mov _offs_jmp, eax .text:004020AF mov eax, o1_v .text:004020B4 cmp o1_t, 0 .text:004020BB jnz short loc_4020C0 .text:004020BD mov eax, [eax+edi] .text:004020C0 .text:004020C0 loc_4020C0: ; CODE XREF: sub_401FA3+118j .text:004020C0 cmp t_cmp, 0 .text:004020C7 jnz short loc_4020CB .text:004020C9 mov eax, [eax] .text:004020CB .text:004020CB loc_4020CB: ; CODE XREF: sub_401FA3+124j .text:004020CB cmp t_cmp, 2 .text:004020D2 jnz short loc_4020DB .text:004020D4 mov eax, [eax] .text:004020D6 and eax, 0FFh .text:004020DB .text:004020DB loc_4020DB: ; CODE XREF: sub_401FA3+12Fj .text:004020DB mov ebx, o2_v .text:004020E1 cmp o2_t, 0 .text:004020E8 jnz short loc_4020ED .text:004020EA mov ebx, [ebx+edi] .text:004020ED .text:004020ED loc_4020ED: ; CODE XREF: sub_401FA3+145j .text:004020ED cmp t_cmp, 1 .text:004020F4 jnz short loc_4020F8 .text:004020F6 mov ebx, [ebx] .text:004020F8 .text:004020F8 loc_4020F8: ; CODE XREF: sub_401FA3+151j .text:004020F8 cmp t_cmp, 3 .text:004020FF jnz short loc_402109 .text:00402101 mov ebx, [ebx] .text:00402103 and ebx, 0FFh .text:00402109 .text:00402109 loc_402109: ; CODE XREF: sub_401FA3+15Cj .text:00402109 cmp t_jcc, 1 .text:00402110 jz short loc_40212D .text:00402112 cmp t_jcc, 2 .text:00402119 jz short loc_402147 .text:0040211B cmp t_jcc, 3 .text:00402122 jz short loc_402161 .text:00402124 cmp t_jcc, 4 .text:0040212B jz short loc_40217B .text:0040212D .text:0040212D loc_40212D: ; CODE XREF: sub_401FA3+16Dj .text:0040212D cmp eax, ebx .text:0040212F jz short loc_40213A .text:00402131 add [edi+_CONTEXT.Eip], 12h .text:00402138 jmp short loc_402193 .text:0040213A ; --------------------------------------------------------------------------- .text:0040213A .text:0040213A loc_40213A: ; CODE XREF: sub_401FA3+18Cj .text:0040213A mov eax, _offs_jmp .text:0040213F mov [edi+_CONTEXT.Eip], eax .text:00402145 jmp short loc_402193 .text:00402147 ; --------------------------------------------------------------------------- .text:00402147 .text:00402147 loc_402147: ; CODE XREF: sub_401FA3+176j .text:00402147 cmp eax, ebx .text:00402149 jnz short loc_402154 .text:0040214B add [edi+_CONTEXT.Eip], 12h .text:00402152 jmp short loc_40219A .text:00402154 ; --------------------------------------------------------------------------- .text:00402154 .text:00402154 loc_402154: ; CODE XREF: sub_401FA3+1A6j .text:00402154 mov eax, _offs_jmp .text:00402159 mov [edi+_CONTEXT.Eip], eax .text:0040215F jmp short loc_402193 .text:00402161 ; --------------------------------------------------------------------------- .text:00402161 .text:00402161 loc_402161: ; CODE XREF: sub_401FA3+17Fj .text:00402161 cmp eax, ebx .text:00402163 jb short loc_40216E .text:00402165 add [edi+_CONTEXT.Eip], 12h .text:0040216C jmp short loc_40219A .text:0040216E ; --------------------------------------------------------------------------- .text:0040216E .text:0040216E loc_40216E: ; CODE XREF: sub_401FA3+1C0j .text:0040216E mov eax, _offs_jmp .text:00402173 mov [edi+_CONTEXT.Eip], eax .text:00402179 jmp short loc_402193 .text:0040217B ; --------------------------------------------------------------------------- .text:0040217B .text:0040217B loc_40217B: ; CODE XREF: sub_401FA3+188j .text:0040217B cmp eax, ebx .text:0040217D jnb short loc_402188 .text:0040217F add [edi+_CONTEXT.Eip], 12h .text:00402186 jmp short loc_402193 .text:00402188 ; --------------------------------------------------------------------------- .text:00402188 .text:00402188 loc_402188: ; CODE XREF: sub_401FA3+1DAj .text:00402188 mov eax, _offs_jmp .text:0040218D mov [edi+_CONTEXT.Eip], eax .text:00402193 .text:00402193 loc_402193: ; CODE XREF: sub_401FA3+195j .text:00402193 ; sub_401FA3+1A2j ... .text:00402193 xor eax, eax .text:00402195 jmp loc_40233A .text:0040219A ; --------------------------------------------------------------------------- ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Короче... тип 3 эмулирует пару инструкций - CMP и JCC. Какой именно JCC - определяет переменная t_jcc. Прошу обратить внимание на переменные o1_t и o2_t. Ими я обозначил типы операндов(первого и второго - инструкций с третьим в этой в ВМ нет)). по коду видно, что: если какое-то oX_t равно 0: тогда oX_v означает смещение в структуре _CONTEXT, которая хранит информацию о регистрах. если 1, то: oX_v есть какое-то число(imm32) или адрес. Почему так? изучайте код! я не собираюсь учить вас мыслить, а лишь обозреваю данный крякми... А вот что значат опеределенные значения t_cmp: 0: cmp dword [operand1], operand2 1: cmp operand1, dword [operand2] 2: cmp byte [operand1], operand2 3: cmp operand1, byte [operand2] >=4: cmp operand1, operand2 Для t_jcc: 1: jz 2: jnz 3: jb 4: jnb Итого, вот такая структура вырисовывается для типа 3: struct { BYTE t_cmp; BYTE o1_t, o2_t; DWORD o1_v, o2_v; BYTE t_jcc; DWORD rel_jmp; } Размер этой структуры - 12h байт. Теперь поподробнее о том, как осуществляется переход. rel_jmp вообще слегка зашифрован )) реальный адрес, куда прыгнет jcc высчитывается так: _offs_jmp = (_rel_jmp ^ 0DEAD1337h) + EIP. По адресам 0040212D - 00402138 можно наблюдать эмуляцию перехода jz. Если он выполняется, то EIP в структуре _CONTEXT устанавливается равным _offs_jmp, а если нет, то просто увеличивается на 12h(размер структуры) - снова новый int3 и так далее... Т.е. прога дёргается на int3, читает после него структуру и в соответствии с ней модифицирует регистры/данные, осуществляет условные переходы. Для примера, давайте разберем первую такую инструкцию.. .text:00401DCB db 3 .text:00401DCC db 4 ; t_cmp .text:00401DCD db 0 ; o1_t .text:00401DCE db 1 ; o2_t .text:00401DCF dd 0B0h ; o1_v .text:00401DD3 dd 1Eh ; o2_v .text:00401DD7 db 4 ; t_jcc .text:00401DD8 dd 0DEAD1219h ; rel_jmp o1_t == 0, то есть o1_v - это смещение в структуре _CONTEXT.. Открываем вкладку со структурами и смотрим _CONTEXT, вот кусок оттуда: ... 000000A4 Ebx dd ? 000000A8 Edx dd ? 000000AC Ecx dd ? 000000B0 Eax dd ? 000000B4 Ebp dd ? 000000B8 Eip dd ? ... То есть B0h - имеется в виду регистр eax o2_t == 1, значит o1_v == 1Eh - просто число. Учитывая t_cmp имеем: CMP EAX, 1Eh t_jcc == 4 значит это jnb... только куда? вставьте следующее выражение в виндовый калькулятор calc.exe в 16-ричном режиме: "0DEAD1219^0DEAD1337=+00401DCA=" ..и смотрите результат :)) он равен 401EF8: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401EF8 push offset aTryHarderMate ; "Try harder, mate!" .text:00401EFD push 0Bh .text:00401EFF push dword ptr [ebp+8] .text:00401F02 call SetDlgItemTextA .text:00401F07 pop large dword ptr fs:0 .text:00401F0E add esp, 4 .text:00401F11 leave .text:00401F12 retn 10h ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Думаю понятна суть этих двух инструкций... если длина ключа выше 1Eh == 30, то он точно неправильный )) [2] Дурацкий IDC, который можно долго-долго ругать... Остальные типы команд анализируются легче. Если понять с 3, который есть cmp+jcc, то точно поймутся и остальные. Я не хочу тратить время на их обсуждение. Скажу лишь, что там запрятаны еще mov, add и xor =) Но таких структур много и не раскрывать же каждую силой мозга... поэтому я решил написать небольшой idc-скрипт для этого. Работает так - встаёшь на структуру(например, на 00401DCB), запускаешь скрипт - он напротив её пишем комментарий... Вообще, он тоже полуручной - гораздо лучше было бы автоматизировать - делать поиск этих int 3, но это мне лень было писать - для первого знакомства с этим корявым IDC мне хватило этого: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] static get_oper(o_t, o_v) { if(o_t == 0) { // тут я забил только используемые регистры )) if(o_v == 0x0A4) return "ebx"; if(o_v == 0x0A8) return "edx"; if(o_v == 0x0B0) return "eax"; } else { return form("%08X", o_v); } } static main() { auto _offs_b, _offs, _offs_jmp; auto _id; auto o1_t,o2_t, o1_v,o2_v, o2_s; auto o1, o2; auto t_cmp, t_jcc, t_mov; auto i_1, i_2; Message(" [ VM recovery script for lord_Phoenix\'s crackme 6 ] \n ....by sotona\n"); i_1 = i_2 = ""; _offs_b = _offs = ScreenEA(); _id = Byte(_offs++); if( _id == 3 ) { // 3 is CMP t_cmp = Byte(_offs++); o1_t = Byte(_offs++); o2_t = Byte(_offs++); o1_v = Dword(_offs); _offs = _offs + 4; o2_v = Dword(_offs); _offs = _offs + 4; t_jcc = Byte(_offs++); _offs_jmp = _offs_b + (Dword(_offs)^0x0DEAD1337) - 1; if( t_cmp == 0 ) { o1 = form("dword [%s]", get_oper(o1_t, o1_v)); o2 = get_oper(o2_t, o2_v); } else if(t_cmp == 1) { o1 = get_oper(o1_t, o1_v); o2 = form("dword [%s]", get_oper(o2_t, o2_v)); } else if(t_cmp == 2) { o1 = form("byte [%s]", get_oper(o1_t, o1_v)); o2 = get_oper(o2_t, o2_v); } else if(t_cmp == 3) { o1 = get_oper(o1_t, o1_v); o2 = form("byte [%s]", get_oper(o2_t, o2_v)); } else if(t_cmp == 4) { o1 = get_oper(o1_t, o1_v); o2 = get_oper(o2_t, o2_v); } i_1 = form("cmp %s, %s", o1, o2); if( t_jcc == 1 ) { i_2 = form("je %08X", _offs_jmp); } else if(t_jcc == 2) { i_2 = form("jne %08X", _offs_jmp); } else if(t_jcc == 3) { i_2 = form("jb %08X", _offs_jmp); } else if(t_jcc == 4) { i_2 = form("jnb %08X", _offs_jmp); } } else if( _id == 4 ) { // 4 is mov t_mov = Byte(_offs++); if( Byte(_offs++) == 1) o2_s = "byte"; else o2_s = "dword"; o1_t = Byte(_offs++); o2_t = Byte(_offs++); o1_v = Dword(_offs); _offs = _offs + 4; o2_v = Dword(_offs); _offs = _offs + 4; o1 = get_oper(o1_t, o1_v); if( t_mov == 3) o2 = form("%s %s", o2_s, get_oper(o2_t, o2_v)); else o2 = form("%s [%s]", o2_s, get_oper(o2_t, o2_v)); i_1 = form("mov %s, %s", o1, o2); } else if( _id == 5 ) { // 5 is add o1_v = Dword(_offs); _offs = _offs + 4; o2_v = Dword(_offs); _offs = _offs + 4; o1_t = 0; o2_t = Byte(_offs++); o1 = get_oper(o1_t, o1_v); o2 = get_oper(o2_t, o2_v); i_1 = form("add %s, %s", o1, o2); } else if( _id == 7 ) { // 7 is xor o1_v = Dword(_offs); _offs = _offs + 4; o2_v = Dword(_offs); _offs = _offs + 4; o1_t = 0; o2_t = 0; o1 = get_oper(o1_t, o1_v); o2 = get_oper(o2_t, o2_v); i_1 = form("xor %s, %s", o1, o2); } MakeComm(_offs_b, i_1); if(i_2 != "") MakeComm(_offs_b+1, i_2); Message("scripteng finished!!!"); } ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] В итоге восстановленный код выглядит вот так: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] cmp eax, 1Eh jnb loc_00401EF8 mov ebx, 0 mov eax, offset reg_name loc_00401DF8: ; цикл xor edx, edx mov edx, byte [eax] add ebx, edx add eax, 1 cmp byte [eax], 0 jnz loc_00401DF8 push ebx push offset aU ; "%u" push offset true_key ; true_key - это 00406457 call wsprintfA add esp, 0Ch cmp eax, 1Eh jz loc_00401EF8 mov ebx, offset true_key add ebx, eax mov eax, dword [reg_name] push eax push offset asc_406328 ; "%X" push ebx call wsprintfA add esp, 0Ch mov eax, dword [reg_key] mov ebx, dword [true_key] cmp eax, ebx jnz loc_00401EF8 push offset true_key push offset reg_key call lstrcmpA cmp eax, 0 jnz loc_00401EF8 ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] [3] Самый обычный кейген. Проверьте крякми с именем "sotona" и ключем "6606F746F73" - оно выдаст сообщение "Congratulations, cracker!". Давайте посмотрим, где это происходит... ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401EE2 ; --------------------------------------------------------------------------- .text:00401EE2 push offset aCongratulation ; "Congratulations, cracker!" .text:00401EE7 push 0Bh .text:00401EE9 push dword ptr [ebp+8] .text:00401EEC call SetDlgItemTextA .text:00401EF1 int 3 ; Trap to Debugger ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] Меняем по адресу .00401EE2 "push offset aCongratulation" на "push offset true_key" ("PUSH 406286" на "PUSH 406457") в любом hex-редакторе. Теперь вместо этого сообщения в Edit-бокс будет выводиться правильный ключ )) но это не всё. также вот что: ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401E94 db 4 ; mov eax, dword [00406438] .text:00401E95 db 1 .text:00401E96 db 0 .text:00401E97 db 0 .text:00401E98 db 1 .text:00401E99 dd 0B0h .text:00401E9D dd offset reg_key .text:00401EA1 ; --------------------------------------------------------------------------- .text:00401EA1 int 3 ; Trap to Debugger .text:00401EA1 ; --------------------------------------------------------------------------- .text:00401EA2 db 4 ; mov ebx, dword [00406457] .text:00401EA3 db 1 .text:00401EA4 db 0 .text:00401EA5 db 0 .text:00401EA6 db 1 .text:00401EA7 dd 0A4h .text:00401EAB dd offset true_key .text:00401EAF ; --------------------------------------------------------------------------- .text:00401EAF int 3 ; Trap to Debugger .text:00401EAF ; --------------------------------------------------------------------------- ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] и ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] .text:00401EC1 push offset true_key .text:00401EC6 push offset reg_key .text:00401ECB call lstrcmpA .text:00401ED0 int 3 ; Trap to Debugger .text:00401ED0 ; --------------------------------------------------------------------------- ; [[ /////////////////////////////////////////////////////////////////////////////////////// ]] 00401E9D - здесь заменяем на true_key, и на 401EC6 "push offset reg_key" на "push offset true_key". - это полностью обманывает проверку - правильный ключ сверяется сам с собой. Теперь мы превратили крякми в настоящий кейген к самому себе )) (с) Hell Knights Crew http://hellknights.void.ru/ Автор: sotona