+ Reply to Thread
Results 1 to 2 of 2

Thread: Подробный разбор keygen.exe

  1. #1

    Default Подробный разбор keygen.exe

    1. Описание задания

    Имеется программа keygen.exe, требуется сгенерировать для неё валидный файл лицензии file.key.

    Используемое ПО:
    • IDA – The Interactive Disassembler 32-bit
      Дизассемблер со встроенным декомпилятором и локальным отладчиком. Понадобится для восстановления логики работы keygen.exe.
    • Python 2.7. Понадобится, чтобы сгенерировать свой file.key.

    2. Подготовительные действия

    Проверим, является ли файл перед нами исполняемым файлом ОС семейства Windows, так как может оказаться, что программист, который написал программу и ради шутки указал в качестве расширения exe, а на самом деле это исполняемый файл ОС семейства *nix. Для этого воспользуемся любой программой, которая может определять формат файлов. Например, программа file, которая присутствует на *nix системах. Запускать её следует из командной строки, указав в качестве аргумента путь к интересующему нас файлу.


    Рисунок 1. Вывод программы file

    Узнаём, что keygen.exe это 32-ух разрядный исполняемый файл формата PE. Для того чтобы разобраться в логике его работы нам понадобится дизассемблер для архитектуры x86. Для этой задачи прекрасно подходит IDA 32-bit. Запускаем eё и открываем в ней keygen.exe.


    Рисунок 2. Указываем IDA открыть keygen.exe

    IDA определяет формат данного файла как исполняемый файл семейства Windows (PE), исполняемый файл MS-DOS или как файл с бинарными данными. Выбираем первое.


    Рисунок 3. Выбор формата PE

    Перед нами оказывается программа в дизассемблированном виде. В правом окне указаны найденные при анализе keygen.exe функции. Выберем функцию main и попробуем разобраться в логике её работы.


    Рисунок 4. Ассемблерный листинг кода функции main


    3. Разбор keygen.exe

    3.1 Декомпилирование функции main

    Чтобы не утруждать себя анализом ассемблерного кода воспользуемся декомпилятором, который активируется клавишей f5 или через верхнее меню View->Open subviews->Generate pseudocode. Результат декомпилирования можно посмотреть в листинге кода ниже.


    Рисунок 5. Результат декомпилирования ассемблерного кода функции main

    Code:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    int result; // eax@2
    DWORD FileSizeHigh; // [esp+0h] [ebp-124h]@3
    int v5; // [esp+4h] [ebp-120h]@16
    int v6; // [esp+8h] [ebp-11Ch]@6
    int v7; // [esp+Ch] [ebp-118h]@10
    int v8; // [esp+10h] [ebp-114h]@11
    HANDLE hFile; // [esp+14h] [ebp-110h]@1
    DWORD nNumberOfBytesToRead; // [esp+18h] [ebp-10Ch]@3
    unsigned int i; // [esp+1Ch] [ebp-108h]@8
    char v12; // [esp+20h] [ebp-104h]@16
    char Buf2; // [esp+78h] [ebp-ACh]@16
    char Dst; // [esp+88h] [ebp-9Ch]@1
    int v15; // [esp+98h] [ebp-8Ch]@16
    char v16[16]; // [esp+CCh] [ebp-58h]@1
    char Dest; // [esp+DCh] [ebp-48h]@1
    char Buf1; // [esp+110h] [ebp-14h]@1
    char v19; // [esp+111h] [ebp-13h]@1
    char v20; // [esp+112h] [ebp-12h]@1
    char v21; // [esp+113h] [ebp-11h]@1
    char v22; // [esp+114h] [ebp-10h]@1
    char v23; // [esp+115h] [ebp-Fh]@1
    char v24; // [esp+116h] [ebp-Eh]@1
    char v25; // [esp+117h] [ebp-Dh]@1
    char v26; // [esp+118h] [ebp-Ch]@1
    char v27; // [esp+119h] [ebp-Bh]@1
    char v28; // [esp+11Ah] [ebp-Ah]@1
    char v29; // [esp+11Bh] [ebp-9h]@1
    char v30; // [esp+11Ch] [ebp-8h]@1
    char v31; // [esp+11Dh] [ebp-7h]@1
    char v32; // [esp+11Eh] [ebp-6h]@1
    char v33; // [esp+11Fh] [ebp-5h]@1

    Buf1 = 0;
    v19 = 1;
    v20 = 2;
    v21 = 3;
    v22 = 4;
    v23 = 5;
    v24 = 6;
    v25 = 7;
    v26 = 8;
    v27 = 9;
    v28 = 10;
    v29 = 11;
    v30 = 12;
    v31 = 13;
    v32 = 14;
    v33 = 15;
    memset(&Dst, 0, 0x44u);
    memset(v16, 0, 0x10u);
    memset(&Dest, 0, 0x34u);
    hFile = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);
    if ( hFile == (HANDLE)-1 )
    {
    printf(Format);
    result = -1;
    }
    else
    {
    nNumberOfBytesToRead = GetFileSize(hFile, &FileSizeHigh);
    if ( nNumberOfBytesToRead > 0x10 )
    {
    if ( nNumberOfBytesToRead <= 0x44 )
    v6 = nNumberOfBytesToRead;
    else
    v6 = 67;
    nNumberOfBytesToRead = v6;
    ReadFile(hFile, &Dst, v6, &FileSizeHigh, 0);
    CloseHandle(hFile);
    memcpy(v16, &Dst, 0x10u);
    for ( i = 0; i < 0x10; ++i )
    {
    v7 = isprint(v16[i]) == 0;
    if ( v16[i] == byte_405048[i] )
    v8 = 0;
    else
    v8 = -1;
    if ( v8 + v7 )
    {
    printf(aFileFile_key_0);
    return -1;
    }
    }
    strcpy(&Dest, (const char *)&v15);
    sub_4012C0(&v12);
    sub_401340(&v12, &Dest, 52);
    sub_4014D0(&v12);
    v5 = memcmp(&Buf1, &Buf2, 0x10u);
    if ( v5 )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = v5;
    }
    else
    {
    printf(aFileFile_keyIs);
    result = -1;
    }
    }
    return result;
    }

    Листинг 1. Результат декомпилирования функции main

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

    3.2. Смена типа переменной Buf1

    Первый фрагмент кода, который мы видим в листинге, состоит из 16-ти присваиваний.

    Code:

    char Buf1; // [esp+110h] [ebp-14h]@1
    char v19; // [esp+111h] [ebp-13h]@1
    char v20; // [esp+112h] [ebp-12h]@1
    char v21; // [esp+113h] [ebp-11h]@1
    char v22; // [esp+114h] [ebp-10h]@1
    char v23; // [esp+115h] [ebp-Fh]@1
    char v24; // [esp+116h] [ebp-Eh]@1
    char v25; // [esp+117h] [ebp-Dh]@1
    char v26; // [esp+118h] [ebp-Ch]@1
    char v27; // [esp+119h] [ebp-Bh]@1
    char v28; // [esp+11Ah] [ebp-Ah]@1
    char v29; // [esp+11Bh] [ebp-9h]@1
    char v30; // [esp+11Ch] [ebp-8h]@1
    char v31; // [esp+11Dh] [ebp-7h]@1
    char v32; // [esp+11Eh] [ebp-6h]@1
    char v33; // [esp+11Fh] [ebp-5h]@1

    Buf1 = 0;
    v19 = 1;
    v20 = 2;
    v21 = 3;
    v22 = 4;
    v23 = 5;
    v24 = 6;
    v25 = 7;
    v26 = 8;
    v27 = 9;
    v28 = 10;
    v29 = 11;
    v30 = 12;
    v31 = 13;
    v32 = 14;
    v33 = 15;

    Листинг 2. Фрагмент кода начала функции main

    Как видно из комментариев оставленных декомпилятором, данные переменные располагаются в стеке последовательно. Можно сделать предположение, что перед нами массив из 16 байт, значения в котором инициализируются в начале функции main. Воспользуемся опцией Set Ivar type на переменной Buf1 и укажем тип char Buf1[16]. В результате IDA сделает нам из этих 16 переменных 1 массив размера 16 байт.


    Рисунок 6. Результат смены типа переменной Buf1


    3.3. Инициализация переменных в стеке

    Далее идёт 3 вызова подряд функции memset. Изначально IDA показывает нам числовые значения в вызове функций в шестнадцатеричном формате. Заменим их на аналоги в десятичной системе исчислений.

    Code:

    memset(&Dst, 0, 68u);
    memset(v16, 0, 16u);
    memset(&Dest, 0, 52u);

    Листинг 3. Инициализация локальных переменных

    Из описания memset (http://www.cplusplus.com/reference/cstring/memset/) следует, что по расположению переменной Dst 68 байт будут инициализированы 0. С v16 и Dest аналогично. Заменим типы переменных на указанные ниже используя Set Ivar type.

    Code:

    char Dst[68]; // [esp+88h] [ebp-9Ch]@1
    char v15[16]; // [esp+CCh] [ebp-58h]@1
    char Dest[52]; // [esp+DCh] [ebp-48h]@1

    Листинг 4. Новые типы переменных.

    3.4. Открытие файла file.key

    После инициализации значений некоторых локальных переменных нам встречается вызов функции CreateFileA (https://msdn.microsoft.com/enus/libr...(v=vs.85).aspx).
    Code:

    hFile = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);
    if ( hFile == (HANDLE)-1 )
    {
    printf(Format);
    result = -1;
    }

    Листинг 5.Фрагмент кода открытия файла file.key

    В данном случае у нас происходит открытие файла на чтение. Чтобы узнать какой файл будет открыт, перейдём по FileName в секцию data, где хранятся статически определённые данные. В нашем случае, по указателю FileName, хранится строка 'file.key'.

    Рисунок 7. Секция data

    Из того же фрагмента видно, что когда файла нет, будет напечатано 'File "file.key" not found'.

    3.5. Промежуточный результат

    После предыдущих пунктов наш декомпилированный код немного изменился. И фрагмент, который находился в else первого if, сейчас выглядит как представлено на листинге ниже.

    Code:

    nNumberOfBytesToRead = GetFileSize(hFile, &FileSizeHigh);
    if ( nNumberOfBytesToRead > 16 )
    {
    if ( nNumberOfBytesToRead <= 68 )
    v6 = nNumberOfBytesToRead;
    else
    v6 = 67;
    nNumberOfBytesToRead = v6;
    ReadFile(hFile, Dst, v6, &FileSizeHigh, 0);
    CloseHandle(hFile);
    memcpy(v15, Dst, 16u);
    for ( i = 0; i < 16; ++i )
    {
    v7 = isprint(v15[i]) == 0;
    if ( v15[i] == byte_405048[i] )
    v8 = 0;
    else
    v8 = -1;
    if ( v8 + v7 )
    {
    printf(aFileFile_key_0);
    return -1;
    }
    }
    strcpy(Dest, &Dst[16]);
    sub_4012C0(&v12);
    sub_401340(&v12, Dest, 52);
    sub_4014D0(&v12);
    v5 = memcmp(Buf1, &Buf2, 16u);
    if ( v5 )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = v5;
    }
    else
    {
    printf(aFileFile_keyIs);
    result = -1;
    }

    Листинг 6. Фрагмент кода, который выполнится, когда file.key существует

    3.6. Количество байт, которое прочтёт программа из file.key

    Фрагмент кода, который выполнится, если файл file.key существует, начинается с вызова функции GetFeliSize (https://msdn.microsoft.com/en- us/library/windows/desktop/aa364955(v=vs.85).aspx). Эта функция возвращает длину файла в байтах.

    Code:

    nNumberOfBytesToRead = GetFileSize(hFile, &FileSizeHigh);
    if ( nNumberOfBytesToRead > 16 )
    {
    if ( nNumberOfBytesToRead <= 68 )
    v6 = nNumberOfBytesToRead;
    else
    v6 = 67;
    nNumberOfBytesToRead = v6;
    ReadFile(hFile, Dst, v6, &FileSizeHigh, 0);
    CloseHandle(hFile);

    }
    else
    {
    printf(aFileFile_keyIs);
    result = -1;
    }

    Листинг 7. Фрагмент проверки длины file.key

    Далее следует проверка длины файла на то, что она больше 16. Если она меньше или равна 16, будет напечатано 'File "file.key" is incorrect'. Значение aFileFile_keyIs узнаём также как значение FileName из пункта 4.4. Если длина больше 16, то идёт проверка на то, что она меньше или равна 68, после чего она присваивается переменной v6 (ЭТО НУЖНО ЗАПОМНИТЬ). Если она больше 68, то v6 присваивается 67. Следующим действием у нас происходит вызов функции ReadFile которая записывает количество байт равное v6 от начала файла в переменную Dst. Последней вызывается CloseHandle, которая закрывает дескриптор file.key, и более мы в программе с данным файлом не взаимодействуем.

    Переименуем v6 в bytes_read для удобного чтения кода в дальнейшем.

    3.7. Проверка валидного начала file.key

    Следующий фрагмент кода начинается с копирования первых 16 байт переменной Dst в v15 с использованием функции memcpy (http://www.cplusplus.com/reference/cstring/memcpy/). Здесь переименуем v15 в first_16_bytes, чтобы упростить чтение кода в дальнейшем.

    Code:

    memcpy(v15, Dst, 16u);
    for ( i = 0; i < 16; ++i )
    {
    v7 = isprint(v15[i]) == 0;
    if ( v15[i] == byte_405048[i] )
    v8 = 0;
    else
    v8 = -1;
    if ( v8 + v7 )
    {
    printf(aFileFile_key_0);
    return -1;
    }
    }

    Code:

    memcpy(first_16_bytes, Dst, 16u);
    for ( i = 0; i < 16; ++i )
    {
    notprintable = isprint(first_16_bytes[i]) == 0;
    if ( first_16_bytes[i] == Thepasswordis[i] )
    byte_from_Thepasswordis = 0;
    else
    byte_from_Thepasswordis = -1;
    if ( byte_from_Thepasswordis + notprintable )
    {
    printf(aFileFile_key_0);
    return -1;
    }
    }

    Листинг 8. Анализируемый фрагмент кода

    Далее в цикле for побайтно выполняется проверка на соответствие v15 (first_16_bytes) некоторым условиям. Первой нам встречается функция isprint (http://www.cplusplus.com/reference/cctype/isprint/), которая проверяет является ли её аргумент печатным символом. Иначе говоря, находится ли значение байта в промежутке от 0x20 до 0x7E (ЭТО НУЖНО ЗАПОМНИТЬ). Потом результат выполнения сравнивается с 0, и в переменную v7 записывается результат сравнения. Т.е. если i-ый байт v15 (first_16_bytes) является печатным символом, то v7 будет равно 0, в противном случае v7 равно 1.

    После идёт сравнение i-го байта v15 и byte_405048. Если внимательно посмотреть на Рисунок 7, то можно увидеть, что IDA не правильно разобрала последовательность байт 'The password is:', определив первый байт 'T' как отдельное значение. Для того чтобы это исправить переопределим данную последовательность байт как отдельное значение и назовём указатель на него как Thepasswordis.


    Рисунок 8. Секция data

    Теперь очевидно, что здесь проверяется равенство первых 16 байт file.key и строки 'The password is:'. В результате проверки в v8 записывается 0, если i-ый байты совпадают, и -1, если нет. Переименуем v8 и v7 в byte_from_Thepasswordis и notprintable соответственно. В последнем if сравнивается сумма byte_from_Thepasswordis и notprintable с 0. Если она не равна ему, то будет напечатано 'File "file.key" is incorrect' и на этом выполнение программы завершится. Возможно 2 варианта (ЭТО НУЖНО ЗАПОМНИТЬ), когда этот сценарий не сработает:
    • i-ый символ из первых 16-ти байтов file.key равен i-ому байту строки 'The password is:';
    • i-ый символ из первых 16-ти байтов file.key не равен i-ому байту строки 'The password is:' и он не является печатным символом.
    3.8. Преобразование пароля и сравнение результата со значением, которое хранится в программе

    Следующий фрагмент кода начинается с выполнения функции копирования строк strcpy (http://www.cplusplus.com/reference/cstring/strcpy/). Даная функция копирует символы Dst, начиная с 17-го байта в Dest, до тех пор, пока не встретит символ окончания строки ('/0' или 0x0) в Dst (ЭТО НУЖНО ЗАПОМНИТЬ).

    Code:

    strcpy(Dest, &Dst[16]);
    sub_4012C0(&v12);
    sub_401340(&v12, Dest, 52);
    sub_4014D0(&v12);
    v5 = memcmp(Buf1, &Buf2, 16u);
    if ( v5 )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = v5;

    Листинг 9. Фрагмент кода преобразования пароля и сравнения результата с Buf1

    Так как первые 16 байт это строка, то можно предположить, что с 17-го байта начинается пароль. Далее у нас присутствуют 3 функции, после которых идёт вызов функции memcmp (http://www.cplusplus.com/reference/cstring/memcmp/) . Это функция сравнивает 16 байт Buf1 и Buf2 и если они равны, то выводит нам сообщение 'Key file is correct! Congrats!'. В противном случае будет выведено 'Secret value doesn',27h,'t mach! Try again.' Получается, что нам нужно вывести первое сообщение, т.е. сделать так, чтобы Buf1 и Buf2 были равны.

    3.9. Анализ механизма хеширования

    На текущий момент времени у нас есть три функции sub_4012C0, sub_401340, sub_4014D0 и не понятно каким образом инициализируемая переменная Buf2. Откроем первую функцию и посмотрим на её листинг.

    Code:

    _DWORD *__stdcall sub_4012C0(_DWORD *a1)
    {
    _DWORD *result; // eax@1

    a1[1] = 0;
    *a1 = 0;
    a1[2] = 1732584193;
    a1[3] = -271733879;
    result = a1;
    a1[4] = -1732584194;
    a1[5] = 271733878;
    return result;
    }

    Code:

    void __stdcall initialization(char *a1)
    {
    *((_DWORD *)a1 + 1) = 0;
    *(_DWORD *)a1 = 0;
    *((_DWORD *)a1 + 2) = 0x67452301;
    *((_DWORD *)a1 + 3) = 0xEFCDAB89;
    *((_DWORD *)a1 + 4) = 0x98BADCFE;
    *((_DWORD *)a1 + 5) = 0x10325476;
    }

    Листинг 10. Функция sub_4012C0 (initialization)

    В том виде, в котором IDA вернула декомпилированный код функции, мало что можно понять. Зная, что функция не возвращает значения, сделаем её void. Учитывая, что здесь инициализируется переданный в неё аргумент, переименуем функцию в initialization. Теперь переведём десятеричные значения в шестнадцатеричные. Таким образом, у нас оказались 4 четырёх байтных числа, 0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476. Отдельно заметим, что первые восемь байт переданного аргумента инициализируются 0. Поищем эти значения в интернете. Получается, что эти значения являются стартовыми значениями в таких алгоритмах хеширования как md5 и SHA-1. Последний нам не подходит, так как отсутствует последовательность 0xC3D2E1F0.

    Таким образом, мы выяснили, что три функции sub_4012C0, sub_401340, sub_4014D0 являются реализацией алгоритма md5, в этом можно отдельно убедиться, если попробовать их разобрать.

    3.10. Промежуточный результат

    Code:

    strcpy(Dest, &Dst[16]);
    sub_4012C0(&v12);
    sub_401340(&v12, Dest, 52);
    sub_4014D0(&v12);
    v5 = memcmp(Buf1, &Buf2, 16u);
    if ( v5 )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = v5;

    Code:

    strcpy(Dest, &Dst[16]);
    initialization(Buf2);
    sub_401340((_DWORD *)Buf2, Dest, 52u);
    sub_4014D0(Buf2);
    not_equal = memcmp(Buf1, &Buf2[88], 16u);
    if ( not_equal )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = not_equal;

    Листинг 11. Фрагмент кода сравнения результатов вычисления md5 хеша от пароля, переданного в file.key, cо значением, хранящимся в keygen.exe

    В приведённом фрагменте:
    • Dest является паролем, который был получен из файла file.key;
    • sub_4012C0 (initialization), sub_401340, sub_4014D0 – функции которые вычисляют md5 хеш от Dest;
    • Buf2 – это указатель на область в стеке, где будут хранится значения для вычисления md5;
    • &Buf2[88] – это область в Buf2, в которой будет находится md5 хеш от Dest.
    Значение Buf1 нам известно. Данного md5 хеша (000102030405060708090A0B0C0D0E0F), нет в открытых базах данных. Не факт, что за разумное время, используя перебор, мы получим ответ, поэтому нужно искать другой способ.

    3.11. Переполнение стека

    Ранее нам встречался один занятный фрагмент кода. В нём интерес представляет факт, что в случае, когда размер файла file.key равен 68, в переменную Dst будет записано 68 байт, что равно её размеру.
    Code:

    if ( nNumberOfBytesToRead <= 68 )
    bytes_read = nNumberOfBytesToRead;
    else
    bytes_read = 67;
    nNumberOfBytesToRead = bytes_read;
    ReadFile(hFile, Dst, bytes_read, &FileSizeHigh, 0);
    CloseHandle(hFile);

    Листинг 12. Проверка длины файла file.key

    Если обратить внимание на стек, то можно заметить, что за Dst без
    разрывов идёт first_16_bytes.

    Code:

    char Dst[68]; // [esp+88h] [ebp-9Ch]@1
    char first_16_bytes[16]; // [esp+CCh] [ebp-58h]@1
    char Dest[52]; // [esp+DCh] [ebp-48h]@1
    char Buf1[16]; // [esp+110h] [ebp-14h]@1

    Листинг 13. Расположение локальных переменных на стеке

    Осталось понять, что делать с этой информацией. Для этого нужно знать что находится в first_16_bytes. Из листинга ниже узнаём, что в него копируются первые 16 байт Dst.

    Code:

    memcpy(first_16_bytes, Dst, 16u);

    Листинг 14. Копирование первых 16 байт Dst в first_16_bytes

    Следующая функция копирования strcpy копирует все символы Dst начиная с 17 до символа окончания строки. Так как 68 байт Dst не содержат символ конца строки, то вместе с Dst после него будет записан first_16_bytes. Как видно из определения переменных выше, после Dest идёт Buf1. Т.е. после того, как 52 символа Dst (если копировать с 17 символа, то из 68 будет скопировано только 52) будут записаны в 52 символа Dest, перезапишет Buf1. это первые 16 байт Dst. Таким образом, имея file.key размера 68 байт можно перезаписывать md5 хеш, который был «вшит» в keygen.exe программистом.

    Code:

    strcpy(Dest, &Dst[16]);

    Листинг 15. strcpy


    3.12. Результат декомпиляции

    Code:

    int __cdecl main(int argc, const char **argv, const char **envp)
    {
    int result; // eax@2
    DWORD FileSizeHigh; // [esp+0h] [ebp-124h]@3
    int not_equal; // [esp+4h] [ebp-120h]@16
    int bytes_read; // [esp+8h] [ebp-11Ch]@6
    int notprintable; // [esp+Ch] [ebp-118h]@10
    int byte_from_Thepasswordis; // [esp+10h] [ebp-114h]@11
    HANDLE hFile; // [esp+14h] [ebp-110h]@1
    DWORD nNumberOfBytesToRead; // [esp+18h] [ebp-10Ch]@3
    unsigned int i; // [esp+1Ch] [ebp-108h]@8
    char Buf2[96]; // [esp+20h] [ebp-104h]@16
    char Dst[68]; // [esp+88h] [ebp-9Ch]@1
    char first_16_bytes[16]; // [esp+CCh] [ebp-58h]@1
    char Dest[52]; // [esp+DCh] [ebp-48h]@1
    char Buf1[16]; // [esp+110h] [ebp-14h]@1

    Buf1[0] = 0;
    Buf1[1] = 1;
    Buf1[2] = 2;
    Buf1[3] = 3;
    Buf1[4] = 4;
    Buf1[5] = 5;
    Buf1[6] = 6;
    Buf1[7] = 7;
    Buf1[8] = 8;
    Buf1[9] = 9;
    Buf1[10] = 0xA;
    Buf1[11] = 0xB;
    Buf1[12] = 0xC;
    Buf1[13] = 0xD;
    Buf1[14] = 0xE;
    Buf1[15] = 0xF;
    memset(Dst, 0, 68u);
    memset(first_16_bytes, 0, 16u);
    memset(Dest, 0, 52u);
    hFile = CreateFileA(FileName, 0x80000000, 1u, 0, 3u, 0, 0);
    if ( hFile == (HANDLE)-1 )
    {
    printf(Format);
    result = -1;
    }
    else
    {
    nNumberOfBytesToRead = GetFileSize(hFile, &FileSizeHigh);
    if ( nNumberOfBytesToRead > 16 )
    {
    if ( nNumberOfBytesToRead <= 68 )
    bytes_read = nNumberOfBytesToRead;
    else
    bytes_read = 67;
    nNumberOfBytesToRead = bytes_read;
    ReadFile(hFile, Dst, bytes_read, &FileSizeHigh, 0);
    CloseHandle(hFile);
    memcpy(first_16_bytes, Dst, 16u);
    for ( i = 0; i < 16; ++i )
    {
    notprintable = isprint(first_16_bytes[i]) == 0;
    if ( first_16_bytes[i] == Thepasswordis[i] )
    byte_from_Thepasswordis = 0;
    else
    byte_from_Thepasswordis = -1;
    if ( byte_from_Thepasswordis + notprintable )
    {
    printf(aFileFile_key_0);
    return -1;
    }
    }
    strcpy(Dest, &Dst[16]);
    initialization(Buf2);
    sub_401340((_DWORD *)Buf2, Dest, 52u);
    sub_4014D0(Buf2);
    not_equal = memcmp(Buf1, &Buf2[88], 16u);
    if ( not_equal )
    printf(aSecretValueDoe);
    else
    printf(aKeyFileIsCorre);
    result = not_equal;
    }
    else
    {
    printf(aFileFile_keyIs);
    result = -1;
    }
    }
    return result;
    }

    Листинг 16. Декомпилированный код keygen.exe

    4. Генератор file.key

    Сейчас, когда мы знаем логику работы keygen.exe и знаем, что файл file.key размера 68 перезаписывает исходный md5 хеш, который хранится в программе, остаётся только сгенерировать свой file.key. Нам известно, что хеш берётся от последних 52 байтов file.key и он должен проходить проверку из пункта 4.7.

    4.1. Генерация пароля

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

    Code:

    def run(deep, array):
    if deep == 52:
    exit()
    else:
    l = list(xrange(1,256,1))
    random.shuffle(l)
    for i in l:
    run(deep + 1, array + chr(i))

    Листинг 17. Рекурсивная функция генерации 52-значного пароля

    4.2. Проверки выявленные в пункте 4.7

    Для генерации file.key нам необходимо реализовать 2 проверки. Для одной из них необходима функция isprint. На Python её можно написать следующим образом.

    Code:

    def check(c):
    if 0x20 > c or c > 0x7E:
    return True
    return False

    Листинг 18. Реализация isprint на python

    Проверка осуществляется для первых 16 байт, где должен находится md5 хеш, поэтому от сгенерированного ранее пароля надо взять хеш. Воспользуемся для этого стандартными средствами Python. Тогда правила из пункта 4.7 можно записать следующим образом.

    Code:

    s = "The password is:"
    ...
    m = md5.new() m.update(array)
    c = m.digest()
    for j in xrange(0,16,1):
    if not ( c[j] == s[j] ) and not ( check(ord(c[j])) ):
    return

    Листинг 19. Проверка из пункта 4.7, реализованная на Python

    4.3. Итоговый код генератора file.key

    Code:

    ################################################## ######################
    #
    #
    #
    ################################################## ######################
    # imports
    import md5
    import random
    ################################################## ######################
    #
    #
    #
    ################################################## ######################
    # vars
    s = "The password is:"
    ################################################## ######################
    #
    #
    #
    ################################################## ######################
    #functions
    def check(c):
    if 0x20 > c or c > 0x7E:
    return True
    return False
    #-----------------------------------------------------------------------
    #
    #
    #
    #-----------------------------------------------------------------------
    def run(deep, array):
    if deep == 52:
    m = md5.new()
    m.update(array)
    c = m.digest()
    for j in xrange(0,16,1):
    if not ( c[j] == s[j] ) and not ( check(ord(c[j])) ):
    return
    print "[",
    for i in array:
    print str(ord(i))+',',
    print "]"
    print
    print len(array), array
    print m.hexdigest()
    f = open("file.key","w")
    f.write(m.digest() +array)
    f.close()
    exit()
    else:
    l = list(xrange(1,256,1))
    random.shuffle(l)
    for i in l:
    run(deep + 1, array + chr(i))
    ################################################## ######################
    #
    #
    #
    ################################################## ######################
    if __name__ == "__main__":
    run(0,"")

    Листинг 20. Код генератора file.key на языке Python


    5. Заключение

    Нам осталось переместить file.key к keygen.exe, запустить его и любоваться надписью 'Key file is correct! Congrats!' в терминале.

    Рисунок 9. Запуск keygen.exe с валидным file.key под wine


    P.S. :
    • При неработающих ссылках на msdn, годное описание api функций windows можно найти по ссылке http://eax.me/winapi-files/
    • Исходный keygen.exe, база idb, генератор file.key и валидный file.key в архиве, которй находится в приложении к посту. А так же архив с PDF-версией статьи.
    Last edited by Darwin; 05-10-2017 at 15:06. Reason: Поправил форматирвоание

  2. 6 пользователя(ей) сказали cпасибо:
    Darwin (05-10-2017) Fa1RLiGHT (06-10-2017) Patrick (10-10-2017) korsader (06-10-2017) ximera (05-10-2017) yashechka (05-10-2017)
  3. #2
    yashechka's Avatar

    Default Re: Подробный разбор keygen.exe

    Cпасибо, попробую повторить.

+ Reply to Thread

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
All times are GMT. The time now is 07:01
vBulletin® Copyright ©2000 - 2017
www.reverse4you.org