Пишем упаковщик по шагам. Шаг пятый. Ресурсы.

Предыдущий шаг здесь.

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

Сначала добавим пару полей в структуру packed_file_info:

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

Сначала займемся распаковщиком (проект unpacker), тем более, изменения будут минимальными. Добавим строки аналогично коду для директории импорта:

С распаковщиком все. Переходим к упаковщику (проект simple_pe_packer). Добавим, опять-таки, аналогичные для директории импорта строки для директории ресурсов:

В принципе, этого уже должно быть достаточно, чтобы файл с ресурсами (формами, например) мог запуститься. Проблемы возникнут, если у файла есть xp manifest или что-то подобное. Поэтому нам теперь необходимо пересобрать директорию ресурсов в упаковщике, как я описывал выше. В начало файла main.cpp добавим новый #include <pe_resource_manager.h>. Он необходим для доступа к вспомогательным классам для работы с ресурсами. Код будем добавлять в то место упаковщика, где удаляются секции исходного файла (прямо перед этим действием):

Итак, что же происходит в этом куске кода? Если вкратце, то мы получаем ресурсы оригинального файла и ищем в них:
1. Иконки
2. Манифест
3. Информацию о версии

Эти три вида ресурсов необходимо в неупакованном виде положить в новую директорию ресурсов. Иконку (точнее, группу иконок) из оригинального файла мы возьмем самую первую, именно ее использует Windows как основную иконку файла. Манифест всегда у файла один, и в нем сказано, какие права нужны файлу для запуска и какие DLL-файлы используются. Там может храниться и другая информация. Наконец, информация о версии - это непосредственно информация о файле, которую также можно просмотреть в проводнике Windows.

Я до этого нигде не описывал, как устроена директория ресурсов, поэтому для общего развития приведу эту информацию сейчас. Посмотрим на ресурсы какого-нибудь exe-файла в CFF Explorer'е:

Как видно, ресурсы организованы в виде дерева. В корневой директории сначала располагаются записи, указывающие на тип ресурса. В них вложены директории с записями, содержащими имя или ID ресурса, а в них, в свою очередь, вложены директории с записями, указывающими язык ресурса. Последние содержат директорию данных, которая указывает уже на данные ресурса.

Так вот, в коде выше мы перечисляем все иконки, имеющие ID или имя. Так как в PE-файлах все ресурсы сортируются, причем сначала идут именованные ресурсы, а потом только ресурсы с ID, исходя из этого, мы и ищем самую первую иконку. Она и будет иконкой приложения, и ее мы добавляем в новую директорию ресурсов, сохраняя ее имя/ID и язык. Аналогично поступаем с манифестом и информацией о версии - у всех файлов не больше одной такой записи, причем всегда неименованные, т.е. имеющие ID, поэтому берем и сохраняем первую запись манифеста и первую запись информации о версии.

Новую директорию ресурсов мы создали, теперь надо ее сохранить в создаваемом нами файле. Сначала добавим пару строк перед строкой, производящей сборку импортов:

Дело в том, что все пересборщики в моей библиотеке для работы с PE автоматически уберут все нулевые байты с конца секции, в которую пересобирают импорты/экспорты/ресурсы и т.д., при условии, что секция последняя в файле. Это совершенно адекватное поведение, позволяющее уменьшить размер файла в пределах файлового выравнивания, так как загрузчик все равно нулевые байты восстановит, но уже в памяти, в количестве [выровненный виртуальный размер секции] минус [физический размер секции]. Таким образом, если у нас какие-то структуры внутри секции заканчиваются нулевым элементом (например, завершающий дескриптор таблицы импортов), физически в файл он записан не будет, если эта таблица импортов собирается в последней секции PE-файла. С этим, кстати, связаны интересные баги в таких редакторах, как CFF Explorer, он опирается только на физические данные файла, а не на виртуальные. Поэтому он может криво отобразить ту же таблицу импорта с отрезанными нулевыми байтами в конце.

Сейчас же нам необходимо, чтобы нулевые байты в конце секции, относящиеся к таблице импорта, не обрезались, так как мы сразу после импортов разместим ресурсы. Если мы обрежем эти нулевые байты, то директория ресурсов наслоится на директорию импортов, и получится полная хрень.

Идем дальше - пересобираем ресурсы:

Здесь все ясно - если какие-то ресурсы есть, мы их записываем в новый PE-файл. Располагаем мы их в самом конце добавленной секции, сразу за импортами. Так как секция последняя на момент пересборки ресурсов, она расширится автоматически.

Осталось убрать одну строчку, которая убирает директорию ресурсов:

Все готово! Можно проверить упаковщик на любом exe-файле, имеющем импорты, ресурсы и даже релокации (хотя мы их пока что не обрабатываем). Давайте возьмем какой-нибудь проект на MSVC++ с MFC и посмотрим с помощью CFF Explorer'а, какие ресурсы в нем были изначально:

После упаковки имеем:

Итак, осталась одна группа иконок с несколькими иконками в ней (иконки приложения), манифест (configuration files) и информация о версии. Разумеется, упакованный файл успешно запускается!

Полный солюшен для этого шага: Own PE Packer step 5

Пишем упаковщик по шагам. Шаг пятый. Ресурсы.: 8 комментариев

  1. мегареспект! При такой паковке ресурсов антивирусы (надеюсь) не будут срать кирпичами при виде пакованного твоим пакером файла. Самое интересное впереди, с нетерпением жду продолжения.
    С уважением!

  2. Можно в перспективе вкрячить что-нить в духе:

    {0x01
    ,0x90 // NOP
    ,0x90 // NOP
    ,0x00},

    {0x01
    ,0xF9 // STC
    ,0xF9 // STC
    ,0x00},

    {0x01
    ,0xF8 // CLC
    ,0xF8 // CLC
    ,0x00},

    {0x02
    ,0xC0FE // INC AL
    ,0xC8FE // DEC AL
    ,0x00},

    {0x02
    ,0x0004 // ADD AL, 0
    ,0x002C // SUB AL, 0
    ,0x02},

    {0x02
    ,0x002C // SUB AL, 0
    ,0x0004 // ADD AL, 0
    ,0x02},

    {0x02
    ,0xC102 // ADD AL, CL
    ,0xC12A // SUB AL, CL
    ,0x00},

    {0x02
    ,0xC12A // SUB AL, CL
    ,0xC102 // ADD AL, CL
    ,0x00},

    {0x02
    ,0x0034 // XOR AL, 0
    ,0x0034 // XOR AL, 0
    ,0x02},

    {0x03
    ,0x00C8C0 // ROR AL, 0
    ,0x00C0C0 // ROL AL, 0
    ,0x01},

    {0x03
    ,0x00C0C0 // ROL AL, 0
    ,0x00C8C0 // ROR AL, 0
    ,0x01},

    {0x03
    ,0xE801EB // Self modifing
    ,0xE801EB // Self modifing
    ,0x00},

    {0x03
    ,0xE901EB // Self modifing
    ,0xE901EB // Self modifing
    ,0x00},

    {0x03
    ,0xC201EB // Self modifing
    ,0xC201EB // Self modifing
    ,0x00}
    };

    Ну и потом:

    {
    command=*Iter;
    _Data=command.Data;
    switch(command.OpCode)
    {
    case 0xC0FE: // INC AL
    _asm
    {
    INC _Value
    }
    break;

    case 0x0004: // ADD AL, 0
    _asm
    {
    MOV CL,_Data
    ADD _Value,CL
    }
    break;

    case 0x002C: // SUB AL, 0
    _asm
    {
    MOV CL,_Data
    SUB _Value,CL
    }
    break;

    case 0xC102: // ADD AL, CL
    _asm
    {
    MOV CL,Count
    ADD _Value,CL
    }
    break;

    case 0xC12A: // SUB AL, CL
    _asm
    {
    MOV CL,Count
    SUB _Value,CL
    }
    break;

    case 0x0034: // XOR AL, 0
    _asm
    {
    MOV CL,_Data
    XOR _Value,CL
    }
    break;

    case 0x00C8C0: // ROR AL, 0
    _asm
    {
    MOV CL,_Data
    ROR _Value,CL
    }
    break;

    case 0x00C0C0: // ROL AL, 0
    _asm
    {
    MOV CL,_Data
    ROL _Value,CL
    }
    break;
    }
    }
    return(_Value);
    }

    Это бы вообще было бы потрясно, чтобы если что - самому подправить код на выходе.
    Как смотришь на это?

      1. Не, я в любом случае буду править, просто думал узнать твое мнение, и как бы ты это реализовал, а потом уже отталкиваясь от этого - плясать дальше.
        Кстати не про антиотладку сейчас думал а о движке трешкода, конечно можно хитрее реализовать, можно вообще сделать, чтобы он подцеплял фейковые сигны из пейдовского user.db.
        Просто мне интересно, как ты видишь готовую версию пакера. Вот и все.

        1. Пока никак. Впереди еще TLS, релокации, экспорты, еще мб что-то по мелочам, потом какой-никакой консольный интерфейс еще надо запилить с опциями пакования...

    1. На самом деле по моим наблюдениям он ставит ту из иконок, которая больше всего подходит по размеру. Зависит от настроек отображения папки ("маленькие значки", "большие значки", "таблица" и т.д.)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *