In this tutorial we'll temporarily deviate from creating our own ASM programs and try to crack a binary which was created by someone else. Writing assembly is one skill, understanding it is another. The binary we'll be analysing can be found here. The keygen was created by Kwazy Webbit from the Reverse Engineering Team.
As with every tutorial we'll first cover the new instructions we'll be encountering during this reverse engineering task.
The
Finally we've made it to the interesting bit. The binary presents us with a tiny window and 2 text fields named Name and Serial, the latter of which can only be numerical. Let's try our luck ...
... anddddd ...
... nope nothing. At this point we don't know if it is expecting a specific Name and Serial, or maybe a specific Name and a derived Serial or even any Name and a Serial number which is derived from it. So let's look at it under IDA.
It was a long, bumpy road but at the end we've manage to find a Name-Serial pair that worked. As an exercise I suggest solving the keygen for a different Name or even for an arbitrary Name. The plan for the next reverse engineering tutorial is to cover one of the Flare-on challenges so stay tuned .. じゃあ、また
x86 Instructions
AND
The
AND
operator performs a bitwise AND of the operands and places the result in the first operand specified. To refresh your memory, the AND
operator only returns TRUE if both operands are TRUE. The following is the truth table for this operator:
+-----------+-------------------------------+ | A | 0 | 0 | 1 | 1 | |-------------------------------------------| | B | 0 | 1 | 0 | 1 | +-----------+-------------------------------+ | A & B | 0 | 0 | 0 | 1 | +-----------+-------------------------------+Some examples:
and eax, ebx ; EAX = EAX & EBX and eax, 0Fh ; EAX = EAX & 0xF and [esi], esp ; [ESI] = [ESI] & ESP and [esi], dword 0ABCh ; [ESI] = [ESI] & 0x0ABC
LEA
LEA
stands for Load Effective Address and is one of the data movement instructions. Let's look at a few examples:
lea eax, [ebp+0C0h] ; if EBP = 0x0000ABCD, then EAX = 0x0000AC8D lea eax, [ebx+ebx] ; if EBX = 0x00001234, then EAX = 0x00002468 lea ebx, [eax+ecx*4] ; if EAX = 0x00000010 & ECX = 0x0000000A then EBX = 0x00000038The syntax used for
LEA
is quite misleading; even though the square brackets are used, the instruction does not reference memory but rather it evaluates the contents of the square brackets directly.
MOVZX & MOVSX
These instructions are special versions of theMOV
instruction which are used to extended signed (MOVSX
) and unsigned (MOVZX
) registers to larger registers.
MOVZX
stands for Move With Zero Extension. When data is moved from a register to a larger register, the value is prepended with zeroes. Some examples are:
movzx eax, bx ; if BX = 0x1000, then EAX = 0x00001000 movzx ebx, ax ; if AX = 0xFFFF, then EBX = 0x0000FFFF movzx ax, bl ; if BL = 0xFF, then AX = 0x00FF movzx bx, al ; if AL = 0x7F, then BX = 0x007F
MOVSX
stands for Move with Sign Extension. This time the resultant value is prepended with either zeros or ones depending on the sign of the original value, and hence the sign is preserved. Let's take the previous examples:
movsx eax, bx ; if BX = 0x1000, then EAX = 0x00001000 movsx ebx, ax ; if AX = 0xFFFF, then EBX = 0xFFFFFFFF movsx ax, bl ; if BL = 0xFF, then AX = 0xFFFF movsx bx, al ; if AL = 0x7F, then BX = 0x007F
ROL & ROR
ROL
stands for Rotate Left whereas ROR
stands for Rotate Right. These instructions shift the bits of the destination to the left/right a number of times; the data that slides off the end of the register is inserted back from the right/left. The following table should clear things up:
+---------------+-----------------------------------------------+--------------+ | EAX | 1100 0011 1010 1110 0001 1111 0000 0101 | 0xC3AE1F05 | +---------------+-----------------------------------------------+--------------+ | ROL EAX, 01h | 1000 0111 0101 1100 0011 1110 0000 1011 | 0x875C3E0B | |------------------------------------------------------------------------------| | ROL EAX, 06h | 1110 1011 1000 0111 1100 0001 0111 0000 | 0xEB87C170 | |------------------------------------------------------------------------------| | ROL EAX, 0Fh | 0000 1111 1000 0010 1110 0001 1101 0111 | 0x0F82E1D7 | +---------------+-----------------------------------------------+--------------+ | ROR EAX, 01h | 1110 0001 1101 0111 0000 1111 1000 0010 | 0xE1D70F82 | |------------------------------------------------------------------------------| | ROR EAX, 06h | 0001 0111 0000 1110 1011 1000 0111 1100 | 0x170EB87C | |------------------------------------------------------------------------------| | ROR EAX, 0Fh | 0011 1110 0000 1011 1000 0111 0101 1100 | 0x3E0B875C | +---------------+-----------------------------------------------+--------------+It stands to reason that rolling a 32-bit register 33 times is the same as rolling it in the same direction once, hence if say the value of a register has to be rolled X times, where X is a large number, instead roll it X % 32 times.
MUL (IMUL) & DIV (IDIV)
As you've probably guessedMUL
performs multiplication while DIV
performs Division; IMUL
and IDIV
are the signed versions.
Although in real life these are basic operations, they might be tricky to handle for the CPU which has a limited number of fixed-sized registers to work with. This is because when multiplying 2 large numbers, the result can be too large to fit into a single register; when dividing, the result can be a decimal number with a decimal part which is too long to fit into a single register. To solve this issue, 2 registers are used, namely EAX, the main register, and EDX.
Let's see some examples on how these 2 registers are used to store the results:
mul ecx ; EDX:EAX = EAX * ECX (unsigned) imul edx ; EDX:EAX = EAX * ECX (signed) imul ecx, 1Fh ; ECX = ECX * 0x1F (signed) div ecx ; EAX = EDX:EAX / ECX (unsigned) ; EDX = EDX:EAX % ECX (unsigned) div cl ; AL = AX / CL (unsigned) ; AH = AX % CL (unsigned) idiv ecx ; EAX = EDX:EAX / ECX (signed) ; EDX = EDX:EAX % ECX (signed)Notice how both of these functions can make use of
EAX
, the accumulator register, without explicit declaration. As mentioned earlier, the EDX
register is used as a secondary storage for overflows in multiplications and remainders in divisions. Another interesting thing to notice is that MUL
always operates and stores the result in EAX/AX/AL
and hence the following instruction is invalid:
mul ecx, 1Fh ; ECX = ECX * 0x1F <- INVALID
SBB
SBB
is Subtract with Borrow/Carry. It differs slightly from the traditional subtract, i.e. SUB
, in that if the Carry Flag is set, it subtracts 1 extra, otherwise it's exactly the same. For example:
sbb ax, 10h (CF = 1) ; if EAX was 0x0050, then EAX = 0x003F sbb ax, 10h (CF = 0) ; if EAX was 0x0050, then EAX = 0x0040
JA(JG), JAE(JGE), JB(JL) & JBE(JLE)
These operators are different conditional jump instructions which operate on either signed or unsigned values. The following is a summary table which contains a self-explanatory description and the jump conditions for each:+------------+---------------------------------------+--------------------+ | Mnemonic | Meaning | Jump Condition | +------------+---------------------------------------+--------------------+ | JA | Jump if Above | CF=0 & ZF=0 | +------------+---------------------------------------+--------------------| | JAE | Jump if Above or Equal | CF=0 | |------------|---------------------------------------+--------------------| | JB | Jump if Below | CF=1 | |------------|---------------------------------------+--------------------| | JBE | Jump if Below or Equal | CF=1 | ZF=1 | +------------+---------------------------------------+--------------------+ | JG | Jump if Greater (signed) | ZF=0 & SF=OF | |------------|---------------------------------------+--------------------| | JGE | Jump if Greater or Equal (signed) | SF=OF | |------------|---------------------------------------+--------------------| | JL | Jump if Less (signed) | SF!=OF | |------------|---------------------------------------+--------------------| | JLE | Jump if Less or Equal (signed) | ZF=1 | SF!=OF | +------------+---------------------------------------+--------------------+
The Binary
Identifying Important Areas
Looking at the strings present in the binary, we notice 2 very interesting ones, "RIGHT" and "You got it!". We know that that's where we need to end up so lets start from there. My personal strategy when tackling binaries in which I known what the point I want to reach is, is to start from there and work my way up as much as I can. Let's apply this. Moving backwards from the desired result, we start identifying the conditions that have to be met:- [0x0040112F] jz short loc_40113D - The Zero Flag must be zero for the program to take the correct path
- [0x0040112A] test eax, eax - Since we require
ZF
to be zero,EAX
must NOT be 0x00000000. - [0x00401125] call sub_401000 - This requires further investigation but we know that when it ends,
EAX
cannot be zero
The Main Fuction
The function is manageable but we can still weed out some irrelevant parts so we can focus on the core functionality. Once again we start from the bottom:- [0x0040107D] inc eax -
EAX
cannot be 0xFFFFFFFF (Recall:EAX
cannot be 0 when the function ends) - [0x0040107E] sbb eax, eax - Subtracting a number from itself gives zero, so
CF
has to be zero, else we getEAX
= -1 - [0x0040107A] neg eax - Only
EAX
= 0x00000000 will not set the Carry Flag - [0x00401077] xor eax, [ebp+serial] -To end up with a
EAX
= 0x00000000,EAX
must be equal to[ebp+serial]
[ebp+name]
. By the end of the loop [ebp+name]
holds the result. Let us name this end result Name', since it is derived from Name. To keep things simple, and also since we'll be solving the binary for a specific input, we do not need to know exactly what the operations performed on Name, to produce Name', are. The only thing we need to know is the value of Name'. By setting a break point at the end of the function, we get Name' = 0x01E55668.
With the value of Name' in hand, we can focus on the last part of the function. I've displayed this here for reference:
As a reminder from the previous analysis, we require the XOR function at address 0x00401077 to result in a 0. The XOR's operands are Name' and Serial', where Serial' is the resultant of Serial after some operations have been performed on it; we'll see what these operations are. This means that at this point, SERIAL' and NAME' have to be equal and we already know that Name' = 0x01E55668.
From the image above we can see that the operations on Serial are:
XOR
Serial with 0x1337C0DESUB
result with 0xBADC0DE5NOT
result
NOT (( Serial ^ 0x1337C0DE ) - 0xBADC0DE5) = 0x01E55668 => ( Serial ^ 0x1337C0DE ) - 0xBADC0DE5 = 0xFE1AA997 => Serial ^ 0x1337C0DE = 0xB8F6B77C => Serial = 0xABC177A2 => Serial = 2881583010Combining everything together we try our luck again ... ... and .. ... we've done it !!
Conclusion
It was a long, bumpy road but at the end we've manage to find a Name-Serial pair that worked. As an exercise I suggest solving the keygen for a different Name or even for an arbitrary Name. The plan for the next reverse engineering tutorial is to cover one of the Flare-on challenges so stay tuned .. じゃあ、また
Great, Thanks !
ReplyDeleteThe Vulnerable Space: (N)Asm101 - 0X04 - Reversing Binaries - Part I (Easycrack.Exe) >>>>> Download Now
ReplyDelete>>>>> Download Full
The Vulnerable Space: (N)Asm101 - 0X04 - Reversing Binaries - Part I (Easycrack.Exe) >>>>> Download LINK
>>>>> Download Now
The Vulnerable Space: (N)Asm101 - 0X04 - Reversing Binaries - Part I (Easycrack.Exe) >>>>> Download Full
>>>>> Download LINK is