Сложность: 2 - Needs a little brain (or luck)
Платформа: Windows
Язык: Assembler
Дано: Eazy Peazy CrackME
Your Goal is to find the correct serial for the CrackME.
Honestly i dont know how hard it can be so try it out your self and let me know.
Written in pure Assembly using MASM.
For anything (really anything) contact me at
federicogorla at hotmail dot com
I'll see ya!
Решение (автор promix17)
опубликовано 31.05.2012
Инструменты: OllyDBG
Наша задача - найти правильный ключ для crackme. Программа содержит диалоговое окно с одним полем ввода (ключ), которое проверяется по кнопке «CHECK». Контроля ввода символов – нет, можно забить любой печатаемый символ.
Загружаем программу в OllyDBG и наблюдаем чистый (в смысле без обфускации, не оптимизированный, среда разработки – MASM) ассемблерный код. Вся логика crackme лежит по адресам с 0040113C по 0040123E и находится без труда. Отлично, но как программа работает?
1) Читаем строку (ключ) из поля ввода, есть проверка – длина строки должна быть 8 символов. Для примера возьмем строку «12345678» = 0x3132333435363738 (в hex-виде).
2) Вычисляем хэш первых 4-х символов ключа. Длина хэша 4 байта. Вычисляем хэш для «1234». Причем, эти четыре символа рассматриваются как 16-тиричное число, т.е. «1234» = 0x34333231 (где первый символ «1», это самый младший байт в числе - 0х31)
3) Вычисляем хэш последних 4-х символов ключа. Длина хэша 4 байта. Вычисляем хэш для «5678». Причем, эти четыре символа рассматриваются как 16-тиричное число, т.е. «5678» = 0x38373635 (где первый символ «5», это самый младший байт в числе - 0х35)
4) Далее полученный хэш (в сумме 8 байт) нормализуем в ASCII символы из дипазона [0x30…0x7A]: код с адреса 004011D5 по 004011F1. Приведу адаптированный вариант этого кода на Python. В результате получим хэш в виде строки.
def norm(x): # x - 16-тиричное число размером в байт res = x # если x лежит в диапазоне [0x30…0x7A], то ничего делать не надо if (res >= 48) & (res <= 122): return res # иначе x нормализуем к [0x30…0x7A] while 1: res &= 0xFF if (res < 48): # пока x < 0х30, то x += 10 (0x0A) res += 10 else: if (res >> 7) & (res > 122): # пока x от 0x80 до 0xFF, то x += 10 (0x0A) res += 10 else: # пока x от 0x7B до 0x7F, то x -= 10 (0x0A) res -= 10 if (res >= 48) & (res <= 122): break return res
5) И, наконец, сравниваем хэш с «8x@rtr25»
И что мы можем сделать? Очевидный вариант: brute force, подобрать хэш методом тупого перебора входящих строк ключа. Благо, хэш четырехбайтный (хотя, их и два). Я адаптировал переборщик паролей на Python (откровенно, тормозное решение – тот же код на С/С++ выполняется за секунды, а на интерпретируемом языке – за минуты и часы). Оба хэша на моей машине подобрались минут за 20. Но мы же никуда не торопимся :)
# циклический сдвиг вправо def ror(num, count, type): cnt = count % type a = type - cnt numcur = num if numcur < 0: numcur &= 0x7FFFFFFF ror_low = numcur >> cnt ror_low = ror_low | (1<<(a-1)) else: ror_low = numcur >> cnt numcur = num ror_hi = numcur << a ror = ror_hi | ror_low if type == 8: ror = ror & 0x000000FF if type == 16: ror = ror & 0x0000FFFF if type == 32: ror = ror & 0xFFFFFFFF return ror # ОСНОВНОЙ КОД check = "8x@rtr25" #brute force первого 4-хбайтового хэша print (“Start one brute force:”) for a in range(48,123): for b in range(48,123): for c in range(48,123): for d in range(48,123): z = a | (b << 8) | (c << 16) | (d << 24) hash1 = ((z * 0x310890) ^ 0x96547) & 0xFFFFFFFF ch_1 = norm((((hash1 & 0xFF) ^ a) << 2) & 0xFF) ch_2 = norm(((((hash1 >> 8) & 0xFF) ^ b) << 2) & 0xFF) ch_3 = norm(((((hash1 >> 16) & 0xFF) ^ c) << 2) & 0xFF) ch_4 = norm(((((hash1 >> 24) & 0xFF) ^ d) << 2) & 0xFF) #сверяем с хэшем «8x@r» if (ch_1 == ord(check[0])) & (ch_2 == ord(check[1])) & \ (ch_3 == ord(check[2])) & (ch_4 == ord(check[3])): print (chr(a)+chr(b)+chr(c)+chr(d)) #brute force второго 4-хбайтового хэша print (“Start two brute force:”) for a in range(48,123): for b in range(48,123): for c in range(48,123): for d in range(48,123): z = a | (b << 8) | (c << 16) | (d << 24) hash1 = ((z * 0x666666) ^ 0x365799) & 0xFFFFFFFF hash1 = ror(hash1, 0x31, 32) ch_1 = norm(ror((((hash1 & 0xFF) ^ a) ^ 0x31) & 0xFF, 0x12, 8)) ch_2 = norm(ror(((((hash1 >> 8) & 0xFF) ^ b) ^ 0x31) & 0xFF, 0x12, 8)) ch_3 = norm(ror(((((hash1 >> 16) & 0xFF) ^ c) ^ 0x31) & 0xFF, 0x12, 8)) ch_4 = norm(ror(((((hash1 >> 24) & 0xFF) ^ d) ^ 0x31) & 0xFF, 0x12, 8)) #сверяем с хэшем «tr25» if (ch_1 == ord(check[4])) & (ch_2 == ord(check[5])) & \ (ch_3 == ord(check[6])) & (ch_4 == ord(check[7])): print (chr(a)+chr(b)+chr(c)+chr(d))
Результаты оформим в виде таблички: строки, удовлетворяющие первому хэшу - слева, а строки для второго хэша - справа.
1-я половинка ключа (найдено 24): 1X=a 3_;J 8d[B Fl\_ Gdh0 Gdhp R534 R53t R5sP RuW^ Y1V[ Yqj> l7oL lwCb m>Ja n6i0 n6ip nvMM q<O7 q<Ow s3BP ssfI x8bH xxFe |
2-я половинка ключа (найдено 31): :m9U ;7<d ;A<2 ;IvA ;s<8 M1qM;qz McqB Mmqh PNn? PbnK Pgn2 U>`Uz`H WAjO [AWS _E@C _Y@7 _w@I fBtX fLt^ f`tj fetQ foto gVxu goxx iW\z iz\[ uE9A uJ9@ ur9x |
Любое сочетание двух половинок ключа даст нам полноценный ключ. Если использовать нашу табличку, то получим различных комбинаций: 24 * 31 = 744 ключа. Вводим любой полученный ключ и наблюдаем:
Примеры:
[+]Serial = xxFegoxx
[+]Serial = GdhpfetQ
[+]Serial = R53tgoxx
[+]Serial = xxFePbnK
[+]Serial = l7oLfoto
[+]Serial = m>JafetQ
[+]Serial = x8bHgoxx