Geoff Chappell, Software Analyst
The RtlGetNtVersionNumbers function gets Windows version numbers directly from NTDLL.
VOID RtlGetNtVersionNumbers ( DWORD *MajorVersion, DWORD *MinorVersion, DWORD *BuildNumber);
The optional MajorVersion and MinorVersion arguments each give the address of a variable that is to receive the corresponding part of the Windows version number. Each can be NULL if the corresponding part is not wanted.
The optional BuildNumber argument gives the address of a variable that is to receive a number that describes the build. This too can be NULL if the number is not wanted. The low 16 bits are the build number as commonly understood. The high four bits of the number distinguish free and checked builds.
The RtlGetNtVersionNumbers function is exported by name from NTDLL in version 5.1 and higher.
The RtlGetNtVersionNumbers function is not documented. While Microsoft’s names for the function’s arguments are not known, this article uses inventions.
This very simple function simply sets each of the given variables with the corresponding number directly from NTDLL’s own data or code. In this sense, the version numbers produced by RtlGetNtVersionNumbers are the true Windows version numbers.
The immediate contrast is with the version numbers that are held in the PEB as the OSMajorVersion, OSMinorVersion and OSBuildNumber members in version 4.0 and higher. Historically, these are the version numbers that are reported by the NTDLL export RtlGetVersion and the older KERNEL32 (and nowadays KERNELBASE) exports GetVersion and GetVersionEx. They still are the starting point for what these functions report. But even though they are set into the PEB by the kernel when creating the process, they never have been reliably the kernel’s version numbers and—like everything else in the PEB—they are anyway susceptible to being changed by arbitrary user-mode software, whether by design or for mischief.
Microsoft’s only known use of the RtlGetNtVersionNumbers function is for the MSVCRT.DLL that is distributed with Windows. Possibly every C and C++ programmer for Windows has at some time thought to use the Visual Studio compiler’s /MD switch so that routines from the C Run-Time Library (CRT) will be imported dynamically from a DLL instead of being statically linked (and adding bloat to the binary). All but a handful will at least once have been surprised that their (significantly smaller) binary doesn’t run on some other Windows computers. The cause, of the program’s failure, not of the programmer’s surprise, is that Visual Studio will ordinarily have built the binary to import not from an MSVCRT.DLL that you can count on to be present but from a numbered variant that is specific to the Visual Studio version and whose run-time presence you can’t count on unless you install it with your binary as some sort of prerequisite extra.
How this does not prompt howls of protest from programmers, I don’t know. Microsoft’s own low-level user-mode code imports from an MSVCRT.DLL that is distributed with Windows. Microsoft’s kits for low-level programming, e.g., the Windows Driver Kit (WDK), often have included an import library MSVCRT.LIB to link with for importing from whatever system-supplied MSVCRT.DLL is present wherever the linked binary runs. But how programmers get their binaries to use the system-supplied MSVCRT is not this note’s interest.
The problem for a system-supplied MSVCRT is that it may be specific to the system version that it’s supplied with yet its determination of this version depends on what process it happens to be loaded into. That processes can be lied to about the Windows version they’re running on, usually but not always to help them over some incompatibility, has a very long history. While it may be a useful facility for dealing with applications, it must be more than an occasional problem for all programmers who write DLLs for general use in arbitrary processes. Quite why the problem applies to MSVCRT so much more than to others that MSVCRT needs the special help of an undocumented NTDLL function is not known, but pretty much the first thing that the system-supplied MSVCRT does is to call RtlGetNtVersionNumbers so that it can decline its initialisation if it is not running on the expected system version.
Only the naive would be surprised that the CRT source code, which Microsoft publishes with Visual Studio, does not show the _CRTDLL_INIT routine’s use of the RtlGetNtVersionNumbers function. The closest that this use of the function brought Microsoft to disclosing it is that for many years, up to and including Visual Studio 2010, the source code did have a typedef for the otherwise unreferenced name NTVERSION_INFO_FNC as a pointer to the function.