![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Новичку | Ссылки | Программирование | Интервью | Архив | Связь | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Крутая защита или CRACKL@B CrackMe #1Обсудить статью на форумеОчень удобно, когда все крэкерские инструменты, книги и статьи в одном месте. Используйте сборник от EXELAB - вот тут.
Статью написал: Bad_guy В этой защите было применено
1. Защита от String Reference взлома. Защита от String Reference взломаО таком способе взлома своих программ программисты обычно никогда не задумываются. А суть взлома здесь в следующем: в скомпилированной программе содержатся строки типа "Registered", "UNREGISTERED", в общем все, которые программист "вбил" в исходники. Начинающих крэкеров учат ломать проги таким способом: дизассемблируем программу с помощью WinDASM, нажимаем кнопочку String Reference и ищем вот такие подозрительные строки, потом кликаем по подорительной строчке и смотрим откуда она вызывается, смотрим выше, находим jne,jnz,jz или je и меняем на противоположную команду, после чего программа становится зарегистрированной. Как здесь от этого я избавился: сначала написал всю программу, затем скопировал все строковые константы в отдельный текстовый файл, вот такие там были строки: lKeoOIwjk34uGFqi * Name: Serial#: Register About ! Unregistered ! REGISTERED Ошибочка, видимо ключ поддельный. До взлома ещё очень далеко. До взлома ещё далеко. Поднапрягись ! А ключик то поддельный ! Ты ещё ничего не сделал !А потом берём и пишем простую программку: program Project1; {$APPTYPE CONSOLE} uses SysUtils; var f,f1: textfile; s,s1: string; i: integer; begin assignfile(f,'name.txt'); reset(f); assignfile(f1,'name2.txt'); rewrite(f1); while not eof(f) do begin readln(f,s); s1 := ''; for i:= 1 to length(s) do s1 := s1+'cr('+Inttostr(integer(s[i]))+')+'; writeln(f1,s1); end; closefile(f); closefile(f1); end.затем берём получивишийся файл name2.txt и из него вставляем вместо строковых констант в исходники новые строки: cr(78)+cr(97)+cr(109)+cr(101)+cr(58); - Так выглядит слово "Name:" и так далее. И ещё вставляем в код программы процедуру cr: function cr(bb: byte):string; begin cr := chr(bb); end;Вот так легко избавится от String Reference крэкеров-новичков. Использование самомодифицирующегося кода (СМК)
C СМК всё сложнее, тут нам надо будет вмешиваться в скомпилированный файл: procedure Reg; var il2: longint; begin {расшифровщик кода} asm push eax push edx push ecx mov edx, 10 // длина полезного кода (в байтах / 4) (1) mov eax,ilong mov il2,eax add il2,40 //указатель на начало полезного кода (2) @im: mov ecx, [il2] mov eax, [ecx] xor eax, $96969696 mov [ecx], eax add il2, 4 dec edx jnz @im pop ecx pop edx pop eax end; {полезный код} form1.Status.Color := clGreen; form1.Status.Caption := cr(82)+cr(69)+cr(71)+cr(73)+cr(83)+cr(84)+cr(69)+cr(82)+cr(69)+cr(68); end;Константы (1) и (2) пришлось подправить вручную в скомпилированном файле. Также кусок полезного кода был сложен по модулю 2 (xor) с константой 96h, затем я поменял атрибуты секции .code исполняемого файла с помощью программы LordPE на E0000020, чтобы код мог самомодифицироваться, то есть был бы доступен на запись. (...мало кто это поймёт с десятого раза...) Немножко самодельной криптографии (хэш)Вспомним криптографию и сделаем небольшую простенькую хэш функцию: Function Md5(ms: string):int64; var mt,ms2: string; imt: int64; imt1: integer; w: array [0..7] of byte absolute imt; begin mt := cr(108)+cr(75)+cr(101)+cr(111)+cr(79)+cr(73)+cr(119)+cr(106)+cr(107)+cr(51)+cr(52)+cr(117)+cr(71)+cr(70)+cr(113)+cr(105); ms2 := ''; ms2 := ms + cr(42); imt := $1AFD2FDE3ABC4975; for imt1 := 1 to length(mt) do begin w[imt1 mod 8] := (w[imt1 mod 8]*integer(ms2[(imt1 mod length(ms2))+1])) mod 256; end; md5 := imt; end;Не подумайте, что это правда алгоритм MD5 - это простенькая самоделка созданная для возможности реализации электронной подписи и контрольной суммы, а она будет полезна для проверок формата регистрационного кода. Использование регистрационного кода в качестве адреса процедурыО таком способе я писал уже в своей статье "Ещё одна защита ваших shareware программ и как её снять", но теперь могу сказать, что в той статье много смешных для опытного крэкера вещей, хотя сам принцип никуда не делся. Вкратце: Для использование регистрационного кода в качестве адреса процедуры надо чтобы регистрационный код так или иначе нёс в себе константу - адрес процедуры, к тому же придётся залезть в отладчик, а ещё пропатчить скомпилированный исполняемый файл, дабы убрать вызов процедуры вида Call 0040213Dи заменить на вызов вида mov eax, Regaddr Call eaxгде Regaddr это тоже самое число 0040213D, но передаваемое в регистрационном коде. Помнится, что пару лет назад все в своих статья писали, что "на вашем компьютере адрес может быть и другой", но мой опыт в крэкерстве и знания формата PE позволяют мне сделать вывод, что адрес этот на всех компьютерах будет абсолютно одинаков, а даже если это и не так, то есть хитрые функции, которые позволяют узнать содержимое EIP (текущий логический адрес в исполняющемся коде), и тем самым подкорректировать значение RegAddr. Теперь подробно. В данном крэкми было сделано всё так: procedure TForm1.Button1Click(Sender: TObject); var snm : string; inm: integer; begin if length(Edit2.Text) = 20 then begin snm := InttoHex(md5(Edit1.Text),16); for inm := 1 to length(Edit2.Text) do begin if not(Edit2.Text[inm] in ['0'..'9','A'..'F']) then begin Showmessage('До взлома ещё очень далеко.'); exit; end; end; if copy(snm,1,8) <> copy(Edit2.Text,1,8) then begin Showmessage('До взлома ещё далеко.'); exit; end; if Copy(InttoHex(md5(Copy(Edit2.Text,1,16)),16),4,4) <> copy(Edit2.Text,17,4) then begin Showmessage('Поднапрягись !'); exit; end; ilong := StrToInt('$'+Copy(Edit2.Text,1,8)); ilong := ilong xor StrToInt('$'+Copy(Edit2.Text,9,8)); //Проверим правильность адреса. if Copy(InttoHex(md5(IntToHEX(ilong,8)),16),5,8) <> IntToHEx($E8484694,8) then begin Showmessage('А ключик то поддельный !'); ilong := 0; exit; end; ilong := ilong xor $5CD37EA6; end else Showmessage('Ты ещё ничего не сделал !'); end;Переменная ilong после всех проверок будет содержать адрес процедуры, если был введён правильный код, если нет, то ноль. Теперь подробно расскажу алгоритм этой процедуры. Edit1.Text - туда вводится имя пользователя (ИП) Edit2.Text - сюда регистрационный код (РК).Сначала сравниваем длину РК - она должна быть 20 символов. Вычисляем хэш ИП. Затем проверяем, чтобы РК содержал только HEX символьные числа ('0'..'9','A'..'F'). Первые восемь символов РК сравниваем с 8 символами хэша ИП. Вычисляем контрольную сумму первых 16 символов РК и сравниваем ее с 4 последними символами РК. Если все предыдущие проверки прошли успешно, то ксорим первые восемь символов РК, с последующими 8 символами, то есть не символы, а числа, полученные на основе перевода из строкового в числовой формат и помещаем их в переменную, которая и будет являться хранителем адреса процедуры (ilong). Мы дошли до места отмеченного в коде, как //Проверим правильность адреса.Всё, что было до этого в данной процедуре, является ерундой и тренировкой для крэкера-новичка, а теперь уже серьёзные вещи пойдут. Copy(InttoHex(md5(IntToHEX(ilong,8)),16),5,8) - этот код считает хэш получившегося в ilong адреса и возращает его в виде 8-символьной строки. А этот код IntToHEx($E8484694,8) возвращает также 8-символьную строку, но с хэшем правильного кода. Прошу обратить внимание на то, что это ничего не даёт крэкеру, потому как хэш - необратимая функция. Однако здесь есть небольшая слабость защиты. Однако её никто не заметил. Но я всё же расскажу о ней: Если взять переменную $E8484694 и процедуру md5, то можно написать небольшой брутфорсер, то есть меняя ilong подвергать её хэшированию и сравнивать с константой. Вы скажете, что это долго и так далее... нет, вы ошибаетесь. Дело в том, что адресное простраство исполняемого кода - не велико, ну к примеру от 401000 до 44A26C в моём случае, а это - всего лишь 300'000 комбинаций, что брутфорсится очень быстро. Так что я поступил весьма легкомысленно в этом месте кода. Вернёмся к коду и увидим, что при положительном результате проверок он заканчивается следующей строкой, значение которой я поясню ниже. ilong := ilong xor $5CD37EA6;И что же дальше... А дальше надо было бы вызвать процедуру регистрации подобным образом: mov eax, ilong Call eaxно я решил ещё раз поиздеваться над своими коллегами и поставил для этого аж целый таймер, который работал со скважностью 512 миллисекунд. procedure TForm1.Timer2Timer(Sender: TObject); begin Test(ilong); end;Теперь посмотрим процедуру Test: procedure Test(kk: longint); begin if kk = 0 then exit; try asm mov eax,kk // Неявный вызов процедуры Reg; call eax // end; except ilong := 0; ShowMessage('Ошибочка, видимо ключ поддельный.'); end; end;Если адрес нулевой - то выходим из процедуры, а если ненулевой, то пытаемся сделать вызов процедуры, находящейся по этому адресу. Если же крэкер решил обмануть программу и подсунуть другой адрес, то он обломается, потому как программа попадёт на случайный код или вообще не в адресное пространство программы (это опасно, но ведь скока проверок было... так что крэкер рискует сам своими компом) и, естественно, полезет окно с ошибкой, которое мы поймаем с помощью блока try...except и выдадим в except свой обработчик ошибки вместо стандартного окошка. Честно говоря поначалу нужно писать не так: asm mov eax,kk // Неявный вызов процедуры Reg; call eax // end;а вот так: Reg; asm mov eax,kk // Неявный вызов процедуры Reg; call eax // end;Reg откомпилировалось у меня как Call 00449F34 и я заменил это на NOP команды с помощью отладчика и HEX редактора, а число 00449F34 запомнил. Строка ilong := ilong xor $5CD37EA6;в коде процедуры Button1Click позволяет за счёт ксоринья с константой подгонять код до нужного нам 00449F34 без перекомпиляции, которая может нарушить адрес функции. Это делается опять же путём правки этой константы в HEX-редакторе. Что-то ещё...Также был поставлен ещё один таймер, который должен был мешать крэкеру прерываться по hmemcpy. Но он работает у меня с интервалом 666 миллисекунд, а это, как я потом понял, слишком большой интервал и можно успеть прерваться по реальному вызову. Но это скорее что-то типа дополнительного прибамбаса, который вообще-то просто обойти в любом случае, особенно учитывая, что сейчас модным стало использование дизассемблеров WinDASM, IDA и прочих, которые дают крэкеру адрес полезного кода. procedure TForm1.Timer1Timer(Sender: TObject); var stm: string; itm : integer; begin stm := Edit1.Text; // вызов hmemcpy for itm := 1 to length(stm) do stm[itm] := cr(42)[1]; // затираем строку звездочками end;Есть ещё одна хитрость, которая была замечена начинающими крэкерами. Дело в том, что после всех необходимых правок в скомпилированном файле, я его решил еще и запаковать компрессором UPX. Но у меня есть ещё и GUI оболочка для него UPXShell называется. И эта программка позволяет сделать так, чтобы запакованный с помощью UPX файл нельзя было распаковать командой upx -d crackme.exeОднако мне стало интересно, как же это достигается. А достигается это просто: после запаковки UPX файл содержит код 555058210С который выглядит в блокноте, как "UPX!.", а программка UPXShell заменяет последний байт в этом коде на FF, то есть получается 55505821FF и в блокноте "UPX!я". После этого программу не распаковать UPX'ом, но если вручную поменять этот байт обратно на 0C, то всё восстановится и программа распакуется. Как это взломалиПока эту защиту реально смогли взломать 2 человека и ещё третий не до конца аккуратно разобрался, хотя и дошел до функции регистрации. Не буду говорить, что это им далось легко - потребовалась фантазия и нестандартный подход к взлому, именно поэтому не каждый, кто называет себя крэкером и может запускать игрушки "без диска", сможет взломать данную защиту.
Возможно, вам будет интересно почитать обсуждение взлома данного крэкми, которое проводилось на
форуме CRACKL@B, поэтому не поленюсь привести его здесь:
В Общем, все из проделанных способов взлома были возможны либо по моей оплошности и недоделке (в случае метода Hex'a) , либо исходя из оставленной лазейки (самомодифицирующийся код был слабо зашифрован), а также, в основном из-за того, что я давал достаточно конкретные намёки на то, как сделана защита. Что нужно доделатьВ принципе, даже использование регистрационного кода в качестве адреса не даёт большой гарантии стойкости защиты. Поэтому вижу только один весьма надёжный трюк - зашифровать самомодифицирующийся код, доступный только в зарегистрированном варианте программы, используя надёжные криптографические алгоритмы, такие как: RC6, DES, Blowfish. Таким образом можно свести вероятность взлома программы практически к нулю. Хотя, лично я думаю, что нет надобности в такой стойкости защиты какой бы то ни было программы. ЗаключениеПока я писал эту статью я понял, что почти не существует таких программистов, которые справятся с созданием такой защиты... так что, по-видимому это "академичная" защита, которую в реальности делать никто не станет. Удачи вам в вашей деятельности, чем бы вы ни занимались, Bad_guy. Комментарии к статье: Крутая защита или CRACKL@B CrackMe #1 elishe 13.03.2006 12:59:57 прикольно. я сперва сломала, прогу - потом нашла описание взлома от dragon'a - делала также. правда долго искала вызов на функцию расшифровки, но потом забросила и подогнала код elishe D84ACEF0919559ACE136 --- Wyfinger 05.05.2008 12:16:52 Потратил два дня на взлом, поначалу ничего не получалось, но потом все стало проясняться. Сначала я сделал патч:распаковал UPX, и подправил в месте перехода после последней проверки (все предыдущие проверки легко просчитываются и у меня был код-заготовка, который проходил все проверки, кроме последней и вываливался на "Поднапрягись") и вместо XOR 5CD37EA6 поставил MOV 00449F34 (адрес Reg). Да, найти процедуру Reg было довольно сложно, сначала я искал по вызовам TControl.SetText, но потом прочитал, что используется шифрование кода. Мой код: Solitary / F46ACC58BDB55B040DA6 Код моего кейгена: // ** Hash function Hash(ms: string):int64; var ms2 : string; imt : int64; imt1 : integer; w : array [0..7] of byte absolute imt; const mt = 'lKeoOIwjk34uGFqi'; begin ms2 := ''; ms2 := ms chr(42); imt := $1AFD2FDE3ABC4975; for imt1 := 1 to length(mt) do begin w[imt1 mod 8] := (w[imt1 mod 8]*integer(ms2[(imt1 mod length(ms2)) 1])) mod 256; end; Result := imt; end; procedure TForm1.Edit1Change(Sender: TObject); var nam, ch1, ch2, ch3 : string; vv : DWORD; begin nam := Edit1.Text; ch1 := Copy( IntToHex( Hash(nam), 16), 1, 8 ); vv := StrToInt( '$' ch1 ); vv := vv xor $49DF975C; ch2 := Copy( IntToHex( vv , 8), 1, 8 ); ch3 := Copy( IntToHex( Hash(ch1 ch2) , 16), 4, 4 ); Edit2.Text := ch1 ch2 ch3; end; P.S. Когда увидел заветную зеленую надпись "REGISTERED" получил настоящее, здоровое моральное удовлетворение. Спасибо. --- Wyfinger 05.05.2008 12:19:47 Еще одно, такую защиту не стоит использовать в реальных программах. Я например большую часть времени искал адрес функции Register, но в reallive крекер может иметь готовый рабочий пароль и тогда он будет знать и этот адрес. --- Материалы находятся на сайте https://exelab.ru ![]() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Вы находитесь на EXELAB.rU | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
![]() |