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

0) Conventions
0.1) Commenting The Code
0.2) Translate The Code
1) Functions
1.1) Calling Conventions
1.2) Local Variables - Parameters
1.3) Return Value
2) Loops
2.1) Counter
2.2) Exiting Condition
2.3) Type Of Loop
3) Control Structures
3.1) If Then Else
3.2) Case
4) Global Variables
4.1) Initialization
5) Generating High Level Source
6) Further work
7) Conclusion

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 Fravia. 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 Fravia 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.
almost forgot to provide you with some links:
+Fravia
all tools you need
a place to get some practice
another place to get some practice
assembler tutorial

Thanks go to seneca and Fravia+.

last words

Please send your comments and all to me -thanks.

Thatīs all for today folks
explode,

  Ignatz


Đ 1999-2010 by the stoicForce