Формат "Новых исполняемых" файлов называют сегментным. Даже в официальной документации Microsoft её заголовок это "Сегментный Новый Исполняемый формат" (ориг. "Segmented NEW Executable Format").
Этот сегментный формат программ поддерживал
Сегментную модель памяти;
Защищенный режим
Intel 286+Динамичкую компановку (Dynamic Linking);
Управление ресурсами;
Возможность экспорта и импорта определенных процедур.
Технический Обзор
Скомпилированный и собранный проект для Windows 3x или OS/2 1.0+
Всегда начинается с MZ-заголовка. То есть первой меткой в файле всегда будет ASCII подпись MZ или ZM.
Поле, которое определенное время было специально занулено - e_lfanew стало указывать на следующий программый заголовок.
Нетрудно догадаться, что подпись в следующем заголовке NE или EN.
Я специально указываю два варианта подписи, так как это не повлияет на дальнейший анализ, но сильно расширит область определения.
Можно сказать, что NE-заголовок в программе одна из важнейших частей, если не самая важная. Он описывает детали, которые считывает загрузчик программ для загрузки этого "образа программы" в память.
Схематично, сам новый сегментный формат описывает всего 7 специфических частей, которые жизненно необходимы
Детали Заголовка;
Таблицу входных точек;
Таблицу сегментов;
Таблицу резидентных имен;
Таблицу не-резидентных имен;
Таблицу импортируемых модулей (проектов);
Таблицу ссылок на модули;
Таблицу ресурсов.
| MZ-Заголовок | | ... | | e_lfarlc=0x40 | Всегда 0x40. | e_ovno=0 | Не важно, чаще всего 0 | ... | | e_lfanew -----------------+ +---------------------+ | | | | | DOS Заглушка | | +---------------------+ | | NE-Заголовок |<----+ | | | ... | +------+ e_modtab | |+-----+ e_imptab +-----+ || | e_enttab +-----|------> [#1{...}][#2{...}...] [...]<--------+ | e_resntab +------+ | | e_nrestab +----+|| +-----------+ e_segtab | ||| | | | ||| | | e_rsrctab | ||| | ... | ||| +------+--------------------+ | ||| | #1 .CODE 0xBABE no_relocs | | ||| | #2 .CODE 0xFEED +------>[#2 релокации] | ... | | ||| +-------------+-------------+ | ||| || | | ||| || | | ||| || +---------------------+ ||| || | Резидентные имена <-------+ || +---------------------+ | || | Импорты <------+ |+--->+---------------------+ | | | #1 .CODE | | +---->+---------------------+ | | #2 .CODE | | +---------------------+ | | Файл ресурса #1 | | | Файл ресурса #2 | | | Файл ресурса #3 | | +---------------------+ | | НеРезидентные имена <----+ EOF
Так же, можно посмотреть на указатель таблицы релокаций.
Для NE, LE/LX, PE собранных файлов без какого-либо вмешательства
он всегда держит абсолютное смещение на 0x40 байт.
NE Заголовок | Структура данных
Заголовок NE это упакованная не выровненная структура. Всегда начинается с
ASCII подписи [N, E] или [E, N]. Это важный признак, который говорит
о том, что исследуемый файл скорее всего Windows или OS/2 16-разрядная программа.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Pod, Zeroable)] #[repr(C)] pub struct NeHeader { pub e_magic: [u8; 2], pub e_ver: u8, // LINK.EXE major version pub e_rev: u8, // LINK.EXE minor version pub e_enttab: u16, pub e_cbent: u16, // Count of entry bundles pub e_crc: u32, pub e_flags: u16, // [program_flags][app_flags] pub e_autodata: u16, // automatic DS (data segment) index pub e_heap: u16, // Initial heap size pub e_stack: u16, // Initial stack size pub e_cs_ip: u32, // CS:IP pub e_ss_sp: u32, // SS:SP pub e_cbseg: u16, // Count of segments pub e_cbmod: u16, // Count of module references pub e_cbnres: u16, // Size of Nonresident names table pub e_segtab: u16, pub e_cbres: u16, pub e_resntab: u16, // Resident names table pub e_modtab: u16, // Module references table pub e_imptab: u16, // Importing module names pub e_nrestab: u32, // Non-Resident names table (raw offset) pub e_cbentmov: u16, // Count of moveable entries pub e_align: u16, // Sector shift. (0 means 512) pub e_restab: u16, // Resources table pub e_os: u8, // Target OS // **OS/2 part** of header. (checks by IBM OS/2 Win-OS/2 module) pub e_flagothers: u8, // OS/2 flags for loader (e.g. HPFS/FAT naming) pub e_pretthunk: u16, // Return Thunk offset pub e_thunk: u16, // Segment reference thunk offset pub e_swap: u16, // minimum code swap pub e_expver: [u8; 2],// Expected Windows version! // (little endian reinterpretation!) }
В этот раз речь пойдет именно о полях этого заголовка.
NE-Заголовок | LINK.EXE
Как всем известно, формат был сделан "на скорую руку", поэтому
читаемость файлов зависит от версии линковщика. По этой причине интерпретация и запись
таблицы входных точек (англ. EntryTable) у LINK.EXE 4.0 и LINK.EXE 5.0+ отличается.
Sunflower об этом предупреждает,
хотя настоящих доказательств такого феномена при себе не имею. Я лишь видел упоминания
в источниках.
NE-Заголовок | e_flags
Поле e_flags (в моей интерпретации это два байтовых поля e_pflags и e_aflags) на самом деле разделяется в документации на две категории.
Программные флаги (англ. "Program Flags");
Флаги "Приложения" (англ. "Application Flags").
Программные флаги это общие характеристики модуля которые
говорят простую но нужную линкеру и разработчику информацию:
// Перевод: // В понимании 16-разрядной DOS/Windows, DGROUP это класс сегментов // которые ссылаются на сегменты используемые для данных. // // Win16 использовала сегментную модель памяти, // чтобы позволить DLL или программе иметь несколько экземпляров, // каждый со своим дескриптором экземпляра (instance handle), и // управлять несколькими сегментами данных. // // Это позволяло одному кодовому сегменту NOTEPAD.EXE // выполнять несколько экземпляров приложения "Блокнот". enum FlagWord { // Как данные обрабатываются/должны обрабатываться? NOAUTODATA = 0x0000, // Нет .DATA сегментов SINGLEDATA = 0x0001, // Общий между всеми запущенными экземплярами программы MULTIPLEDATA = 0x0002, // Сегменты разделяются и являются различными для каждого экземляра // LINK.EXE флаги/дополнительные флаги LINKERROR = 0x2000, // Во время связывания произошла ошибка. LIBMODULE = 0x8000, // Опциональный флаг; Красный флаг для определения регистров // Если AX = 0, в случае DLL модуля -- модуль явно содержит ошибку и не загрузится. // Для драйверов .DRV это не работает. }; #define GLOBINIT 1 << 2 // Глобальная инициализация #define PMODEONLY 1 << 3 // Запускается только в защищенном режиме #define I8086 1 << 4 // 8086 инструкции #define I286 1 << 5 // 80286 инструкции #define I386 1 << 6 // 80386 инструкции #define I8087 1 << 7 // 80x87 (FPU) инструкции.
DGROUP можно представить как механизм,
который позволял запускать несколько копий программы,
используя один общий код, но изолированные данные для каждой копии.
Подробнее об этом будет в другой части.
Флаг MULTIPLEDATA, если обнаруживается, то являлся намеком на использование
DGROUP и в таком случае следует проверить поле e_autodata заодно.
e_autodata это сокращенное сочитание "Automatic Data Segment Index", что означает
номер сегмента в таблице сегментов. (нумерация начинается с 1). Условно, загрузчик использует
это значение, чтобы автоматически загрузить правильный сегмент данных в регистр DS
при создании нового экземпляра приложения
Флаг LIBMODULE говорит не просто, что файл является динамическим объектом.
Если модуль является динамической библиотекой .DLL,
то CS:IP в заголовке вместо этого является процедурой загрузки библиотеки DLL,
а SS:SP не используется, так как библиотека не получает собственный стек.
Он использует дескриптор модуля в AX, который представляет собой непрозрачный тип данных, представляющий EXE-файл или DLL-библиотеку при загрузке в память. При возврате он выдает AX!=0 для успешного выполнения и AX==0 для сбоя, что необычно, но очень важно.
Флаги "Приложения" описывают поведение окна, как не странно. Win-OS/2 и Windows 1.x-3x
использует их.
enum ApplicationFlag { None, // Нет окна?/опционально FullScreen, // Полный экран (Не избегать Windows/P.M. API) WinPMCompat, // Соместимость с Windows/P.M. API WinPMUsage // Активное использование Windows/P.M. API };
Больше информации можно найти в Oracle VirtualBox или другой
документации (не от Microsoft).
NE-Заголовок | e_os
Значение e_os к сожалению опционально, и не все программы
или библиотеки его держат при себе.
Вы можете десериализовать структуру заголовка любого модуля
из Windows 1x и убедиться в том, что там этот флаг держит ноль.
Шрифты .FON скомпилированные для Windows 1.x-3x тоже держат это поле нулём.
enum targetos { Unknown = 0x00, // Любая ОС (не UNIX) OS2 = 0x01, // IBM OS/2 Win286 = 0x02, // Windows/286 Dos4 = 0x03, // Многозадачная DOS 4.x Win386= 0x04, // Windows/386 Boss = 0x05 // Borland OSS }
NE-Заголовок | OS/2 часть
e_flagothers
Во многих источниках упоминалось, что новый заголовок
особенно на первых этапах разработки был меньше, и не имел поля
e_pretthunks, e_flagothers, и последующие. Говорят, что это "OS/2 часть заголовка". В структуре я обозначил их комментарием специально.
Предположительно, флаги e_flagothers проверяются или в случае e_os=0x01 (имеется ввиду флаг ОС держит значение OS/2), или только загрузчиком из OS/2 1.0+, что мне кажется более правдоподобным.
// e_flagothers содержит флаги #define NOFAT 0x1 // OS/2 Игнорировать правило 8.3 (Стоит у модулей для HPFS) #define PMODE 0x2 // OS/2 2.x Protected Mode исполняемый #define PFONT 0x4 // OS/2 2.x Пропорциональные шрифты #define GANGL 0x8 // OS/2 Область GANDLOAD (групповой загрузки)
Может для кого-то будет неожиданно здесь увидеть упоминание OS/2 2.0,
но IBM планировала продолжать начатое и вероятно взялась бы дорабатывать дальше
"новый" сегментный формат тоже. Именно из-за устройства OS/2 здесь идет речь о
каком-то "OS/2-защищенном режиме", но в большинстве проверенных файлов я не встречал
использование этого флага.
e_expver
Поле e_expver указывает ожидаемую версию Windows. Это позволяло системе определить, совместима ли программа с текущей версией системы. Согласно некоторым источникам, приложение, скомпилированное для Windows 3.1, могло отказаться загружаться в Windows 3.0. Но я лично полагаю, что не все так просто и проблема в немного другом.
Так же по значению e_expver можно угадать приблизительно в каком режиме ЦП
запускается программа.
|
|
| Real Intel mode | Intel Protected mode |
|---|---|---|---|---|
1, 2 |
| [x] | [ ] | |
1, 2 |
| [x] | [x] | |
3 |
| [x] | [x] | |
3 |
| [ ] | [x] |
В пустых ячейках может быть любое значение.
e_pretthunks и e_thunk
Если флаг e_flagothers держит флаг GANGLOAD_AREA, тогда следующие части заголовка
интерпретируются иначе:
Поле e_pretthunks тоже проверяется только OS/2, по заверению документов и
имеет собой ввиду смещение для возврата из шлюзов (англ. "thunks") или начала GANGLOAD_AREA.
Если e_pretthunks является информацией о GANGLOAD области, то e_thunk хранит
размер этой самой области. Иначе это ссылка на сегмент.
Итого
В этой части я постарался перевести и разобрать немного
подробнее детали NE заголовка.
В основном, информация популярна и её не так сложно найти.
https://web.archive.org/web/20250804204414/https://www.fileformat.info/format/exe/corion-ne.htm
https://web.archive.org/web/20250918163525/https://wiki.osdev.org/NE#NE_Header
https://web.archive.org/web/20250219120408/http://benoit.papillault.free.fr/c/disc2/exefmt.txt
https://web.archive.org/web/20210411075350/http://bytepointer.com/resources/index.htm
Моя задача была начать об этом разговор и перевести на русский это. Сделать это как небольшой обзор.
Ведь на Хабре уже много кто делал обзор PE слинкованых программ?
Так же приложу свои же не переведенные части, где я дальше копаю
в этот формат сегментации.
В дальнейших статьях по поводу анализа двоичных данных
будет практически всегда фигурировать Sunflower.
Плагины, используемые Sunflower основываются строго на документах, которые я держу в отдельном репозитории.
А всё, что будет дальше будет в статьях - является результатом выполнения Sunflower и не
основывается на дизассемблировании и декомпиляции.
