Windows 95 and 98 IO.SYS
/WINBOOT.SYS
consists of three parts: the initial loader, the payload and the device configuration manager executable.
- The initial loader (MSLOAD) comprises the first three or four0 512-byte sectors of the file; the first sector begins with the signature
MZ
, the second starts with the signature BJ
and the last ends with the signature MS
. The FAT boot sector puts the initial loader at real mode address 0070:0000
, checks the first two of these signatures, then starts execution at address 0070:0200
. The job of MSLOAD is to copy boot sector variables into its data area (the first sector), relocate itself to a higher memory address, load the rest of the file starting at segment 0x70 again and then transfer control to 0070:0000
.
- MSLOAD is immediately followed by the payload, which is the DOS kernel proper, loaded as described above. Although it is loaded into a single contiguous region of memory, it is too large to fit into a single 64 KiB real-mode segment, and so the DOS kernel is further split into regions addressed using different segment bases; discovering those will be up to you. The length of this part can be determined by reading the word at offset 8 of the file, multiplying it by 16 and subtracting the length of MSLOAD.
- The device configuration manager (MSDCM1) is a small executable whose purpose is to read the Registry and prompt the user to pick a hardware configuration if there are multiple configurations defined. As it turns out, it is no accident that
IO.SYS
begins with the signature MZ
, just like DOS executables; the file is simultaneously a regular EXEPACK-compressed2 DOS executable, and the values in the MZ header at the beginning of the file allow it to be executed using interrupt 0x21 service 0x4b, which will load the third part of the file. If the real-mode kernel detects that it is booting a Windows installation, the kernel will do just that early in the boot process, before even CONFIG.SYS
is processed.3 The word at offset 8 is simply the ‘header length’ field of the MZ header, used both by MSLOAD to determine the length of the payload, and by the MZ loader to skip over the payload when loading MSDCM.
In Windows Me, the IO.SYS
file has a similar structure, with the following differences:
MSDCM has been integrated into the payload as a subroutine; it is no longer independently executable. The MZ
signature and the ‘header length’ field remain where they were (as MSLOAD still uses them), but the other fields of the executable header are filled with zeroes.
The payload may be compressed; this is indicated by the signature CM
. If MSLOAD sees the payload starting with that signature, it looks for the end for the compressed stream and will then use the decompression routine located at a 16-byte-aligned offset right after the payload (also preceded by a signature CM
, and some headers). If the compression signature is not found, the payload is loaded directly, like in previous versions.
The compression format itself is a rather unremarkable LZSS-ish bitstream split into chunks. Rudolph Loew’s IO8DCOMP can decompress the file in-place, or you may see how GRUB4DOS accomplishes it.4
To recap:
- MSLOAD can be disassembled by loading the first four sectors of IO.SYS as a raw binary at address
0070:0000
and placing the entry point at 0070:0200
;
- The payload can be disassembled by cutting it out of the file, decompressing it (if compressed), loading it starting at segment 0x70, placing the entry point at the beginning of that segment (i.e. at
0070:0000
), and discovering other segments by trial and error;
- MSDCM (before Millennium) can be disassembled by using an EXEPACK decompressor2 and disassembling the output as an MZ executable.
As it happens, the Windows Me ‘crippling’ code is located entirely within MSLOAD; there is no need to decompress the file. In fact, taking a copy of IO.SYS
from the Emergency Boot Disk and overwriting the code in its first four sectors with the loader from the hard disk’s copy of IO.SYS
would accomplish the same thing as applying the patch. (In fact, a somewhat well-known patch by ‘Manifest Destiny’ goes exactly that route.)
0 Three in versions prior to Windows 95 OSR2, four in OSR2 and later versions. Although in pre-OSR2 versions MSLOAD fits in three sectors, the boot sector still loads four.
1 I call it so because the string ‘MSDCMMSG
’ appears repeated in the uncompressed executable image.
2 Although IO.SYS uses a variant of the EXEPACK format with a 16-byte header that some decompressors may fail to recognise. David Fifield’s reimplementation of EXEPACK in Rust manages to read it.
3 In interactive boot, this step can be skipped by answering N
to the prompt ‘Process the system Registry [Enter=Y,Esc=N]?’; it is also controlled by the SystemReg=
setting in MSDOS.SYS
/WINBOOT.INI
.
4 As it turns out, this compression format was only slightly altered from the one used to store the embedded boot-up splash screen (LOGO.SYS
) in earlier versions of Windows. A very similar bitstream (only framed differently) is also used to compress W4-format VMM32.VXD in Windows Millennium. As far as I can tell, it is identical to the “DS” format used in DriveSpace. Or DoubleSpace. At least one of the two.
nasm -b 16 io.sys
) of the first 2048 bytes of Windows 98 SE io.sys and Windows ME io.sys. The disassembly was used only to double check that I've implemented the load protocol correctly. my source code.