by
Lord Soth
published by +Tsehp
All
rights reserved to the Immortal Descendants
Visit us
on the web at : http://www.ImmortalDescendants.org/
Abstract
Hi folks,
In this essay
I'll try and explain some of the most unused commands that SI, our debugger
recognizes.
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 ?
Of course,
the methodology changes considerably between protection schemes and crackers
themselves,
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:
bpx getwindowtexta
, 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)"
Holy mother!
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:
u createfilea
Simple, isn't
it ?
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.
Note: Everything
below 00400000h is set aside for the DOS VM, and anything above 7FFFFFFFh
is globaly
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:
Push
EBP
; 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
bpx VMM_GetDDBList
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.
SoftIce Expressions
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
operators:
Indirection operators
->
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)
Bitwise operators
>> (bitwise
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)
Logical operators
!
(logical NOT)
example: !eax
(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)
Special operators
.line number
example: .123
(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.
Built-in Functions
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.
Byte
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
Whew, that
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
Type casting
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]]......
Well hopefully
by the time you've finished reading up to this point, you are more familiar
with the
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
This command
can be very handy in automating SI tasks. I bet you've heard this one before,
but I
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
a quoted
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
this one...):
macro params = "dd ss:esp"
By the way,
macro definitions can also be made using the symbol loader. To access the
macro
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\" "
This macro
will set a memory execution BP using a debug register, on the address you
specify when
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...)
Just imagine
that when working on a keygenning or otherwise some manipulations of values,
you
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
wisely!
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:
watch <expression>
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
being evaluated
* The expression
type (if SI cannot determine, it uses DWORD)
* The value
of the expression (the result)
Note however
that if an expression is out of scope SI will print an error message for
that watch in the
watch window.
In order to
delete a watch, simply move over it (either mouse or keys, and press delete).
It's as simple
as that!
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
location.
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.
Data
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.]
Issuing this
command without specifying a window number just cycles to the next window.
Of course
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
:-)
Format
No, don't worry,
this command will NOT format your HD. Instead it is a way for you to change
the
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:
format
Which just
cycles through all known types of formats. The order is Byte, Word, Dword,
Short real,
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.
DEX
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] ]
This command
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
This example
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.
DO
By now you
should already be familiar with this command. This is not a typical command
because this
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
(_) operator
is used to
evaluate a certain register or expression and use it's current value inside
a BP condition.
So for example:
_eax
Would be the
current value of EAX. This can be used in BP conditions to create some
weird BPs.
Consider this
BP:
bpx eip if (TID == _TID)
Issue this
once in a thread you want to debug, and SI *should* break once the current
active thread
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
underscore
operator.
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]
Issuing ADDR
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
context handle
* 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
An interesting
thing to note is when a context change occurs, windows copies the information
from the
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
that context.
The syntax
for the command is:
query [ [-x] address ] | [ process-type ]
-x
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
Without any
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.
Stack
Oh goodie,
here we've reached one of the potentially better commands SI has to offer.
This command
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 ]
In reality,
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
Well folks,
we've finally reached the part of the essay which you've all been waiting
for.
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]
This command
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).
Alright, now
that we have set the range for the trace buffer, we obviously need to somehow
manipulate
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:
XRSET
This command
(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]
This command
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
instruction),
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 50
And you'll
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:
XT
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,
for
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
well.
For an example
of how it works, just set the range on some poor unsuspecting API in Windows.
For example:
u createfilea
Will unassemble
CreateFileA
for us. My next step would be to put a BPX at the end of the function,
just
so SI will
pop (assuming I want to monitor execution of the instructions in that API).
So I'll issue:
bpx setfileattributesa-5
And that will
highlight the last line of CreateFileA
, and now we come to the actual thing itself.
Issue this:
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:
xrset
Then exiting
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:
show b
The output
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.
Zap
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):
zap
And that's
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.
WMSG
The WMSG command
is used to show messages that are known to SI. This might seem dumb at
first,
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 ]
The partial
name parameter can be the first few letters of a windows message name followed
by an
asterisk if
needed.
For example:
WMSG wm_get*
Will output the following lines:
000D WM_GETTEXT
000E WM_GETTEXTLENGTH
0024 WM_GETMINMAXINFO
0031 WM_GETFONT
0087 WM_GETDLGCODE
Notice that
not only the names of the messages are displayed, but also their constant
values, which
can be useful
at times when placing breakpoints that involve messages.
Summary
This essay's
purpose was to give a bit more insight into the wonderful capabilities
of SoftIce.
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
of SI.
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...
Okie Dokie,
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
quite late..)
For comments, flames, bug reports (hehe), requests, or just plain annoy me:
LordSoth@ImmortalDescendants.org
lordsoth_8@excite.com
(I almost never use my hotmail anymore)
and of course, ICQ: #5178515
Have fun reversing!
Lord Soth