Editing Binaries in DOS

By Susam Pal on 18 Jul 2002

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:


Status for device COM1:


An invalid parameter leads to an error like this:

C:\>MODE 0

Invalid parameter - 0


We will edit this error message to be slightly more helpful. The following debugger session shows how.

-S 0 FFFF 'Invalid parameter'
-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
Writing 05C11 bytes


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 initialized 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


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.

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.......<
117C:0100 JMP 330
-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, '$'
Writing 05C11 bytes


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:

Welcome to Soup Kitchen!

Status for device COM1:

C:\>SOUP 0
Welcome to Soup Kitchen!

No soup for you! - 0


That's our modified program that prints a welcome message and our own error message created with the humble DOS debugger.