Files
notes/docs/lectures/malware/04_disassembler.md
T

5.3 KiB
Raw Blame History

Disassembler

Sucessful reverse engineers do not evaluate each instruction individually unless they must. The process is too tedious.

Global vs Local Variables

Globbal variables can be accessed and used by any function in the program.

Local variables can be accessed only by the function in which they are defined.

Both types of variables are declared similarly in c but completely differently in assembly.

Global variables are referenced by memory addresses

Local variables are referenced by stack addresses

Recognising if Statements

In IDA, branches will be represented as such:

int x = 1;
int y = 2;

if (x == y){
    printf("x equals y\n");
} else {
    printf("x does not equals y\n");
}
00401006 mov [ebp+var_8], 1
0040100D mov [ebp+var_4], 2
00401014 mov eax, [ebp+var_8]
00401017 cmp eax, [ebp+var_4]
0040101A jnz short loc_40102B
0040101C push offset aXEqualsY_ ; "x equals y.\n"
00401021 call printf
00401026 add esp, 4
00401029 jmp short loc_401038
0040102B loc_40102B:
0040102B push offset aXIsNotEqualToY ; "x is not equal to y.\n"
00401030 call printf

Here the instruction jnz on line 5 causes the branch. A cmp is done between ebp+var_8 (y) and ebp+var_4(x). If the values are not equal, then we jump to “x is not equal to y”

This is what that looks like in IDA

1646766562.png

Recognising Loops

Finding for loops

For loops have 4 basic components:

  1. initialisation
  2. comparison
  3. execution instructions
  4. increment/decrement
int i;

for (i=0; i<100; i++)
{
    printf("i equals %d\n", i);
}
mov [ebp+var_4], 0 ; INITIALISATION
jmp short loc_401016
loc_40100D:
mov eax, [ebp+var_4] ; INCREMENT START
add eax, 1
mov [ebp+var_4], eax ; INCREMENT STOP
loc_401016:
cmp [ebp+var_4], 64h ; COMPARISON
jge short loc_40102F ; COMPARISON
mov ecx, [ebp+var_4]
push ecx
push offset aID ; "i equals %d\n"
call printf
add esp, 8
jmp short loc_40100D ; UNCONDITIONAL JUMP

1646766968.png

Note: the box on the bottom right is the function’s epilogue

Finding While Loops

While loops look similar to for loops in assembly, but are easier to understand.

int status = 0;
int result = 0;

while (status == 0)
{
    result = performAction();
    status = checkResult(result);
}

The assembly for this code will look similar from before however it lacks the increment section.

mov [ebp+var_4], 0
mov [ebp+var_8], 0
loc_401044:
cmp [ebp+var_4], 0
jnz short loc_401063 ; CONDITIONAL JUMP
call performAction
mov [ebp+var_8], eax
mov eax, [ebp+var_8]
push eax
call checkResult
add esp, 4
mov [ebp+var_4], eax
jmp short loc_401044 ; UNCONDITIONAL JUMP

A conditional jump occurs on line 5 and an unconditional jump at line 13, but the only way for this code to stop executing repeatedly is for that conditional jump to occur.

Understanding Function Call Conventions

Function call conventions govern:

  • The order in which parameters are placed on the stack or in registers
  • Whether the caller or callee is responsible for cleaning up the stack

Calling convention depends on the compiler used

int adder(int a, int b)
{
    return a+b;
}

void main()
{
    int x=1;
    int y=2;
    
    printf("adder(1,2): %d", adder(x,y));
}

1646767431.png

Switch Statements

Switch statements are compiled in two different ways: if style or using jump tables

switch(i)
{
    case 1:
        printf("i = %d", i+1);
        break;
    case 2:
 		printf("i = %d", i+2);
 		break;
 	case 3:
 		printf("i = %d", i+3);
 		break;
 	default:
 		break;
}
If Style

1646767721.png

Jump Table

This example is usually found with large contiguous switch statements. The compiler optimises the code to avoid needing to make so many comparisons

1646767841.png

This assembly uses a jump table which defines offsets to additional memory locations. The switch variable (stored in ecx) is used as an index into the jump table.

edx is multiplied by 4 and added to the base of the jump table to determine which case code block to jump to.

It is multiplied by 4 because each entry in the jump table is an address that is 4 bytes in size.

Disassembling Arrays

int b[5] = {123, 87, 487, 7, 978};
void main()
{
	int i;
 	int a[5];
 	for(i = 0; i<5; i++)
 	{
 		a[i] = i;
 		b[i] = i;
 	}
}

In assembly, arrays are accessed using a base address as a starting point. The size of each element is not always obvious, but can be determined by seeing how the array is being indexed.

00401006 mov [ebp+var_18], 0
0040100D jmp short loc_401018
0040100F loc_40100F:
0040100F mov eax, [ebp+var_18]
00401012 add eax, 1
00401015 mov [ebp+var_18], eax
00401018 loc_401018:
00401018 cmp [ebp+var_18], 5
0040101C jge short loc_401037
0040101E mov ecx, [ebp+var_18]
00401021 mov edx, [ebp+var_18]
00401024 mov [ebp+ecx*4+var_14], edx ; LOCAL
00401028 mov eax, [ebp+var_18]
0040102B mov ecx, [ebp+var_18]
0040102E mov dword_40A000[ecx*4], eax ; GLOBAL
00401035 jmp short loc_40100F

In both cases ecx is used as the index, which is multiplied by 4 to account for the size of the elements. This is added onto the base address of the array to access the proper array element.