Statistics

Members: 1925
News: 292
Web Links: 1
Visitors: 3703883

Who's Online

Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home
Dumping Memory To Disk
User Rating: / 0
PoorBest 
Written by Jan Verhoeven   


This piece of code allows you to make a memory dump of any region of conventional memory (i.e. below 1 Mb) to a diskfile.

 

 

The program itself.

The source is documented so it speaks for itself. At points of interest I have insterted "break" and "restart" lines with space for additional remarks.

So just read the source and read the remarks. This way, the text is where the code is, and you don't need to go back and forth in the text. I think this will easier to read than explanation afterwards.

--- Mem2File ------------------------------------------------- Start ---

name mem2file
title Send an area of memory to a diskfile. page 80, 120

; version 1.0 : Had to be compiled for each area/filename OK: 01-01-1991

; version 1.1 : Same as above, for A86 format               OK: 01-01-1999
; version 1.2 : Make it commandline driven                  OK: 01-02-1999
; version 1.3 : Make it reliable                            OK: 02-02-1999

; ----------------------
stdout = 1

tab      =  9
lf       = 10
cr       = 13
clr MACRO               ; macro called CLeaR
mov  #1, 0          ; move it with zero
#EM                 ; and get outa here
Dum1   STRUC            ; a structure definition

OffVal dw ?
SegVal dw ?

ENDS
; ----------------------
DATA segment ; this is where the volatile data lives

ByteF = $

dummy    db    ?        ; just to fool D86....
; if this dummy variable is not here, D86 will
; reference variable "Start" as "ByteF".
even
Start    dw    ?, ?     ; segment:address to start
Stop     dw    ?, ?     ; segment:address to stop
Blocks   dw    ?        ; number of 16K chucks to save
Rest     dw    ?        ; remaining part to save
ArgNum   dw    ?        ; nr of bytes in this argument
OldClp   dw    ?        ; current pointer into command line

Handle dw ?
Length dw ?, ?

FileName:                       ; this storage is used twice....
Argument db     80 dup (?)      ; storage for next argument from command-line
Output   db    16K dup (?)      ; buffered output

--- Mem2File ------------------------------------------------- Break ---

An A86 enhancement: if you need 16K elements of data, just ask for it. No need to remember that 16 Kb is 16.384 bytes. The "K" will do.

No big deal, just a nice feature.

Also, if you need to process large binary numbers you may group them into sub-units separated by underscores. So the number:

1000100100111101

is hard to read back. But if we insert "_" markers like:

1000_1001_0011_1101

the grouping of bits makes them easier to understand. Not that it is a matter of life and death, but it can come in handy once in a while.

--- Mem2File ------------------------------------------------ Restart --

Bytes = $ - ByteF ; number of volatile databytes ; ----------------------
CODE segment ; no ORG, so we start at 0100

jmp main

HexTable db '0123456789ABCDEF', 0

         db    'VeRsIoN=Mem2File 1.3', 0
db    'CoPyRiGhT=CopyLeft Jan Verhoeven, {
 This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
 }', 0

Mess001 db 'Mem2File collects a part of conventional memory and '

         db    'sends it to a file.', cr, lf, lf
db    'The syntax is:', cr, lf, lf
db    tab, 'Mem2File  segm1:offs1 [-] segm2:offs2 '
db    '<path>file.ext.', cr, lf, lf
db    'Mem2File is GNU GPL style FREE software. ', cr, lf
db    'Please read the GNU GPL if you are in doubt.', cr, lf, lf

Mess002 db 'Mem2File was made by Jan Verhoeven, NL-5012 GH 272, '

         db    'The Netherlands', cr, lf
db    'E-mail address : {
 This e-mail address is being protected from spam bots, you need JavaScript enabled to view it
 }', cr, lf, lf

Len001 = $ - Mess001
Len002 = $ - Mess002

Mess004 db 7, 'Error! All numbers are expected to be hexadecimal.'

db cr, lf
Len004 = $ - Mess004
;------------------------

InitMem: mov   di, ByteF        ; Init volatile memory with zero's.
mov   cx, Bytes        ; saves a lot of strange problems.
mov   al, 0
rep   stosb
ret

;------------------------
--- Mem2File ------------------------------------------------- Break ---

I use volatile data to store data that does not need initialising. This saves a lot of diskspace and it loads a lot faster. Drawback of volatile data can be that any rubbish left there by other programs can make your software go berzerk if you yourself forget to initialise the data.

Therefore I -always- prime the volatile data memory with zero's. Just to have a well defined starting position.

It should not be necessary, but, on the other hand, how much overhead and extra execution time is such an initialisation routine?

--- Mem2File ------------------------------------------------ Restart --

L0:      mov   b [di], 0        ; terminate argument string
mov   [OldClp], si     ; done, => clean up.
clc                    ; indicate "No Error"
L3:      pop   di, si, ax       ; restore registers, ...
ret                    ; ... and leave.
GetArg:  push  ax, si, di       ; get next argument from command-line in ASCIIZ
format
mov   si, [OldClp]     ; now, where did we leave last time?
cmp   si, 0            ; Have we ever used this routine?
IF  E mov  si, 081             ; if not, prime SI, ...
mov   di, offset Argument      ; ... DI and ...
mov   [ArgNum], 0              ; ... nr of chars in argument.
L1:      lodsb                  ; get byte
cmp   al, ' '          ; skip over spaces, ...
je    L1
cmp   al, tab          ; ... and tabs.
je    L1
cmp   al, 1            ; ONLY if AL is 0, we get a carry
jc    L3               ; if CARRY, we're done

--- Mem2File ------------------------------------------------- Break ---

This construction is what I particularly like. I want to check if AL is Zero. Normally you can code

         cmp   al, 0
jz    L3

but L3 is the error-exit and needs the carrybit to be set as an error flag. Normally you would enter a

stc

instruction to fullfill the specification. But that is poor programming. It is better to let the software do this for us.

AL can have any value between 020 and 0FF, plus 00, tab, lf and cr. 01 is not an option. So the sequence

         cmp   al, 1            ; ONLY if AL is 0, we get a carry
jc    L3               ; if CARRY, we're done

will send us to the errorexit WITH the carryflag set, all in one, without explicitly having to set the carry flag.

--- Mem2File ------------------------------------------------ Restart --

L2:      stosb                  ; else store char in Arguments array
inc   [ArgNum]         ; adjust counter
lodsb                  ; and get next char
cmp   al, ' '          ; is it a delimiting space?
je    L0
cmp   al, tab          ; or a tab?
je    L0
cmp   al, ':'          ; or a colon?
je    L0
cmp   al, 0            ; or an end-of-line?
jne   L2               ; if not, loop back,
mov   si, 0FFFF        ; else make SI ridiculously high, ...
stc                    ; ... set carry flag, ...
jmp   L0               ; and get out.

--- Mem2File ------------------------------------------------- Break ---

Ok, ok, ok. I was influenced to make this function by a compiler. No, it wasn't C. It was Modula-2.

GetArg (if necessary A86 can operate in a case sensitive mode!) extracts the next argument from the command tail. It puts it in a seperate buffer at address "Arument" which can hold 80 bytes. Shoyuld be more than enough for one word or expression.

--- Mem2File ------------------------------------------------ Restart --

;------------------------

L1:      stc                    ; byte not in table!
pop   dx               ; we came here with carry set!
ret                    ; exit
L2:      sub   bx, dx           ; calculate position in table
pop   dx
clc                    ; make sure carry is cleared
ret

--- Mem2File ------------------------------------------------- Break ---

This is a typical A86 construction. The subroutine is called TableFind and it starts in the next line and ends in the previous one!

This is done to have the local labels declared for when they are needed in the main functionbody. All jumps are "backward". For the CPU there's no big influence, but for the assembler there is. No guessing about labels.

--- Mem2File ------------------------------------------------ Restart --

TableFind:                      ; find AL in ASCIIZ table [BX] ...
push  dx               ; ... and report position
mov   dx, bx           ; keep value of SI
L0:      cmp   b [bx], 0        ; is it end of table?
je    L1               ; if so, jump out
cmp   al, [bx]         ; compare byte with table
je    L2               ; if same, jump out
inc   bx               ; else increment pointer
jmp   L0               ; and loop back

;------------------------
MakeUpper:

         cmp   al, 'a'          ; too low?
jb    ret

--- Mem2File ------------------------------------------------- Break ---

An A86 enhancement: a conditional return instruction. All sensible CPU's have conditional CALL and RET instructions. Not the 80x86 line. This CPU was meant to be structured.

So you have to put a conditional jump before the call, and introduce yet another silly labelname for the next instruction.

The "Jcc ret" is a good way to circumvent this ommission. What it does is the same as what, on a Z-80, would be done with a "RET cc" instruction.

There is one catch, however: there must be a RET instruction within reach PRIOR to the "Jcc Ret" (internal) macro.

If that is a problem, you could also use the line:

IF cc Ret

So either way you, the programmer, win.

--- Mem2File ------------------------------------------------ Restart --

         cmp   al, 'z'          ; if in range, ...
ja    ret
and   al, not bit 5    ; ... make uppercase

--- Mem2File ------------------------------------------------- Break ---

A86 is very programmer-oriented and allows us to write down what and how we think. So if I need to set bit 0 of register Ax, I will simply write

or ax, bit 0

Any value between 0 and 15 is valid in A86 (0 - 31 for A386) to refer to the respective bit in the respective source.

--- Mem2File ------------------------------------------------ Restart --

ret
;------------------------

BadNumber:                      ; hey typo, you made a dumbo!
mov   dx, offset Mess004
mov   cx, Len004
mov   bx, StdOut
mov   ah, 040
int   021
mov   ax, 04C02        ; and exit with errorcode 2
int   021

;------------------------
SyntErr: mov dx, offset Mess001

         mov   cx, Len001
mov   bx, StdOut
mov   ah, 040          ; print out "help" screen and ...
int   021
mov   ax, 04C01        ; ... exit with errorcode 1
int   021
;------------------------
L8:      mov   ax, dx           ; Convert has result in DX, that's why.
pop   dx, bx
ret
Convert: push  bx, dx           ; convert ASCII to Hex.
mov   si, offset Argument
clr   dx               ; dx will contain result

--- Mem2File ------------------------------------------------- Break ---

Here the macro is invoked. It is used to load the DX register with zero. If later you decide to change the way in which you want to clear registers, just change the macro.

In LST files (the assembler listings) the expansions are controlled by means of the +L switch. If you issue the option "+L35" macro's will not be expanded in the listings file.

--- Mem2File ------------------------------------------------ Restart --

L1:      lodsb                  ; get first character
cmp   al, 0            ; end of string?
je    L8
call  MakeUpper                ; if not, make uppercase
mov   bx, offset HexTable
call  TableFind                ; and lookup in table
jc    BadNumber
shl   dx, 4            ; multiply DX by 16

--- Mem2File ------------------------------------------------- Break ---

This is another A86 goody. I coded a "SHL DX, 4" instruction, although I do not know what the target processor will be.

No problem with A86. It will find out with which CPU you are assembling and use that. If your CPU supports this function, it is implemented as such. If it doesn't this instruction is expanded as a macro into the following:

         shl   dx, 1
shl   dx, 1
shl   dx, 1
shl   dx, 1

More code in the executable, but it makes programming easier.

If on a modern CPU, you can force A86 to act as if the CPU were a vintage 88 with the commandline switch +P65.

--- Mem2File ------------------------------------------------ Restart --

         or    dl, bl           ; bx = index into table
jmp   L1               ; repeat until done

;------------------------
Credits: mov dx, offset Mess002

         mov   cx, Len002
mov   bx, stdout
mov   ah, 040
int   021              ; print some egotripping data
ret

;------------------------

main:    call  InitMem          ; prime volatile data
mov   al, [080]        ; get tail length
cbw                    ; make 16 bits long
mov   si, 081          ; point to start of tail
add   si, ax           ; point to end of tail
mov   [si], ah         ; make commandtail ASCIIZ
call  GetArg           ; get argument from command tail
jc    SyntErr          ; if error, get out
call  Convert                  ; convert text to hex
mov   [Start.SegVal], ax       ; store it
call  GetArg                   ; etcetera
jc    SyntErr
call  Convert
mov   [Start.OffVal], ax
L0:      call  GetArg
jc    SyntErr
cmp   b [Argument], '-'        ; single '-' character?
je    L0                       ; if so, ignore it
call  Convert
mov   [Stop.SegVal], ax
call  GetArg
jc    SyntErr
call  Convert
mov   [Stop.OffVal], ax
call  GetArg
IF  C jmp  SyntErr

--- Mem2File ------------------------------------------------- Break ---

This is one of the A86 enhancements. This IF construct prevents that you have to make up all kinds of ridiculous labelnames like jmp_001F in a construct as follows:

         call  GetArg
jnc   jmp_01F
jmp   SyntErr

jmp_01F: ...

The IF construct (not to be confused with the "#IF" construct which is for conditional assemblies) enables you to just make a fast jump by stating the reverse condition in the IF statement and acting further like a high level language:

IF C jmp SyntErr

Neat, isn't it?

--- Mem2File ------------------------------------------------ Restart --

         mov   si, offset Argument
add   si, [ArgNum]
mov   b [si], 0                ; make it ASCIIZ
mov   dx, offset FileName      ; same as Argument buffer....
mov   cx, 0
mov   ah, 03C
int   021                      ; create the file
IF  C jmp  SyntErr

--- Mem2File ------------------------------------------------- Break ---

See how powerful the IF construct can be? It is a very convenient way to circumvent the foolish conditional instructions of the x86 architecture.

--- Mem2File ------------------------------------------------ Restart --

mov [Handle], ax

         mov   ax, [Stop.SegVal]
mov   dx, 0                    ; prime DX
mov   cx, 4
L0:      shl   ax, 1
rcl   dx, 1
loop  L0                       ; shift upper 4 bits of address into DX
add   ax, [Stop.OffVal]
adc   dx, 0                    ; now, dx:ax = linear address to stop at
mov   [Stop.SegVal], dx
mov   [Stop.OffVal], ax        ; store linear address STOP
mov   ax, [Start.SegVal]
mov   dx, 0                    ; prime DX
mov   cx, 4
L0:      shl   ax, 1
rcl   dx, 1
loop  L0                       ; shift upper 4 bits of address into DX
add   ax, [Start.OffVal]
adc   dx, 0                    ; now, dx:ax = linear address to start
from
mov   [Start.SegVal], dx
mov   [Start.OffVal], ax       ; store linear address START
cmp   dx, [Stop.SegVal]        ; start > stop?
ja    >L1                      ; fix it!
jb    >L2

--- Mem2File ------------------------------------------------- Break ---

A86 likes to have as much as possible labels declared before they are referenced. That's why many times there is code "before" the subroutine name is declared.

For local labels (i.e. labels that consist of 1 letter and the rest decimal digits) it is a MUST that they are defined before being referenced.

If, for some reason, you do not want to put a label backwards in memory, you can forward reference a local label by prefixing it with a ">" sign. A86 now knows that the local label still has to come. Not a luxury since many A86 programmers can do with 4 or 5 local labels in over 2000 lines of code.... Especially L0 is always very well available.

--- Mem2File ------------------------------------------------ Restart --

         cmp   ax, [Stop.OffVal]        ; start > stop?
jbe   >L2                      ; if not, OK
L1:      push  [Stop.SegVal]
push  [Stop.OffVal]            ; swap start and stop addresses
mov   [Stop.SegVal], dx
mov   [Stop.OffVal], ax
pop   [Start.OffVal]
pop   [Start.SegVal]
L2:      mov   dx, [Stop.SegVal]
mov   ax, [Stop.OffVal]
add   ax, 1
adc   dx, 0                    ; limits are INCLUSIVE
sub   ax, [Start.OffVal]
sbb   dx, [Start.SegVal]       ; dx:ax = bytes to move
shl   ax, 1
rcl   dx, 1
shl   ax, 1
rcl   dx, 1                    ; dx = nr of 16 Kb blocks to move
mov   [Blocks], dx             ; store it
shr   ax, 2                    ; ax = remainder to move
mov   [Rest], ax               ; save it
mov   ax, [Start.SegVal]       ; end of linear addressing, we're going
to DOS!
mov   cl, 12
shl   ax, cl
mov   [Start.SegVal], ax
mov   es, ds           ; use es to refer to data
mov   bx, [Handle]
lds   dx, d [Start]

--- Mem2File ------------------------------------------------- Break ---

Normally this instruction would require a secretary with 100 letters per minute typing rate:

lds dx, dword ptr [Start]

But the "ptr" argument is always the same, so it is only there to please the assembler and humiliate the programmer: entering data that nobody needs.

Therefore A86 only needs the first letter of such prose. In our case: the "dword ptr" is abbreviated to a "d". "Byte ptr" is a "b". "Word Ptr" is a "w". Simple as that.

So if your coding skills outweigh your typing speed, you should consider switching to the superior assembler. :)

--- Mem2File ------------------------------------------------ Restart --

es cmp [Blocks], 0 ; if less than 16 Kb, skip this one.

--- Mem2File ------------------------------------------------- Break ---

For people who still remember that "Seg ES" is a legal instruction (used for a segment override) this might bring back memories.

A86 allows the user to put the segmentation override before the actual instruction. This way, the operand field looks neater. And it is also the way in which D86 shows segmentation overrides.

--- Mem2File ------------------------------------------------ Restart --

         je    >S0
mov   cx, 16K
L0:      mov   ah, 040
int   021
mov   ax, ds           ; store ds into ax
add   dx, 04000        ; next buffer to load data from
IF  C add  ax, 01000   ; if carry, inc ds
mov   ds, ax           ; ds:dx now ready for next bufferfull of data
es dec   [Blocks]
jnz   L0

S0: es cmp [Rest], 0

         je    >L1
mov   ah, 040
es mov   cx, [Rest]
int   021
L1:      mov   ds, es
mov   bx, [Handle]
mov   ah, 03E
int   021              ; close file
call  Credits          ; show my ego
mov   ax, 04C00
int   021              ; exit to DOS

--- Mem2File -------------------------------------------------- End ----

That's it.

{ This e-mail address is being protected from spam bots, you need JavaScript enabled to view it }