Follow

Абсолютно совместимый DOS стаб.

github.com/Nekun/compatstub

Как известно, каждый исполняемый PE EXE начинается с DOS-заглушки: поскольку у него одинаковое расширение с 16-битным исполняемым форматом MZ EXE, принятым в DOS, в начало PE помещают небольшую MZ-программу, чтобы при попытке запуска в DOS выводилось сообщение об ошибке и происходило корректное завершение, а заголовок расширяют полем с указателем на начало заголовка PE.

Так вот, оказывается что эта программа совместима лишь с MS[PC]-DOS 2.0+: при попытке запустить заглушку в PC-DOS 1.00 в консоль выводится мусор и система зависает, хотя поддержка EXE в ней есть и даже есть ранняя версия LINK в этом формате на загрузочной дискете! Обращаемся к официальному мануалу[1], видим что сискола int21h/4Ch для завершения ещё нет. Меняем на традиционный прыжок в PSP на int20h, всё так же. Запускаем debug.com our_exe.exe: видим, что хотя PSP сформирован правильно и управление передано в новый выделенный сегмент (в отличие от COM, EXE грузится по нулевому смещению в отдельный от PSP CS), а в нём пусто. Загрузчик не загрузил исполняемый модуль!

В том же мануале [1] есть описание формата EXE и видим, что он ещё не стабилизировался, и некоторые поля ещё числятся резервными: в частности, e_cblp, который указывает размер последней 512-байтной страницы загружаемого модуля. Так как размер EXE-шника указывается в следующем поле e_cp в 512-байтных страницах, то в DOS 1.x размер исполняемого файла должен быть кратен 512 байтам. Обратившись к публично доступным исходникам [2], смотрим код загрузчика (в DOS 1.x загружает command.com, в 2.x, емнип, перенесли в ядро). Согласуясь с исходником, отреверсил COMMAND.COM PC-DOS 1.0, и увидел что отличия в коде несущественные. Так вот, при подсчёте размер заголовка в 16-байтных параграфах пересчитывается в размер в 512-байтных страницах, округляется в большую сторону и вычитается из числа страниц для загрузки по CS:0: github.com/microsoft/MS-DOS/bl . Потом, размер читаемого блока задаётся 512 байт и загружаемый модуль начинает читаться из файла по прежде высчитанному округленному в большую сторону размеру заголовка в страницах. Стало быть, размер заголовка тоже должен быть кратен 512! Ну а стабы PE очень маленькие, без таблиц релокации и обычно занимают с заголовком порядка сотни байт. По-видимому, с самого начала формат EXE разрабатывался с расчётом, что им будут пользоваться только действительно по необходимости, когда нельзя втиснуться в один сегмент 8086 и вплоть до DOS 2.0 забыли предусмотреть и произвольный размер кода, и размер заголовка без кучи релоков.

В общем, сделал абсолютно обратно совместимый стаб: 512 байт на заголовок, 512 байт на код, jmp [<initial ds>:0] для завершения, и теперь наш код работает даже на PC 5150+64KB RAM+PC-DOS 1.00, равно как и во всех последующих DOS. На удивление мало линкеров позволяют заменить дефолтный стаб: в GNU ld этого вообще не предусмотрено, а в lld-link хоть и есть опция /stub, но она не задействована в коде. Пока обнаружил только форматёр fasm и MS LINK. Мануально, к сожалению, поменять стаб тоже отнюдь не просто: 32-битные PE почти всегда position-dependent, а загрузчик ложит по ImageBase образ всего файла, начиная с MZ: поэтому изменив размер заглушки, смещения в коде будут разрушены. Рудиментарную таблицу релоков нередко стрипают, и информации для патчера не остаётся.

[1] textfiles.com/bitsavers/pdf/ib
[2] github.com/microsoft/MS-DOS/bl

@ru @rf

@nekun

> Мануально, к сожалению, поменять стаб тоже отнюдь не просто: 32-битные PE почти всегда position-dependent, а загрузчик ложит по ImageBase образ всего файла, начиная с MZ: поэтому изменив размер заглушки, смещения в коде будут разрушены.

Не-не-не. Загрузчик по ImageBase располагает только заголовки, а данные секций (где расположен непосредственно код и данные) загрузчик раскидывает исходя из геометрии, описанной в заголовках секций.

В 99.9% случаев первая секция расположена (virtual address) на следующей странице, после заголовка. А MZ + PE заголовок это сильно меньше, чем страница. Подвинуть только PE заголовок, если я ничего не путаю, не составит труда: нужно после перемещения данных только подправить в MZ заголовке смещение до PE заголовка. Чуть сложнее двигать данные секций в файле: нужно будет подпатчить смещение в файле в заголовке каждой секции, но и это не супер-сложная задача.

Sign in to participate in the conversation
Qoto Mastodon

QOTO: Question Others to Teach Ourselves
An inclusive, Academic Freedom, instance
All cultures welcome.
Hate speech and harassment strictly forbidden.