crypto-keygenning4newbies part 1: "Подстановочные/перестановочные алгоритмы и хэш-функции"
  ===========================================================================================
  
    Количество  софта растет с каждым днем. Листая день ото дня какой-нибудь softpedia.com, все время удивляюсь: сколько
  можно клепать одно и то же? Да, я конечно понимаю, разнообразие - это хорошо. Только вот разнообразия как такового там
  и нет.  Стандартные интерфейсы,  стандартные функции...  Стандартные авторы ;) И каждый ведь хочет еще и денег за это!
  Взял стандартную библиотеку, прикрутил интерфейс - и ура! Новый DVDtoAVImegaConverter by Vasilij готов! =)

    И вот так же день ото дня смотря внутрь этих творений,  разглядывая процедуры регистрации,  удивляюсь однообразности
  мышления авторов. Никто не спорит, количество идиотов на Земле постоянно растет. Но не в геометрической же прогрессии.

    Одна только радость: многие уже начинают осознавать, что если дата  релиза кейгена и программы различаются на десять
  минут (это если не брать в расчет  релиз кейгена задолго до релиза софта ;), то это как-то отрицательно сказывается на
  продажах. Такие люди пытаются в некоторой степени усложнить свои алгоритмы,применяя какие-нибудь стандартные md5 и иже
  с ними. Иногда попадается blowfish и даже (нифига! =) RSA. Прогресс на лицо как говорится. Что не может не радовать.

    Хотя конечно кто-то может со мной не согласиться, мол чем легче,тем лучше. Может оно и так скажу я вам.Если релизить
  ради объемов, то тут сложно не согласиться. Но иногда хочется чего-то нового и интересного. 
  
    Так вот, дорогие  мои реверсеры, реверсерихи и  их реверсерята. Если отойти от рассуждений о серости жизни, придется
  вернуться  к нашим  баранам.  На самом  деле  информации по  написанию кейгенов  для  софта, где  юзается всевозможная
  криптография не так много, как хотелось бы. Нет, что касается именно  криптографии (алгоритмы, математические выкладки
  и  т.п. вещи), то тут  все хорошо. Я же говорю  именно об исследовании программ.  Поэтому, дабы укрепить и направить в
  нужном направлении  неокрепшие умы юных  крякеров, решено  было написать некое пособие, которое научит вас (я надеюсь)
  распознавать, а  главное обходить и  использовать в  своих  целях стандартные алгоритмы, которые в большинстве случаев
  используются сейчас  в  софте. Причем моя задача - не показать вам как решается конкретная задача (например, написание
  кейгена  для  того же  DVDtoAVImegaConverter  by  Vasilij). Я  хочу  научить вас обходить  любые (за исключением особо
  хитрожопых) реализации конкретного алгоритма.

    Существовал  когда-то  замечательный  проект  "keygenning4newbies",  который  на  примерах  объяснял  азы  написания
  ключегенераторов для маленьких. Вдохновившись этой идеей, я хочу построить свои объяснения схожим образом.Т.е. сначала
  я буду рассказывать теорию об алгоритме, потом рассматривать на гипотетических примерах его реализацию, а затем давать
  некое задание (в виде кейгенми),которое вы можете решить для закрепления, так сказать, прочитанного. И да простят меня
  авторы за заимствование названия.


  1. Введение
  -----------

    Нам, как  потенциальным взломщикам  какого-либо  криптоалгоритма намного  легче,  чем какому-нибудь криптоаналитику,
  вторую неделю  страдающему над набором  непонятных байтов. И вот почему:  у нас  есть доступ к реализации этого самого
  алгоритма.  Он перед глазами  и  мы можем манипулировать  им как захотим. Вводить любые входные данные и анализировать
  выходные.Смотреть на работу алгоритма в любых создаваемых ситуациях. Мы даже можем рипнуть этот код и использовать его
  в своих проектах ;) Все это несомненно только нам на руку.

    Всю доступную  для  исследований кучу  софта  можно условно разделить  на  несколько  групп. Главный критерий такого
  разделения - это,собственно процедура проверки регистрационных данных.Далее я в своих повествованиях буду основываться
  на таком разделении.

    Итак, первая большая группа  -  это всевозможные перестановочные и подстановочные алгоритмы. И это именно та группа,
  где авторы в большей степени проявляют свою фантазию. Ведь нет  же каких-то  стандартизированных рекомендаций, готовых
  библиотек и т.п.  вещей. Поэтому  приходится что-то  придумывать. Здесь встречается все, начиная от банального xor`а и
  заканчивая достаточно сложными (пока не разберешься как оно работает) вещами.

    Группа два  -  это различные  хэш-функции.  CRC (хоть  он  строго по определению  и  не хэш, т.к.  все-таки частично
  реверсируем), md2/4/5, sha, и т.п. прелести. Причем довольно часто  авторы используют их очень  однообразно, а от того
  и ненадежно. Например, часто встречаешь такое: подсчитать хэш от введенного имени  пользователя и сравнить с введенным
  ключом. Но бывают случаи и поинтереснее.

    Группа три - блочные алгоритмы. Это небезызвестные blowfish, rc5, cast... гост и т.д. В  некотором отношении это уже
  более серьезные вещи, нежели все предыдущее.

    И последняя, четвертая группа - это алгоритмы с открытыми ключами. Самый яркий из них - это конечно  же RSA, который
  все чаще и чаще начинает встречаться. Так же в дополнение можно рассмотреть что-то еще. ElGamal например.

    Для исследований нам в общем-то понадобится только отладчик. Какой именно использовать - дело ваше,лично я пользуюсь
  OllyDbg. Ну и конечно потребуется некий язык программирования для написания кейгенов. Я буду использовать ассемблер.

    Что касается литературы, то как я и говорил в начале, ее хватает. Самая известная книга  -  это наверное "Прикладная
  криптография" Шнайера. Рекомендуется к прочтению всем. Ну и в общем-то  интернет с его  поисковиками никто не отменял.
  Да, и еще. Как говорил когда-то Billy Belcebu в своем "Путеводителе по написанию вирусов под Win32":

  "Что потребуется для написания вирусов
    - Windows 95 или Windows NT или Windows 98 или Windows 3.x + Win32s :)
    - Пакет TASM 5.0 (который включает TASM32 и TLINK32)
    - ...
    - "Нейромантик" Вильяма Гибсона, это священная книга".

  Нашей же священной книгой будет "Криптономикон" Нила Стивенсона ;)


  2. Подстановочные и перестановочные алгоритмы
  ---------------------------------------------

    Простота  -  залог совершенства? Не в нашем случае... На самом деле наоборот,  чем сложнее и  объемнее алгоритм, тем
  больше вероятность  того,  что  он отпугнет  потенциальных  исследователей :) Иногда встречаются такие авторы, которые
  намеренно "раздувают" код, наделяют его мелкими деталями, которые  сложно отследить не то что в ассемблерном листинге,
  но и в исходном сорце. Конечно это помогает автору. Отчасти.

    Очень трудно  в  данной группе  выделить что-то  конкретное.  Как  я  и говорил, здесь преобладает фантазия авторов.
  Единственное, что наверное тут можно порекомендовать - это  поконкретнее комментировать  исследуемый листинг  и почаще
  проверять свои догадки в отладчике ;) Сильно помогает не запутаться.

    Итак, что же это за подстановочные и перестановочные алгоритмы? А это самое первое, с  чего и началась  криптография
  как таковая. Все довольно просто.Подстановочный алгоритм заменяет определенный символ (или группу символов) на другой,
  в то время как перестановочный  просто меняет  местами символы открытого текста  определенным способом. Чтобы добиться
  лучшего эффекта, их часто совмещают друг с другом.

    Не зная с чего начать, вспомнил упоминавшуюся у Шнайера программу rot13, которая просто смещает каждый  символ на 13
  (по модулю)  позиций вправо. Т.е.  меняет 'a' на 'n', 'b' на 'o' и  т.д. Покопавшись в  интернете, нашел реализацию by
  Adrian Frith. Ее и рассмотрим.

    Ассемблерный листинг процедуры rotc13 (исходники на С можно найти в файлах,  которые прилагаются к статье)  выглядит
  следующим образом:

  ;---------------------------------------------------------------------------------------------------------------------
  
  loc_401027:
      movsx   eax, byte ptr [ecx]   ;ecx - указатель на введенную строку
      cmp     eax, 61h
      jl      short loc_40103F
      cmp     eax, 7Ah
      jg      short loc_40103F
      add     eax, 0Dh
      cmp     eax, 7Ah
      jle     short loc_40103F
      sub     eax, 1Ah

  loc_40103F:
      cmp     eax, 41h
      jl      short loc_401054
      cmp     eax, 5Ah
      jg      short loc_401054
      add     eax, 0Dh
      cmp     eax, 5Ah
      jle     short loc_401054
      sub     eax, 1Ah
  loc_401054:
      mov     [ecx], al
      mov     al, [ecx+1]
      inc     ecx
      test    al, al
      jnz     short loc_401027

  ;---------------------------------------------------------------------------------------------------------------------

    Ничего сложного нет. Просто к каждому символу прибавляется/отнимается определенное значение, чтобы получить смещение
  на те самые 13 мест.

    Первый вывод,который можно сделать из подстановочных алгоритмов такой: если автор особо не утруждает себя и алгоритм
  очень простой, то  количество символов  ключа  будет величиной постоянной.  Очень  возможно, что она будет совпадать с
  длиной имени :) Или, например, часто  бывает так, что  к имени добавляется какая-то константа, т.е. к введенному имени
  "dzen" в  процедуре  генерации  добавляется какая-нибудь  метка (название программы  допустим) и затем уже выполняется
  подстановочный  алгоритм.  Второй  вывод - это  то, что саму процедуру генерации можно просто рипнуть из ассемблерного
  листинга программы,  т.к.  он (алгоритм) ни  на чем конкретно не  завязан. Хотя бывают исключения и листинг приходится
  немного видоизменять.

    Что касается написания кейгена для подобных "защит", то тут  так же  все очень просто. Все, что  нам нужно сделать -
  это найти  процедуру, которая  отвечает  за  видоизменение строки и  вставить ее  в наш код. Какую именно строку автор
  предполагает  изменять нужно  конкретно смотреть  в листинге.  Как я и говорил - это может быть что угодно, начиная от
  имени пользователя и заканчивая слепленной вместе имени юзера, имени компьютера и названия программы.

    rot13 - это только пример. В реальных  ситуациях число, на которое  будет смещаться символы  может быть любым, в том
  числе и зависеть от чего-либо (некое подобие привязки к системе). Это тоже нужно учитывать при анализе.

    В качестве тренировки предлагаю вам написать кейген для part2_1.exe, который можно найти в приложении  к статье. Это
  немного видоизмененная версия приведенного выше алгоритма.

    Следующий тип, который можно выделить - это алгоритмы,которые в своей работе для подстановки/перестановки используют
  какую-то константную строку (или  может даже текст).  Например,  часто встречается такой  способ: есть строка, которая
  представляет  собой  все  шестнадцатеричные символы,  т.е.  "0123456789ABCDEF".  Программа для  каждого  символа имени
  пользователя производит какой-то арифметический  расчет (причем не важно какой),результат которого будет индекс в этом
  массиве символов. Конечно же строка может  представлять из себя все что угодно и иметь любой размер, но смысл остается
  прежним.

    Хорошим примером может служить алгоритмы baseXX (где XX = 16, 32  или  64). Конкретно  base64  часто  используется в
  интернете для  кодирования  передаваемой информации. Более  подробно  о  спецификации можно  почитать в RFC3548. Чтобы
  ознакомиться с алгоритмом  в действии, посмотрите реализацию base64  by Quantum, которую можно найти на wasm.ru (или в
  приложении к статье). Замечу лишь то,что все это используется не так часто  как хотелось бы, поэтому более подробно на
  этом останавливаться не хочется.

    Алгоритм написания кейгена для подобных вещей в общем-то никак не меняется по сравнению  с  предыдущим способом. Нам
  нужно только рипнуть тот кусок кода, где происходит вычисление индекса нужного символа  и найти исходную строку. И еще
  немного на счет подобных массивов символов: никто не мешает этой строке и не быть константной.Она может вычисляться на
  этапе запуска программы,на этапе проверки серийного номера, может использоваться вся та же системная информация и т.п.
  Так же стандартная практика - это  вычислять индекс не  для строки, а для некоего числового массива, элементы которого
  потом переводятся в символы. В общем есть где развернуться и авторы это с радостью используют.К счастью обход подобных
  алгоритмов ничего сложного из себя не представляет.

    Для "осмысливания" приведенного метода предлагаю вам написать кейген для part2_2.exe.

    Третий способ, который я хотел бы здесь рассмотреть, у Шнайера называется "полиалфавитный подстановочный шифр".Но мы
  не будем придерживаться точного определения, данного  в книге. Просто возьмем  его за основу. Суть метода в том, чтобы
  использовать не одну, а несколько  различный процедур для  генерации серийника (как  подстановкой, так и перестановкой
  символов). Причем выбор какую  из процедур  использовать не последователен,  а зависит от некоторых условий. Они могут
  использоваться как в группе, так и по одиночке. Алгоритм может усложняться путем усложнения как кода, который отвечает
  за вызовы определенных функций, так и самих функций. Стоит отметить, что данный способ часто  используется не только в
  подстановочных/перестановочных  алгоритмах,  но  и  во многих других,  т.к.  его идея  универсальна сама  по  себе. Но
  рассмотреть его я решил здесь.

    В качестве  примера  возьмем  программу "Ace DVD  Audio  Extractor 1.2.13". В  ней используется 5  функций генерации
  символа, и  какую из  них использовать  зависит от  введенного e-mail  адреса. Как обычно существует некая арифметика,
  которая в данном конкретном  случае на выходе  выдает число  от 0 до 4 - индекс в массиве, содержащем ссылки на нужные
  функции. На выходе же каждой из функций получаем определенный символ ключа. Вот как это выглядит в действительности:

  ;---------------------------------------------------------------------------------------------------------------------

  loc_40B88C:                           ; Упомянутая арифметика. Здесь могут идти любые вычисления,
          ; которые выдумает автор. Как именно это считается совсем
          ; не важно, важен лишь результат. Поэтому часто можно просто
          ; рипнуть нужный код и вставить в кейген.
          ; ebp - указатель на строку с e-mail; ecx - длина
      movsx   edx, byte ptr [esi+ebp]
      mov     eax, edx
      mov     edi, 6Fh
      shl     eax, 4
      add     eax, edx
      cdq
      idiv    edi
      add     ebx, edx
      inc     esi
      cmp     esi, ecx
      jl      short loc_40B88C

      mov     eax, ebx
      lea     ebp, szKey
      imul    eax, ebx
      xor     esi, esi
      mov     edi, eax
      lea     ebx, [eaxё]

  loc_40B8B6:
      mov     eax, esi
      mov     ecx, 6
      cdq
      idiv    ecx
      cmp     edx, 5
      jz      short loc_40B8EA
  
      mov     eax, edi
      mov     ecx, 5
      cdq
      idiv    ecx     ; вычисления закончились, результат в edx (индекс)
      mov     edx, dword_44AD58[edx*4]; edx - указатель на одну из 5 ф-ий генерации
              ; символа
      mov     dword_450638, edx
      push    ebx
      call    edx
      add esp, 4

  ;---------------------------------------------------------------------------------------------------------------------

    Далее каждый символ ключа записывается в буфер, форматируется неким образом и сравнивается с введенным пользователем
  ключом. Все стандартно.

    Сами же функции похожи друг на друга. Различие лишь в константах, которые там используются.  И в общем-то это плохо,
  т.к. легко обнаруживается  и обходится. Если  бы автор позаботился об  их разнообразии и  сделал бы так, чтобы функции
  нельзя было банально рипнуть, не изменив должным образом, защита продержалась бы на несколько минут дольше =)

    Функции выглядят так (двумя восклицательными знаками отмечены константы, которые различаются):

  ;---------------------------------------------------------------------------------------------------------------------

  sub40B780 label far
      mov     ecx, [esp+4]
      mov     eax, 78787879h    ;!!
      imul    ecx
      mov     eax, edx
      sar     eax, 3      ;!!
      mov     ecx, eax
      shr     ecx, 1Fh
      add     eax, ecx
      mov     ecx, 0Ah
      cdq
      idiv    ecx
      xor     eax, eax
      add     dl, 30h
      retn

  ;---------------------------------------------------------------------------------------------------------------------

    В качестве  упражнения попробуйте  написать  кейген к  part2_3.exe. Это  именно  тот пример, где просто взять код из
  программы и использовать в своих целях не получится. Используются несколько  алгоритмов для генерации нужного символа,
  которые были описаны выше  в этом тексте. Вам  нужно лишь  разобраться  какой и в каком случае используется ;) Если вы
  внимательно читали, то ничего сложного быть не должно. Нужно только лишь немного подумать.

    И на этом наверное стоит пока остановиться, т.к. описывать всевозможные реализации - дело совсем  не нужное. Я думаю
  основную  идею  того,  что в  этой  группе  должно быть вы  поняли и  сможете отличить  все  это от простого сравнения
  захардкоденого пароля, дебильных математический расчетов и подсчета суммы символов имени пользователя :) Главное - это
  несомненно практика, поэтому просто больше исследуйте и ломайте, а остальное приложится.


  3. Хэш-функции
  ---------------

    "Однонаправленная  функция  H(M) применяется  к  сообщению произвольной длины M и возвращает  значение фиксированной
  длины h". По-моему лучше и не скажешь :) В этом предложении заложена вся суть любой  хэш-функции. Почему таких функций
  существует несколько видов? А это как раз к вопросу о разнообразии,с которого я начал эту статью. Что-то лучше, что-то
  хуже, что-то  работает быстрее, но  есть большая  вероятность коллизий, что-то работает медленнее, но надежнее. Да и в
  конце  концов  существуют различные  стандарты  безопасности, куда  могут  входить  как  32-битные, так  и  160-битные
  хэш-функции. Грубо говоря есть из чего выбирать.

    За математическими аспектами, выводами и доказательствами можете обратиться к соответствующим источникам.Скажу лишь,
  что вывести такую функцию дело довольно сложное. А вывести хэш-функцию, где 100%  отсутствуют коллизии  не удалось еще
  никому.

    Подобные вещи довольно часто встречаются в защитных механизмах различных программ, поэтому стоит поподробнее изучить
  этот вопрос (я имею в виду не только в рамках  данной статьи). Все дело  в  том, что  использовать такие функции очень
  легко, достаточно передать указатель на буфер, с которого  хотим подсчитать хэш и его длину. Остальное уже написано до
  нас.

    Наиболее часто  встречающаяся  функция  -  это md5. Остальные  встречаются  довольно  редко. Но не стоит обходить их
  вниманием. А начнем мы все-таки с CRC как с наиболее простой для понимания.


  3.1 CRC16 и CRC32
  -----------------

    Как и упоминалось, crc строго по определению не совсем хэш-функции. Но т.к. в софте они встречаются довольно  часто,
  причем в различных своих вариациях, я думаю стоит рассмотреть и их.

    Информации по  данным  алгоритмам  существует  очень  много. Я  бы  порекомендовал почитать  вот  это: "Элементарное
  руководство по CRC-алгоритмам обнаружения ошибок",Ross N. Williams. Так же в качестве самообразования советую почитать
  "CRC, и как его восстановить" by Anarchriz/DREAD, где  и освещаются в некоторой  степени недостатки данного алгоритма.
  Далее я  буду предполагать, что  читатель  хотя  бы  ознакомился с "Элементарным руководством", т.к. в обратном случае
  понять что-то будет довольно сложно.

    Начнем, пожалуй, с crc32, как с более распространенного. Благо алгоритм не такой большой, позволю себе  привести его
  здесь. В  качестве варианта реализации возьмем,  к  примеру, crc32.dll  by  G. Adam Stanislav  (смотрите  приложение к
  статье):

  ;---------------------------------------------------------------------------------------------------------------------

  loc_81D51092:         ;eax = 0ffffffffh, ebx - указатель на строку
            ;ecx - длина строки
      mov     dl, [ebx]
      call    crc32
      inc     ebx
      loop    loc_81D51092

      not eax   ;результат в eax

  ;______________
  crc32 proc near
      xor     dl, al
      movzx   edx, dl
      shr     eax, 8
      xor     eax, ds:dword_81D54000[edx*4]
      retn

  ;---------------------------------------------------------------------------------------------------------------------

    Вот  так  вот просто  и  незамысловато. Единственное, что нужно упомянуть - это таблица, элементы которой ксорятся с
  текущими  значениями вычислений. Есть  два варианта  ее использования: или она  зашита в программе, или вычисляется на
  стадии работы. Если первое, то вам повезло, т.к. это  достаточно весомый аргумент того, что данная процедура вычисляет
  именно crc32, а не какую-нибудь  самопальную хрень. Ведь элементы этого самого массива - константы и не могут меняться
  от реализации к реализации. Второй вариант - это вычисление таблицы на стадии работы. Конечно тут тоже ничего сложного
  быть не должно, но бывают моменты, что, сидя по несколько часов за отладчиком,не замечаешь таких простых вещей.Но т.к.
  и результат вычислений есть стандартные значения - это сильно помогает.Существует кстати еще и третий вариант, который
  вы увидите в кейгенми ;)

    Важно понять одно: алгоритм можно закодировать различными способами,поэтому ваша задача не только определять код "на
  глаз", но и понимать что именно он делает.

    Вычисление таблицы на стадии работы может, например, выглядеть так (взято из статьи "CRC, и как его восстановить"):

  ;---------------------------------------------------------------------------------------------------------------------

      xor ebx, ebx
  InitTableLoop:
      xor eax, eax
      mov al, bl
      xor cx, cx
  entryLoop:
      test  eax, 1
      jz  no_topbit
      shr eax, 1
      xor eax, poly
      jmp entrygoon
  no_topbit:
      shr eax, 1
  entrygoon:
      inc cx
      test  cx, 8
      jz  entryLoop
      mov dword ptr [ebx*4 + crctable], eax
      inc bx
      test  bx, 256
      jz  InitTableLoop

  ;---------------------------------------------------------------------------------------------------------------------

    Или так (crc.dll by G. Adam Stanislav):

  ;---------------------------------------------------------------------------------------------------------------------

      mov     ecx, 256        ; repeat for every DWORD in table
      mov     edx, 0EDB88320h
  $BigLoop:
      lea     eax, [ecx-1]
      push    ecx
      mov     ecx, 8
  $SmallLoop:
      shr     eax, 1
      jnc     @F
      xor     eax, edx
  @@:
      loop    $SmallLoop
      pop     ecx
      mov     crc32tbl[ecx*4][-4], eax
      loop    $BigLoop

  ;---------------------------------------------------------------------------------------------------------------------

    Или даже так (кусок из 'Beagle worm', опубликованного в 29A #8):

  ;---------------------------------------------------------------------------------------------------------------------

      xor     eax, eax
  @a_gen:
      mov     edx, eax
      shl     edx, 1

      push    9
      pop     ecx
  @l:
      shr     edx, 1
      jnc     @no_xor
      xor     edx, CRCPoly
  @no_xor:
      loop    @l

      mov     dword ptr[CRCTableм*4], edx
      inc     al
      jnz     @a_gen

  ;---------------------------------------------------------------------------------------------------------------------

    CRC32 в  основном используется как часть более сложного алгоритма. Часто  приходится видеть некую константу в ключе,
  которая при близком рассмотрении является хэш-суммой какой-либо строки,например того же имени программы, автора и т.д.
  Иногда некое значение используется для вычисления других  частей ключа. В таких  случаях это число приходится находить
  путем прямого брутфорса, т.к. если вы не забыли: хэш-функция не обратима по определению. Но о брутфорсах в другой раз.

    Кейгенми  с  именем part3_1.exe  ждет  вас =) В  нем  приводится пример того, что может  существовать и  рекурсивное
  вычисление ключа (хотя в строгом понимании рекурсия там не используется).

    CRC16 - это 16-битная реализация этого же алгоритма. Соответственно сама циклическая сумма есть word, как и элементы
  256-значной  таблицы.  Но  принцип вычислений  не  меняется.  Он  может  выглядеть  к  примеру так (кусок из программы
  'Parallaxis Cuckoo Clock'):

  ;---------------------------------------------------------------------------------------------------------------------

  loc_10003D15:           ;edi - указатель на строку, esi - длина
              ;результат в ax
      xor     edx, edx
      mov     ebx, eax
      mov     dl, byte ptr [ecx+edi]
      and     ebx, 0FFh
      xor     edx, ebx
      xor     ebx, ebx
      mov     bl, ah
      mov     ax, word ptr [edx*2+tabl]
      xor     ax, bx
      inc     ecx
      cmp     ecx, esi
      jl      short loc_10003D15

  ;---------------------------------------------------------------------------------------------------------------------

    Медитируйте:  part3_2.exe  ;)  В кейгенми  используется упоминавшийся  в  начале данного  раздела  способ реализации
  алгоритма, который уж очень отличается от стандартного. К примеру если посмотреть PEiD`ом (плагином 'Krypto ANALyzer')
  на  part3_1.exe,  то,  как ни  странно, обнаружится  наш  crc32. А вот если посмотреть на part3_2.exe, то peid скромно
  промолчит. Потому как не за что зацепиться: ни констант, ни всего остального. Также в отличие от предыдущего кейгенми,
  просто  так позаимствовать  код из файла  не  получится. Поначалу  написание ключегенератора  может показаться сложной
  задачей, но поверьте - это не так ;) Главное как обычно - это разобраться в сути.Более того, данный способ в некоторых
  вариациях используется и в реальных программах. Поэтому дерзайте.

    Итак, подведем итог того как можно обнаружить CRC16/32. Первое и основное - это константные  значения таблицы. Можно
  просто  запомнить первые  несколько  значений чтобы  "узнать" начало массива в реальной программе. Второе - это размер
  таблицы. Если она генерится,то наверняка выделится (или будет использоваться зарезервированный) участок размером 256*4
  (CRC32)  или  256*2 (CRC16) байт.  Третье  -  это использование  таблицы. Если  вы до сих пор не обратили внимание, то
  элементы _ксорятся_  с определенным значением. Это конечно встречается, но не так часто. В основном массивы используют
  немного для другого :) И  последнее  -  это полином. Для  CRC32 он  равен 0EDB88320h, для CRC16 - 8005h (или 1021h для
  CRC16/CITT).

    К  слову:  хороший  ресурс, в онлайне  считающий  наиболее  распространенные  реализации  crc (как  16, так и  32) -
  http://www.lammertbies.nl/comm/info/crc-calculation.html


  3.2 Message Digest 5 (MD5)
  --------------------------

    Наиболее часто  встречающийся  в  программах алгоритм.  Почему  авторы  всевозможного софта его так  любят я, честно
  сказать, ответить затрудняюсь. Возможно есть в нем что-то привлекательное. Так же как и все остальное, используется он
  довольно однообразно, а от того и небезопасно.В основном мысль сводится к тому, что, раз это хэш-функция, то значит ее
  нужно к чему-нибудь применить. К имени пользователя например, или к имени программы, а потом немного поменять выходные
  данные. И т.д. и т.п. В общем,за редкими исключениями, все это так же не представляет особых хлопот при исследовании и
  написании кейгенов.

    Опишу кратко что из себя представляет md5. Данный алгоритм был спроектирован Роном  Риверсом (который придумал также
  md2 и md4; мы рассмотрим их далее). Во время своей работы md5 разбивает  входной текст и оперирует блоками по 512 бит.
  Соответственно, если текст не  кратен 512, то он  дополняется некоторыми определенными значениями. На выходе алгоритма
  получаем 128-битную величину (4 дворда), которая и является хэшем.

    В общем случае алгоритм можно разделить на 4 этапа:

    1) Выравнивание входного буфера (должен быть кратен 512)
    2) Инициализация т.н. 'переменных сцепления'
    3) Хэширование (состоит из 4 шагов)
    4) Формирование результата

    Начнем  с  шага 2, т.к.  выравнивание - дело довольно непоследовательное и от реализации  к реализации способ записи
  может меняться. Короче говоря, не за что зацепиться, чтобы в точности идентифицировать md5. Можно только отметить, что
  сначала сообщение дополняется до 512-64 бит,  а только затем дополняется до 512 (последние 64 бита - изначальная длина
  сообщения).

    В большинстве  реализаций  в  начале работы к  передаваемой для обработки строке  добавляется 80h, т.е.  если текст,
  который мы  хотим  хэшировать  -  это "dzen",  то он  будет дополнен  так:  "dzenA" ('A' = 80h). На это стоит обращать
  внимание.
  
    Шаг 2  -  инициализация  переменных, нужных в  дальнейшей работе.  Начальные значения - это  не нули как  можно было
  подумать, а 1234567h, 89abcdefh, 0fedcba98h, 76543210h. И это первое,на что вы должны обращать внимание. В стандартной
  реализации сразу бросается в глаза подобный код:

      MOV DWORD PTR [ESI],67452301
      MOV DWORD PTR [ESI+4],EFCDAB89
      MOV DWORD PTR [ESI+8],98BADCFE
      MOV DWORD PTR [ESI+C],10325476  

    Конечно никто не  обязывает  использовать mov, или  скажем push/pop. Значения могут и вычисляться каким-нибудь особо
  извращенным способом. Но это уже частности :)

    Шаг 3  -  это собственно хэширование. Состоит из 4 этапов, на которых вычисляются промежуточные значения. Каждый шаг
  есть некая  математическая  формула (точнее  -  алгебраическая...  алгебра  логики: xor, or,  not и все такое ;) Важно
  отметить те операнды, которые  участвуют  в  вычислениях. Это  инициализированные ранее  переменные сцепления, подблок
  512-битного участка сообщения и... _константы_! Да да, опять значения, которые изменять в реализациях нельзя, т.к. они
  строго прописаны в спецификации. Что несомненно нам только на руку.

    И шаг 4 - это формирование результата.Здесь просто объединяются переменные сцепления и (возможно) получившееся число
  переводится в текст.

    Вот как выглядит  например хэш-сумма  слова 'hello': 5D41402ABC4B2A76B9719D911017C592. А пример реализации алгоритма
  (by roy|crisis crackers) можно посмотреть на www.wasm.ru, или в приложении к статье.

    ...Сижу и думаю какую бы программу привести в пример. Чтобы не очень сложно и понятно одновременно :) Мда, не радуют
  нас авторы  подобным  софтом... либо  все  до безобразия просто,  либо наоборот запутанно. Нет, нашел вот: "MP3 Stream
  Creator 2.0.2005.311". Самого релиза программы у меня почему-то нет, поэтому приведу только выдержки из кейгена.

    Данная софтина в равной степени использует как подстановочно-перестановочный алгорим,так и md5.Генерация правильного
  ключа осуществляется так:

  1) Исходная строка в виде "MSCD" объединяется с произвольно формируемой.
  2) Каждый второй символ из формируемой строки копируется в отдельный буфер и от него считается хэш.
  3) Из получившегося хэша копируются последние 8 символов.
  4) Получившиеся символы "переворачиваются" определенным образом.
  5) И затем вставляются на каждое второе место в строке, сформированной на шаге 2.

    Вот так это выглядит в действительности:

  ;---------------------------------------------------------------------------------------------------------------------

  _create_str:
      invoke  GetTickCount      ;некое подобие rand() =)

      and eax, 0fh      ;формируем символ
      cmp al, 9
      jg  @F
      add al, 30h
      jmp _stos
  @@:
      add al, 37h
  _stos:
      stosb         ;вставляем в буфер шага 1

                ;проверяем и обрабатываем каждый
      mov edx, ecx      ;второй символ
      and edx, 80000001h
      test  edx, edx
      jnz @F
      mov byte ptr [esi+ebx], al    ;копируем в буфер шага 2
      inc ebx
  @@:
      cmp byte ptr [edi], '-'
      jnz @F
      inc edi
  @@:     inc ecx
      cmp ecx, pSomeLen
      jnz _create_str

                ;и подсчитаем хэш
      invoke  lstrcpyn, offset szKey, offset pNechB, 9
      invoke  procMD5hash, offset szKey, 8, offset stMD5Result

  ;---------------------------------------------------------------------------------------------------------------------

    Это пример относительно нормального алгоритма.Вы не поверите как часто встречаешь что-то вроде serial = md5(name)...

    Вот теперь можно писать кейген для part3_3.exe :) Выше я упомянул про "переворачивание" символов, но  не привел код.
  Вы увидите его там.

    Итог  может  быть  такой. MD5  -  это "алгоритм  констант" если  так  можно  выразиться :) Т.е.  везде в  его работе
  используются некие известные значения, которые легко можно опознать: начиная от  инициализации  переменных сцепления и
  заканчивая циклом  хэширования.  Поэтому  прежде  всего следует  ориентироваться  именно на это. Второе - это формулы,
  которые используются в алгоритме. Если  вы способны написать  нечто вроде '(x^y) v ((!x) ^ z)' (! - логическое не, все
  остальное я думаю понятно) на ассемблере, то вам это может помочь. Сами формулы можно найти у Шнайера.


  3.3 Message Digest 4 (MD4)
  ---------------------------

    Начиная с данного алгоритма, все последующие встречаются в программах намного реже, чем уже описанные.
  
    MD4  -  это упрощенная версия md5. Точнее  md5 - это усовершенствованный md4 ;) Ну да ладно, я  думаю смысл понятен.
  Разработал его все тот же Рон Риверс. Основное отличие версии 4 от 5 в том,что в первой шаг хэширование состоит только
  из 3 этапов, а так  же  отличаются  функции подсчета  текущего значения в раунде 2. Вот и все, если  не брать в расчет
  другие более мелкие отличия. Все вычисления и сам процесс работы не отличаются от уже описанных в md5.

    Техника обнаружения данного алгоритма в программе так же не отличается от md5, поэтому позволю  себе воздержаться от
  написания  кейгенми.  Если вам  интересно  посмотреть на  работу  md4  в действии, в  приложении вы найдете библиотеку
  InsideProCrypt by модератор с форума insidepro.com =) Там в качестве бонуса можно найти и алгоритм sha-1.


  3.4 Message Digest 2 (MD2)
  --------------------------

    Если вы думаете, что MD2 - это упрощенная версия md4, то вы ошибаетесь ;) Хотя выглядит выходной хэш точно так же (в
  смысле структуры), алгоритм очень отличается. Считается, что md2  -  одна из самых  медленных хэш-функций. Может оно и
  так, я не проверял. Хотя в нашем случае это  в  общем-то не  принципиально, т.к. по идее на первом плане должна стоять
  надежность. А вот как раз этому md2  отвечает: до сих пор в нем не было найдено уязвимостей,в отличие например от того
  же md4.

    Рон Риверс  приложил руку к разработке  и этого алгоритма. Умный все-таки мужик :) Как и у предыдущих хэш-функций, у
  md2 существуют несколько шагов, которые должен пройти алгоритм. Вот они:

  1) Выравнивание входного текста (должен быть кратен 16 байтам) и добавить к тексту 16 байт контрольной суммы
  2) Инициализация 48-байтного блока необходимым образом
  3) Хэширование
  4) Копирование и инициализация следующего блока входного текста. Шаг 3 и 4 выполняются в цикле, пока не кончится
     текст
  5) Формирование результата

    Все конечно  хорошо,  только  вот  с  учетом какого-нибудь  оптимизирующего  компилятора типа msvc++ все приведенные
  действия сливаются с другим кодом и  очень сложно  заметить  алгоритм. Остановимся пожалуй  только на шаге 3, т.к. все
  остальное не представляется интересным.

    Итак, шаг 3 есть функция сжатия, которая формирует значение для текущего блока сообщения. Единственное, на что стоит
  обратить внимание - это то, с чем просходит xor (что за ксор и за всеми  остальными подробностями смотрите Шнайера или
  исходники в приложении). Это 256-байтная таблица, которая состоит из... мм... чисел, стоящих после запятой у числа Пи.
  Да, я знаю, что убого звучит, но как-то сформулировать по-другому не получается ;) Эти значения - константы, а они нам
  как раз и нужны. Вот как это выглядит:

  ;---------------------------------------------------------------------------------------------------------------------

      mov     dl, PI_SUBST[edx]
      mov     bl, [espМг~Є_34]
      xor     bl, dl
      movzx   edx, bl
      mov     dl, PI_SUBST[edx]
      mov     [espМг~Є_34], bl
      mov     bl, [espМЧ]
      xor     bl, dl
      movzx   edx, bl
      mov     dl, PI_SUBST[edx]
      mov     [espМЧ], bl
      mov     bl, [espМг~Є_32]
      xor     bl, dl
      movzx   edx, bl

  ;---------------------------------------------------------------------------------------------------------------------

    и т.д.  в  определенных вариациях. Это  уже что-то, такой код легко узнать. Поэтому это в общем-то единственное, что
  стоит запомнить в md2. PI_SUBST - это упоминавшаяся таблица.

    К слову о практике. Если вы действительно  хотите научиться  распознавать различные криптоалгоритмы,  то самое время
  научиться не  только  читать подобные тексты, но  и исследовать самим. Самый простой способ - это взять программы, где
  100% известно какой алгоритм используется (возможно кто-то уже описал способ ее взлома, или производитель сам упомянул
  эту информацию). Попробуйте сами, без всяких  туториалов найти то место, где осуществляется генерация правильного кода
  и определить какой алгоритм  в этом деле используется. Чем больше программ вы исследуете, чем больше реализаций одного
  и того же алгоритма увидите, тем проще вам будет в дальнейшем. Уж поверьте ;)

    Напишите кейген для part3_4.exe. В нем для генерации хэша используются средства самой винды, поэтому получилось так,
  что я отошел от  привычного повествования  -  давать прямые реализации  какого-либо алгоритма. Но я думаю это не будет
  лишним, т.к. данные средства так же используются  при написании  защит, хотя  и  реже. API винды  намного медленнее по
  сравнению с прямой реализацией, а так же требуют множество деталей, на которые порой не хочется отвлекаться. Настройка
  контекста, создание ключей, проверка ошибок и т.д. и т.п. Посмотрите в кейгенми и вы все поймете.


  3.5 Secure Hash Algorithm (SHA)
  ------------------------------

    Разработанный  NIST  при  поддержке NSA,  SHA  является  расширенной версией MD4, причем расширенной незначительно в
  отношении самого алгоритма. В отношении  же безопасности он  обошел своего родственника  по всем параметрам. SHA - это
  стандартизированный  алгоритм, который используется  в связке с DSA  (Digital Signature Standart).  Выходным значением
  является 160-битная хэш-сумма.

    Рассмотрим основные отличия SHA от своего предшественника, которые будут нам интересны:

    1) В работе используется не 4, а 5 переменных сцепления.Новая переменная инициализируется значением 0C3D2E1F0h в
       начале работы.
    2) Главный цикл хэширования состоит из 20 операций, в которых используются 3 предопределенные функции (каждая на
       определенном этапе).
    3) Используются следующие 4 константы: 5A827999h, 6ED9EBA1h, 8F1BBCDCh и 0CA62C1D6h.

    Как  и  в md4/5 инициализация переменных сцепления - это первое, на что стоит обращать внимание. Выглядеть это может
  например так:

      MOV DWORD PTR [ESI],67452301
      MOV DWORD PTR [ESI+4],EFCDAB89
      MOV DWORD PTR [ESI+8],98BADCFE
      MOV DWORD PTR [ESI+C],10325476  
      MOV DWORD PTR [ESI+10],F0E1D2C3

    Функции вычисления текущего значения при хэшировании так же могут быть идентифицированы,т.к. предопределены :) Более
  того, на этапе 2 и 4 используется одна и та же функция (A xor B xor C), а функция 3 была позаимствована из MD4.

    Ну  и  конечно  же  наши любимые константы, которые  упоминались в третьем пункте. Именно по ним (плюс инициализация
  переменных) и нужно ориентироваться.

    В 2001 году NIST принял в качестве  стандарта 3  расширенные версии SHA (так же известного как SHA-1) - это SHA-256,
  SHA-384 и SHA-512, которые названы по длине выдаваемой хэш-суммы.Я не буду более подробно останавливаться на них, т.к.
  в общем-то  отличий  от оригинальной SHA в них  не так  много  и при желании читатель  может сам  ознакомиться с ними.
  Основное - это этап хэширования, поэтому если его (этот этап) не брать в расчет, техника обнаружения не изменяется.

    Вы наверное заметили, что с каждым разделом я все меньше описываю КАК обнаружить алгоритм,  останавливаясь только на
  основных  отличительных  моментах.  Некоторые могут подумать,  что  автор, четвертый день  строча на клавиатуре, начал
  выдыхаться :) На самом деле это не так (хотя тоже сказывается), я хочу лишь приучить вас самим исследовать программы и
  искать нужную информацию. Прочитав один туториал, многому не научишься, поэтому стоит иногда отвлечься от теории.

    Кейгенми part3_5.exe представляет собой банальную версию навесной защиты, где весь код, обеспечивающий безопасность,
  сосредоточен в отдельной dll. Правда в нашем случае это именно _банальная_ версия =)


  4. Заключение
  --------------

    На  этом  часть первую моего  повествования можно считать  оконченной. Во второй части  рассмотрим группы 3 (блочные
  алгоритмы) и 4 (алгоритмы с открытыми ключами). Но до того времени как я  сподоблюсь ее закончить, вы сможете в полной
  мере изучить  уже описанное. Стоит  отметить, что  все, о чем я рассказал вовсе не панацея от всех бед. Существует еще
  огромное количество алгоритмов, которые я не затронул. Взять хотя бы интересный haval,которого существует 15 вариаций.
  Или   ripe-md.   Или   tiger,   или  adler32...  Да   мало  ли   что  еще.  Я  уже   и   не  говорю   о   всевозможных
  перестановочно-подстановочных фантазиях авторов программ =) Поэтому один совет - исследуйте,изучайте алгоритмы, пишите
  свои реализации, чтобы понять все тонкости. И будет вам счастье ;)

    Варианты  решений кейгенми вы  можете  присылать мне  на  почту ([email protected]),  я  их  опубликую  на  форуме
  (forum.blacklogic.net), где все те, у кого что-то не получилось, смогут их впоследствии прочесть.Поэтому одна просьба:
  пишите описание взлома доступным русским языком, понятным не только вам.

    Хотелось бы  персонально поблагодарить gloom`а  за первое прочтение, указание на ошибки, исправление неточностей, за
  отличную реализацию crc16 и за поддержку в написании второй части ;) 

    Ну и конечно же респект всем группам и людям, создававшим приятный фон при написании этого текста, в  числе которых:
  Apocalyptica,  Distemper,  Animal Jazz, IFK,  Элизиум,  Paul Oakenfold, DJ  List,  Слот,  Dolphin,  DDT,  всевозможные
  французские рэперы и другие =)

  dzen, 11.05.05-15.05.05