Reversing.generals
by Ignatz / stoicForce for
newbies/intermediate crackers.
Prelude
Greetings! After quite some work, i can now present my personal
experience regarding successful reversing. where reversing really means
reversing and not cracking. cracking, of course also makes use of
reversing, but during my work with Opera 3.62, i realized that cracking is
just a small part in the world of reversing. read this, even if you just
want to crack, bad boy. it will sureley help you.
Waypoints into the light
or just TOC if you prefer
Conventions
As you start to reverse, you will soon see, that it is essential to
stick to some general conventions. like the way you name a vaiable or you
comment a loop. this should always look the same, due to sticking to a
convention. never delete information, always add information. this is
the most important rule for me, it is. this means, for example, never
overwrite an address but add the name to the address. mov [00534160], eax
mov [00534160_SerialFlag], eax ; like this you wonīt lose vital information
i now want to present some more thoughts on this topic.
Commenting The Code
It is very important that you use clear definitions and donīt mind if
you write some extra words. youīll love yourself for that afterwards, and
youīll hate yourself if you can not follow your steps just because you
didnīt comment well enough. It will also make some code parts clearer for
you, because you have to think about what this piece of code does if you
want to comment it seriously. as a general rule: comment the code as if
someone else, who never reversed before, had to read, sorry my bad,
-understand- it. commenting is about understanding the code not reading
the code.
Translate The Code
This is the most important part if you really want to reverse the code
- changing the asm commands into pseuo-high level language source. if you
do a good job here, it will be peanuts to generate the reversed code in
your prefered language. itīs important to realize the difference in
translating the asm-source and commenting it. if you just comment the
code, the outcoming sourcecode is not defined at all. for example, there
are at least 3 differet ways to realize a loop. but if you read trough the
asm-code you will see if itīs a while, a repeat or a for loop. letīs look
at an example:
xor exc, exc
:00440000
inc ecx ; comment: counter of loop is increased
.
.
cmp ecx, 05 ; comment: counter is compared
jbe 00440000 ; repeat this while the counter of
; loop is 4 or less continue loop
-----
; reversed 1
while (i < 5) {
still to come
}
-----
;reversed 2
for (i=0; i<5; i++) {
still to come
}
-----
;revesed 3
i = 0
repeat
i++;
still to come
until (i >= 5 )
the comments have nothing to do with the code you finally find
suitable. they just make you and others understand whatīs going on. The
reversed style does not make anyone understand the code better, unless he
knows how to reverse C code better then reversing asm for example. it is
just another form of the same thing. like translating form latin to
english. in this case it is not absolutely clear what kind of loop it is.
it looks like a repeat until, but itīs not really important either, unless
you want to make an exact copy of the original source. If you
understood the difference between commenting and translating, step to the
next section.
Functions
A program consists of many different functions. thus, it would be wise
to reverse one function after the other. this allows us to stay focused on
one part of the code. another benefit is, that the structure of the code
will nearly reveal by itself. if you have reversed a couple of functios,
you will see a structure coming out of the dark very quickly. this
structure will contain the returnvalues, parameters, stackadjustments,
other commands and further more. Letīs see what we need to describe a
function. 1. Function Name 2. Parameters 3. Return Value 4.
Purpose Of The Function finding a name for a function is the most
important part. it has to be a name that describes the purpose very well
and is easy to understand. this will make it easier for you to interpret
the code if you find the function again somewhere else. the parameters
should be described by name and type, sometimes include a small
description, too. this will become easier when you revealed the calling
convention. ad 3: you will get the returnvale of a function in eax. this
means a function can not return a string or object. it can only return a
pointer to the string or object. an other possibility is that a function
manipulates/overwrites one of the input parameters, thus making this an
output parameter, too. so you have to take a good look at how the
returnvalue is used by the program and how it uses the parameters. at
last, when youīre done, write a short description of the
function. maybe you thought about a small problem. functoins call other
functions and they call functions and so on. so where would you start ?
this is up to you. it depends on you at what depth you start reversing, if
you use a bottom-up, or top-down strategie (start at the depth of a
messagebox call for example). we will now take a closer look at the
methods.
Calling Conventions
One of the most important things to find out is, how the program passes
parameters to functions and how the program handles the stack adjustments.
there are two major conventions. the C and the pascal convention. if you
donīt know how the stack is used during calls, then refer to my tutorial
"Our friend stack and his cousin call". i will only repeat the basics.
parameters are passed through the stack. this means that the stack has to
be restored after the function is completed. if itīs not restored, the
caller will have a corrupt stack, with which it canīt work with. So the
calling convention consists of two parts: 1. Put values on the stack
2. Restore stack Let us now have a look at the two most common
conventions. you will see all neccessary things when learing about
them. ) The C calling convention (also used by the windows API) in
this case, the parameters are pushed on the stack in reversed order and
the caller has to restore the stack. example: procedure test1(Par1, Par2, Par3: integer);
asm:
push par3 ; push in reversed order
push par2
push par1
call test1 ; call function
add esp 0C ; restore stack value
) The Pascal calling convention in this case, the parameters are
pushed in the same order as they appear in the declaration of the function
or procedure. but now, the functoin has to do the stack adjustments. procedure test1(Par1, Par2, Par3: integer);
asm:
push par1 ; push in same order
push par2
push par3
call test1 ; call function
... ; no add esp,0C
; this was already done within the test1 call
Local Variables - Parameters
When you identified how the parameters are passed to the function, you
can easily use this knowledge to name the parameters in the function. two
lines of code can be found at the beginning of nearly every function. (1.2.I)
push ebp ; save the original base pointer
mov ebp, esp ; set basepointer to the stack (parameters!)
this is a very efficient way to access the parameters without having to
worry about the stack anymore. every parameter can now be accessed
realtive to ebp. examples for c convention: par1 = ebp+8, par2 =
ebp+0C and so on. donīt forget, that the return address and ebp are
also put on the stack when the function is called. this is why the first
parameter is not at ebp but ebp+8. you see how easy it is to work with the
parameters now. no more annoying push or pop instruction necessary.
The next line will often stand right under the two lines above. (1.2.II)
sub esp, 00000018 ; make room on stack
; for local variables
even here, the stack is reserved, but not used with the push and pop
instructions. this part of the memory is directly accessed relative to ebp
just as the parameters are. the only difference is, that itīs not ebp+8
but ebp-8 for example. note that ebp-8 is the third local variable
(assuming all variables are WORD values). now you can imagine, where a
stackoverflow comes from. what we get out of this finding is the
following : (assuming that 1.2.I is used in the call): *every* ebp + X
is a parameter passed to the call (assuming that 1.2.II is used in the
call): *every* ebp - X is a local variable knowing this, it is not so
hard anymore to identify and then name the parameters as well as the local
variables of a call.
Return Value
If the call returns a value, it is always returned in eax or in one of
the input parameters. you will often reckon if it is an input parameter,
by checking if an address or a value is provided to the call. if itīs a
value it is definitly not a parameter you need to care about afterwards.
if it is an address, to a string for example, then you should take a look
at the next lines of code to figure out wether it is used again or not. in
most cases you will see that itīs used again. (thatīs more efficient, than
wasting memory) i will now show you what a pascal programmer has in front
of his eyes when he creates a function. this should make the concept even
clearer. here is a normal declaration of a function:
function MyAdd(x, y :integer) : integer; // declaration
var erg: integer; // local var
begin
erg := x + y; // do the things
add := erg; // return value
end;
This is a normal function. it just adds two integers. there are two
parameters and a local variable that is used to temporarily store the
result, which is then returned by the function. the assembler has to care,
that the two parameters are removed from the stack after the function is
done and it has to reserve enough memoryspace for the local
variable...might look like this: ; *Pascal*
push ebx ; par1
push ecx ; par2
call MyAdd ; call the function
; in this example, the function restores the stack (pascal)
mov ..., eax ; returnvalue in eax
; *C*
push ecx ; par2
push ebx ; par1
call MyAdd ; call the function
add esp, 8 ; restore the stack
mov ..., eax ; returnvalue in eax
;-------------------------------
; *MyAdd*
;-------------------------------
push ebp ; save baspointer
mov ebp, esp ; get a grip on the parameters
sub esp, 4 ; make room for the local var
; adding
mov ebx, [ebp+0C] ; get first parameter
mov ecx, [ebp+8] ; get second parameter
add ebx, ecx ; add
mov [ebp-4], ebx ; move result to local var
mov eax, [ebp-4] ; return local var
; returnvalue is eax
leave ; this instruction does the folowing:
; mov esp, ebp
; pop ebp
add esp, 8 ; ONLY for pascal!
ret
here you should see the differences between pascal and c convention. it
is also obvious how the assembler works with parameters and local
variables. i will now show you the same program with
variable-parameters. to notice the differences better, i made the whole
example in pascal. now we declare a procedure, not a function, because it
has no explicit returnvalue. procedure MyAdd2(var res:integer; x, y :integer) // declaration
begin
res := x + y; // do the things
end; // no explicit returnvalue
the point is, that the parameter res will be overwritten by the
procedure with the result. this means, you have to give a variable
parameter to the procedure which can be overwritten; it cannot be a
constant or a number. let us see what this would look like in assembler:
;* i will only do the pascal style since you already
;* saw everything important regarding the
;* c - pascal differences in the last example
lea ebx, res ; because itīs a var you have to pass the address!
push ebx ; put address as 1st param on stack
push edx ; edx contains x
push ecx ; ecx contains y
call MyAdd2 ; call the function
mov ..., res ; since res was overwritten by MyAdd2
; it now has the result
;-------------------------------
; *MyAdd2*
;-------------------------------
push ebp ; save baspointer
mov ebp, esp ; get a grip on the parameters
; no local variables
; adding
mov eax, [ebp+10] ; get first parameter
mov ecx, [ebp+0C] ; get second parameter
add eax, ecx ; add
mov ebx, [ebp+08] ; get the address into ebx
mov [ebx], eax ; move result to variable-parameter (erg)
leave
add esp, 0C ; adjust stack
ret
in this example, you can see the difference between an explicit
returnvalue and a variable-parameter. i hope you got the point, so we can
continue to the next section.
Loops
Identifying loops can be a difficult job. especially if the are big and
encapsulated. this makes it even harder. but through my years of reversing
i figured that a jump backwards is very often a jump done by a loop. for a
normal "if then else" statement, you would skip code, by jumping forward,
but with loops you have to return in the code which means you have to jump
back. that should be clear now. this means, after all itīs not so hard to
identify loops. when you identified a loop you have to reverse it of
course. let us now have a closer look at the loopīs main parts.
Counter
if you already made some contact with asm-programming you will have
noticed, the loops normally use the ecx register as the loop counter. in
case that ecx is not the counter, just look at the jump-statements of the
loop. there must be a condition checked before (like test eax, eax) the
jump. use your brain to figure that out. brains is the best tool ever
developed.
Exiting Condition
when you identified the counter you also see where the counter is
checked. then all you have to do is look at the statement before the
loop-jump and voila.
Type Of Loop
This is not as important as you might think. you can transfer every
type of loop into another one. the only really important thing to notice
is, if the loop is done at least once or if not. As an example a while
loop might not be entered due to the fact, that the condition at the
beginning is not met. a repeat until loop will always be executed for once
at least, because there is no check at the beginning. Loops are not so
hard to figure out. all you have to do is think a bit.
Control Strcuctures
These are especially important for crackers. This is obvious. The
program checks if you are regged or not. this makes heavy use of control
structures. thus understanding these is essential to every serious
reverser. generally they can be divided into if-then-elseif-else and case
statements. for control structures it is not so important to identify them
and reckon that there is something, but it is essential to understand the
changes this statement means to the programexecution. only with that
knowlege you can interpret and judge a control statement correctly. i will
only point out some examples here. i think this shows best what i mean.
If Then Else
This is a piece of code taken out of Opera 3.62. The purpose of the
code is to set the displayed program name in the program bar to either
"Opera 3.62 (Unregistered version)" for unregistered users, or to "Opera
3.62" for regged users. it got insane reversing this one. i believed that
there was a statement like this: if (regged) progname = "Opera 3.62"
else progname = "Opera 3.62 (Unregistered version)"
but what it does is the following: cmp byte ptr [PrgDisplayName_00531640], bl
; do the following only if PrgDisplayName=""
jne 0045FE49
mov esi, 00000080
; esi = 80 (maxCount)
push esi
; maxCount (maximum chars to append)
push edi
; append at edi = PrgDisplayName
String Resource ID=20092: "Opera 3.62"
|
push 00004E7C
; str to append
mov ecx, ebp
call 00460BD8 - AppendStr(ToApp,EndOfOrig,Count)
push edi
call 00501E10 - int strLen(str S)
; calculates lenght of the prgDisplayName string
pop ecx
sub esi, eax
; calcuate maxCount
mov dword ptr [005315F4], eax
push esi
; maxCount
lea eax, dword ptr [eax+PrgDisplayName_00531640]
; eax = end of DisplayName
push eax
; EndOfOrig
String Resource ID=21428:" (Unregistered version)"
|
push 000053B4
; str ToAppend
mov ecx, ebp
call 00460BD8 - AppendStr(ToApp,EndOfOrig,Count)
* Referenced by (C)onditional Jump at Addresses:
|:0045FDD3(C), :0045FE12(C)
|
cmp dword ptr [ebp+flg_Regged_000004EC], ebx;=0
; check regFlag
mov eax, dword ptr [005315F4]
; length of Opera 3.62 string
je 0045FE5E
; if regFlag = 0 then set end of PrgDisplayName
; right behind "3.62", this means
; discrad the "(Unregistered version)" part
mov byte ptr [eax+PrgDisplayName_00531640], bl
; mov 00 after the Opera 3.62!
; "thats why the first chracter of manually
; entered Names vanished because the lenght
; function was 0"
jmp 0045FE65
As you can see, the program goes a different way. it does not differ
between regged and unregged the way i thought. it always puts the not
regged part in place. this really confused me until i saw how this really
works. the program overwrites the string a third time if you are regged.
then it sets the end of the string zero, so that the not regged part is
ignored. look at it in pseudo code. s = "Opera 3.62\0";
a = " (Unregistered version)\0";
append_this_to_at(a, s, endOf(s));
if regged then
s[10]="\0";
end;
Case
This also is some code from Opera 3.62. It decides which help page to
show in the browser. I came across this code by accident, but it is a very
useful example for a switch/case statement. Before you look at it i want
to point out, that there are two different ways in writing a switch
statement. one is to write a series of jump sequences. this would equal a
"if then elsif elsif elsif...end" and there is the type Opera 3.62 uses
here. it is faster than the previously mentioned method. here, the program
calculates a jumpmark [4*eax+004932A3] to the corresponding code, instead
of going through every line. this only works, because the code has the
same length for every branch taken - 4 bytes. normally this is not the
case, so you have to deal with a series of "cmp jne" statements which
refer to the "if then elsif elsif elsif...end". lets look at the code now.
:00493070 mov eax, dword ptr [ebp+10]; eax = Par1
:00493073 add eax, FFFFB1DD
; Par1 = Par1 - 20003d "look at stringrefs"
:00493078 cmp eax, 0000008A
; if eax smaller than 138d jump toindex.html
:0049307D ja 0049305E
:0049307F movzx eax, byte ptr [eax+00493357]
:00493086 jmp dword ptr [4*eax+004932A3]
; switch statement, calculate jumpmark
* Data Obj ->"keys.htm"
|
:0049308D push 0051DD58 ; a jumpmark
:00493092 jmp 00493063
* Data Obj ->"prefmenu.htm#print"
|
:00493094 push 005256B0 ; another jumpmark
:00493099 jmp 00493063
* Data Obj ->"dialogs.htm#direct"
|
:0049309B push 0052569C ; ...
:004930A0 jmp 00493063
* Data Obj ->"prefmenu.htm#sethome"
|
:004930A2 push 00525684
:004930A7 jmp 00493063
* Data Obj ->"dialogs.htm#fileuplf"
|
:004930A9 push 0052566C
:004930AE jmp 00493063
* Data Obj ->"dialogs.htm#hotlist"
|
:004930B0 push 00525658
:004930B5 jmp 00493063
* Data Obj ->"dialogs.htm#locked"
|
:004930B7 push 00525644
:004930BC jmp 00493063
; and so on ...
Global Variables
In most programs there are variables and constants that have to be
accessible everytime. these cannot be local variables, because these are
discarded after the owning function terminates. Thus they are only
accessible by the funciton itself. the variables that can be accessed all
the time are called global variables (in contrast to local). as a reverser
you should keep an eye out on those, because they contain flags like
registrationflags, demoflags, trialdate, ... and other data like
programname, parameters, ... . identifying global variables can be a hard
job. i figured two ways Opera accessed its variables. one way was direct
addressing. this means, if a variable is at :00543380 the program accesses
it by this number. direct mode: mov eax, dword prt [00543380]
other than that, it might use relative addressing mode. you can see an
example of this in the next section. with this mode, the program accesses
the variable relative to a baseaddress. this base is stored in a register.
if you see something like this itīs a bit harder to identify the global
variable. still it is possible. you just have to search for the offset
value. donīt get confused everything will be clear in a second. just look
at the example. mov eax, [esi + 4EC]
| |
base offset
all you have to do now is search for the offset value. if you can find
it serveral times in the same context, then you found a global variable.
like i found the regflag here: ; first appearence
:004CB0AF mov eax, dword ptr [esi+flg_Regged_000004EC]
:004CB0B5 cmp eax, ebx
:004CB0B7 lea edi, dword ptr [esi+flg_Regged_000004EC]
; second appearence
:004D9543 mov dword ptr [esi+flg_Regged_000004EC], eax ; check that
:004D9549 pop ebx
:004D954A je 004D9556
; third appearence
:004D963C mov eax, dword ptr [ecx+flg_Regged_000004EC] ; return with regFlag
:004D9642 ret
; there are still many more occurences of this addressing but i think you got the point
Intialization
These variables have to be initialized. you can see how ths looks in
this example. when you see a part of code that looks similar, you know
what you got. * Referenced by a (U)nconditional or (C)onditional Jump at Address: ; /*INIT SECTION */
|:0045BD04(U)
|
:0045BD12 mov dword ptr [esi+regName_00000138], ebx
:0045BD18 mov dword ptr [esi+0000013C], ebx ; the variables are addressed via esi+X
:0045BD1E mov dword ptr [esi+00000144], ebx ; this means relative addressing
:0045BD24 mov dword ptr [esi+00000148], ebx
:0045BD2A mov dword ptr [esi+0000014C], ebx
:0045BD30 mov dword ptr [esi+00000150], ebx
:0045BD36 mov dword ptr [esi+00000154], ebx
:0045BD3C mov dword ptr [esi+00000158], ebx
:0045BD42 mov dword ptr [esi+0000015C], ebx
:0045BD48 mov dword ptr [esi+00000160], ebx
:0045BD4E mov dword ptr [esi+00000164], ebx
:0045BD54 mov dword ptr [esi+0000038C], ebx
:0045BD5A mov dword ptr [esi+00000184], ebx
:0045BD60 mov dword ptr [esi+00000188], ebx
:0045BD66 mov word ptr [esi+00000212], 0008
:0045BD6F mov dword ptr [esi+00000218], ebx
:0045BD75 mov dword ptr [esi+0000021C], ebx
:0045BD7B mov dword ptr [esi+00000224], ebx
:0045BD81 mov dword ptr [esi+00000220], ebx
~something deleted~
:0045BE9F mov dword ptr [esi+00000358], ebx
:0045BEA5 mov dword ptr [esi+0000036C], ebx
:0045BEAB mov dword ptr [esi+00000380], edi
:0045BEB1 mov dword ptr [esi+0000037C], edi
:0045BEB7 mov dword ptr [esi+00000378], edi
:0045BEBD mov dword ptr [esi+00000374], edi
:0045BEC3 mov dword ptr [esi+00000370], edi
:0045BEC9 mov dword ptr [esi+00000384], edi
:0045BECF mov dword ptr [esi+00000388], edi
:0045BED5 mov dword ptr [esi+000003C4], edi
:0045BEDB mov word ptr [esi+000003B4], bx
:0045BEE2 mov dword ptr [esi+000003EC], ebx
:0045BEE8 mov dword ptr [esi+000003F4], ebx
:0045BEEE mov dword ptr [esi+000003F8], ebx
:0045BEF4 mov dword ptr [esi+000003F0], edi
:0045BEFA mov word ptr [esi+000004D8], bx
:0045BF01 mov dword ptr [esi+000003DC], ebx
:0045BF07 mov dword ptr [esi+000002A8], ebx
:0045BF0D mov dword ptr [esi+000002AC], ebx
:0045BF13 mov dword ptr [esi+00000250], ebx
:0045BF19 mov dword ptr [esi+00000254], ebx
:0045BF1F mov dword ptr [esi+00000258], ebx
:0045BF25 mov dword ptr [esi+0000025C], ebx
:0045BF2B push 00000720
:0045BF30 mov dword ptr [esi+flg_Regged_000004EC], ebx ; INIT
here is not much to say. you can recognize very easily how each
variable is set. most of them are flags but some of them are addresses to
strings or other data. which is which - this to find out is your work.
Generating High Level Source
The last step in reverse engeneering is to recreate the high level
sourcecode. how to do this you might ask. there are two general
approaches. one is to strictly follow the target program and also follow
its instructions, sometimes even without exactly knowing what they do, to
find out later when reading the recreated source. -or- understanding the
code and writing your own™ source, without having reversed everything
since you were able to guess what the code does. the first approach
focuses on reconstructing the original source files, thus you have to
write it in the same language as it was originally written. the second
approach only wants to create source that does the same as the original
(g.e. it doesnīt matter if you show a nag with a dialogbox or a messagebox
the user will know he failed to reg anyway but the reversed program is not
exacly the same), hence it can be written in any language. the drawbacks
are of course, that you will never know if your program does the same
since you only look for functionality - a lot of testing has to be done in
this case. but it is much faster than looking trough every line of asm
source. the plus on the first method is that you can sometimes continue
without understanding the meaning of the code. it might get clear later
on. as always it depends on you which approach (also mix em up) you
want. enough theory let us see an example now. :004BCF49 push ebp ; save base pointer
:004BCF4A mov ebp, esp ; set basepointer for the function
:004BCF4C sub esp, 00000548 ; make room for stack
:004BCF52 push ebx ; save ebx
:004BCF53 mov ebx, ecx ; ebx = ecx
:004BCF55 cmp dword ptr [ebx+00000714], 00000000 ; if [ebx+714] == 0
:004BCF5C je 004BCF66 ; contine with function
:004BCF5E push 00000001 ; eax = 1
:004BCF60 pop eax ; .
:004BCF61 jmp 004BCFEF ; leave with with eax = 1
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BCF5C(C) /* continue with ?WriteRegFile(?) */
|
:004BCF66 push esi ; push source
:004BCF67 push edi ; push destination
:004BCF68 mov ecx, ebx ; ecx = ebx
:004BCF6A call 004BC81A
:004BCF6F mov ecx, 0000012F ; ecx = 0x12F (303d); 303*4 = 1212 (0x4BC)
:004BCF74 mov esi, ebx ; sourceaddress of RegInfo
:004BCF76 lea edi, dword ptr [ebp+FFFFFAB8] ; destination address of RegInfo
:004BCF7C lea eax, dword ptr [ebp+FFFFFAB8] ; .
:004BCF82 repz ; while not finshed
:004BCF83 movsd ; move the RegInfo
:004BCF84 push eax ; pointer to RegFileBuffer
:004BCF85 mov ecx, ebx ;
:004BCF87 call 004BCF14 - Decrypt(chr *ToDecrypt) ; encrypt the RegInfo
* Reference To: KERNEL32.SetFileAttributesA, Ord:0268h
|
:004BCF8C mov esi, dword ptr [005121A4] ; put address of SetFileAttributes into esi
:004BCF92 push 00000080 ; attributes to set
:004BCF97 lea edi, dword ptr [ebx+CRegFileSize_000004BC]; edi = addr of filename
:004BCF9D push edi ; address of filename
:004BCF9E call esi ; Make File writable
:004BCFA0 push 00001010 ; fuMode (action and attribs)
:004BCFA5 lea eax, dword ptr [ebp+CRegInfBuf_FFFFFF74]; eax = address of buffer
:004BCFAB push eax ; address of buffer
:004BCFAC push edi ; addres of Filename
* Reference To: KERNEL32.OpenFile, Ord:01E8h
|
:004BCFAD Call dword ptr [00512228]
:004BCFB3 mov ebx, eax ; ebx = hfile OpenFile(fName,[opts]) (FileHandle)
:004BCFB5 cmp ebx, FFFFFFFF ; if open succeeds
:004BCFB8 jne 004BCFBE ; then continue with ebx = eax
:004BCFBA xor ebx, ebx ; else handle = 0
:004BCFBC jmp 004BCFE6 ; skip writing part.
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BCFB8(C)
|
:004BCFBE push CRegFileSize_000004BC ; number of Bytes to write
:004BCFC3 lea eax, dword ptr [ebp+FFFFFAB8] ; eax = address of Encrypted RegInfo
:004BCFC9 push eax ; Pointer to buffer holding encrypted reginfo
:004BCFCA push ebx ; filehandle
* Reference To: KERNEL32._lwrite, Ord:02F7h ; is used to write Regfile !
|
:004BCFCB Call dword ptr [005121CC]
:004BCFD1 xor ecx, ecx ; set ecx = 0 because of setne cl later
:004BCFD3 cmp eax, FFFFFFFF ; check for errors
:004BCFD6 setne cl ; set if an error occoured
:004BCFD9 push ebx
:004BCFDA mov dword ptr [ebp-04], ecx ; save result _lwrite
* Reference To: KERNEL32._lclose, Ord:02F2h
|
:004BCFDD Call dword ptr [005121C8] ; close file
:004BCFE3 mov ebx, dword ptr [ebp-04] ; ebx = result of _lwrite
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BCFBC(U)
|
:004BCFE6 push 00000021 ; make file WriteProtected
:004BCFE8 push edi ; pointer to filename
:004BCFE9 call esi ; SetFileAttributes
:004BCFEB pop edi ; resotre values
:004BCFEC mov eax, ebx
:004BCFEE pop esi
* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:004BCF61(U)
|
:004BCFEF pop ebx
:004BCFF0 leave
:004BCFF1 ret ; bye
After reading the notes it is clear what the program does. I will not
attempt to generate C++ source that does the same. might look like this
#define CRegFileSize 1212
// global Var gRegFileName
/* Name: WriteRegInfoToFile
* Purpose: Writes the encrypted Reginfo into the file OUser350.dat
* and sets its fileattributes to writeonly
* Returnvale: Either 1 if function fails, or it returns the result of
* the _lwrite function which is temporarily stored in res.
* Remarks: Have to find out about the first flag and the first
* function. Very strait forward implementation.
*/
int WriteRegIfnoToFile(void) {
// variables
char *CryptRegInfo, // Holds the encrypted Registrationinformation
*RegInfo, // Holds the original Registrationinformation
*RegFileBuffer ; // Filebuffer for the RegFile
handle hFile; // Handle to Regfile
int res; // result of function
if !unknownflag_[ebx+714] {
exit 1;
} else {
(void) unknownfunction_004BC81A(uPar1, uPar2);
(void) StrCpyN(CryptRegInfo, RegInfo, CRegFileSize);
(void) encrypt(CryptRegInfo);
(void) SetFileAttributes(gRegFileName, FILE_ATTRIBUTES_NORMAL);
if (hFile = OpenFile(gRegFileName, RegFileBuffer, 10)) {
res = _lwrite(hFile, CryptRegInfo, CRegFileSize);
_lclose(hFile);
}
(void) SetFileAttributes(gRegFileName, FILE_ATTRIBUTES_READONLY);
return res;
}
}
Further work
This should be the biggest section, since there is still loads and
loads of work to do. i will only point out some headwords: dll reversing,
object oriented reversing, ocx-vxd reversing, language specific reversing,
packed and encypted programs, commercial protection schemes... . what i
want to enforce now is windows reversing. identifying and reversing the
WindowProcs, MenuHandlers, Messages, and so on and so on. Maybe you will
find another tutorial on this and other topics. but letīs see what time
will bring. A lot of these things can also be handled by you. i would like
so see some tutorials tools and other stuff. Now we know where to start
you so letīs get movinī.
Conclusion
To me, reversing is like solving a puzzle. you start out at one point
put peice after peice together until youīre stuck. then you start another
colony somewhere else and slowly the little colonies start growing
together. you will be able to see the big picture clearer and clearer with
every piece you add to the whole. after a while you can already predict
what itīs going to be like and then, in the end you marvel at your genius
work, show it to others and enjoy it, tell stories about how you did it
and canīt wait to start all over again with another one, because of the
fascinating dynamics and the great fun. never forget that, whatever you
are doing enjoy your work be proud of it and make it something special.
last words
|