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
CleverLibre Social

CleverLibre Social is an inclusive social instance for open discussion, learning, and community.
All cultures welcome.
Hate speech and harassment strictly forbidden.