+ Reply to Thread
Results 1 to 1 of 1

Thread: Реверс-инжиниринг баз данных. Часть 3. Переиспользование кода. Заключение

  1. #1
    MorteNoir's Avatar

    Default Реверс-инжиниринг баз данных. Часть 3. Переиспользование кода. Заключение

    Введение

    Реверс-инжиниринг баз данных. Часть 1. Введение
    Реверс-инжиниринг баз данных. Часть 2. Основные подходы
    Реверс-инжиниринг баз данных. Часть 3. Переиспользование кода. Заключение

    Во второй статье цикла мы изучали устройство БД программы Microcat Ford USA. Были исследованы основные структуры данных, отвечающие за хранение информации об автомобилях и деталях. Последний компонент, который осталось разобрать, это схемы деталей. Напомню, как выглядит ось зависимостей между структурами и архитектура БД.



    Рис. 1. Ось зависимостей



    Рис. 2. Архитектура БД

    Обзор MCImage.dat

    В прошлый раз мы выяснили, что файл MCData.idx, представляющий деревья деталей, связан с MCData.dat, содержащим детали, и с MCImage[2].dat, в котором хранятся изображения схем деталей. С последним он связан полем image_offset, которое содержит смещение до изображения, и полем image_size, не показанным на рис. 2, означающим размер изображения. Применим подходы [2.8] и [2.9] и посмотрим, как именно изображения хранятся в файле.



    Рис. 3. Определяем смещение и размер изображения



    Рис. 4. Начало изображения

    Что это? На известный формат не похоже, как не похоже и на то, что изображение сжато — слишком много нулей и повторяющихся байт. Смотрим дальше.



    Рис. 5. Середина изображения

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

    Поиск и отладка кода вывода изображения

    Поищем строку «image» в библиотеках программы и получим следующий список:

    Code:
    C:\MCFNA\
     18.12.02│186432│A     │CSIMGL16.DLL
     28.05.07│ 26048│A     │FNASTART.DLL
     19.08.12│215024│A     │FNAUTIL2.DLL
     31.10.97│  6672│A     │IMUTIL.DLL
     23.05.06│2701 K│A     │MCLANG02.DLL
     06.09.06│2665 K│A     │MCLANG16.DLL
     14.04.97│146976│A     │MFCOLEUI.DLL
     06.09.06│2395 K│A     │NAlang16.dll
     14.04.97│ 57984│A     │QPRO200.DLL
     14.04.97│398416│A     │VBRUN300.DLL
    Листинг 1

    В модулях CSIMG16, FNAUTIL2 и IMUTIL обнаруживаются экспортируемые процедуры, которые могут быть интересны.



    Рис. 6



    Рис. 7



    Рис. 8

    Необходимо найти такую функцию, которая будет принимать на вход сжатое изображение и на выходе отдавать распакованное. Уверенности в том, что такая процедура вообще существует и экспортируется, нету, потому что байты из MCImage.dat могут быть сжатыми / зашифрованными каким-нибудь общим для всей программы алгоритмом, реализованным в недрах MCFNA.exe. Поэтому вместо того, чтобы сразу дизассемблировать указанные выше библиотеки, подойдём с другой стороны. Изображение в конечном итоге должно выводиться на экран некими системными функциями, вероятно, из WinAPI. Нужно понять, что это за функции, поставить на них брейкпоинты и отследить, какие из них вызываются и откуда.

    WinAPI позволяет работать с изображениями разных форматов, но самый простой из них это BMP, работа с которым не требует ничего, кроме обычных вызовов модулей USER.exe / GDI.exe (16-битных аналогов user32.dll и gdi32.dll). В пользу гипотезы, что сжатые схемы деталей могут быть битмапами, свидетельствует факт наличия в папке Res картинок с расширением BMP, RLE (сжатый BMP), JPG и GIF.

    Открыв список API (см. ссылки) для работы с BMP, приметим те, которые отвечают за создание и загрузку битмапа: CreateBitmap, CreateBitmapIndirect, CreateCompatibleBitmap, CreateDIBitmap, CreateDIBSection, LoadBitmap. Теперь можно переходить к отладке.

    Но сначала ещё одно замечание. Некоторые NE-файлы используют особенность под названием Self-Loading. По аналогии с TLS-коллбеками PE, этот механизм позволяет выполнить код до передачи управления на OEP. В нашем случае это делается для распаковки Shrinker’ом оригинальной программы.

    Я опробовал несколько 16- и 32-битных отладчиков, ни один из них не сравнится с WinDbg по возможностям отладки NE. 16-битные Open Watcom и Insight Debugger не смогли запустить MCFNA.exe, полагаю, из-за Self-Loading. OllyDbg 1 / 2 позволяет отлаживать NE косвенно, через NTVDM, но при останове на брейкпоинтах на 16-битном коде выбрасывает исключения. x64dbg не поддерживает NE. А вот WinDbg, как обычно, порадовал, из основных его плюсов: различает загрузку NE-модулей программы от PE-библиотек NTVDM; останавливается на бряках hardware и software; распознаёт 16-битный код и корректно его дизассемблирует, в том числе отображает и понимает адреса в виде сегмент:смещение. Бывают проблемы с листингом в окне Disassembly, но это несущественно, т. к. в Command всё в порядке.



    Рис. 9

    Теперь немного о том, как 16-битный код хранится в памяти процесса NTVDM. Благодаря другим исследователям (см. ссылки) и опытным путём было выяснено, что все модули загружаются по адресам в диапазоне от 0x10000 до 0xA0000, который чем-то напоминает доступную память в реальном режиме процессора. Для того, чтобы найти какую-либо функцию, необходимо выполнить обычный поиск байт. В частности, нам нужно взять начальные байты функций создания битмапа и найти их в диапазоне от 0x10000 до 0xA0000.



    Рис. 10. Пример поиска функции по первым байтам



    Рис. 11. Пример поиска функции по первым байтам

    Запустим программу под WinDbg. Выполняя поиск всех процедур по очереди обнаруживаем, что LoadBitmap из USER.exe не находится, а значит, остаётся только GDI.exe. На все найденные адреса ставим программные точки останова.



    Рис. 12

    Продолжаем исполнение и сразу же при переключении окна с WinDbg на Microcat прерываемся на CreateCompatibleBitmap. Так происходит каждый раз, видимо, прорисовывается интерфейс, поэтому отключаем этот брейкпоинт. Продолжаем исполнение, выбираем автомобиль, проходим по дереву деталей и в момент, когда должна отобразиться схема деталей, два раза останавливаемся на CreateDIBitmap. Это единственная функция, на которой происходит остановка.



    Рис. 13. Брейк на CreateDIBitmap

    Посмотрим, откуда делались вызовы в обоих случаях. Для этого возьмём со стека два ворда, [ss:sp + 2], который является сегментом, и [ss:sp + 0], смещение, объединим их и посмотрим, что располагается по полученному адресу.



    Рис. 14. Стек и вызывающая функция на первом останове



    Рис. 15. Стек и вызывающая функция на втором останове

    В первом и втором случае вызывающий код располагается в разных сегментах, следовательно, физически находится в двух разных библиотеках на диске. Выполним поиск байт «8b f8 83 3e 08 1f 00 74 2c 83 7e fa 00 74 15 ff» и «8b f8 83 7e f4 00 74 0d ff 76 fe ff 76 f4 6a 00» в файлах. Первая последовательность находится в VBRUN300.DLL, рантайм-библиотеке Visual Basic, вторая — в FNAUTIL2.DLL. Тот самый FNAUTIL2, в котором мы нашли несколько экспортов, касающихся изображений!

    Анализ кода вывода изображения

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



    Рис. 16

    Экспортируемой процедурой, которая вызывала CreateDIBitmap, оказалась GETCOMPRESSEDIMAGE. На вход она принимает следующие аргументы: ret_val_far_pointer, куда сохранится код ошибки; screen_height и screen_width – разрешение изображения, в котором его нужно вывести на экран; image_size – размер сжатого изображения; offset – смещение до изображения в файле MCImage.dat (вспомните image_offset из начала статьи); unk_structure_ptr – указатель на структуру, в которой должны быть определённые константы и хендл файла MCImage.dat. Код на Visual Basic, вызывающий процедуру, помещает аргументы по конвенции Pascal, слева направо, поэтому в дизассемблере они кажутся расположенными неестественно.



    Рис. 17

    Далее происходит позиционирование в файле MCImage.dat по заданному оффсету и вызывается read_and_unpack_image – та самая процедура распаковки, которую мы искали и которая останется чёрным ящиком.



    Рис. 18

    После распаковки размер изображения подгоняется к заданному в screen_height и screen_width разрешению, вызывается get_palette_handle, в которой создаётся палитра функцией CreatePalette, а затем вызывается create_bitmap, которую мы обнаружили в отладчике и которая вызывает CreateDIBitmap для создания битмапа из распакованных байт.



    Рис. 19

    Наконец, память, использовавшаяся под распакованные байты и более ненужная, освобождается, и функция возвращает HBITMAP.



    Рис. 20

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

    Переиспользование кода распаковки

    Поскольку библиотека FNAUTIL2.dll 16-битная, наша программа должна быть той же разрядности. С этой целью я выбрал компилятор Open Watcom C. Ниже приведён код, вызывающий GETCOMPRESSEDIMAGE из FNAUTIL2.

    Code:
    typedef struct {
        long unk_1;
        long unk_2;
        int unk_3;
        int mcimage;
    } ImageFileData;
    
    HBITMAP decrypt_image(char* mcimage_path, unsigned long enc_image_offset, unsigned long enc_image_size) {
        int mcimage = open(mcimage_path, O_RDONLY | O_BINARY);
        if (mcimage == -1) {
            printf("ERROR: cannot open mcimage '%s'\n", mcimage_path);
            return NULL;
        }
    
        ImageFileData data = {0};
        data.mcimage = mcimage;
        long screen_width = 0;
        long screen_height = 0;
        int ret_val = 0;
        HBITMAP bitmap = GETCOMPRESSEDIMAGE_proc(&ret_val, &screen_height, &screen_width, enc_image_size, enc_image_offset, &data);
        if (!bitmap) {
            printf("ERROR: GETCOMPRESSEDIMAGE failed (MCImage = '%s', Offset = %p, Size = %p)\n", 
                mcimage_path, enc_image_offset, enc_image_size);
            close(mcimage);
            return NULL;
        }
    
        close(mcimage);
        return bitmap;
    }
    Листинг 2

    На входе decrypt_image принимает путь к MCImage.dat, смещение до картинки и её размер. Файл открывается, инициализируется структура ImageFileData, которая в дизассемблере была названа unk_structure_ptr, и остальные параметры, которые затем передаются в процедуру распаковки в обратном порядке. Возвращает decrypt_image хендл битмапа. Код, вызывающий decrypt_image, затем сохраняет битмап на диск вызовом save_bitmap.

    Code:
    int save_bitmap(HBITMAP bitmap, char* dec_image_path) {
        int ret_val = 0;
        unsigned bytes_written = 0;
    
        HDC dc = GetDC(NULL);
    
        unsigned lpbi_size = 256 * 4 + sizeof(BITMAPINFOHEADER); // 1 << 8 (biBitCount) + 0x28
        BITMAPINFO* lpbi = (BITMAPINFO*)calloc(1, lpbi_size);
        if (!lpbi) {
            printf("ERROR: memory allocation for BITMAPINFO failed\n");
            return 0;
        }
    
        // BITMAPINFOHEADER:
        // 0x00: biSize
        // 0x04: biWidth
        // 0x08: biHeight
        // 0x0C: biPlanes
        // 0x0E: biBitCount
        // 0x10: biCompression
        // 0x14: biSizeImage
        // 0x18: biXPelsPerMeter
        // 0x1C: biYPelsPerMeter
        // 0x20: biClrUsed
        // 0x24: biClrImportant
        lpbi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
        lpbi->bmiHeader.biPlanes = 1;
        ret_val = GetDIBits(dc, bitmap, 0, 0, NULL, lpbi, DIB_RGB_COLORS);
        if (!ret_val) {
            printf("ERROR: first GetDIBits failed\n");
            free(lpbi);
            return 0;
        }
    
        // Allocate memory for image
        void __huge* bits = halloc(lpbi->bmiHeader.biSizeImage, 1);
        if (!bits) {
            printf("ERROR: huge allocation for bits failed\n");
            free(lpbi);
            return 0;
        }
    
        lpbi->bmiHeader.biBitCount = 8;
        lpbi->bmiHeader.biCompression = 0;
        ret_val = GetDIBits(dc, bitmap, 0, (WORD)lpbi->bmiHeader.biHeight, bits, lpbi, DIB_RGB_COLORS);
        if (!ret_val) {
            printf("ERROR: second GetDIBits failed\n");
            hfree(bits);
            free(lpbi);
            return 0;
        }
    
        // Open file for writing
        int dec_image;
        if (_dos_creat(dec_image_path, _A_NORMAL, &dec_image) != 0) {
            printf("ERROR: cannot create decrypted image file '%s'\n", dec_image_path);
            hfree(bits);
            free(lpbi);
            return 0;
        }
    
        // Write file header
        BITMAPFILEHEADER file_header = {0};
        file_header.bfType = 0x4D42; // "BM"
        file_header.bfSize = sizeof(BITMAPFILEHEADER) + lpbi_size + lpbi->bmiHeader.biSizeImage;
        file_header.bfOffBits = sizeof(BITMAPFILEHEADER) + lpbi_size;
        _dos_write(dec_image, &file_header, sizeof(BITMAPFILEHEADER), &bytes_written);
    
        // Write info header + RGBQUAD array
        _dos_write(dec_image, lpbi, lpbi_size, &bytes_written);
    
        // Write image
        DWORD i = 0;
        while (i < lpbi->bmiHeader.biSizeImage) {
            WORD block_size = 0x8000;
            if (lpbi->bmiHeader.biSizeImage - i < 0x8000) {
                block_size = (WORD)(lpbi->bmiHeader.biSizeImage - i); // Explicit casting because the difference will always be < 0x8000
            }
    
            _dos_write(dec_image, (BYTE __huge*)bits + i, block_size, &bytes_written);
    
            i += block_size;
        }
    
        _dos_close(dec_image);
        hfree(bits);
        free(lpbi);
        return 1;
    }
    Листинг 3

    Входными аргументами для save_bitmap являются HBITMAP и путь к файлу, в который будет записано изображение. Сначала выделяется память для структуры BITMAPINFO и включённых в неё BITMAPINFOHEADER и RGBQUAD, задающих размеры и цвет изображения. Затем выделяется память, где будут лежать байты битмапа, сконвертированные из HBITMAP. Выделение делается посредством halloc, возвращающей указатель с атрибутом __huge, означающим, что память может быть больше 64 КБ. Вызов GetDIBits копирует битмап из хендла в выделенную память. Наконец, в файл на диск записывается BITMAPFILEHEADER, BITMAPINFO и биты картинки. Код записи в файл пришлось оформить в цикл, т. к. _dos_write не может за один вызов сохранить более 64 КБ.

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



    Рис. 21



    Рис. 22. Окончательная ось зависимостей

    Заключение

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

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

    Ссылки

    Отказ от ответственности

    Информация предоставлена исключительно в ознакомительных целях. Автор не несёт никакой ответственности за любое неправомерное применение знания, полученного из данной статьи. Копирование и распространение информации без разрешения правообладателей незаконно.
    Attached Files
    Last edited by MorteNoir; 29-12-2017 at 23:15.

  2. 3 пользователя(ей) сказали cпасибо:
    Darwin (29-12-2017) korsader (30-12-2017) ximera (01-01-2018)
+ Reply to 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 08:15
vBulletin® Copyright ©2000 - 2018
www.reverse4you.org