published by +Tsehp
rights reserved to the Immortal Descendants
Visit us on the web at : http://www.ImmortalDescendants.org/
In this essay
I'll try and explain some of the most unused commands that SI, our debugger
The purpose of this document (essay in plain english :) is to give some insight into some of the more
powerful feautures this wonderful tool has to offer. The reason, quite simple: most of the Fravias
out there (mostly the newbies) aren't familiar with the true capabilities of the debugger. Either because
they've just started using it, or maybe it has something to do with the fact that most of them just don't
bother to take the time to either download (DL) or read the SI manual and/or command reference.
So in order to let you guys know what you're missing, I'll try and explain here some of the neat stuff
you can do with SI, hopefully it will inspire people to investigate the tool further and maybe, just
maybe, it will inspire new reversing techniques.
So, now that you know what this essay is, and why, let's get on with it.
In the beginning, there was a breakpoint
OK, this part
of the essay is really all about basic SI issues. This will remind you
what we generally
use and when this part is done and the fun stuff begins, you'll understand the subtle differences.
So, how does a cracker approach a program he/she wishes to crack ?
the methodology changes considerably between protection schemes and crackers
however, some things are common. For example, we all know that the most basic thing in order to get
close to a protection scheme is to break on services we think it uses from the OS. In Windows, these
are generally API calls. What is even more saddening (if there exists such a word hehe) is the fact
that we usually concentrate mainly on these types of BPs.
We usually either guess which imports a program needs, or look directly into the import table (provided
it is not encrypted as with most of today's packers) and find the relevant APIs to break on.
This worked wonderfully for many different purposes. Catching a program getting text out of a dialog
control with GetDlgItemTextA or GetWindowTextA are all too familiar nowadays.
Many other APIs exist for other purposes such as time checks, registry access, file access (yup, the
almighty CreateFileA function...) and many more.
This has become the standard for newbies when starting to crack, and don't get me wrong, it IS a very
good place to start learning, but after a while, it seems that API breaking is the most important skill
a cracker can have, which is of course not true at all.
A cracker can also break on system interrupts for exmaple :-)
Ok, seriously though, even the most complex breakpoints on API calls are simply not enough nowadays.
This is because programmers (some of them anyway) learn and learn to trick us when we crack.
They are just plain evil!!
However, we can have tricks of our own, and now we desire even more new tricks that will help us
reverse whatever we need to.
So, from function calling, some have evolved to conditional breakpoints, breakpoints with some action
performed after them (know that nifty DO command ?), bpcounts and some more exciting things,
such as macros (oh feel the rush!!).
These are not the only possible things a cracker can do. Other ideas were to follow a program's execution
path for the goodguy and badcracker paths and noticing the difference (was it _mammon who first
showed how to do that ??) and some other nifty tricks.
What we'll try doing is to add some more tricks to this, and let's start building from the beginning, with
the normal BPs.
So, suppose you're trying to keygen this cool program, and as it happens, it uses GetWindowTextA to get
the dialog's text. Instead of issueing the following command:
, then pressing F5 and filling in the fields, we can do something nicer:
bpx getwindowtexta do "dd ss:esp"
This will display
the stack in the current data window, everytime the BP is hit (i.e, SI
breaks), which is a good
thing (naturally..). However, why not go further deep ?
As you may or may not know, the symbol GetWindowTextA is synonymous to a memory address to break on.
So in effect, you can do a bpx getwindowtexta+1 and hopefully SI will break at the instruction one byte after
the beginning of GetWindowTextA.
With this in mind, it shouldn't be too hard to do something along the lines of this:
bpx beginpaint-3 do "db *(esp-0c)"
what is THAT ?
Ok, no panic, it's just some neat way of breaking in and at the same time, viewing the text. How is this done ?
Well, as it happens, BeginPaint is the funtion that comes right after GetWindowTextA in the User32 DLL,
and if you look closely, the last instruction in GetWindowTextA is exactly 3 bytes long, and is a RET 000C.
So, putting a BP at the address of BeginPaint-3 will break before GetWindowTextA returns.
You can of course obtain this address using conventional methods. Break on the function and just look at
the address of the last instruction :-)
However, sometimes it's good to know that SI has the ability to convert addresses to names easily. So for
example, to unassemble CreateFileA, do this anytime:
Ok, back to our example, you have to remember that 3 parameters are passed on the stack when the function
is called. The first is the window handle to get the text out of, the second is a long pointer to a string, which is
usually somewhere in the application program space (0040000h - 7FFFFFFFh), and another argument which
is a maximum count of characters, which is not important to us.
below 00400000h is set aside for the DOS VM, and anything above 7FFFFFFFh
allocated, as in system DLLs and such.
Well, the last
part of the BP example tells SI to display (with byte alignment) the memory
address pointed to by
the current value of ESP, minus 12 bytes. These 0Ch bytes are important. Notice that when GetWindowTextA
is started, if you do a : dd ss:esp (shows the stack), the pointer for the new string is the 2nd DWORD.
Right after the LEAVE instruction is executed (which restores the stack's original state, almost), ESP is larger
by 0Ch bytes, and therefor the RET is a RET 000C (which also dumps 12 bytes from the stack, plus the 4 when
it returns, which was the caller's address).
Knowing this, ESP-0C will contain the pointer to our new string, and doing: db *(esp-0c) will show us exactly
the string we're after. This is a nice and elaborate trick to break in just before the function returns. It's mainly
good when you know the target program is using some SMC (Self Modifying Code) and manipulates the
return address somehow. One way to do that is to use a JMP or a Push and then Ret, but having pushed
a different return address to the stack.
With a bit of inverstigative breaking, you can use tricks like these to do many innovative things when it comes
to the stack and local variables.
Speaking of local variables, we should really review functions prolog and epilog (jee I hope I spelled those
correctly hehe), as a side note.
What are these things you ask ?
Well, the function prolog is used to allocate space on the stack for local variables, and the epilog is used to
free up that space and restore everything back before the funtion returns.
The code that usually is executed when a funtion is called is similar to this:
; saves value of EBP
Mov EBP,ESP ; saves value of ESP into EBP
Sub ESP,XXX ; allocate space
What is done
here is first we save the contents of EBP, because we are going to be using
it, then we save
the contents of ESP in EBP, and then we substract from ESP. Since the stack is backwards growing,
the lower ESP is, means more stuff are on the stack, and they are accessed using positive offsets from ESP.
The use of EBP is to store the value of the stack pointer before the local variables are allocated on the stack,
and hence, all function arguments are accessed using positive offsets from EBP, and all function locals are
accessed using negative offsets from EBP.
The function epilog is where everything is set back to normal, and is usually done with the LEAVE instruction.
Seeing as I'm distracted from the main topic, I'll move on to more important things.
It's possible to use SI's powerful expression evaluation engine to create very ingenius commands and breakpoints.
Just because some program uses an API, it doesn't mean you can't set a BP that will get you closer to the
protection and/or the data manipulated by that protection.
One other thing to note about breakpoints is the sticky breakpoint. A sticky breakpoint is a BP that is armed
whenever SI is not tracing. That is, whenever you step over a CALL, or you just press F5 and continue execution.
This means that within SI, breakpoints are not armed, and if you trace over something, it will not alert you.
Other cases where BPs seem not to work are when you restart the symbol loader and reload the target.
Usually this should not happen, however, nothing in this world is perfect, and hence it's possible that SI thinks
the BP is active, but it will not work. Either that, or the program systematically seeks the Int3 opcode (CCh)
and replaces it with the origianl opcode for that function. After all, it would be really easy to write a program
that computes the procedure entry points in all of Window's DLLs and save the first byte, then use that when
looking for a CCh at the beginning of an API call.
I suspect some packers out there are doing it, although I have no confirmation of this.
So, the alternative is using debug registers. Debug registers are special registers inside the CPU that are used
for, well, you guessed it, DEBUGGING! :-)
SI can use debug registers to set all sorts of breakpoints. When I mean all sorts, I usually mean BPs for reading,
writing and execution. A debug register BP will never fail, unless the target program is specifically looking for
set debug registers and is using the SI backdoor to disable them or something.
To set a debug register BPX on something:
bpm getwindowtexta x
Will put a
byte execution BP on GetWindowTextA and
will use some DR SI uses arbitrarily. To find out which
DR is used, list the breakpoints (bl).
Well, other variaties of BPs do exist. I'm not just talking about conditional breakpoints and BPs with actions in
them. I'm also talking about the BPCOUNT/BPMISS types of BPs. These are good when you know the
function or memory address will be accessed several times, or missed several times.
To sum it all up, here are a few neat BP examples:
bpx cs:(ss:esp->0) puts a BP on the caller address, just when the function starts
if (eax->3=='SICE' || eax->3=='SIWV')
puts a BP on the VMM function
(used against MeltIce, taken from Frogs Print's Frogsice)
bpx sendmessagea if (esp->4 == LVM_INSERTITEM) do "db *((esp->0c)+0a)"
That last one
will break if an LVM_INSERTITEM message is sent to a ListView control window,
and will display
the item's name. How this works is that the lParam (offset 0Ch from ESP) is a pointer to a structure called
LV_ITEM, which has a pointer to a name in it (hopefully offset 0Ah from the beginning of the structure start hehe, just
like in the documents).
That specific message is used to (guess!) insert items into a ListView control.
You can use these types of BPs for all sorts of interesting things, like scrollbars, buttons, static controls (setting images
in static controls for example is done using STM_SETIMAGE message).
This stuff is again useful because SI recognizes many windows messaegs, which is a great thing for us. After all, the
messaging system is what makes Windows work (and do meaningful things that is...).
Well, how about
referring to BPs in expressions ? What do I mean ?
As it seems, SI knows how to use BP expressions and do things with them. The most obvious thing would be to
unassemble a code location that a BPX has been set on.
For example: bpx createfilea
Will put a BP on CreateFileA. In addition, anytime you enter this: u bp0 (if bp 0 is the CreateFileA BP),
SI will unassemble at the beginning of CreateFileA.
I really do suggest reading the SI manual and command refernce and try to get some more ideas out of them, since
they contain plenty of very useful information about SI's behaviour.
And with that, let's move on to some other SI commands and functionalities.
Now that I've
talked (typed really..) endlessly on how BPs can be made much more flexible
and poweful using things
like expressions and actions, it might be a good time to start talking about expressions. Everyone knows that SI
can evaluate values of all sorts, but there are a few things that some people might have missed.
In this section, I'll overview expressions and what they can do for you. Let's first start by listing some of the major
example: ebp->8 (gets DWORD pointed
to by ebp+8)
. example: eax.1c (gets DWORD pointed to by eax+1c)
* example: *eax (gets DWORD pointed to by eax)
@ example: @eax (gets DWORD pointed to by eax)
shift right) example:
bl >> 1 (shifts bl's contents
by 1 spot)
<< (bitwise shift left) example: bl << 2 (shifts bl's contents by 2 spots)
& (bitwise AND) example: ax & 0Fh
| (bitwise OR) example: ax | 0FFh (set's AL to FF)
^ (bitwise XOR) example: ax ^ ax (zeroes out AX)
~ (bitwise NOT) example: ~ax (reverses all bits of AX)
(NOTs EAX's values when testing condition)
&& (logical AND) example: eax && symbol (a symbol can be a variable in source code debugging)
|| (logical OR) example: ax || 0FFFFh (always evaluates to be TRUE..)
== (compare equality) example: eax == 0100h
!= (compare inequality) example: eax != 0
< (smaller than)
> (larger than)
<= (lower or equal to)
>= (greater or equal to)
(value will be the address of line 123 in source level debugging)
# (Protected Mode selector)
$ (Real Mode selector)
OK, let's not
dwell on those anymore. Notice that SI's operators are tightly related
to C/C++ programming
operators. This is only logical as it's a debugger that was designed to debug C/C++ programs both in Asm
mode and in native mode. So basically these operators give you exactly the full power of the C language when
building expressions, either for your BPs, macros (we'll get there) and actions.
The true power of these operators are that we can use them while cracking, just as easily as when we debug
in source level with full symbols. Always bare that in mind when going after a protection. You can use tricky
BP conditions to help you zero-in on the protection scheme.
What do you
know... Did you know SI has built-in functions for use with expressions
I bet some of you did :-)
Then again, some of you might not. In any case, I'll list them here just for reference.
get low-order byte
example: ? Byte(0x1234) = 0x34
Word get low-order word example: ? Word(0x12345678) = 0x5678
Dword get low-order dword example: ? Dword(0xFF) = 0x000000FF
HiByte get hi-order byte
HiWord get hi-order word
Sword convert byte to signed word example: ? Sword(0x80) = 0xFF80
Long convert byte/word to signed example: ? Long(0xFF) = 0xFFFFFFFF
WSTR display as unicode string example: ? WSTR(eax)
Flat convert selector relative address to linear (flat) address
CFL carry flag's value (boolean type of course)
PFL parity flag's value
AFL auxililary flag's value
ZFL zero flag's value
SFL sign flag's value
OFL overflow flag's value
RFL resume flag's value
TFL trap flag's value
DFL direction flag's value
IFL interrupt flag's value
NTFL Nested Task flag's value (not NT flag hehe)
IOPL IO priviledge level flag (? IOPL = returns current IO priv level)
VMFL Virtual Machine flag
IRQL Windows NT OS IRQ level (returns unsigned char)
DataAddr returns the address of the first item in the data window (dd @DataAddr)
CodeAddr returns the address of the first instruction in the code window
Eaddr effective address of current instruction (more on this later)
Evalue current value of effective address
Process KPEB (Kernel Process Environment Block) of the active OS process
Thread KTEB (yada yada yada....)
PID active process ID
TID active thread ID
BPcount number of times a BP has been triggered
BPtotal total times a BP has been evaluated
BPmiss total times a BP has been evaluated to FALSE
BPlog BP silent log (does not pop up SI)
BPindex currently triggered BP index
was some typing hehe..
Note about these functions. Some of them are of course not needed most of the times, but some are.
In fact, these functions can be used with any expression within SI.
The Eaddr and Evalue functions are used to return the effective address and effective value used by
the currently executed instruction.
For example, if EIP points to the following instruction: MOV ECX, [ESP+4]
Eaddr will return the value of ESP+4. Likewise, Evalue will return the address pointed to by ESP+4.
Note that these values are usually printed in the register window below the flags section.
Another note, hehe, this info was shamelessly copied from the manual :p
Well, SI has
the ability to cast types, just like C and C++ do.
So the C style type casting of : TypeName ( expression )
is a good way to convert between certain types.
A side note is that TypeName is case sensitive, so be alert.
SI can also do type casting on structure members (assuming you're debugging in source level):
TypeName ( expression )->member [->member [->member]]......
by the time you've finished reading up to this point, you are more familiar
powerful options SI has. As anything else, time and practice can make these options more easily
used by you to better help you crack.
Useful SoftIce commands
Well, we finally
reached the interesting part of this essay. In this nifty section I'll
give brief descriptions
of some of the commands SI has that apparently nobody is using.
These range from basic display options to some deep OS poking, so please, buckle up and prepare
thyself, because what you're about to see is sooooo exciting (yeah we've all heard THAT one before
Macro - Define or list macros
can be very handy in automating SI tasks. I bet you've heard this one before,
should really stress this one. Macros are wonderful tools in that they allow you to actually sort of
define new commands.
Syntax: macro [macro-name] | [*] | [= "macro body"]
The macro name
is a case insensitive string of 3-8 characters. The macro body should be
string that contain SI commands, seperated by semicolon/s (;).
For example, to define a macro that will show the stack and the parameters (I forgot where I found
macro params = "dd ss:esp"
By the way,
macro definitions can also be made using the symbol loader. To access the
definition screen, choose: Edit -> SI initialization.. and then move to macro definitions.
A macro body can contain literally any SI command (almost) and any defined macro to this
point. One other thing worth mentioning with macros is macro arguments.
It is possible to pass arguments to a macro, and a macro such as this is absolutely legal:
macro asm = "a %1"
Where %1 will
be expected to hold an address to assemble into. This of course is just
a very simple
example of arguments. Bare in mind though, that SI supports up to 8 arguments at most, but somehow
I doubt that will pose a problem.
To use the asm macro, just call it, like this: asm 00401234
And SI will start assembling at 00401234h. If you don't specify an argument, it will be ignored, if
the command using it permits (the A command without arguments will assemble at the current EIP).
Those special sign characters, such as % and ", can make your life a bit harder if you'd like to put them in
BPs with actions in them. For example, suppose you'd like to have a macro that will put a one time
breakpoint for execution in any address you specify. Remember that when you use the DO command,
you have to specify the action in quotes, so in order to put a quote character inside a macro defintion,
you'll have to escape it using the backslash character (\).
Now you must be wondering why on earth would you want to set a one time BP with a macro, when
you can easily do it with the G command (e.g: G 1234, will execute to address 1234, and G .123 will
execute to line .123 in source level debugging).
Well, the answer is, with a macro you can also define a DR type of BP. Consider the following macro:
macro oneshot = "bpm %1 x do \"bc bpindex\" "
will set a memory execution BP using a debug register, on the address you
you call the macro, and once it hits, it will issue: bc bpindex, which will clear the just-triggered BP.
Isn't it nice ??
Do you remember the command a little earlier that put a BP on GetWindowTextA's end, while also
displaying the newly copied string ?
Well, such a command is classic for use with a macro. Here is the example:
macro gwtend = "bpx beginpaint-3 do \"db *(esp-0c)\" "
Will set a
BP on the end of GetWindowTextA
and will perform the displaying of the text string at
the same time once the BP is hit.
I'm hoping by now it's starting to be clear to you what macros can do for you. Imagine defining
macros for many of the most used APIs and windows messages, that can give you valueable info
with a very simple and intuitive clicks on the keyboard.
Who knows, I might just start such a little project ;)
Ok, now to deleting and editing macros:
macro [macro-name] * - delete the macro who's name is <macro-name>
macro * - delete all macros (oops!! hehe)
macro <macro-name> - edit the macro who's name is <macro-name>
macro - list all macros from within SI (doh...)
that when working on a keygenning or otherwise some manipulations of values,
can define some macros that will do the manipulations for you just as easily.
A good example would be to define a macro that will, let's say decrypt some portion of data,
provided you know how it's being done. Another good example is to make a macro for rebuilding
PE imports, once you find out how to rebuild them (naturally...).
So, once again, the only limit to the usefulness of macros is your own imagination. Use SI's abilities
Watch - set watch on memory location or register
Ok, a note
of wisdom on watches. A watch is just a way for you to monitor some value
all the time.
For example, you might want to set a watch on the value of EAX in an unpacking code, so you'll
have good suspicion of when you find the OEP of the unpacked program.
The basic syntax of this command is as follows:
Where the expression
can be any expression really. It can have symbols, registers, memory locations,
and whatever you wish. Use the WW command to enable/disable the watch window.
By the way, the ALT-W key combination can be used to switch to or from the watch window, and
other key combinations exist as well (ALT-C to enter/leave the code window, etc...).
In the watch window, the following information is displayed for an active watch:
* The expression
* The expression type (if SI cannot determine, it uses DWORD)
* The value of the expression (the result)
that if an expression is out of scope SI will print an error message for
that watch in the
In order to delete a watch, simply move over it (either mouse or keys, and press delete). It's as simple
A good use of watches can be done when monitoring flags in a program. As every cracker knows, flags
are a very needed type of data in applications, because they dictate program flow or functionality.
Once you find a flag, you can set a watch on it, which will help you determine if some code changed
the flag or not (just a typical example..).
You might be wondering why would I want to use a watch when a memory BP can accomplish things
even better, namely it can pop SI at the right spot where a program is changing our said memory
Well, memory breakpoints use debug registers, and those that are allocated to us for use are DR0 to
DR3, which is just 4!
This is clearly not enough and we might need the debug registers for something important.
Notice though that watches only show the value of the expression you chose to watch, so if you set
a watch on a memory location, if that memory contents changes, SI will not pop like in a BP.
A watch helps you see expressions changing while inside SI, or in between SI popups.
The watch function is simple, yet might prove useful. Don't neglect it so quickly.
The DATA command
is quite well a very simple command. SoftIce supports up to four data windows,
just in case you were'nt aware of this.You can navigate between them using this command.
The general syntax is:
data [window num.]
command without specifying a window number just cycles to the next window.
since SI supports only 4 windows, the valid range of this command is 0 to 3.
I suggest (you can accept it, or not hehe) that you devote data window 3 for the stack. This can easily
be done with another SI command, so even the PARAMS macro we defined earlier might not be
needed. Soon I'll describe how to commit a data window to some specific data.
If anything this command gives us the ability to analyze multiple data views at once, which is a necessity
with today's programs. I remember how hard my life were before I found out this command exists.
As you can imagine, reading the SI manual and command reference paid off :-)
No, don't worry,
this command will NOT format your HD. Instead it is a way for you to change
format (doh) of the data being displayed in the current data window.
As you know (or not), it is possible to display data in a data window with byte alignment, word alignment
and dword alignment (and some others). With this command, you can change the format of the data
being displayed, without issuing the display command all over.
The syntax of the command is as follows:
cycles through all known types of formats. The order is Byte, Word, Dword,
Long real and 10-byte real, and once that is done, it's back to Byte.
You'd expect they would add an optional parameter to this command, but they didn't. Isn't it just annoying ?
Well, as it happens, we have macros at our disposal, and we can define macros that will change the
format for us (hopefully.. hehe)
Just keep this command in mind when you look at some memory data which you know should be a real
number (either a float or a double, which translates to short real and long real respectively) and you
don't quite figure out the value stored. After all, real numbers are represented entirely different than normal
integers in memory.
A weird sounding
name for a command, is it not ?
Well, the DEX command can be used to assign a data expression to a data window. Remember when I
said that it would be a good idea to commit data window 3 to the stack ?
The basic syntax of the DEX command is as follows:
dex [data window num. [expression] ]
associates an expression with a data window. The meaning of this is that
every time SI
pops up, the expressions are re-evaluated and the specific data window's display changes accordingly
to reflect the change.
To list all the expressions associated with data windows, just issue DEX with no parameters.
To delete an association, issue DEX with the data window number you wish to remove association with.
In our stack example, commiting data window 3 to the stack can be done like this:
dex 3 ss:esp
is taken straight from the SI manual, and is fairly a simple and obvious
use for this command.
In essence, the real usage of this command is to be able to display constantly changing memory locations
when you know a pointer to that location is always kept in some memory location or register.
This can help automate evaluation of conditions in a program, when data is constantly changing.
By now you
should already be familiar with this command. This is not a typical command
command is only used after defining a BP of some sort. It is used to perform actions while a BP is
evaluated to TRUE (i.e: the BP hits).
When you want to add a BP action, the format is similar to this:
...... do "bp action to be performed..."
The dots are
the BP expression, followed by the DO command, followed by a quoted string
which is the
BP action to be performed. Let me remind you that some characters need special escaping when
used inside quoted strings (such as % and ").
The use of BP actions was already covered in the first section, so let's move on.
The underscore operator (quasi-operator if you will)
There is a
special operator in SI that has a very special type of meaning. The underscore
is used to evaluate a certain register or expression and use it's current value inside a BP condition.
So for example:
Would be the
current value of EAX. This can be used in BP conditions to create some
Consider this BP:
bpx eip if (TID == _TID)
once in a thread you want to debug, and SI *should* break once the current
is that thread ID. This should give a way for you to always break into code of a specific thread, even
if you continued execution by exiting SI.
The reason all the "should"s are there is because I was never able to actually make this work.
Nonetheless, the SI manual specifically gives this type of BP as an example of the power of the
If any of the people reading this essay manages to duplicate the behaviour (make it work that is), please
contact me ASAP, if it's not too much trouble.
ADDR - Display or switch to address context
When I was
thinking about what commands to put in this section, I wasn't sure whether
to include this
command or not. In theory, this command sounds really cool. It's function is to let you navigate through
any process memory without too much trouble, but in reality, it's harder to use.
The syntax for this command is as follows:
addr [context-handle | process-name]
with no parameters will just list all available address contexts, starting
with the currently
active context. However, this on it's own is not very interesting most of the times, isn't it ?
The real magic is the ability to go to any running process address space and that way look at whatever
data you want at any time. Remember though that the address space for a process is kept and enforced
by the CPU while in protected mode, so in effect, there "should" be no trespassing.
In fact, this is not exactly true. The CPU does not know of processes at all, only page tables. The OS
is responsible for this (thank you goes to the Owl for pointing this to me).
We have come to learn though, that in Windows9x, trespassing occurs only too often. Whether this is
the fault of the globally viewable address space above 2Gb (8000000h and above), or the 16-bit
libraries which the CPU can't enforce protected mode rules for their code and data sections (tables
again), and general security (you can use an API to view any process' memory).
In any case, because SI is such an omnidevice, it can let us do pretty funky stuff, and this is no exception.
Moving on, let's list what is displayed when this command is issued without parameters:
* address of the private page table entry array (PGTPTR) of the context
* number of valid entries in the PGTPTR array
* starting and ending linear addresses represented by the context
* address of the mutex object used to control access to the context's page tables
* name of the process that owns the context
thing to note is when a context change occurs, windows copies the information
PGTPTR array into it's page directory (pointed to by the CR3 register, according to the manual).
This will affect the addressing of the lower 2 GB of the virtual memory space. That is, all addresse
ranging from 0 to 7FFFFFFFh are remapped according to the new context, and data of that process
is now viewable to you.
Out of the information that is displayed when the ADDR command is used to switch memory contexts,
the most important for us (at least normally) are the minAddr and maxAddr.
Those represent the minimum linear address of the address context and the maximum linear address of
the address context.
I suspect some experimentation is needed in order to fully grasp the use of this command, however,
it combines well with yet another SI command that is used to map the memory use of a process in it's
own context (hence it's a good thing to be able to switch contexts).
Qeury - Display the virtual address map of the process
So you've switched
into the address space of some poor process, now what ?
Well, here comes in the query command, which can give you information about the memory pages in
The syntax for the command is:
query [ [-x] address ] | [ process-type ]
Shows the mapping for a specific linear address within every context where
it is valid
address Linear address to query
process-type expression that can be interpreted as a process
parameters, query will display the virtual address map of the current process
(or for a
different process if you've switched contexts).
With parameters it is possible to get information about the mappings of a particular linear address,
although I don't see any real use for this right now off the top of my head.
In my opinion this command can be used while cracking to locate memory segments of all sorts.
For example, at some times when the PE header has been messed with it is possible to not know
where in memory some of the PE sections are loaded at. If you list the memory map for a process,
it's possible that you might stumble upon something suspicious. If you write down the virtual size
of the PE sections (the size they should take when loaded into memory), you might be able to locate
important process information this way.
You never know, this command might prove useful when dumping, so don't foget to give it a go when
you dump or unpack, it might help out in gaining a bigger picture look on what is going on.
here we've reached one of the potentially better commands SI has to offer.
is used to display the call stack of a task. What is a call stack you ask ? (notice the rhymes ?? heh)
Well, a call stack is a list of the recent calls made inside the program/vxd/whatever. The reason it's
called a call stack is not because the information is retrieved from the stack, but because the structure
of function calls is such that it looks like a LIFO (Last In First Out, just like a stack).
What this command does essentially, is traverse the stack, starting from the current stack frame
(which is SS:EBP), finds the current function's stack frame, and finds out the return address of the caller,
and when it does, it also goes to the caller and does the same, hence giving you something sort of a
list of the calls that lead you to the code location you are at now.
This wonder works in all modes, whether DOS, Win16 apps, Win32 apps, and Ring-0 code.
However, since Ring-0 code is often coded in assembly, it might not be very useful to try and extract
information about the call stack, because asm does not require a call frame for each function call.
Remember the function prolog and epilog we've discussed earlier ? Those are not needed by an asm
programmer most of the time, so don't expect this command to automagically give you a call stack
in weird places of the system.
Having said that, let's review the syntax for this command.
For Windows 3.1/9x:
stack [ task-name | SS:[E]BP ]
And for Windows NT:
stack [ thread type | stack frame ]
it should be intuitive to just use this command with no parameters once
you're in the context
of the process you wish to debug. But it's possible to override this behaviour with the parameters.
It's possible to give a different stack frame or task name, but these don't always work since the stack
frame might not be valid anymore (the last SS:[E]BP ).
This command will traverse the stack, starting from the currently known SS:EBP and will look for stack
frames along with the caller routines. As you guessed, this can also be done manually by yourself.
After all, the stack grows backwards, so displaying SS:EBP at any function call, will mean that negative
offsets from it are the local variables, and possitive offsets are what was pushed prior to the prolog,
which is generally the function arguments that are passed when calling it. Along with those you can also
find the return address of the caller routine (it has to be somewhere in there), and knowing in which
address range your code is, can help you determine which is the caller routine's return address.
You can keep doing this for basically as long as you want to, just bare in mind that it would be a good
idea to unassemble a certain calling funtion, so that you can recalculate where the next SS:EBP is supposed
to be. This is not a simple task, but it can be done if someone really decides he needs the information,
and the STACK command fails to give it.
In short, the purpose of this command in my opinion is not only to give you a chance to see the path of
the code, but to also give you the chance to put BPs in some of those intermediate functions where you
suspect interesting things are happening. This command is especially useful when working with a dead
listing, because looking at the code and then having the code flow can give great insight on what is going
on in a protection (i.e: who calls who and why/when).
Show/Trace/Xrset/Xt/Xp/Xg and the backtrace buffer
we've finally reached the part of the essay which you've all been waiting
This little section describes the SI back trace buffer and how to use it. Now, I'm sure some of you got
some ideas on how this buffer works, but actually, it takes some thinking and planning and some luck
to use it appropriately.
We all know the common problem. Sometimes we would like to know what was the program's flow of
execution before SI popped (due to a BP or whatever..).
The trace buffer, while able to give us this information, will only help us if we set everything up right. If we
don't, the trace buffer will not give us anything at all.
The reason this happens is very simple. SI has a limited sized buffer to use for storing logged instructions,
and because of that, it would be pointless to have a backtrace utility without specifying a certain range of
locations to monitor. What I'm trying to say basically is that SI can only monitor and log instructions that
are in a predefined range of addresses.
If any of the instructions in that range are executed, SI will log them into the backtrace buffer, and allow us
to enter trace simulation mode, which in turn allows us to see what was executed.
Once there are instructions in the buffer, we can either display them, trace them and basically do anything
we wish with them, while using the trace commands (the special trace simulation commands).
So in order to even start this, we need to learn how to set a range for monitoring. This can be done with
the following command:
bpr xxxxxxxx yyyyyyyy T [W]
sets a range breakpoint from address XXXXXXXX to address YYYYYYYY for tracing,
with the addition of being able to specify write access as well.
If you don't specify write access, all instructions in that range that are executed, are logged to the SI back
trace buffer. However, if you specify TW, then all instructions that will cause the BP to trigger will be
logged in the buffer. In the most general case we will only be dealing with normal trace range BP, so don't
worry about the W flag right now.
Now when deciding on the range of the breakpoint, bare in mind a couple of things. First, a certain amount
of intuition is needed to get good results. It's not just enough to do something like "put a range BP on the
entire code segment of the program", even if you do know it's entire size and location (from the PE header).
The reason is you want to monitor as small a part of memory as possible so that you can make something
out of what the trace buffer logs. Also, keep in mind that the bigger the range you specify, the slower your
system will become (and it won't matter how fast your CPU is, it's all relative).
This is the range BP's only downside really, especially so when you put them on places the system uses
many times (such as the stack).
that we have set the range for the trace buffer, we obviously need to somehow
the buffer itself. The first thing to remember is to clear the buffer before any new logging occurs. Why ?
Well, quite simple, if the buffer contains lots of older instructions, it will be very confusing to differentiate
between what you were looking for and the other data. So clear the buffer, and this can be done like this:
(although case INsensitive, like all other SI commands) will clear the
back trace buffer,
but only if you're currently NOT in trace simulation mode. This is important to remember. You can't
clear the buffer while tracing through the backtrace buffer. You have to exit trace simulation mode and
then use XRSET.
Once everything is cleared and the range is specified, you are ready to log instructions into the trace
buffer. To do that you will naturally have to exit SI (unless of course you want to trace forever yourself).
Once you do exit SI, you will not be able to review the buffer untill SI pops again, so it's better to
devise a way to make SI pop a short period of time after you know the code location in question has
been executed. For example, if you wish to know what code brought you to a certain message box,
it would only be a good idea to put an execution BP on MessageBoxA.
Basically I suggest you manage your breakpoints in such a way that the time period will be as short as
possible, meaning the time interval between when you leave SI to start the logging, and when you
pop back in, is as short as possible.
Once SI pops,
you can do 2 things really. You can either display the contents of the
trace buffer, or
start trace simulation mode to see the program flow. Both of these methods have their merit, and
I'll explain how both are done.
Before I do however, it's important to understand how the instructions in the buffer are addressed.
The SI trace buffer has a certain size of course, so it has a limited number of bytes for logging.
What is crucial to remember is that each and every instruction in the buffer has an index. The lower
the index (minimum is 1), the newer the command is, or in other words, the more recent it is.
From that we gather that the instruction with index 1 is the last executed instruction in the BP range
that we set.
The most important thing to remember is that you can either show or trace from a certain instruction
you specify, not just from the beginning of the buffer. This poses a problem. How can you tell how
many bytes in the buffer you want to start from ?
The solution is that you don't have to. Since each instruction is given an index (no matter how much
bytes it takes up in memory), you can refer to an instruction without having to calculate where it
begins. So to refer to the 10th recently executed instruction, you would give the index 10, which
of course is very different from the 10th byte in the buffer (very VERY different). Always remember
that when you use the buffer.
So in general, when you want to show the contents of the buffer, you issue this command:
show [ b | start ] [ L length]
will show instructions in the buffer starting from <start> (if specified),
and for a given
length (if specified). Again, the length argument is the number of instructions to show, not the length
in bytes of the unassembly. For the sake of convenience, the B character can be given as argument
instead of the <start> argument (which is hex by the way), and that will tell SI to show instructions
from the beginning of the trace buffer, which of course means it will first show the oldest instructions
in the buffer.
Something to remember is that when you do work with a range BP, when you pop into SI and take
a look into the buffer or trace through it, the next time you pop back in SI, those instructions will
NOT be removed from the trace buffer!
This of course means that other instructions will be logged and the old instructions will be pushed
to the beginning of the buffer, but they will not disappear as long as there is enough room left in
the buffer for them. As a custom, I would suggest using a macro that will clear the buffer when
exiting SI, something like this:
macro xclear = "xrset; x;"
You can even
map a function key for it if you wish (I use F5 almost out of instinct,
so I might not
remember to clear the buffer).
So far we've looked at how to display the contents of the trace buffer, which can be nice for dumping,
but let's not forget that tracing through it can give valueable information. So indeed, SI gives us the
capability to simulate the program flow of execution inside the buffer.
In order to start trace simulation, use the trace command, like this:
trace [ b | off | start ]
As you may
have guessed, B means from the beginning of the trace buffer (the oldest
START is a hex number specifying the index from which to begin (when the newest is 1), and OFF
means to exit trace simulation mode.
For example, to start trace simulation mode from the 50th instruction in the buffer, issue this:
trace through the last 50 instructions SI logged. Remember that if there
are calls or jumps,
those instructions will not be linear (one after the other in memory), but they will ALL be in the
range we set in our BP.
The TRACE command, when used without parameters, will show the state of the trace simulation,
either ON or OFF.
To trace through code in the back trace buffer, you use slightly different commands.
The commands are:
the same as T in normal tracing (that is, trace one instruction)
XP the same as P in normal stepping (that is, step over one instruction, function call or rep instruction)
XG the same as G in normal mode (go to specified address).
While in trace
simulation mode, the normal T/P/G/X/HERE and XRSET commands cannot be used,
obvious reasons (you use different tracing commands inside trace simulation and you cannot reset the
buffer while in trace simulation).
Finally, the last thing to remember about trace simulation mode is that the ONLY register that changes
is EIP. This is a bit saddening, because it means you will have no way of knowing how the registers have
changed while those instructions have executed. This is because SI cannot record the state of all registers
for each and every instruction, that would be pushing it too far. Keep in mind then that trace simulation
is only used to determine program flow, and not to re-evaluate certain register values and such.
Still, the trace buffer is a good tool once put to good use, and with a little intuition, it can serve you very
For an example of how it works, just set the range on some poor unsuspecting API in Windows.
for us. My next step would be to put a BPX at the end of the function,
so SI will pop (assuming I want to monitor execution of the instructions in that API).
So I'll issue:
And that will
highlight the last line of CreateFileA
, and now we come to the actual thing itself.
bpr createfilea setfileattributesa-5 t
And that will
set the range BP for us.
The next logical thing to do is to clear the trace buffer using:
SI. Click once on the start menu, and SI will pop.
When back in SI, notice the triggered BP is the BP we put at the last line of CreateFileA.
We then want to say, dump the contents of the back buffer, so issue:
will show the instructions performed by the CPU in that range. Notice that
it shows the
REPNZ SCASB instruction a lot of times. This happens because it is executed a lot of times, after
all it's in a loop!
It's as easy as that to just trace the back buffer too.
Instead of using SHOW, try using : trace b.
Once you do, SI will put you at the beginning of the buffer, which is (not surprisingly) the first instruction
in the CreateFileA API.
You can use XT to trace instructions, but I suggest using XP to step over the REPNZ SCASB, so you
don't get caught inside the loop.
Now you must be thinking (I'm hoping anyway) that there is something strange in all of this. How can
this API be so small ?
Well, it isn't, notice the nice JMP at the end, that will take you to an ordinal inside the kernel.
One more thing to notice, if there was a call inside the range, and the CPU would go into that call, SI
will NOT log the instructions that are executed inside the call because they are probably not in the range
that we specified. So specify your range wisely!!
I hope I somehow managed to explain to you how the SI backtrace buffer can be used, and hopefully
you'll figure out some good uses for it. It is a very powerful feature of the debugger, and with the proper
use (imaginative use) it can prove worthy.
I wasn't sure
if this command is used often or not, but I figured since I never used
it before (wasn't even
aware of it), that it is worth mentioning.
The zap command is used to replace embedded Int3 or Int1 instructions to NOPs, with the appropriate
number of bytes. I can't think of a better use for this command other than to zap (hehe) offending apps
that try to make funny use of the Int3 and Int1 instructions.
The syntax (albeit simple):
it basically :-)
I don't know how well you might use this command, but it's there if needed, and I think it can come in
handy on some occasions.
The WMSG command
is used to show messages that are known to SI. This might seem dumb at
but do remember that when in SI you don't have any way of looking into the Win32 reference, plus,
you can't match partial message names and see what pops up.
This command serves both of these purposes, and for the cracker, this is a good tool.
The WMSG command syntax is:
wmsg [ partial-name | message-number ]
name parameter can be the first few letters of a windows message name followed
asterisk if needed.
Will output the following lines:
not only the names of the messages are displayed, but also their constant
can be useful at times when placing breakpoints that involve messages.
purpose was to give a bit more insight into the wonderful capabilities
I hope that by explaining some of the more neglected commands and features, you might better
reverse programs you encounter with these somewhat new tools. I bet some would be new to some
people, but already known for others. Of course there are a few more commands that I haven't
discussed in this essay, mainly because they are almost never used. Things such as VXD, VCALL,
and some others are not your ordinary command and so I didn't want to include them needlessly.
If you do think it should have been included in the essay, you can of course mail me, or just go and
read the SI command reference, which can be DLed from some places on the net. I highly suggest
any cracker read the SI manual and command reference as they really really teach you the capabilities
An essay is fine and all that, but it can never replace the work of researching into your tools, which
usually means digging into some literature. Trust me, it pays off most of the times.
There are some really good SI commands which can be used while reversing, and some pretty awesome
techniques. All it takes is to invest some time into learning them.
May the force be with you! ;-)
And take special notice to the CLASS command (which shows window classes, which can in
turn lead you to find out where in memory lies a program's default window procedure (or some more
if it has some more classes registered...).
greetings go to the follwing:
ALL the Immortal Descendants members, the snake, whizkid, the +Sandman, +tsehp, CrackZ, Miz,
Jeff (wherver you are dude..), FatBoyJoe (an ID member but still..), Carpathia, +Fravia, +ORC (yes
even him hehe), The Hobgoblin, Lazarus, +Frog's print, Spath, DQ, _mammon, Ghirri (IceDump rox!),
ALL the visitors of the newbies forum and just about any other person I forgot (and I do apologize, it's
For comments, flames, bug reports (hehe), requests, or just plain annoy me:
email@example.com (I almost never use my hotmail anymore)
and of course, ICQ: #5178515
Have fun reversing!