Newbie Project #1

In The Beginning There Was...

v1.6, 26 April 2001
by LoRd HrUn

 

[ Lesson 2: Assembler, the start of everything ]



Assembler is the mother of all programminglanguages. In the end, all languages (C++, Visual Basic, Delphi, Pascl, Java etc) are translated to assembler. Where in the most languages we deal with prety straigtforwarded syntaxes, it's just a little bit different in assembler. There, we use abbreviations and numbers and it all seems a mess and...

An example:

:00401C7E BF01000000 mov edi, 00000001
:00401C83 6A00 push 00000000
:00401C85 85FF test edi, edi
:00401C87 7419 je 00401CA2
:00401C89 681CF34000 push 0040F31C
:00401C8E 6874F34000 push 0040F374
:00401C93 6A00 push 00000000
:00401C95 FF15E8634100 Call dword ptr [004163E8]
:00401C9B 33C0 xor eax, eax
:00401C9D 5F pop edi
:00401C9E 5E pop esi
:00401C9F C21000 ret 0010

What the hell?? Welcome to the REAL programmingworld!
As I said: every program will be translated to assembler. You can not just go and see this assembler-listig! Therefore, we use SoftICE or Win32dsm (see later). In this lesson, I'll show you the basics you have to master *perfectly*. Without this knowledge, you mustn't even think about it to crack a program.. ever! I warned you ;-)

Fasten your seatbelts, here we go.

 

Part 1: Overview of the most important datastructures

BIT
The most little piece of data that exists. It can be either a 0 or a 1. Nothing more, nothing less. If you put a serie of bits together, you'll become the 'binary number system'. ie. 00000001 = 1, 00000010 = 2, 00000011 = 3 etc. Click here to get a more comprehensive list.

BYTE
A byte consists of 8 bits. It can have a maximal value of 255 (0..255). To make it easier to read binary numbers, we use the 'hexadecimal number system'. It's a 'base-16 system', while binary is a 'base-2 system' Remark: the first character, the '0', and the last one, the 'h' indicate that we are dealing with a hexadecimal number. In fact, you may just drop those two.

ie.

dec
hex
alias
0
0
000h
1
1
001h
2
2
002h
3
3
003h
4
4
004h
5
5
005h
6
6
006h
7
7
007h
8
8
008h
9
9
009h
10
A
00Ah
11
B
00Bh
12
C
00Ch
13
D
00Dh
14
E
00Eh
15
F
00Fh
...
...
...
19
13
013h
...
...
...
174
AE
0AEh
...
...
...


Whith this little program (Base Calculator) you can easily convert decimal to hexadecimal to binary etc..


WORD
A word is just 2 bytes put together or 16 bits. A word can have a maximal value of 0FFFFh or 65,535.


DOUBLE WORD

A double word is 2 words together or 32 bits. Max value = 0FFFFFFFF or 4,294,967,295. Phew, large enough if you ask me.


KILOBYTE

Everyone knows the term kilobyte. However, a kilobyte does NOT equals 1000 bytes! Actualy, there are 1024 (32*32) bytes.


MEGABYTE

Even more familiar is this structure. Not just 1 million bytes, but 1024*1024 or 1,048,578 bytes.

 

Part 2: The registers

Look again at our assembler-example at the very beginning of this lesson. EDI, EAX, ESI,... are called 'registers'. Registers are actualy special places in your computer's memory where we can store data. You can see a register as a little matchbox, wherein we can store something: a name, a number, the sentence 'Sorry, you have entered a wrong serial number. Please try again' ;-) If you have experience whith programming, you can see a register as a variable.

We distinguish:


a) byte-sized registers

As the name says, these registers all exactly 1 byte in size. This does not mean, that the whole register is fully loaded with data! Eventualy empty spaces in a register, are just filled with zeroes.

AL and AH
BL and BH
CL and CH
DL and DH

These are the byte-sized registers, all 1 byte or 8 bits in size.

b) word-sized registers

Are 1 word (= 2 bytes = 16 bits) wide. A word-sized register is constructed of 2 byte-sized registers.

general purpose registers


AX (word-sized) = AH + AL -> the '+' does *not* mean: 'add them up'. AH and AL exist apart from eachother, but together they form AX. This means that if you change AH or AL (or both), AX will change too!

AX -> 'accumulator': used to mathematical operations, store strings,...
BX -> 'base': used in conjunction with the stack (see later)
CX -> 'counter'
DX -> 'data': mostly, here the remainder of mathematical operations is stored

DI -> 'destination index': ie. a string will be copied to DI
SI -> 'source index': ie. a string will be copied from SI

index registers

BP -> 'base pointer': points to a specified position on the stack (see later)
SP -> 'stack pointer': points to a specified position on the stack (see later)

segment registers

CS -> 'code segment': instructions an application has to execute (see later)
DS -> 'data segment': the data your application needs (see later)
ES -> 'extra segment': duh! (see later)
SS -> 'stack segment': here we'll find the stack (see later)

c) doubleword-sized registers

2 words = 4 bytes = 32 bits. eax ebx ecx edx edi ...
Everythime you encounter an 'E' in front of a 16-bits register, it means that you are dealing with a 32-bits register. So, ax = 16-bits; eax = the 32-bits version of eax.

d) flag register

This register is a collection of different 1-bit flags. A flag is a sign, just like a green lamp means: 'ok' and a red one 'not ok'. A flag can only be '0' or '1', 'not set' or 'set'.

O overflow flag
D direction flag
I interrupt flag
T trace flag
S sign flag (shows if t's a positive or negative number)
Z zero flag (is used *a lot* when cracking!)
A auxilary cary flag
P parity flag
C cary flag


The 'zero flag' is at the moment the only flag you have to concern about.

Use of (ie) the zero-flag:

cmp ax, bx -> compare (cmp=compare) bx with ax. Are both equal? -> z-flag = 1
jz 00124531 -> z-flag is set? jump then to 00124531 (jz = jump if zero)


Whith such a (simple) routine, a program can for example check if the serial you entered equals the correct one. Correct (z-lag=1)? Jump then to the 'Thank you for registering!'-routine. Else (z-flag=0): jump to the 'Your have entered an invalid serial number!'-routine.

e) special
IP -> 'instruction pointer': points to the next instruction. DO NOT TOUCH!

 

Part 3: Segments en offsets

I briefly talked about segments, in the 'word-sized registers' section. Do you remember CS, DS, ES and SS? A segment is just a piece in memory where instructions (CS), data (DS), the stack (SS) or just an extra segment (ES) are stored.
Every segment is divided in 'offsets'. IN 32-bits applications (like the ones written for Windows 95/98/ME/2000), these offsets are numbered from 00000000 to FFFFFFFF. Thus 65,536 pieces of memory, 65,536 memory addresses per segment.

The standard notation for segments and offsets is: SEGMENT:OFFSET. Together, this couple forms a specific place (address) in memory.

Think of it like this: you can compare an application with a book. A segment is a page in that book. An offset is a specific roww at that page.

Example:
CS:IP -> points to the next executing instruction in your program (IP, the instruction pointer, is the offset in the Code Segment)
SS:SP -> points to the current position in the stack (offset = SP, stack pointer; SS = Stack Segment)

 

Part 4: The Stack

The Stack is a part in memory where you can store different things to use it later. Imagine the stack as a paperbasket where you put in sheets. The basket is the stack; a sheet is a memoryaddress (indicated by the stack pointer) in that stacksegment.

Remember following rule: The last sheet of paper you put in the stack, is the first one you'll take out!

Use of the stack (where ax = 1, bx = 2, cx = 3 en dx = 4):
(the command 'push' = put, push, save the contents of a register onto the stack)

push ax -> contents of the stack = (1)
push bx -> contents of the stack = (2) (1) -> (2) is put on top of (1)
push cx -> contents of the stack = (3) (2) (1) -> (3) on top of (2) on top of (1)
push dx -> contents of the stack = (4) (3) (2) (1) -> ...

If we want to fetch something from the stack (ie. (2)), we have to take everything from above (2) and save it somewhere. Therefore, we use the command 'pop'. Pop = 'fetch the first following value from the top of the stack and save it into the specified register'.

We adapt this to our little stack example:
pop ax -> contents of the stack = (3) (2) (1) -> (4) is fetched from stack and saved in ax
pop bx -> contents of the stack = (2) (1) -> (3) is fetched from stack and saved in bx
pop cx -> contents of the stack = (1) -> (2) is fetched from stack and saved in cx
pop dx -> contents of the stack = leeg -> (1) is fetched from stack and saved in dx

At the end, we become: ax = 4, bx = 3, cx = 2 and dx = 1. Got it?

 

Part 5: Important registers when cracking

most important : eax, ebx, ecx, edx, flag register
less : edi, esi
more less : ebp, esp, cs, ds, ss, es, eip
away with it! : fs, gs.

And important register-couples (segment:offset):

CS:IP -> points to the next instructioncode
SS:SP -> points to the current stack position
DS:SI -> used as source in stringoperations
ES:DI -> used as destination in stringoperations

 

Part 6: Operations

With just registers and segments and offsets, we are nowhere. We also need instructions to do something with 'that serial number we have saved in EAX'.

Attention: We *always* work backwards, thus from the operand behind the comma, to the operand in front of the comma.

Here are the most important ones:

MOV = move (actualy it copies) a value, register,... into a register.

* MOV (register, value) -> put a value into a register. Ie. mov eax, 00000001
* MOV (register, register) -> put a register into a register. Ie. mov eax, ebx
* MOV (register, [register]) -> mov the address that is saved in [register] into 'register'

Imagine:
ecx = 01873EA
eax = 5423957

'mov eax, [ecx]' means:

if you go to memoryaddress 'ds:ecs' (=ds:01873EA), you see there for example the value 0304569. This value is MOVed into eax. After this instruction, eax = 0304569. Thus: MOV the value you find at address ds:ecx into eax. Ecx is used here as a memoryaddress (indicated by [..]) and NOT as a variable with a value in it!

* Sometimes, you see: mov eax, [edx+10].

This means: MOV the value you find at addres edx+10 into eax.

In SoftICE (see later), you can dump this value like this: 'd edx+10'. In the datawindow, you see for example: '00xx:10000168 F6 A3 E0 00 E0 ...'. This is thus the address you find at edx+10. Cause addresses are *ALWAYS* stored backwards in memory, we do a 'd 00 E0 A3 F6' to see the value at that address. With a regular 'd edx+10', you'll only see the ADDRESS where the value is stored! Remember this!

 

CMP (register, value), CMP (register, register), CMP (register, [register])

If you master the MOV-instruction, than you'll understand easily this CMP-instruction.

CMP means: compare.

Verrrrry interesting for crackers, cause this instruction is used a lot to compare two serial numbers, to compare your 'remaining days you can use the programm' with 'the 30 days limit', etc. The CMP instruction NEVER shows up allone. It's always used in conjunction with a JUMP.

 

JMP (register) or JMP (address)

ie.
jmp ecx -> jump to offset xx (ecx = xx)
jmp 018249 -> jump to offset 018249

Maybe you ask: 'But, hey, a memoryaddress is ALWAYS indicated as SEGMENT:OFFSET?' You're right! If you cannot find a segment at the jmp, CS is used by default. 'JMP 018249' realy means: 'JMP cs:018249'. If our application needs to read or write DATA, the default segment is DS (ie. DS:SI).

There are a lot variants of JMP: see the end of this lesson where to find all of them.

I give you the most important jumps:

1) the basic or unconditional jump (see above):
JMP Unconditional means: you needn't a condition to jump. Jump, in every case (is the zero-flag set or not, are 2 numbers equal or not,... it doesn't matter: jump!)

2) the conditional jump: JNZ, JZ, JG,...
This one, you can understand easily with a very simple protectionscheme:


Imagine, you have an application where you have to put in a serial number to register. Imagine the correct serial = 123456 (01E240 in hex). After you typed in a random number, this number is saved in eax.
Now, our application will compare both numbers to check if they are equal: your number in eax is compared to the correct one somewhere in memory. Here we go:
cmp eax, 01E240 -> compare the correct number (01E240) with the contents of eax (your nr)
jnz 18563124 -> is the result not 0 (nz = not zero), then jmp to address 18563124

Actualy when using the CMP instruction, the processor will substract both numbers. If the result is 0 (means: eax is 01E240), then the zero-flag is set. If the result doesn't equal 0 (both numbers differ), the zero-flag is not set. Easy enough ;-)

JNZ means: Jump if Not Zero = jump if the z-flag is off
JZ means: Jump if Zero = jump if the z-flag is on

In SoftICE you can easily follow the status of the z-flag (and all other flags as well), and if the instruction will either jump or not.

 

CALL (address)

CALL = call a procedure that begins at (address). A procedure is a piece of code where all instructions belong to eachother. In an application you have for example a procedure to save a file, to print something, to compare a serial number,...

This procedure is saved somewhere in your application, and (address) points to the address where the procedure begins. At eht end of a procedure, you always find the instruction 'RET', which means 'RETURN': return back to the original code, to the first instruction beneath the CALL. The address that calls the procedure is the 'caller'; the procedure that is called is the 'callee'.

Example:
(Remark: because we are working with INSTRUCTIONS, the defaultsegment is CS. ':001' actualy means: 'CS:001')

:001 mov ax, 10
:002 call 004
:003 jmp 006
:004 mov ax, 9
:005 ret
:006 mov bx,ax

The instruction at offset :002 calls the procedure at address :004. The application jumps to address :004, performs there a 'mov ax, 9', and returns to the first following instruction beneath the CALL (=address :003). Then it jumps to address :006.

At this moment, these are the most important instructions. We'll encounter a lot more instructions when we are going to crack. I'll explain them at the proper time. This helpfile (Opcodes) gives you a complete overview of all the asm-instructions you'll ever need.

 


This lesson is one of the most important ones in the course. It's the base, the concrete on which you'll build your cracking sessions. Pay attention to it carefully!
Please, only start with lesson 3 (SoftICE) when you master this lesson perfectly.