Решение Polynomial - KeygenMe

Сложность: 2 – Needs a little brain (or luck)
Платформа: Windows
Язык: C/C++
Дано: KeygenMe

This one is gonna be tricky! This keygen-me uses a large number of obfuscation and anti-debug tricks that make it difficult to debug and analyse, along with complex math to make the key generation process difficult to understand.

What to expect:
* Inline asm obfuscation
* PE format malformation
* Decoy data and code
* Heavy use of anti-debugger techniques
* Complex key generation code

Written entirely in C and inline assembler. Requires the user to input a name and corresponding serial key via command line interface.

Only a full standalone keygen is a valid solution - NO PATCHING!

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

Решение не будет строго следовать решению автора redoC, оно будет взято за основу. Действительно, сам crackme не сложен. Да, есть ряд простых антиотладочных трюков и не более. Схема построения – берем данные имени, пропускаем через некий алгоритм, и тупо сравниваем результат с введенным ключом. Статического анализа достаточно, чтобы понять логику crackme. Я использовал IDA, автору redoC достаточно было OllyDbg (для упрощения анализа он использовал предварительно выгруженный из IDA map-файл и загруженный в OllyDbg с помощью плагина GODUP – у меня map-файл из IDA 6.1 не подцепился).

Антиотладка

Перечислим все антиотладочные трюки, встречаемые по ходу выполнения программы.
Перед тем, как лезть вглубь crackme обнулим мусорные «data directories» в PE-заголовке - Export Table, Exceptions, Security, Debug, Copyright, Delay Import, COM. Далее приступаем к анализу кода.

Псевдокод программы:
1) Точка входа 0х00402478 – выполняется CRT код (обычно C/C++-программа опирается на мощную поддержку С Run-Time Library - библиотека времени исполнения языка C, далее - CRT; более редкое название - RTL - Run-Time Library. Многим функциям этой библиотеки для правильной работы требуется дополнительная инициализация - CRT startup code. В частности, для вывода текста на консоль с помощью функции printf необходимо, чтобы дескриптор стандартного вывода stdout был предварительно связан с устройством вывода операционной системы - например, стандартным выводом и консолью Win32. То же самое справедливо и для функций работы с кучей - таких, как malloc для C и оператора new для C++)
2) 0х00401810 – стартует функция _main. И здесь же через конструкцию PUSH EAX -> RETN передаем управление на адрес 0х4015F0
3) 0х0040164Dпрерывание 2D
4) 0х0040167Eвызов IsDebuggerPresent
5) 0х004016A7 – тайминговая проверка GetTickCount
6) 0х004016В3 – печатаем в окно консоли «Please enter your name:»
7) 0х004016D1трюк с OpenProcess (открываем системный процесс с PID = 4)
8) 0х004016F1изменение размера образа программы (антидамп) через PEB
9) 0х00401707проверка PEB.NtGlobalFlag
10) 0x00401727 – поиск окна отладчика OllyDbg с помощью FindWindowA(“OLLYDBG”)
11) 0x00401745 – поиск окна отладчика Immunity Debugger с помощью FindWindowA(“Immunity Debugger”)
12) 0x00401774 – поиск окна отладчика WinDbg с помощью FindWindowA(“WinDbgFrameClass”)
13) 0x0040177E – ждем ввода данных имени
14) 0х0040178B – печатаем в окно консоли «Please enter your serial:»
15) 0x00401793 – ждем ввода данных ключа
16) 0x004017B0 – вызов функции 004010F0алгоритм проверки имени и сравнение с ключом

В алгоритме проверки имени есть еще парочка антиотладочных трюков:
17) 0х00401168 – трюк, работающий только при отладке приложения с помощью OllyDbg – вызов OutputDebugStringA с параметром «%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s»
18) 0х004012D2 – обычный вызов OutputDebugStringA, проверка возвращаемого значения (выполняя код под отладчиком, функция возвращает в EAX значение больше 1, а выполняя код без отладчика – возвращает значение 0 или 1)
19) 0х004013BFпроверка PEB.BeingDebugged
20) 0х0040157В – тайминговая проверка GetTickCount

Убираем антиотладку

Чтобы разбирать алгоритм непосредственно под отладчиком надо сделать несколько изменений – патчей.
(18) 0x004012D2 = 0x6D2 (смещ. в файле) = 7E 07 (JLE SHORT 004012DB) = изменяем на JMP SHORT 004012DB (EB 07)
(19) 0x004013BF = 0x7BF (смещ. в файле) = 74 05 (JE SHORT 004013C6) = изменяем на JMP SHORT 004013C6 (EB 05)
(20) 0x0040157B = 0x97B (смещ. в файле) = 76 04 (JE SHORT 00401581) = изменяем на JMP SHORT 00401581 (EB 04)
(3) 0x0040164D = 0xA4D (смещ. в файле) = CD 2D (int 2D) = NOP NOP (90 90)
(7) 0x004016D1 = 0xAD1 (смещ. в файле) = 74 12 (JE SHORT 004016E5) = изменяем на JMP SHORT 004016E5 (EB 12)
(4) 0x0040167E = 0xA7E (смещ. в файле) = 74 05 (JE SHORT 00401685) = изменяем на JMP SHORT 00401685 (EB 05)
(5) 0x004016A7 = 0xAA7 (смещ. в файле) = 76 05 (JE SHORT 004016AE) = изменяем на JMP SHORT 004016AE (EB 05)
(9) 0x00401707 = 0xB07 (смещ. в файле) = 74 09 (JE SHORT 00401712) = изменяем на JMP SHORT 00401712 (EB 09)
(10) 0x00401727 = 0xB27 (смещ. в файле) = 74 08 (JE SHORT 00401731) = изменяем на JMP SHORT 00401731 (EB 08)
Специально для OllyDbg по адресу 0040C100 (смещение в файле 0хА700) заменяем строку «%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s» на что-нибудь более вменяемое «test».

Разбор алгоритма

Я думаю, автор crackme начинающий программист. Такая ошибка, как выход за границу массива – непростительна. Об этом чуть ниже. Другой финт – алгоритм спроектирован так, что к любому имени в большинстве случаев (но не всегда) будет подходить один и тот же ключ, т.е. в процессе работы алгоритма (самая распространенная операция здесь - умножение) получается большое число, но для анализа берется 64 младших бита и чаще всего при окончании расчета они равны – 0xFFFFFFFFFFFFFFFF, что при дальнейших телодвижениях дает нам ключ-константу - 38260FE8.

Алгоритм проверки имени – функция 004010F0 (адаптирован под Python)

# ПОДГОТОВКА
# начальные значения: переменная-константа
startvalue = 0x11B6F6B1098AB633;
 
# начальные значения: массив 512 байт по адресу 0x40C000
XBox = [0x5D, 0x10, 0x5B, 0x27, 0xC4, 0x3B, 0x51, 0x59, 0x6D, 0xDD, 0x29, 0x69, 
0xB9, 0x2E, 0x37, 0xBA, 0x97, 0x86, 0x44, 0x2C, 0x70, 0xB0, 0xCF, 0xBE, 0xC3, 
0xAC, 0xB, 0xFA, 0xDB, 0xCB, 0xC9, 0xCD, 0xC7, 0x65, 0x2, 0x3D, 0xCE, 0xF6, 
0x2D, 0x90, 0x39, 0x7, 0xC8, 0xE7, 0xA, 0x4D, 0xBD, 0x77, 0xFD, 0xDA, 0xF7, 
0x92, 0x15, 0xF3, 0xB5, 0xF9, 0x1D, 0xD3, 0xE2, 0x1A, 0xA2, 0xD, 0xD1, 0xA8,
0xC1, 0xB3, 0x8F, 0xE0, 0x41, 0x1B, 0x71, 0x63, 0x81, 0x9C, 0x1, 0xD2, 0x50, 
0x84, 0x5, 0x72, 0xFE, 0x2B, 0x36, 0x64, 0x5E, 0xD9, 0x9F, 0xFC, 0x9, 0x3A, 0xD5, 
0xAE, 0x99, 0x34, 0xE5, 0x88, 0x58, 0xF0, 0xF4, 0x83, 0x98, 0x80, 0x7E, 0x18, 0xAA, 
0xC, 0x6E, 0xF1, 0x62, 0x57, 0x13, 0xBB, 0xE4, 0xA9, 0x52, 0x1C, 0x16, 0x61, 0xE1, 
0x14, 0x55, 0x23, 0xB1, 0xEC, 0x12, 0x9E, 0xEB, 0x85, 0x87, 0x2F, 0xC2, 0x4B, 0x4F, 
0x67, 0x82, 0xC0, 0x47, 0xAD, 0x6, 0x49, 0xAF, 0xA4, 0x42, 0x6F, 0xF2, 0xCC, 0xA7, 
0x19, 0xCA, 0xD8, 0xB4, 0xDE, 0xDC, 0xEF, 0x5C, 0x75, 0xB6, 0xD7, 0xA0, 0xA3,
0x73, 0xF5, 0x68, 0x95, 0x5A, 0xD6, 0x8E, 0x43, 0x7A, 0x33, 0xF, 0xDF, 0x4E, 0x89, 
0xA5, 0x60, 0x31, 0xB2, 0x17, 0xC5, 0xEA, 0xE8, 0x3, 0x22, 0x11, 0x7C, 0x6B, 0x93, 
0x0, 0x3E, 0x1E, 0x53, 0x9B, 0xFF, 0xED, 0x7F, 0xFB, 0x5F, 0x1F, 0x25, 0x24, 0x38, 
0x7B, 0xC6, 0xA6, 0xB8, 0xBF, 0x96, 0x21, 0x94, 0x35, 0x40, 0xAB, 0x30, 0xD0, 0xEE, 
0xD4, 0xE6, 0x74, 0x9A, 0x28, 0x76, 0x46, 0x56, 0x4A, 0x8A, 0x4C, 0x2A, 0xA1, 0x78, 
0x8D, 0x66, 0x3F, 0x9D, 0xBC, 0x54, 0x6A, 0xE3, 0x4, 0x8, 0x79, 0xB7, 0x20, 0x26, 
0x6C, 0xF8, 0xE, 0x3C, 0x32, 0x45, 0x8B, 0x7D, 0x8C, 0xE9, 0x91, 0x48]
 
# вспомогательная функция 
# берем значение, если старший бит установлен (0х80), преобразуем в число со знаком. 
def znak(value):
    res = value
    if res & 0x80:
        res |= 0xFFFFFFFFFFFFFF00
    return res
 
# НАЧАЛО АЛГОРИТМА
# здесь указываем входные данные алгоритма: имя. Для примера, взяли "redoC"
name = "redoC"
 
# получить длину имени
lenname = len(name)
 
# промежуточный массив данных, длиной 512 байт
cField = []
 
# 1-й цикл обработки
for i in range(512):
    # берем каждый байт имени, используем его как индекс в массиве XBox. Значение 
    # из массива XOR со счетчиком цикла
    cField.append(XBox[ord(name[i % lenname])] ^ (i & 0xFF))
    if i > 0:
    # Вот здесь возникает косяк – выход за границу массива. 
      # Сам код проблемы
      # movs edx, byte cField[i-1]
      # movs  eax, Xbox[edx]
      # Поясняем: значение cField[i-1] берется как число со знаком (movs). 
      # Например: cField[i-1] = 0x8A, то после выполнения команды
      # movs edx, byte cField[i-1] в edx будет 0xFFFFFF8A. 
      # а далее следует 
      # movs  eax, Xbox[edx], где edx будет рассматриваться как 
      # отрицательный индекс и мы выйдем за пределы 
      # массива (адрес массива у нас 0040C000, то мы будем обращаться 
      # к данным лежащим до нашего массива, благо там находится область 
      # выравнивания между секциями – по сути, сплошные нули) 
 
      # проверка отрицательного индекса 
        zn = 0
        if (cField[i-1] & 0x80) == 0:
            #если знаковый бит обнулен, берем значение из массива
            zn = XBox[cField[i-1]]
 
        cField[i] ^= zn
    cField[i] ^= ((i * i - 11) >> 1) & 0xFF
 
# 2-й цикл обработки
for i in range(16,496,4):
    cField[i] = (cField[i] * cField[i+4]) & 0xFF
 
# считаем финальное значение
for i in range(0,512,8):
    x = 0
    x = znak(cField[i])
    x = (x + znak(cField[i+1]) * 0xFF) & 0xFFFFFFFFFFFFFFFF
    x = (x + znak(cField[i+2]) * 0xFF00) & 0xFFFFFFFFFFFFFFFF
    x = (x + znak(cField[i+3]) * 0xFF0000) & 0xFFFFFFFFFFFFFFFF
    x = (x + ((znak(cField[i+4]) * 0xFF000000) & 0xFFFFFFFF)) & 0xFFFFFFFFFFFFFFFF
    x = (x + znak(cField[i+5]) * 0xFF00000000) & 0xFFFFFFFFFFFFFFFF
    x = (x + znak(cField[i+6]) * 0xFF0000000000) & 0xFFFFFFFFFFFFFFFF
    x = (x + znak(cField[i+7]) * 0xFF000000000000) & 0xFFFFFFFFFFFFFFFF
    startvalue = (((startvalue * x) & 0xFFFFFFFFFFFFFFFF) - 1) ^ x
    #print hex(startvalue)
 
# на выходе из цикла в большинстве случаев startvalue = 0xFFFFFFFFFFFFFFFF
# еще ряд телодвижений
startvalue = (startvalue % 0xC8BE1499) ^ 0xC7D8E9F0
# операция NOT
startvalue = startvalue ^ 0xFFFFFFFF
 
# ВЫВОД  РЕЗУЛЬТАТОВ
print ("[*] Username: "+name)
print ("[+] Serial:"+format(startvalue,"08X"))


Примеры:
[*] Username: redoC
[+] Serial: 97817E04

[*] Username: crackmes
[+] Serial: 38260FE8

[*] Username: polynomial
[+] Serial: 38260FE8

[*] Username: blah
[+] Serial: 38279AFC

image00.jpg

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