- PVSM.RU - https://www.pvsm.ru -

«Digital Rain» для Windows в 314 байтах

В комментариях к недавнему топику [1] возникло обсуждение: до какого размера можно ужать Windows EXE, печатающий в консоли «Hello, World!» Ответ: 268 байт, [2] меньшие файлы Windows просто отказывается загружать.

Раз для «Hello, World!» предел возможного ужатия уже достигнут, то мне стало интересно, до какой степени удастся ужать программу, делающую хоть что-нибудь более интересное.

Сначала похвастаюсь результатом: моя программа всего на 46 байт больше теоретического минимума!

«Digital Rain» для Windows в 314 байтах - 1

base64

TVprZXJuZWwzMgAAUEUAAEwBAQC4AwABAPdlEIlFEMN4AA8BCwEFDL0UEEAAjXyNAFfraD
gQAAAzyesoDAAAAAAAQAAAEAAAAAIAAAAAAAACAgoCBAAAAAAAAAAAQAAAAAIAALFQ68AD
AAAAEgEAAAAAAABQABkAABAAAFAAGQADAAAAAAAAAAAAAAAoEQAAKAAAAAAAAAAAAAAA/9
Wr4vvrEQAAMAAAABAAADkBAAABAAAAi/df6wMAAAAzybFQV4sHgPwZdygPttyNHJvB4waN
HItQweAYwegei0RFOIhEMwKIpDPC/v///9WIJDNY/sSA/GR8Av/Vq+LFjUVcUFH/dWhWZI
tBMItAEP9wHP9VWOuiV3JpdGVDb25zb2xlT3V0cHV0QQBsEAAAAAAAAAAAAAACAAAAbBA=

(Если найдётся доброволец захостить эти 314 байт, добавлю сюда ссылку.)

«Digital Rain» для Windows в 314 байтах - 2 [3]

Предыдущими энтузиастами было установлено, что самая маленькая программа для Win2000 и WinXP занимает 133 байта [4]; самая маленькая 32-битная программа для Windows x64 — 268 байт [5]. Последнее ограничение жёстко зашито в загрузчик Windows x64: если от начала заголовка PE до конца файла меньше IMAGE_NT_HEADERS64=0x108 байт, то Windows отказывается загружать файл. Заголовок PE не может начинаться раньше, чем по смещению 4, поэтому содержимое программы, занимающее (в минимально возможном варианте [6]) даже меньше, чем 268 байт, приходится добивать нулями до минимально допустимого размера.

Существующие примеры программ размером 268 байт не содержат ни одной секции, и фактически, целиком помещаются внутри заголовка PE, для загрузки которого Windows выделяет одну страницу памяти (4КБ). Но для «Digital Rain» нужно хранить буфер экрана (80 столбцов × 25 строк × 4 байта на символ = 8000 байт), поэтому без секции в программе не обойтись. Это не так уж расточительно: заголовок секции — всего 0x28 байт, из которых больше половины не используются, так что их можно занять кодом. Фактически, «внутри» секции — после его заголовка — в моей программе располагаются часть исполнимого кода, имя импортируемой функции, и часть таблицы импорта (последние 0x16 её байт нулевые, и в файле не хранятся). Таблица импорта и следующие за ней 8КБ памяти во время работы используются для хранения данных (массив состояния и буфер экрана). Все инициализированные данные, часть служебной информации (цепочка импорта и имя библиотеки), и часть кода — распиханы по заголовкам. «Дважды используемые» поля подписаны на вышеприведённой презентации: данные лежат в MajorOperatingSystemVersion, MinorOperatingSystemVersion, MajorImageVersion, MinorImageVersion, SizeOfStackCommit, SizeOfHeapReserve и LoaderFlags, а код — в SizeOfCode, SizeOfInitializedData, SizeOfUninitializedData, BaseOfCode, CheckSum, а также в поле имени секции и в её заголовках PointerToRelocations, PointerToLinenumbers, NumberOfRelocations, NumberOfLinenumbers и Characteristics. Для поля Characteristics (флаги секции) достаточно было, чтобы был установлен MSB (IMAGE_SCN_MEM_WRITE); на значения некоторых остальных полей также накладываются ограничения — например, SizeOfStackCommit и SizeOfHeapReserve должны помещаться в память. Значение поля PointerToLinenumbers системой по недоразумению принимается за размер таблицы отладочной информации, и если там значение больше размера программы в памяти (0x4000), то программа рушится при загрузке.

В предыдущих «минимальных» программах использовались значения FileAlignment=4 и SectionAlignment=4. В файле без секций Windows x64 такое позволяет; но если хотя бы одна секция объявлена, то FileAlignment должен быть не меньше 0x200, а SectionAlignment должен быть 0x1000. Поэтому создать программу размером 268 байт, в которой была бы секция, невозможно: перекрытие e_lfanew=4 и SectionAlignment недопустимо. Сдвинуть заголовок PE на 4 байта мало, потому что ImageBase должен быть кратен 0x1000. Получается, что минимальный возможный размер программы с секцией — это 276 байт, с перекрытием e_lfanew=0xC и BaseOfData. Иными словами, моя программа на 38 байт больше минимально возможной, которая использовала бы >4КБ памяти.

Работоспособность своей программы я проверял на Win7 и на Win2008. На WinXP моя программа, к сожалению, не работает: та отказывается загружать программы, у которых в каталоге менее 0xD записей. Если в программе «Hello, World!» весь код занимает 0x10 байт, и им не жалко набить в файл 0x50 нулевых байт ради совместимости с WinXP — то мне обидно было ради этого раздувать программу на четверть размера. Приношу свои извинения всем любителям винтажных ОС.

Автор: tyomitch

Источник [7]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/nenormal-noe-programmirovanie/111372

Ссылки в тексте:

[1] недавнему топику: https://habrahabr.ru/post/275861/

[2] Ответ: 268 байт,: https://habrahabr.ru/post/275861/#comment_8755925

[3] Image: https://habrastorage.org/files/a2a/e26/154/a2ae2615410449b181a8c434fb9a3f2f.gif

[4] 133 байта: http://www.phreedom.org/research/tinype/

[5] 268 байт: http://www.bigmessowires.com/2015/10/08/a-handmade-executable-file/

[6] в минимально возможном варианте: http://www.alex-ionescu.com/?p=211

[7] Источник: https://habrahabr.ru/post/276371/