These tests demonstrate how to use GoBug's stack panes
to view data on the stack. For example you might want to view arguments on the stack or local data. It can be quite mindbending sometimes working
out what is on the stack and where it is. Hopefully with GoBug it all
makes sense.
Start Testbug as the debuggee and follow the instructions below.
There are 5 tests to choose from:-
In ST53 the value is kept in the user pointer
in the Thread Environment Block (TEB) which is a thread-specific area accessible using the FS
segment override. In Windows NT, 2000 and XP, if you use the FS segment
register to address memory, the system assumes you are intending to address the TEB.
In Windows 9x and ME, the value of FS changed from thread to thread because it held a 16-bit selector value. This does not happen in later Windows versions and FS remains the same value.
Useful TEB values are:-
   Passing arguments (EBP)
   Passing arguments (ESP)
   Using EBP as spare register
   Local data in stack frames
   Ways of saving return address
Passing arguments
(EBP)
In the first test, 3 pieces of data are put on the
stack before the call to RECEIVE_ARGSEBP, and then recovered back
by that function.
Using GoBug's "action, run to .. procedure" menu item, set the breakpoint to STACK_TEST1 and single step down as far as
the call to RECEIVE_ARGSEBP. Watch the value of ESP as you do each
push and watch the ESP stack pane. See how ESP reduces in value by
4 bytes (one dword) each time you push, and see that the data is put
on the stack at [ESP-4] so that after the push ESP points the data.
Now trace into RECEIVE_ARGSEBP by pressing F5 and scroll the EBP stack
pane until it shows the same data as showing in the ESP stack pane.
Note that after the instruction equalising EBP and ESP, the date you
pushed earlier can now be addressed by EBP plus 8h, 0Ch and +10h.
Trace into into CALL TO_NOTHING while watching the stack panes and note
how the return address of the call is put on the stack at [EBP-4]. In
other words, further use of the stack by push and calls will not
interfere with your data held on the stack. The value of EBP has not
changed and it is still addressable and valid. An important part of
this code is the RET 0Ch in RECEIVE_ARGSEBP which restores ESP to
the correct value to suit the caller.
Here is the source code:-
STACK_TEST1:
PUSH ADDR SPLOPFILE
PUSH 33333333h
PUSH [hWnd]
CALL RECEIVE_ARGSEBP
RET
;
RECEIVE_ARGSEBP:
PUSH EBP ;receiving 3 parameters from caller
MOV EBP,ESP
;now [EBP+8]=3rd param, [EBP+0Ch]=2nd param, [EBP+10h]=1st param
CALL TO_NOTHING
MOV ESP,EBP
POP EBP
RET 0Ch
;
TO_NOTHING:
NOP
NOP
NOP
RET
Passing arguments
(ESP)
This test is the same as the first, but you need
to concentrate more on ESP. After the call to RECEIVE_ARGSESP the
data on the stack is addressable by ESP +4h, +8h and +0Ch. After
the call to TO_NOTHING the data moves to ESP +8h, +0Ch, and +10h.
That is, ESP has moved, not the data. The point is, the data is
much more difficult to keep track of using ESP. Again an important part of
this code is the RET 0Ch in RECEIVE_ARGSESP which restores ESP to
the correct value to suit the caller.
RECEIVE_ARGSESP:
;now [ESP+4]=3rd param, [ESP+8]=2nd param, [ESP+0Ch]=1st param
CALL TO_NOTHING
RET 0Ch
;
STACK_TEST2:
PUSH ADDR SPLOPFILE
PUSH 33333333h
PUSH [hWnd]
CALL RECEIVE_ARGSESP
RET
Using EBP as
spare register
This simple test shows the EBP stack pane view when
EBP is used as a spare register.
Set breakpoint to STACK_TEST3 and single-step down this code. See
how the EBP stack pane changes values as EBP changes. In this code
the EBP register is of course carefully saved and restored before its
value is changed. Note when viewing the EBP stack pane when EBP does
not point to the stack area, the contents of the stack pane is not
to be relied on!
STACK_TEST3:
PUSH EBP
MOV EBP,200h
MOV EBP,2000h
MOV EBP,666666h
MOV EBP,0FFFFFFFFh
POP EBP
RET
Local data in stack
frames
These two tests demonstrates how a stack frame can be
made to hold local data on the stack, it shows how much easier it
is to use EBP to address such data, and it also helps to shows the
inter-action between GoBug's ESP and EBP stack panes. The first test
uses ESP to address the data, and the second uses EBP.
Set a breakpoint at STACK_TEST4 and
go to the test. Make a note of the eip at that address. Press F5 to
trace into ST41. Now at this point the return address is held on the
stack at [ESP]. Scroll the EBP pane a little until you get to the
same stack address (you will see a line comes into view - this is at
the current value of ESP). Now press F5 so that the instruction
SUB ESP,64 (or SUB ESP,40 in hex as shown in the codepane) is carried out.
Note that the ESP pane changes to the current position of ESP which is
now higher up the stack. The EBP stack pane has not changed and is
still showing the return address noted earlier. ESP now points to the
top of the stack frame which will hold local data and the REP STOSB
instruction fills this with zeroes. Watch as the zeroes reach the return
address in the EBP stack pane but do not rub it out (just!). Now we
are going to put some local data on the stack with the
instruction MOV D[ESP+8],22222222h. Have a look at that in the ESP
stack pane. Now comes the interesting part. Press F5 again to call
GET_LOCDATA1. See that the return address of that call is
even higher up the stack. The local data is not written over.
However, ESP is now a lower value than before (higher up the stack) and
in order to recover the data you need MOV EAX,[ESP+0Ch] and not
MOV EAX,[ESP+8h] which would read from the wrong place. Finally after
the return, ESP is restored to the correct stack position with ADD ESP,64
(ADD ESP,40 in hex) and the call to ST51 is finished.
Now press F5 again to trace into ST52. This time ESP is given to
EBP in the traditional way then the stack frame is made. Scroll back
the EBP pane as before to show the top of the stack frame. This time
the data is inserted at [EBP-38h], but this time since EBP does not
change it can still be addressed at [EBP-38h] even in the call
to GET_LOCALDATA2.
I also show here that using eax to address the stack also works
perfectly well. You don't have to use EBP, however it is traditional
to do so, mainly because in 16-bit assembler [ESP] and [EBP] always
read from the stack segment, whereas to use other register would
require the SS segment override. In 32-bits this is not necessary, but
it is a good idea to stick to the tradition since it helps to make your
code more understandable to others.
Therefore it can be seen that by addressing all local data using
EBP you are less likely to make errors arising from the changes to ESP.
You can make any number of stack frames you like subject to memory.
For example there could be another stack frame made in GET_LOCALDATA2.
If you did that and addressed [EBP-38h] you would be addressing the
2nd stack frame and not the 1st one. It's local data after all.
STACK_TEST4:
CALL ST41
CALL ST42
RET
;
ST41:
SUB ESP,64 ;make space for 16 dwords
MOV EDI,ESP
MOV ECX,64
XOR EAX,EAX
REP STOSB ;fill stack space with zeroes
MOV D[ESP+8],22222222h ;insert some data locally
CALL GET_LOCADATA1
ADD ESP,64
RET
;
GET_LOCADATA1:
MOV EAX,[ESP+0Ch] ;get local data back
RET
;
ST42:
PUSH EBP
MOV EBP,ESP
SUB ESP,64 ;make space for 16 dwords
MOV EDI,ESP
MOV ECX,64
XOR EAX,EAX
REP STOSB ;fill stack space with zeroes
MOV D[EBP-38h],22222222h
CALL GET_LOCADATA2
MOV ESP,EBP
POP EBP
RET
;
GET_LOCADATA2:
MOV EAX,[EBP-38h] ;get local data back
MOV EAX,EBP ;ready to use eax this time
MOV EAX,[EAX-38h] ;get local data back
RET
Ways of saving
return address
These four tests demonstrate various ways of saving and
restoring the return address either side of a stack frame made by
reducing the value of ESP by the number of dwords required.
In each case ESP is itself restored by using RET with the correct
number of bytes to suit the size of the stack frame.
In ST51 the return address is moved to the top of the stack frame
before the RET which is rather neat! In ST52 the return address is
kept in a register, but you have to make sure the value in the register
is not written over.
ST54 is a variation on ST51 and can be used in cases where eax has to be kept and restored.
FS:[0] = exception list
FS:[4] = StackBase
FS:[8] = StackLimit
FS:[14h] = a dword for private thread use
FS:[18h] = linear address of the TEB
In ST53 use is made of FS [14h] which is a unique dword for each thread.
STACK_TEST5:
CALL ST51
CALL ST52
CALL ST53
CALL ST54
RET
;
ST51:
MOV EAX,[ESP]
SUB ESP,64 ;make space for 16 dwords
MOV [ESP],EAX ;move return address
RET 40h
;
ST52:
POP EAX ;save caller in eax
SUB ESP,64 ;make space for 16 dwords
PUSH EAX ;put caller back
RET 40h
;
ST53:
FS POP [14h] ;put caller into user pointer
SUB ESP,64 ;make space for 16 dwords
FS PUSH [14h] ;put caller back
RET 40h
;
ST54:
XCHG EAX,[ESP] ;swap caller with eax
SUB ESP,64 ;make space for 16 dwords
MOV [ESP],EAX ;put caller back on stack
MOV EAX,[ESP+64] ;restore eax
RET 40h