Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

Взломай меня всецело (ZeroNightsCrackme, Часть 2)

Anna | 15.06.2014 | нет комментариев
И вновь каждому здравствуй! В прошлый раз я раскрыл решение ZeroNightsCrackMe. Все кто поспел его своевременно решить, мог получить приглашение на экскурсию в один из офисов Лаборатории Касперского, а так же презент, в виде лицензионного ключа на три устройства. Но, помимо каждого прочего, в Касперском известили, что крякми был облегченным, т.е. существует больше трудная его версия и она будет разослана тем, кто пожелает её посмотреть (но без презентов, в своё наслаждение, так сказать). Безусловно же я не мог отказать себе в том, Дабы не покрутить эту версию, следственно удостоверил свое желание на участие.

17 февраля пришло письмо с новым крякми. Именно о его решении (и не только) я и поведаю в этой статье.

Параметры крякми те же:

  • Файл: ZeroNightsCrackMe.exe
  • Платформа: Windows 7 (64 bit)
  • Упаковщик: Отсутствует
  • Антиотладка: Не натыкался
  • Решение: Валидная пара Mail / Serial

Инструменты:

  • OllyDbg 2.01
  • Немножко серого вещества

Приступим к решению…

Выходим на охоту

Как традиционно запускаем подопытного и проводим поверхностный обзор. Реакция аналогична прошлому крякми.

image
Рис. 1

Зная правило работы прошлого крякми, приступим к поиску ключевых моментов и обнаружим:

  1. Функцию занимающуюся обработкой введенных данных.
  2. Алгорифм проверки таблицы валидации.
  3. Таблицу валидации.
  4. Алгорифм заполнения таблицы валидации.
  5. Данные для алгорифма заполнения таблицы валидации.
  6. Алгорифм перевоплощения серийного кода во внутреннее представление.
  7. Таблицу реформирования.
  8. Возможный диапазон символов.

Позже чего сравним все ключевые моменты с предыдущей версией и обнаружим различия.

По звериным тропам

Функция обработки введенных данных

Первым делом обнаружим функцию обработки введенных данных. Делается это достаточно легко. Жмем правой кнопкой мыши в окне дизассемблера и выбираем «Search for => All referenced strings»:

image
Рис. 2

Дальше жмем по строке «Good work, Serial is valid !!!» и попадаем сюда:

image
Рис. 3

Выше и будет находиться желанная функция (в моем случае это CALL 0x9b12b0). Ей передается три параметра. В Arg2Arg1 передается размер серийного кода и указатель на серийный код соответственно, а в регистре ECX указатель на email.

Алгорифм проверки таблицы валидации

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

image
Рис. 4

Адрес таблицы валидации

Ставим брейкпойнт в начале алгорифма и запускаем крякми на выполнение (разумеется, заранее введя всякие данные и нажав кнопку Check).

image
Рис. 5. Вводим тестовые данные

image
Рис. 6. Остановка на таблице валидации

Сейчас определим адрес самой таблицы. Сделать это дозволено перейдя на строку «CMP DWORD PTR SS:[ECX*4 EBP-28],1» и подсмотрев целевой адрес.

image
Рис. 7. Определение адреса таблицы валидации

В моем случае адресом таблицы является 0x36f308 (выделено красным).

image
Рис. 8. Дамп таблицы валидации

Алгорифм заполнения таблицы валидации

Поиск алгорифма изготавливаем тем же методом, тот, что был продемонстрирован при решении прошлого крякми, а именно:

  • Продолжаем выполнение крякми (жмем F9 в Ольке);
  • Ставим бряк на функцию обработки введенных данных, в моем случае это CALL 9b12b0 (рис. 3);
  • Переключаемся на крякми и во всплывшем окне (говорящем об успешности либо не успешности) нажимаем «Ок» (тем самым продолжая работу крякми);
  • Дальше жмем кнопку «Check» для запуска пересчета серийника, позже чего вы обязаны остановиться на вызове CALL 0x9b12b0;
  • Стоя на вызове CALL 0x9b12b0, поставьте бряк на запись, на адрес 0x36f308;
  • И вновь жмите F9.

Если все было сделано верно, окажетесь тут:

image
Рис. 9. Алгорифм заполнения таблицы валидации

Если сравните новейший алгорифм со ветхим, то подметите, что они отличаются:

image
Рис. 10. Ветхий алгорифм (скриншот из прошлой статьи)

Представление «нового алгорифма» на Питоне выглядит дальнейшим образом:

def create_table(first_part, second_part):

    result = []

    curr_second = 0
    out_index = 0
    while(out_index < 3):

        inner_index = 0
        while(inner_index < 3):

            curr_first = 0
            accumulator = 0
            index = 0
            while(index < 3):

                first = first_part[inner_index   curr_first]
                second = second_part[index   curr_second]
                hash = 0

                if (first != 0):
                    while (first != 0):
                        if (first & 1):
                            hash ^= second

                        second  = second
                        first = first >> 1

                accumulator ^= hash
                index  = 1
                curr_first  = 3

            result.append(accumulator & 0xff)
            inner_index  = 1

        out_index  = 1
        curr_second  = 3

    return result

Перейдем к поиску данных которые применяются этим алгорифмом.

Данные для алгорифма заполнения таблицы валидации

Проанализировав код алгорифма было найдено два места, откуда берутся данные для его работы:

image
Рис. 11. Массивы с которыми оперирует новейший алгорифм

Выше они выделены серым прямоугольником. В моем случае, по адресам 0x9b11b0 и 0x9b11b2, идет обращение к дальнейшим массивам:

  • 0×00758628 (рис. 12)
  • 0×00758908 (рис. 13)

image
Рис. 12

image
Рис. 13

В всяком массиве находится 9 элементов по одному байту.

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

Различие ветхой версии от новой

В ветхой версии крякми работа с серийным кодом происходила дальнейшим образом:

  1. Серийный код разбивался на две части.
  2. Всякая часть переводpermark! result = create_table(part_a, part_b) if result == [0, 0, 0, 0, 0, 0, 0, 0, 0]: data_zero.append(a) elif result == [1, 1, 1, 1, 1, 1, 1, 1, 1]: data_ones.append(a) print(“ZERO:”, data_zero) print(“ONES:”, data_ones)
    image
    Рис. 14

    Отлично у нас есть группа элементов дающих «нули» и «единицы». Как получить желанную таблицу [1, 0, 0, 0, 1, 0, 0, 0, 1]?

    Самые внимательные/догадливые могли подметить (скажем, по комментариям к предыдущей статье), что мы имеем дело с матрицами, которые при умножении друг на друга обязаны давать единичную матрицу [1, 0, 0, 0, 1, 0, 0, 0, 1]. Следственно, для приобретения единичной матрицы нам необходимо либо две единичные матрицы, либо две обратные.

    Дабы получить требуемую единичную матрицу, дозволено применять дальнейший паттерн:

    # Паттерн
    part_a = [y, x, x, x, y, x, x, x, y]
    part_b = [y, x, x, x, y, x, x, x, y]
    
    result = algo(part_a, part_b)

    Где в место x – подставьте всякий единичный символ, а взамен y – всякий нулевой.

    Дозволено применять и другие паттерны, обнаружить их дозволено с поддержкой дальнейшего «заклинания»:

    happy = [1,32]
    for byte_1 in happy[:]:
        for byte_2 in happy[:]:
            for byte_3 in happy[:]:
                for byte_4 in happy[:]:                       
                    for byte_5 in happy[:]:
                        for byte_6 in happy[:]:
                            for byte_7 in happy[:]:
                                for byte_8 in happy[:]:
                                    for byte_9 in happy[:]:
    
                                        part_1 = [byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_8, byte_9]
                                        part_2 = [byte_1, byte_2, byte_3, byte_4, byte_5, byte_6, byte_7, byte_8, byte_9]
    
                                        result = create_table(part_1, part_2)
    
                                        if result == [1, 0, 0, 0, 1, 0, 0, 0, 1]:
                                            print("%s | %s " % (part_2, part_1))

    image
    Рис. 15

    Произведя замену, дозволено получить, скажем, такие паттерны:

    patterns = [
            # Pattern 0
            [
                [y1, x1, x1, x1, y2, x1, x1, x1, y3],
                [y1, x1, x1, x1, y2, x1, x1, x1, y3]
            ],
    
            # Pattern 1a
            [
                [y1, x1, x1, x1, x1, y1, x1, y1, x1],
                [y1, x1, x1, x1, x1, y1, x1, y1, x1]
            ],
    
            # Pattern 1b
            [
                [y1, x1, x2, x3, x4, y1, x5, y1, x6],
                [y1, x2, x1, x5, x6, y1, x3, y1, x4]
            ],
    
            # Pattern 2a
            [
                [y1, x1, x1, x1, y1, x1, x1, x1, y1],
                [y1, x1, x1, x1, y1, x1, x1, x1, y1]
            ],
    
            # Pattern 2b
            [
                [y1, x1, x2, x3, y2, x4, x5, x6, y3],
                [y1, x1, x2, x3, y2, x4, x5, x6, y3]
            ],
    
            # Pattern 3a
            [
                [x1, x1, y1, x1, y1, x1, y1, x1, x1],
                [x1, x1, y1, x1, y1, x1, y1, x1, x1]
            ],
    
            # Pattern 3b
            [
                [x1, x2, y1, x3, y2, x4, y3, x5, x6],
                [x6, x5, y3, x4, y2, x3, y1, x2, x1]
            ],
    
            # Pattern 4a
            [
                [x1, y1, x1, y1, x1, x1, x1, x1, y1],
                [x1, y1, x1, y1, x1, x1, x1, x1, y1]
            ],
    
            # Pattern 4b
            [
                [x1, y1, x2, y2, x3, x4, x5, x6, y3],
                [x3, y2, x4, y1, x1, x2, x6, x5, y3]
            ],
    
            # Pattern 5
            [
                [x1, x2, y1, y2, x3, x4, x5, y3, x6],
                [x4, y2, x3, x6, x5, y3, y1, x1, x2]
            ]
        ]

    Сейчас, когда мы разобрались с приобретением желанной единичной матрицы (т.е. таблицы валидации), перейдем к иным задачам.

    Как подобрать подходящие part_a и part_b?

    Мы знаем следующее:

    part_a  = algo(part_1, salt)
    part_b  = algo(part_2, salt)
    
    valid_table  = algo(part_a, part_b)

    Откуда следует, что, скажем, part_a зависит от part_1 и salt. Что в свою очередь, сужает допустимые комбинации для part_a. Появляется правомерный вопрос.

    Какие комбинации мы можем применять?

    Думаю многие теснее додумались что необходимо сделать? Верно, применять очередное «заклинание»!

    Вот одно из них:

    # serial_data для email “support@reverse4you.org”
    serial_data = [52, 233, 91, 105, 65, 15, 50, 176, 90, 40, 225, 81, 207, 79, 34, 19]
    
    def get_items(first_part, second_part):
    
        result = []
        inner_index = 0
        while(inner_index < 3):
    
    curr_first = 0
            accumulator = 0
            index = 0
            while(index < 3):
    
                first = first_part[inner_index   curr_first]
                second = second_part[index]
                hash = 0            
                if (first != 0):
                    while (first != 0):
                        if (first & 1):
                            hash ^= second
    
                        second  = second
                        first = first >> 1
    
                accumulator ^= hash
                index  = 1
                curr_first  = 3
    
            result.append(accumulator & 0xff)
            inner_index  = 1
    
        return result
    
    a = 0x3
    b = 0x5
    c = 0x7
    
    first_part = [a, b, c, b, c, a, c, a, b]
    
    second_part_new = [0, 0, 0]
    count = 0
    result_table = []
    
    for byte_1 in serial_data:
        second_part_new[0] = byte_1
    
        for byte_2 in serial_data:
            second_part_new[1] = byte_2
    
            for byte_3 in serial_data:
                second_part_new[2] = byte_3
    
                res = get_items(first_part, second_part_new)
                print("index: %s, table: %s" % (count, res))
    
                count  = 1
    
    print("Count: %s" % count)

    Если ваше «заклинание» удачно отработает, вы увидите, что для part_a и part_b доступно каждого4096 вариантов (вернее говоря «под вариантов»).

    image
    Рис. 16

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

    Первая жертва (1-й валидный ключ)

    Если вы были внимательны, то наверно подметили, что все 4096 вариантов дозволено поделить на две группы, те у которых «все элементы четные» и те у которых «все элементы не четные».

    index: 0035, table: [116, 222, 172] <= Все элементы четные
    index: 0560, table: [172, 116, 222] <= Все элементы четные
    index: 0770, table: [222, 172, 116] <= Все элементы четные

    index: 0117, table: [1, 229, 111] <= Все элементы не четные
    index: 1287, table: [229, 111, 1] <= Все элементы не четные
    index: 1872, table: [111, 1, 229] <= Все элементы не четные

    Впрочем, если вы посмотрите на доступные нам «паттерны», то увидите, что все они требуют того, Дабы в всяком из «вариантов» были как «четные», так и «не четные» элементы, т.е:

    Вот две матрицы, которые дают нам единичную:

    part_a
    [176, 176, 65] <= Есть четные и не четные
    [176, 65, 176] <= Есть четные и не четные
    [65, 176, 176] <= Есть четные и не четные

    part_b
    [176, 176, 65] <= Есть четные и не четные
    [176, 65, 176] <= Есть четные и не четные
    [65, 176, 176] <= Есть четные и не четные

    valid_table = part_a * part_a
    [ 1, 0, 0 ]
    [ 0, 1, 0 ]
    [ 0, 0, 1 ]

    Так как «варианты» с «четными» и «не четными» элементами отсутствуют – приходим к итогу, что в крякми находится оплошность. В стает правомерный вопрос.

    В чем заключается оплошность?

    Позже стремительных размышлений приходим к итогу, что оплошность кроется в фиксированной матрице[0x3, 0x5, 0x7, 0x5, 0x7, 0x3, 0x7, 0x3, 0x5]. Для приобретения четных и нечетных «вариантов» нужно заменить «0×3»«0×5» и «0×7» на «0×2»«0×3» и «0×8» соответственно, либо на иной вариант, где будет два четных и один не четный элемент, скажем, на такие «0×4»«0×7» и «0×8» (как вариант).

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

    Дабы удостовериться в том, что вы исполнили верную замену (скажем, если вы решили вставить другие символы чудесные от «0×2»«0×3» и «0×8»), следует применять следующее «заклинание»:

    serial_data = [52, 233, 91, 105, 65, 15, 50, 176, 90, 40, 225, 81, 207, 79, 34, 19]
    
    a = 0x2
    b = 0x3
    c = 0x8
    
    first_part = [a, b, c, b, c, a, c, a, b]
    
    second_part_new = [0, 0, 0]
    count = 0
    result_table = []
    
    for byte_1 in serial_data:
        second_part_new[0] = byte_1
    
        for byte_2 in serial_data:
            second_part_new[1] = byte_2
    
            for byte_3 in serial_data:
                second_part_new[2] = byte_3
    
                res = get_items(first_part, second_part_new)
                print("index: %s, table: %s" % (count, res))
    
                if (res[0] % 16 == 0 and res[1] % 16 == 0 and res[2] % 16 == 1) or\
                   (res[0] % 16 == 1 and res[1] % 16 == 0 and res[2] % 16 == 0) or\
                   (res[0] % 16 == 0 and res[1] % 16 == 1 and res[2] % 16 == 0):
                        result_table.append(res)
                count  = 1
    
    print("Count:", count)
    print("Good:", result_table)

    Если приманка была подобрана верно (как в нашем случае, «0×2, 0×3, 0×8»), то в ваших западнях (в поле «Good») окажется, как минимум, один зверь (группа состоящая из трёх массивов). Пример итога для фиксированной матрицы (с элементами «0×2», «0×3» и «0×8») показан ниже:

    image
    Рис. 17

    Как видите, нам улыбнулась успех, следственно в наши ловушки попало целых три диких зверя, что безусловно же будет содействовать сервировке торжественного стола (т.е. может применяться для образования part_a и part_b).

    Самые внимательные теснее подметили, что итог в строке «Good» дозволено разбить на группы, в всякой из которой будет по три строки.

    [0, 144, 81]
    [81, 0, 144]
    [144, 81, 0]

    [144, 145, 0]
    [0, 144, 145]
    [145, 0, 144]

    [0, 144, 209]
    [209, 0, 144]
    [144, 209, 0]

    Еще больше внимательные наверно подметили, что все эти символы входят в комплекты «нулевых» и «единичных» символов ;)

    image
    Рис. 18

    Ну, а самые прозорливые (я верю) теснее во всю пируют за огромным столом, так как они сумели выследить большого зверя, приманив его сходственным «заклинанием»:

    # Колляция зверя
    # [0, 144, 209]
    # [209, 0, 144]
    # [144, 209, 0]
    
    a = 144
    b = 209
    c = 0
    
    # Применяем один из доступных паттернов
    part_a = [c, a, b, b, c, a, a, b, c]
    part_b = [a, b, c, c, a, b, b, c, a]
    
    # part_a1 = [0, 144, 209]
    # part_a2 = [209, 0, 144]
    # part_a3 = [144, 209, 0]
    # part_a = part_a1   part_a2   part_a3
    
    # part_b1 = [144, 209, 0]
    # part_b2 = [0, 144, 209]
    # part_b3 = [209, 0, 144]
    # part_b = part_b1   part_b2   part_b3
    
    result = create_table(part_a, part_b)
    print(result)

    На этом мы и завершим решать данный крякми… Как перевести внутренние байты в типичный вид, Дабы их дозволено было ввести в окно крякми, я думаю разберетесь сами.

    А пока вы разбираетесь мы перейдем к рассмотрению нового (поправленного) крякми. Сразу хочу сказать, что все что мы разглядели для данного крякми, актуально и для нового, следственно ограничимся поверхностным изложением правила его работы и дадим ссылку на кейген (для больше любопытных либо напротив ленивых).

    Бонус (кейген изложение работы нового крякми)

    чтобы не запутаться с имеющимися версиями – внесем некоторую ясность в их нумерацию:

    • ZeroNightsCrackMe_v1 – рассмотрен тут.
    • ZeroNightsCrackMe_v2 – является черновой версией и описан выше в этой статье.
    • ZeroNightsCrackMe_v3 – поверхностно рассмотрен ниже дается кейген.
    Алгорифм проверки таблицы валидации и сама таблица валидации

    Как и во всех предшествующих версиях v1 и v2.

    Алгорифм заполнения таблицы валидации

    Как и в черновой версии v2 (рассматривался выше в этой статье).

    Данные для алгортима заполнения таблицы валидации

    Правило действия такой же, как и в самой первой версии v1, но применяются другие микшеры.

    Алгорифм превращения серийного кода во внутреннее представление, таблица реформирования и возможный диапазон

    Как и во всех предшествующих версиях v1 и v2.

    Кеген для новой версии

    Крякми версий v2 и v3 дозволено обнаружить в этой теме. Там же обнаружите кейген для новой версииv3 от меня Дарвина.

    Пароль на архив от кейгена: Darwin_1iOi7q7IQ1wqWiiIIw

    Проверка кейгена для третьей версии крякми:

    > keygen_v3.py habrahabr.ru > result.txt

    image
    Рис. 19

    image
    Рис. 20

    Каждому дочитавшим до конца большое спасибо! До скорых встреч!

Источник: programmingmaster.ru

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB