Решение promix17 - Entangled Code

Сложность: 2 - Needs a little brain (or luck)
Платформа: Windows
Язык: Assembler
Дано: Entangled Code

Entangled Code by Promix17
Level of dufficulty: 3

The purpose is to find correct password.
Hope, it will be interesting
Don't give up and good luck :-)
Send me your solutions, questions, comments and offers.
E-mail: ur.xednay|71ximorp#ur.xednay|71ximorp

Решение (автор bike)
опубликовано 04.06.2012

Примечание:
1. badboy-сообщение - это сообщение, которое отображается пользователю в случае неверной пары имя-ключ. Текст сообщения разный, содержание всегда одно: “Вы ввели неправильное имя или пароль (ключ)”. Либо другой вариант - пользователю ничего не сообщается, а программа ожидает ввода правильного ключа.
2. goodboy-сообщение - это ура-сообщение, которое показывается пользователю в случае корректной пары имя-ключ.

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

Говорящее название «EntangledCode» («запутанный код») подразумевает нетривиальный подход к решению задачки. Приступим. Crackme по размеру маленький (буквально 4Кб), поэтому совершенно несложно найти адрес, где читается введенный пароль – это 0x0401296. GetWindowTextA ожидает в качестве пароля строку длиной 16 байт (четыре двойных слова - DWORD). Первые 4 байта пароля должны быть «FIFA» - явная проверка в лоб, просто. Следующие 4 байта пароля XORятся с константой и полученное значение выступает как адрес, куда передается управление инструкцией CALL. Код, разбирающий пароль на составляющие приведен ниже:



Итак, пароль должен быть длиной в 16 байт. Обозначим через P0 первые 4 байта, т.е. первый DWORD (это строка «FIFA»), через P1 второй DWORD, через P2 – третий, через P3 – четвертый. С P0 нам все понятно, а P1 после XOR с константой должен указывать на выполняемый код. Логично, что адрес должен находиться в пределах адресного пространства самой программы.
Просматривая код (0x0401000 – 0x0402000) в поисках места, куда можно передать управление (CALL ECX), по адресу 0x040136F обнаруживаем вызов Windows API функций MessageBox и ExitProcess. Скорее всего, MessageBox показывает goodboy-сообщение о верном пароле, после чего программа завершается (ExitProcess). Вот только текст сообщения не определен (конечно, он есть, но не «читабельный» - 17726B5E). Возможно, текст расшифровывается паролем где-то в другом месте. И находим его в блоке кода 0x0401000 – 0x04010D0 (блок содержит и данные, и код вперемешку), а точнее здесь:



При расшифровки текста, сообщение XORится с двумя значениями, взятыми со стека. Обратите внимание, что команд POP – три. Две идущие подряд команды POP EAX означают, что двойное слово на вершине стека для алгоритма не важно и отбрасывается. Предположим, что этим двойным словом является обратный адрес при вызове CALL ECX, а два других POPа могут оказаться Р3 и Р2, которые добавили на стек перед командой CALL.
Далее, после операции P3 XOR P2 XOR «зашифрованный текст» получаем читабельное сообщение. При желании при любых P2 и P3 можно получить некий текст. Но, скорее всего, для P2 и P3 автор crackme предусмотрел некоторые ограничения и их надо найти. Более подробно рассмотрим еще один интересный блок (он находится выше блока расшифровки):



Если мы доберемся до 0x0401091 (EBX должен быть равен 1), то у нас на выполнение две команды PUSH и команда RETN. И это очень хорошо вписывается в код чуть выше: если бы EAX был бы равен 0x040107B, то, выполнив две команды PUSH EAX и команду RETN, мы бы попали на 0x040107B. Здесь значение EBX увеличивается на 1 (и стало равно 2). Затем EBX сравнивается с 3 и условие не выполняется. Далее следует команда RETN (0x0401081), которая берет в качестве обратного адреса значение со стека, а у нас там хранится второй 0x040107B, и мы снова возвращаемся на 0x040107B. И снова инкрементируем EBX (стало равно 3). И вот тут условие EBX = 3 срабатывает, и управление передается дальше.
Вероятно, описанный сценарий событий – правильный путь, подходит он в самый раз. Все, что нам нужно, определить отправную точку. Первое, что приходит в голову – 0x0401000. Т.е. для CALL ECX, ECX = 0x0401000. Но, погоняв этот вариант под OllyDBG, увидим, что этот путь не совсем хорош, в нужный нам момент не хватает необходимого значения на стеке. Еще раз, просмотрев код, обнаружим, что адрес 0x0401060 подходит под возможную отправную точку. Также здесь устанавливается EBX = 1, то, что нам надо. Итак, для CALL ECX принимаем ECX = 0x0401060:

ECX = P1 XOR 0x450D5127 = 0x0401060 =>
P1 = 0x0401060 XOR 0x450D5127
P1 = 0x454D4147 (строка «GAME»)

Следовательно, первая половина пароля «FIFAGAME». Теперь, если в IDA или OllyDBG (предварительно указав пароль «FIFAGAME») проследить за ходом выполнения, начиная с адреса 0x0401060 до адреса 0x0401091, то в EAX получаем значение равное (P3 + 0xC0DEDEAD) XOR 0xDEADC0DE XOR 0xDBDCFEA1. А как мы обсуждали выше, перед выполнением PUSH EAX, PUSH EAX, RETN в регистре EAX должно быть значение 0x040107B. Значит:

EAX = 0x40107B = (P3 + 0xC0DEDEAD) XOR 0xDEADC0DE XOR 0xDBDCFEA1 =>
P3 = (0x40107B XOR 0xDBDCFEA1 XOR 0xDEADC0DE) – 0xC0DEDEAD
P3 = 0x44524F57 (строка «WORD»)

Пароль уже стал «FIFAGAME…WORD». Далее выполняется следующий код:



Этот цикл считает сумму байт по адресам 0x0401000 – 0x04010AA и ожидает следующее:

P2 XOR СуммаБайт == 0x3A8729F2

P2 = 0x3A8729F2 XOR СуммаБайт, т.е. P2 получить легко, но обратите внимание, что буфер, где хранится пароль, располагается по адресу 0x0401030 и вносит свой вклад в сумму байт. Значит, само значение P2 также в сумме (P2 является единственной неизвестной в сумме, мы уже вычислили P0, P1, P3). Странно, сумма складывается по DWORD (двойное слово – 4 байта), при этом адрес увеличивается на 1 вместо 4. Я не знаю, как математически найти P2, но мы можем написать брутфорс:). Для всех значений P2 от 0 до 0xFFFFFFFF вычислим сумму и проверим равенство (см. код брутфорса). Вы увидите, что существует только одно решение: Р2 = 0x53534150 (строка «PASS»). Теперь у нас есть полный пароль: «FIFAGAMEPASSWORD».

Код брутфорса на Python:

# -*- coding: cp1251 -*-
# байты с адреса 0x0401000 по 0x04010AA
sumVector = [0x81,0xC7,0xAD,0xDE,0xDE,0xC0,0xEB,0x06,0x45,0x6E,0x74,
0x65,0x72,0x00,0x33,0xFE,0x57,0xEB,0x07,0x62,0x75,0x74,0x74,0x6F,0x6E,
0x00,0x33,0xFF,0xEB,0x42,0xBE,0xDE,0xC0,0xAD,0xDE,0x83,0xF8,0x00,0x74,
0xD8,0x90,0x00,0x00,0x00,0x00,0x58,0xEB,0x11,0x46,0x49,0x46,0x41,0x47,
0x41,0x4D,0x45,0x00,0x00,0x00,0x00,0x57,0x4F,0x52,0x44,0x00,0x5E,0x90,
0xEB,0x1B,0x54,0x79,0x70,0x65,0x20,0x79,0x6F,0x75,0x72,0x20,0x70,0x61,
0x73,0x73,0x77,0x6F,0x72,0x64,0x20,0x68,0x65,0x72,0x65,0x2E,0x2E,0x2E,
0x00,0xBB,0x01,0x00,0x00,0x00,0x33,0xC0,0x83,0xFF,0x00,0x74,0x08,0x56,
0xEB,0xAF,0x65,0x64,0x69,0x74,0x00,0x83,0xFB,0x00,0x74,0x0B,0xEB,0x0B,
0x43,0x83,0xFB,0x03,0x74,0x01,0xC3,0xEB,0x12,0x51,0xC3,0x58,0x35,0xA1,
0xFE,0xDC,0xDB,0x83,0xFB,0x01,0x75,0x03,0x50,0x50,0xC3,0x50,0xC3,0x33,
0xF6,0xB9,0xA8,0x10,0x40,0x00,0x81,0xE9,0x00,0x10,0x40,0x00,0xBB,0x00,
0x10,0x40,0x00,0x03,0x33,0x43];
 
def get_num(off):
    num = 0
    num |= (sumVector[off+3] << 24) 
    num |= (sumVector[off+2] << 16) 
    num |= (sumVector[off+1] << 8) 
    num |= sumVector[off]
    return num
 
def set_num(off, num):
    sumVector[off+3] = (num >> 24) & 0xFF
    sumVector[off+2] = (num >> 16) & 0xFF
    sumVector[off+1] = (num >> 8) & 0xFF
    sumVector[off] = num & 0xFF
 
i = 0
constSum = 0
brutesum = 0
 
# считаем сумму байт без 3-го DWORD пароля (P2)
for i in range(0x35):
    constSum += get_num(i)
    constSum &= 0xFFFFFFFF
for i in range(0x3C, 0xA8):
    constSum += get_num(i)
    constSum &= 0xFFFFFFFF
 
# брутим P2
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)
                set_num(0x38, z)
                brutesum = constSum;
                # считаем полную сумму с учетом P2
                brutesum += get_num(0x35)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x36)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x37)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x38)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x39)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x3A)
                brutesum &= 0xFFFFFFFF
                brutesum += get_num(0x3B)
                brutesum &= 0xFFFFFFFF
                # проверка
                if (z ^ brutesum) == 0x3A8729F2:
                    print ("Нашли P2: "+format(z,"08X"))


Если ввести правильный пароль, то goodboy-сообщение (0x04010B9) расшифруется в «читабельный» вид:

image00.jpg

Пока не указано иное, содержимое этой страницы распространяется по лицензии Creative Commons Attribution-ShareAlike 3.0 License