Editing Binaries in DOS
Both MS-DOS and Windows 98 come with a debugger program
named DEBUG.EXE
that make it possible to edit binary
files without requiring additional tools. Although the primary
purpose of this program is to test and debug executable files, it
can be used to edit binary files too. Two examples of this are
shown in this post. The first example edits a string of bytes in an
executable file. The second one edits machine instructions to alter
the behaviour of the program. Both examples provided in the next
two sections can be reproduced on MS-DOS version 6.22. These
examples can be performed on Windows 98 too after minor adjustments.
Editing Data
Let us first see an example of editing an error message produced by
the MODE
command. This DOS command is used for
displaying and reconfiguring system settings. For example, the
following command sets the display to show 40 characters per line:
C:\>MODE 40
The following command reverts the display to show 80 characters per line:
C:\>MODE 80
Here is another example of this command that shows the current settings for serial port COM1:
C:\>MODE COM1 Status for device COM1: ----------------------- Retry=NONE C:\>
An invalid parameter leads to an error like this:
C:\>MODE 0 Invalid parameter - 0 C:\>
We will edit this error message to be slightly more helpful. The following debugger session shows how.
C:\>DEBUG C:\DOS\MODE.COM -S 0 FFFF 'Invalid parameter' 117C:19D1 -D 19D0 19FF 117C:19D0 13 49 6E 76 61 6C 69 64-20 70 61 72 61 6D 65 74 .Invalid paramet 117C:19E0 65 72 0D 0A 20 0D 0A 49-6E 76 61 6C 69 64 20 6E er.. ..Invalid n 117C:19F0 75 6D 62 65 72 20 6F 66-20 70 61 72 61 6D 65 74 umber of paramet -E 19D0 12 'No soup for you!' D A -D 19D0 19FF 117C:19D0 12 4E 6F 20 73 6F 75 70-20 66 6F 72 20 79 6F 75 .No soup for you 117C:19E0 21 0D 0A 0A 20 0D 0A 49-6E 76 61 6C 69 64 20 6E !... ..Invalid n 117C:19F0 75 6D 62 65 72 20 6F 66-20 70 61 72 61 6D 65 74 umber of paramet -N SOUP.COM -W Writing 05C11 bytes -Q C:\>
We first open MODE.COM
with the debugger. When we do
so, the entire program is loaded into offset 0x100 of the code
segment (CS). Then we use the S
debugger command to
search for the string "Invalid parameter". This prints the offset
at which this string occurs in memory.
We use the D
command to dump the bytes around that
offset. In the first row of the output, the byte value 13 (decimal
19) represents the length of the string that follows it. Indeed
there are 19 bytes in the string composed of the text "Invalid
parameter"
and the following carriage return (CR) and line
feed (LF) characters. The CR and LF characters have ASCII codes 0xD
(decimal 13) and 0xA (decimal 10). These values can be seen at the
third and fourth places of the second row of the output of this
command.
Then we use the E
command to enter a new string length
followed by a new string to replace the existing error message.
Note that we enter a string length of 0x12 (decimal 18) which is
indeed the length of the string that follows it. After entering the
new string, we dump the memory again with D
to verify
that the new string is now present in memory.
After confirming that the edited string looks good, we use
the N
command to specify the name of the file we want
to write the edited binary to. This command starts writing the
bytes from offset 0x100 to the named file. It reads the number of
bytes to be written to the file from the BX and CX registers. These
registers are already initialised to the length of the file when we
load a file in the debugger. Since we have not modified these
registers ourselves, we don't need to set them again. In case you
do need to set the BX and CX registers in a different situation, the
commands to do so are R BX
and R CX
,
respectively.
Finally, the W
command writes the file and
the Q
command quits the debugger. Now we can test the
new program as follows:
C:\>SOUP 0 No soup for you! - 0 C:\>
Editing Machine Instructions
In this section, we will see how to edit the binary we created in the previous section further to add our own machine instructions to print a welcome message when the program starts. Here is an example debugger session that shows how to do it.
C:\>DEBUG SOUP.COM -U 117C:0100 E99521 JMP 2298 117C:0103 51 PUSH CX 117C:0104 8ACA MOV CL,DL 117C:0106 D0E1 SHL CL,1 117C:0108 32ED XOR CH,CH 117C:010A 80CD03 OR CH,03 117C:010D D2E5 SHL CH,CL 117C:010F 2E CS: 117C:0110 222E7D01 AND CH,[017D] 117C:0114 2E CS: 117C:0115 890E6402 MOV [0264],CX 117C:0119 59 POP CX 117C:011A 7505 JNZ 0121 117C:011C EA39E700F0 JMP F000:E739 -D 300 117C:0300 07 1F C3 18 18 18 18 18-00 00 00 00 00 00 00 00 ................ 117C:0310 00 00 FF 00 00 00 00 00-FF 00 00 00 00 00 00 00 ................ 117C:0320 00 00 00 00 00 00 00 00-00 00 FF FF 90 00 40 00 ..............@. 117C:0330 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 117C:0340 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 117C:0350 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 117C:0360 00 00 00 FF 00 00 00 00-00 00 00 00 00 00 00 00 ................ 117C:0370 02 00 2B C0 8E C0 A0 71-03 A2 BA 07 A2 BC 07 3C ..+....q.......< -A 117C:0100 JMP 330 117C:0103 -A 330 117C:0330 MOV AH, 9 117C:0332 MOV DX, 33A 117C:0335 INT 21 117C:0337 JMP 2298 117C:033A DB 'Welcome to Soup Kitchen!', D, A, '$' 117C:0355 -W Writing 05C11 bytes -Q C:\>
At the beginning, we use the debugger command U
to
unassemble (disassemble) some bytes at the top of the program to see
what they look like. We see that the very first instruction is a
jump to offset 0x2298. The debugger command D 300
shows that there are contiguous zero bytes around offset 0x330. We
replace some of these zero bytes with new machine instructions that
print our welcome message. To do this, we first replace the jump
instruction at the top with a jump instruction to offset 0x330 where
we then place the machine code for our welcome message. This new
machine code prints the welcome message and then jumps to offset
0x2298 allowing the remainder of the program to execute as usual.
The debugger command A
is used to assemble the machine
code for the altered jump instruction at the top. By default it
writes the assembled machine code to CS:0100 which is the address at
which DOS loads executable programs. Then we use the debugger
command A 330
to add new machine code at offset 0x330.
We try not to go beyond the region with contiguous zeroes while
writing our machine instructions. Fortunately for us, our entire
code for the welcome message occupies 37 bytes and and the last byte
of our code lands at offset 0x354.
Finally, we write the updated program in memory back to the file
named SOUP.COM
. Since the debugger was used to load
the file named SOUP.COM
, we do not need to use
the N
command to specify the name of the file again.
When a file has just been loaded into the debugger, by default
the W
command writes the program in memory back to the
same file that was loaded into the memory.
Now our updated program should behave as shown below:
C:\>SOUP COM1 Welcome to Soup Kitchen! Status for device COM1: ----------------------- Retry=NONE C:\>SOUP 0 Welcome to Soup Kitchen! No soup for you! - 0 C:\>
That's our modified program that prints a welcome message and our own error message created with the humble DOS debugger.