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.
Motivation
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.
Procedure
- 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
SEG:OFF
format. - Invoke DEBUG, specifying the .exe from which you wish to dump memory as an argument, e.g. “
debug neuro.exe
“. - Run to the breakpoint you’ve selected, e.g. “
g 0b27:dd68
“. - 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
AX
,BX
andCX
. - Store the number of bytes to dump in
BX:CX
. (Note that this is *not* aSEG:OFF
value, this is a 32-bit value spread across two registers.) - Dump memory, specifying the starting address, e.g. “
w 5113:0000
“. - Restore the original values of
AX
,BX
andCX
.
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.
Example
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:
0xa583c5d83269cedf37fd2743971940ad
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 0xde68
and 0xdedc
. Using a technique discussed previously, we know that these opcodes will be found at memory addresses 2000:1a18
and 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 AX
, BX
, and CX
so that we can restore them later. On my machine, their values are 0x0003
, 0x0000
, and 0x0013
. Next, following the procedure above, we name the dump file, set BX:CX
to 0001:0000
(to dump an entire 64K segment) and issue the “w DS:0
” command to dump the data segment. Finally, we restore the AX
, BX
, and 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
Notes
You can dump the entire 1M address space by setting BX:CX
to 10:0000
in step (6), and specifying an address of 0:0
in step (7).