Switch to Windows 95
In November last year I wrote about the forgotten and obscure feature of early Windows 95 builds that lets you run Windows 3.1 in a window on Windows 95. Since then I was wondering if this would still work on the final build (950) of Windows 95, considering so much has changed since build 58s.
As mentioned in my previous post about this feature, it hasn't changed since build 81 (except for a branding updated right before its removal) and was removed between builds 328 and 331. Considering just how much Windows 95 has changed between builds 81 and 950 (or even between 81 and 328, for that matter), I honestly didn't expect this to work. But of course I had to try it anyway.
Setting up the system works much like it does with build 58s - install Windows 3.1 and 95 RTM in separate directories, copy the required files into proper locations and modify Windows 3.1's SYSTEM.INI to use special drivers that make this work. And when I tried to run it...
Can't do, you're not using Windows 4.0! Or are you?
As it turns out, WIN31.EXE performs a version check on Windows 95. These are usually relatively easy to bypass, so I decided to fire up IDA Pro and see what exactly is being checked and compared. You probably know Windows 95 reports version 3.95 to legacy (16-bit) Windows applications for compatibility reasons (I recommend you read this brief post by Raymond Chen to see why). However, this program wants Windows version 4.0. You see, early builds of Chicago simply reported version 4.0 to all applications. 3.95 wasn't introduced until build 122 or so, I think.
IDA Pro reveals the check is performed not once, but twice - first in the DOS section of the program, and then again in the Windows section. This is done with interrupt 2Fh function 1600h, which checks for the presence of Enhanced mode Windows. Aside from some special values for old versions of Windows, it otherwise returns Windows' major version in register AL and minor version in AH. This information is provided by Ralf Brown's Interrupt list, though it incorrectly claims Windows 95 returns version 4.0 via this function. If it did, WIN31.EXE would work and there would be no need for this post. The program then performs a compare (CMP) operation on the returned value in register AX and 4. If the two don't match, the program will quit with the error message you see above. Otherwise, it performs a short jump and continues. This is done with the "jump if zero" (JZ) instruction and that's where the problem is - Windows 95 RTM will return 3 in AL and 95 in AH, which together makes up the value 0x5F03 in register AX. If you're interested in the specifics of compare and conditional jump instructions on the x86, there's plenty of sources available online.
Left: the check in Windows section, right: the check in DOS section
Luckily, this is easy to fix. All you have to do is replace the "jump if zero" instruction with "jump if not below or equal/above" (JNBE aka JA), which will in our case perform the jump to the specified address for all AX values greater than 4. Alternatively, a regular jump (JMP) can also be used, which ignores the reported version entirely and always performs jump. Patching with JMP instead of JNBE would make this program work on all builds, as using JNBE prevents it from starting on early builds that report version 4.0.
As mentioned above, this has to be done twice, since there are two checks. The opcodes of both can be found in IDA's hex view. They're immediately followed by JZ, the opcode is 0x74. Opcode for JNBE is 0x77, for JMP it's 0xEB. A hex editor, such as HxD, can be used to find and replace the two relevant values.
Left: patched conditional jump in Windows section, right: patched conditional jump in DOS section
After replacing the original WIN31.EXE with our patched copy and running it, Windows 3.1 will start as expected, with an icon for Switch to Chicago on the desktop. Clicking it takes us back to Windows 95 (basically it minimizes the window in which Windows 3.1 is running). Interestingly, when running WIN31.EXE on Windows 95 RTM, the control panel in Windows 3.1 doesn't cause a GPF in DUALMOUS.DRV like it does on build 58s.
Works like a charm after the patch
Thanks to Rai-chan from RoL for pointing WIN31.EXE performs two version checks.
Update on 26th November 2020: I made some minor updates to the post in light of build 328 leak.