Сломай меня полностью (ZeroNights 2013)

Привет всем Хабровчанам! Меня зовут Дарвин и сегодня я расскажу вам о том, как решал крякми от ZeroNights 2013 и Лаборатории Касперского для форума r0 Crew и его полу-приватной Киевской встречи.

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

Инструменты:
  • OllyDbg SnD 2.2
  • IDA Pro 6.1
  • Немного серого вещества

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

Анализ


Взлом приложения всегда начинается с запуска и поверхностного исследования его поведения. Давайте запустим наш крякми и посмотрим, как он реагирует на ввод данных:

image
Рис. 1

Как и можно было ожидать, нам сообщают о неправильно введенном серийном коде.

Теперь давайте откроем его в IDA Pro, осуществим поиск по строкам и пробуем найти место в котором выводится эта табличка.

Идём на вкладку «Strings windows» (если её у вас нет, то открываем) и ищем строку «Fail, Serial is invalid !!!»:

image
Рис. 2

Под интересующей нас строкой видим другую не менее интересную. Жмём по любой из строк и переходим на ссылающийся код. В итоге попадаем сюда:

image
Рис. 3

Из чего становится ясно — проверка происходит в функции sub_4012D0. Если прокрутить немного вверх (буквально на пол экрана), то увидим ещё один интересный момент:

image
Рис. 4

Здесь мы видим два вызова функции GetWindowTextA. Учитывая предыдущий рисунок, становится очевидным, что обе функции используются для получения данных с полей формы Email / Serial.

Исходя из полученных данных, существует два сценария дальнейшего развития:

  1. Начать с начала. Поставить бряки на функции GetWindowTextA и далее следить за изменением данных. Метод долгий и можно заблудиться.
  2. Начать с конца. Найти код влияющий на результат (в нашем случае на вывод окошка успешности / не успешности) и раскрутить его в обратном направлении. Метод быстрый, хардкорный и интересный.

Когда решал крякми, выбрал второй путь и не только из-за того, что это быстрее (ИМХО), но и из-за того, что хотел схитрить и подсмотреть валидную пару. В некоторых крякми такое работает, но там где это работает обычно просят восстановить сам алгоритм, а не только предоставить одну валидную пару. Забегая в перед скажу, что с под смотром валидной пары (вполне ожидаемо) я обламался =)

Но давайте по порядку…

Ищем ключевые детали


Так как мы решили идти с конца, то первое, что нужно сделать — это определить, что же влияет на результат успешности или не успешности введенного серийника?

Таблица валидации

Чтобы дать ответ на поставленный вопрос, давайте сначала посмотрим на строки кода следующие сразу за вызовом sub_4012D0. Исходя из Рис. 3 видно, что происходит проверка регистра EAX. Если он равен нулю, то переходим на «Fail», если нет то на «Good».

Окей, тогда для вызова положительного результата, функция sub_4012D0 в регистре EAX должна возвращать не нулевой результат.

Зная это давайте зайдем в саму функцию sub_4012D0, найдем её конец и попробуем определить, что влияет на установку в EAX не нулевого значения. Сказано сделано:

image
Рис. 5

Ида не двузначно дает понять, что функция имеет как минимум два выхода (выделены синей рамкой). Причём, выход находящийся в зеленой рамке обнуляет регистр EAX, а выход в красной рамке, наоборот, устанавливает в еденицу.

С этим определись, теперь давайте разберемся, какой код предшествует блоку в красной рамке? Для этого воспользуемся Олькой (OllyDbg). Почему Олей, во-первых: потому что она дает более ясное представление (конкретно в нашей задаче), чем Ида (в ней отвлекают различные разрывы и перекрестные ссылки), а во-вторых: мы будем потихоньку переходить к отладке крякми.

image
Рис. 6

Примечание: прежде чем открывать OllyDbg желательно отключить ASLR, механизм защиты, который отвечает за рандомизацию адресного пространства. Делается это в реестре, в ветке: «HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management», создать ключ «MoveImages» (DWORD) со значением «0», после чего перезагрузить ОС. Если этого не сделать, после каждой перезагрузки приложения, адреса будут меняться...

Итак, на Рис. 6 видно, что установке регистру EAX=1 предшествует некий двойной цикл (адреса 0x401440-0x401464). Это наш первый ключевой элемент и отправная точка к решению крякми.

Если поставите бряк на адрес 0x401440 и пройдете до первой проверки:

Примечание: Стоит сказать, что если в поле ввода вы используете строку отличную от «111122223333444455», вам придется столкнутся с еще двумя проблемами. Первая заключается в длине, а вторая в допустимых символах, которые могут использоваться в серийном коде. Определение кода, отвечающего за эти ограничения, я оставлю за вами.

image
Рис. 7

Увидите, что в строке 0x401449 единица проверяется с каким-то значением, которое расположено по адресу (в моем случае) 0x18FB28. Далее, если успешно пройдете весь цикл (манипулируя ветвлениями или значениями по требуемым адресам; иначе вы понятное дело зафейлитесь), то обнаружите, что всего существует 9-ть элементов (каждый размером 4 байта):

image
Рис. 8

Этим 9-ти элементам я дал имя «Таблица валидации». Если вы, как и я, подгоняли значения элементов (во время их проверки в цикле) у нас с вами должен был получиться такой результат:

image
Рис. 9

То есть, в итоге (сокращенно) у нас должно получиться, что-то типа: 100010001.

Продолжим определение ключевых элементов и попробуем определить:

Кто пишет по адресам 0x18FB28-0x18FB48?

Код отвечающий за заполнение «Таблицы валидации»

Для обнаружения этого кода проделываем следующие действия:
  • Продолжаем выполнение крякми (жмем F9 в Ольке);
  • Ставим бряк на вызов sub_4012D0 (либо он уже должен там стоять);
  • Переключаемся на крякми и во всплывшем окне (говорящем об успешности или не успешности) нажимаем «Ок» (тем самым продолжая работу крякми);
  • Далее жмем на кнопку «Check» для запуска пересчета серийника, после чего вы должны остановиться на вызове sub_4012D0;
  • Стоя на вызове sub_4012D0, поставьте бряк на запись, на адрес 0x18FB28;
  • И снова жмите F9.

Если все сделали правильно, окажетесь здесь:

image
Рис. 10

Здесь, красной рамкой выделено вычисление и сохранение первого элемента «Таблицы валидации», синей рамкой выделено продолжение вычислений оставшихся восьми элементов.

Сам код занимает регион адресов 0x4011D4 — 0x4012BB и представляет собой цикл с тремя итерациями. На каждой итерации вычисляются и сохраняются значения трех элементов таблицы. Представление этого кода на Питоне выглядит следующим образом:

for out_index in range(3):
    
    for inner_index in range(3):
    
        quotient, remainder = 0, 0
        
        for index in range(3):
            x, y = index + (out_index*3), index + (inner_index*3)
                   
            byte = first_serial[x] * second_serial[y]
            
            quotient = int(byte / 7) 
            remainder += byte % 7
            
            remainder = remainder % 7
                
        result_table.append(remainder)


Что значат first_serial и second_serial станет ясно немного позже. Сейчас же мы продолжим анализировать Рис. 10.

Что мы имеем:
  • Адрес 0x657020 (с непонятными данными);
  • Адрес 0x18fadc (с непонятными данными);
  • Адрес 0x18faec (со значением 0x7);
  • Адрес 0x18fae8 (со значением 0x18fb30).

image
Рис. 11

image
Рис. 12

Внимательно изучив код выяснится, что:
  • К вычислению таблицы вадлидации, адрес 0x18fae8 не имеет никакого отношения;
  • По адресам 0x657020 и 0x18fadc находится массивы с 9-ю элементами. У массива 0x657020 каждый элемент равен 1 байту, а у массива 0x18fadc четырём байтам;
  • Значение 0x7 из адреса 0x18faec довольно активно используется при вычислении каждого из элементов таблицы валидации.

Имея эти данные попробуем определить, что же собой представляют массивы по адресам 0x657020 и 0x18fadc. Самые внимательные наверное уже заметили, что если сложить 9 + 9 = 18. Размер серийника тоже равен 18. Поэтому можно предположить, что это он и есть. Но он явно не соответствует тому, который мы вводили изначально. Хотя наиболее внимательные могли дополнительно подметить, что элементы из массива 0x657020, хоть и смутно, но чем-то все же напоминают первую половину последовательности элементов из введенного нами серийника (111122223).

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

Что скрывается за адресом 0x657020?

Продолжим наш анализ и попробуем определить, что же все таки содержится в массиве 0x657020?

Для этого:
  • Снимаем все бряки, которые были установлены на память:
  • Продолжаем выполнение крякми (жмем F9 в Ольке);
  • На вызове sub_4012D0 должен стоять бряк;
  • Переключаемся на крякми и во всплывшем окне (говорящем об успешности или не успешности) нажимаем «Ок» (тем самым продолжая работу крякми);
  • Далее жмем на кнопку «Check» для запуска пересчета серийника, после чего вы должны остановиться на вызове sub_4012D0;
  • Стоя на вызове sub_4012D0, поставьте бряк на запись, на адрес 0x657020;
  • Далее 2 раза жмите на F9 (первый раз вы окажетесь где-то в библиотечном коде, а во второй в нужном месте).

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

image
Рис. 13

Протрассировав код выделенный красным, выясним следующее:
  • По адресу 0x657020 записываются первые 9 байта нашего серийного кода (111122223);
  • Серийный код берется с адреса 0x18FBC4 (в моем случае).

Наши подозрения по поводу массива 0x657020 начинают потихоньку подтверждаться. Касаемо адреса 0x18FBC4 (который хранит весь введенный нами серийник), то с ним вы должны были еще познакомиться в самом начале, здесь:

image
Рис. 14

Здесь, красной рамкой выделена информация касающаяся серийника, синей, место, где сохраняется e-mail.

Однако, давайте вернемся к нашему серийнику, а то мы немного отвлеклись. После того как мы протрассировали инструкции из Рис. 13, по адресу 0x657020 у нас хранится половина нашего серийника:

image
Рис. 15

Но, как мы знаем, здесь должны храниться немного другие байты. Значит, скорее всего, над этой половиной серийника будут проводиться какие-то манипуляции. Чтобы обнаружить какие, нажмите еще раз F9. После чего окажемся тут:

image
Рис. 16

Здесь, красной рамкой выделено вычисление и сохранение нового значения для первого элемента массива 0x657020, синей рамкой выделено продолжение вычислений оставшихся восьми элементов.

Сам код занимает регион адресов 0x401070 — 0x401165. Для каждого элемента проводятся одни и те же вычисления. Эквивалентный код этих вычислений на Питоне представлен ниже:

def find_index(byte):
    
    byte_1 = byte >> 4
    byte_2 = byte_1 << 3
    res = (byte - byte_2)
    res =  res & 0x0f
    
    return res

# Псевдокод для вычисления первого элемента массива 0x657020
# byte = 0x657020[0] = 0x31
# index = find_index(byte)
# 0x657020[0] = 0x40CB28[index]


Если внимательно присмотритесь к Рис. 16, заметите некий адрес 0x40CB28. К его содержимому мы вернемся чуть позже.

После выполнения всех преобразований получим результат очень похожий на тот, что был у нас на Рис. 11.

image
Рис. 17

Если сравните Рис. 11 с Рис. 17, то заметите, что байты немного перемешаны… Для наглядности продублирую ниже одиннадцатый рисунок:

image
Рис. 18

Для обнаружения кода, который занимается перемешиванием байтов, еще раз нажмите F9.
После чего мы попадем на уже знакомое нам по Рис. 13 место:

image
Рис. 19

Проанализировав код, станет ясно, что из адреса 0x656EC8 берутся новые значения:

image
Рис. 20

Код, отвечающий за заполнение массива 0x656EC8, показан ниже. Как его я его нашёл, думаю разберетесь сами. Статья всё таки не резиновая…

image
Рис. 21

Что здесь происходит?

В регистре EBX содержится адрес 0x657020 из которого и происходит прямое копирование элементов в массив 0x656EC8. Присмотревшись к копированию, можно заметить, что некоторые элементы перемешиваются.

0x657020: 0, 1, 2, 3, 4, 5, 6, 7, 8
0x656EC8: 0, 1, 2, 4, 5, 3, 8, 6, 7

Теперь когда мы ответили на наш вопрос «Что скрывается за адресом 0x657020?», можно продолжить исследование и ответить на другие вопросы:
  • Что скрывается за адресом 0x40CB28 (был только что обнаружен при анализе массива 0x657020);
  • Что скрывается за адресом 0x18FADC (массив, который хранит 9-ть элементов размером 4 байта каждый).

Производная ASCII таблица

Разбор данных скрывающихся за массивом 0x18FADC, оставим на самостоятельное освоение. Действовать нужно по аналогии с массивом 0x657020. Скажу только, что массив 0x18FADC хранит вторую половину серийного кода.

Тут же мы разберем, что из себя представляет адрес 0x40СB28. Для этого осуществляем следующие манипуляции:
  • Отключаем все бряки поставленные на память;
  • Продолжаем выполнение крякми (жмем F9 в Ольке);
  • На вызове sub_4012D0 должен стоять бряк;
  • Переключаемся на крякми и во всплывшем окне (говорящем об успешности или не успешности) нажимаем «Ок» (тем самым продолжая работу крякми);
  • Далее жмем на кнопку «Check» для запуска пересчета серийника, после чего вы должны остановиться на вызове sub_4012D0;
  • Стоя на вызове sub_4012D0, поставьте бряк на запись, на адрес 0x40СB28;
  • Далее жмите на F9.

Если всё сделали правильно, окажемся тут:

image
Рис. 22

Код в красной рамке создает ASCII таблицу, которая сохраняется по адресу 0x40СB28. В синей рамке, на основании вашего мэйла, происходит преобразование созданной таблицы. Конечный результат этого преобразования я назвал «Таблицей замен».

Представление этого кода на Питоне выглядит следующим образом:

mail = "support@reverse4you.org"

def get_table(mail):
    
    ascii_table = []
    for index in range(256):
        ascii_table.append(index)    
    
    mail = list(mail)
    len_mail = len(mail)
    
    index = 0
    accumulate_index = 0
    while(index < 256):        
        mail_index = index % len_mail
        
        byte_ascii = ascii_table[index]
        byte_mail = ord(mail[mail_index])
        
        accumulate_index += byte_mail
        accumulate_index += byte_ascii
        accumulate_index = accumulate_index & 0xFF
        
        byte = ascii_table[accumulate_index]
        ascii_table[accumulate_index] = byte_ascii
        ascii_table[index] = byte
        
        index += 1
        
    return ascii_table


Рад вас поздравить, дойдя до этой точки мы раскрыли все неизвестные нам моменты и обнаружили все ключевые элементы необходимые для создания своей валидной пары mail / serial.

Соберём всё в кучу


Что мы имеем?

  1. Допустимый диапазон. Серийный код должен быть равен 18-ти символам, которые в свою очередь должны состоять из символов [0-9], [a-z], [A-Z];
  2. Таблица преобразования. Используется для преобразования введенного серийного кода во внутреннее представление;
  3. Алгоритм превращения серийного кода во внутреннее представление;
  4. Алгоритм заполнения таблицы валидации;
  5. Таблица валидаци: 100010001.

Что нужно?

Составить серийный код таким образом, чтобы пройдя все преобразования мы получили требуемую «Таблицу валидации» (100010001).

Как делал я?

Должен вам признаться мне пришлось немного потупить… Вместо того, чтобы манипулировать «нулями» и «единицами» (до этого я дошёл спустя несколько часов) я начал подбирать пароль по внутреннему представлению серийного кода, т.е. пытался подобрать нужные мне нули и единицы с помощью таких значений как, например, 0xс0, 0x28,…, 0xff (т.е. первые 16 символов «Таблицы замен»). Что конечно же было ошибкой. Надо сказать, что я искал сложную задачу, которой попросту не существовало. Плюс, я еще выкинул несколько очень важных байт, которые мне постоянно мешали (при делении на любое число они давали «нуль»)… В общем, долго рассказывать…

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

image
Рис. 23

Потом уже, под конец вечера (сразу после ужина), меня посетило несколько здравых мыслей, которые в итоге привели меня к тому, что надо использовать «нули» и «единицы», а не необработанные байты «0xc0, 0x28, ..., 0xff». Как только ко мне пришла в голову эта идея, я сразу вспомнил и про те злополучные байты, которые меня постоянно раздражали и мешали мне (те, которые, при взаимодействии с любым числом, давали «нуль»). Поняв это, я мигом оказался у компьютера и буквально за 1-2 минуты собрал для себя валидную пару.

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

Последующей ссылке можно найти три кейгена Pashkin'a, BoRoV'a и меня любимого Darwin'a (кейген BoRoV'a мне больше всего понравился).

Пароли от архивов:
  • Pashkin: reverse4you.org_4_r00t
  • Darwin: AggGgAgaGcGGggCGCG
  • BoRoV: доступен в чистом виде без архива

На этом всё, всем спасибо за внимание.

***

Продолжение статьи можно найти тут.
  • +43
  • 21.8k
  • 9
Share post

Comments 9

    +2
    Крякми/Кейгенми с конференции ZeroNights 2013.
    Автор: Лаборатория Касперского
    Уровень: Средний
    Решение: Валидная пара mail/serial.

    Ничего себе средний уровень. Хочу статью про сложный крякми:)
      +8
      Да там всё просто, просто решение сложно расписано.
      Задание было ориентировано на участников конференции, а туда люди не с улицы пришли.
        +3
        Сложный — это все то-же самое, но внутри сурового упаковщика-виртуализатора :)
          0
          Вот, к примеру, солюшн сложного кейгенми (имхо):
          _http://gdtr.wordpress.com/2011/09/26/hyperelliptic-curve-crypto-dcoders-keygenme-2/
          +1
          Очень интересно и доступно всё описали.
          Большое спасибо.
          Вспомнил студенчество.
            +1
            Что за бред. Тут же просто перемножение матриц 3х3 по модулю 7. Первые 9 чисел — первая матрица, вторые 9 — вторая. После перемножения матриц должна получиться единичная, поэтому для создания ключа надо взять две обратные матрицы или просто единичные.
              +2
              Тоже кейгенил. Пошел другим путем — нагенировал брутом заранее матриц, которые при перемножении дают единичную.

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

              Версия для XP

              P.s. извиняюсь, коммент в общую ветку планировал
              +1
              Тоже решил. Правда я поступил проще… переписал алгоритм проверки на PHP и подобрал Serial. На все про все ушло где-то полдня…
                0
                Продолжение статьи можно найти тут.

                Only users with full accounts can post comments. Log in, please.