Формат "Новых исполняемых" файлов называют сегментным. Даже в официальной документации 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 и не
основывается на дизассемблировании и декомпиляции.
horhex64
Как сделать такую схему из символов?
art2021 Автор
Если я правильно понял вас, то в разметке Markdown есть возможность написать сырой текст (который не рендрится). В начале строки ставятся "
```
" и через строку```
.