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