As I’ve mentioned before, I like to use the DOS DEBUG command to investigate older 16-bit programs. Today, I present a brief guide to using that tool to take snapshots of running programs.
The DOS DEBUG command provides only primitive facilities for the inspection of the contents of memory. When analyzing a program, it’s sometimes helpful to deploy more powerful tools – such as custom code – to manipulate a memory image. A first step to using external tools is extracting the memory image, and writing it to a file.
- You’ll need to select a breakpoint at which to dump memory. The selection is left as an excercise for the reader, but I’ll assume you’ve got one picked out in
- Invoke DEBUG, specifying the .exe from which you wish to dump memory as an argument, e.g. “
- Run to the breakpoint you’ve selected, e.g. “
- Name the dump file, e.g. “
n neuro.bin“. (Note that DEBUG won’t write files with a .hex or .exe suffix – pick another one.)
- Note the values of
- Store the number of bytes to dump in
BX:CX. (Note that this is *not* a
SEG:OFFvalue, this is a 32-bit value spread across two registers.)
- Dump memory, specifying the starting address, e.g. “
- Restore the original values of
About that Breakpoint …
Picking a breakpoint at which to stop a new, unfamiliar program is not trivial. In general, you want to stop somewhere “interesting” – a point at which memory has been populated with meaningful data. Until disassembly and analysis reveals the location of significant function calls in the program, interrupts are your best bet for finding interesting inflection points in the code.
For instance, the
INT 10h interrupt is used for a large number of important (but relatively rare) video operations: setting the video mode, manipulating the palette, etc. Breakpoints set on
INT 10h opcodes will tend to be hit at interesting points in the code.
Consider the game “Neuromancer”, from 1989. For this example, I’ll be working with the PC version of the game, which includes a NEURO.EXE file with a md5 hash of:
In order to take a meaningful memory dump, we need to find a good place to break program execution (i.e. after the program has had a chance to store data to memory). If we search the executable for
INT 10h opcodes (two adjacent bytes equal to
0xcd 0x10) we find two, at file offsets
0xdedc. Using a technique discussed previously, we know that these opcodes will be found at memory addresses
2000:1a8c. (Those are the addresses on my machine, at any rate: Yours may vary.)
When we run neuro.exe through the debugger, we find that neither breakpoint is hit until we exit the program, and the video mode is restored to 80×25 text. This is fine for our purposes, however, as memory has certainly been initialized by the time the program gets around to quitting.
Once we’re at our breakpoint, we need to note the values of
CX so that we can restore them later. On my machine, their values are
0x0013. Next, following the procedure above, we name the dump file, set
0001:0000 (to dump an entire 64K segment) and issue the “
w DS:0” command to dump the data segment. Finally, we restore the
CX registers, and let the program run to completion with the “
g” command. Here’s a list of the commands issued to DEBUG during this process:
g 2000:1a18 2000:1a8c ::Play game a bit, then exit:: n neuro.bin r bx 1 r cx 0 w ds:0 r ax 3 r bx 0 r cx 13 g
You can dump the entire 1M address space by setting
10:0000 in step (6), and specifying an address of
0:0 in step (7).