DrunkenIronman allows customizing the message displayed
on the Blue Screen instead of the usual error identifier,
such as DRIVER_IRQL_NOT_LESS_OR_EQUAL
.
This is done by patching the message table resource
of the kernel binary (ntoskrnl.exe
) with the
user-supplied string.
The message table is a PE resource format that stores a mapping between integer identifiers and strings.
Usually, message tables are generated by the
Message Compiler (mc.exe
).
Message tables are often used to store human-readable
descriptions of error codes. For instance, the MUI
for kernel32.dll
contains a message table
with the Win32 error codes and other error values.
The format of the message table resource is documented in the MSDN. However, it will also be provided here for ease of reference.
Generally speaking, a message table resource consists of one or more blocks of messages, each block holding messages with consecutive identifiers. The message strings can be either ANSI or Unicode.
A message table resource begins with this structure, which describes the blocks of messages in the table.
typedef struct
{
DWORD NumberOfBlocks;
MESSAGE_RESOURCE_BLOCK Blocks[1];
} MESSAGE_RESOURCE_DATA, *PMESSAGE_RESOURCE_DATA;
Each block of messages is described by the following header:
typedef struct
{
DWORD LowId;
DWORD HighId;
DWORD OffsetToEntries;
} MESSAGE_RESOURCE_BLOCK;
The header specifies the range of IDs (inclusive) covered by the block,
and an offset relative to the beginning of the message table resource
to the actual string data. The strings are stored in MESSAGE_RESOURCE_ENTRY
structures (described below) and are ordered by ID in ascending order.
Each message string is stored in the following structure:
typedef struct
{
WORD Length;
WORD Flags;
BYTE Text[1];
} MESSAGE_RESOURCE_ENTRY, *PMESSAGE_RESOURCE_ENTRY;
- The
Length
field specifies the size, in bytes, of the whole structure. - The
Flags
field specifies whether the string is ANSI or Unicode. If equal to1
, the string is Unicode. If equal to0
, the string is ANSI. - The
Text
field contains the actual string data. It is unspecified whether the string is null-terminated.
The DrunkenIronman driver implements a message table parser, which provdes the following functionality:
- Deserializing a message table resource into an in-memory representation.
- Adding or replacing individual messages.
- Serializing the in-memory representation to a new message table resource.
At first glance it may seem like all that is required to patch a message
table resource in memory is to simply overwrite it with a new resource,
making sure the new resource does not exceed the old one in size. In most
cases this would probably be true, but when it comes to ntoskrnl.exe
there is a small subtlety.
Apparently, the Windows kernel obtains pointers to specific strings in its own message table early in the boot process. Overwriting the whole message table will most likely result in some strings chaning their location, and so the aforementioned pointers will become invalid.
Because our only interest is in replacing messages, not adding new ones, this problem can be solved by only writing strings that are no longer than the originals.
While implementing this behaviour I stumbled upon the fact that,
for some obscure reason, Microsoft's Message Compiler adds excessive padding
after some strings in the message table. I say 'excessive' because only at
most 1 byte of padding is required for each MESSAGE_RESOURCE_ENTRY
, seeing
as it must be aligned on a WORD boundary.
The current implementation, in order to maintain generality, assumes that
ntoskrnl
's message table resource is correctly padded, and reads
the string data as-is. When patching entries, the implementation makes sure
that the new entry is exactly the same size as the previous one, padding
as necessary.
When serializing, however, the implementation stores only the actual string data (up to the null-terminator, if there is one), and fills the rest with zeroes. This assumes that the padding in the original message table was zeroes, as well.
Because there is no way to preserve the exact same layout of the message table
resource without resorting to dirty hacks, the implementation also assumes
that the message entries are stored consecutively, in block order. For
ntoskrnl.exe
this happens to be correct.