[PoP:Rev] Reverse Engineering x86 ELF: 0x3

New Concepts Covered


In this article, we’ll understand how conditions look like in disassembly by looking at a simple program that tells us whether our user input is more than 10, less than 10, or equal to 10.

Source Code

#include <stdio.h>

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x > 10)
		puts("Input is more than 10");
	else if (x < 10)
		puts("Input is less than 10");
	else
		puts("Input is equal to 10");

	return 0;
}

Disassembly

Dump of assembler code for function main:
   0x0804845b <+0>:	push   ebp
   0x0804845c <+1>:	mov    ebp,esp
   0x0804845e <+3>:	sub    esp,0x4
   0x08048461 <+6>:	mov    DWORD PTR [ebp-0x4],0x0
   0x08048468 <+13>:	lea    eax,[ebp-0x4]
   0x0804846b <+16>:	push   eax
   0x0804846c <+17>:	push   0x8048540
   0x08048471 <+22>:	call   0x8048340 <[email protected]>
   0x08048476 <+27>:	add    esp,0x8
   0x08048479 <+30>:	mov    eax,DWORD PTR [ebp-0x4]
   0x0804847c <+33>:	cmp    eax,0xa
   0x0804847f <+36>:	jle    0x8048490 <main+53>
   0x08048481 <+38>:	push   0x8048543
   0x08048486 <+43>:	call   0x8048320 <[email protected]>
   0x0804848b <+48>:	add    esp,0x4
   0x0804848e <+51>:	jmp    0x80484b4 <main+89>
   0x08048490 <+53>:	mov    eax,DWORD PTR [ebp-0x4]
   0x08048493 <+56>:	cmp    eax,0x9
   0x08048496 <+59>:	jg     0x80484a7 <main+76>
   0x08048498 <+61>:	push   0x8048559
   0x0804849d <+66>:	call   0x8048320 <[email protected]>
   0x080484a2 <+71>:	add    esp,0x4
   0x080484a5 <+74>:	jmp    0x80484b4 <main+89>
   0x080484a7 <+76>:	push   0x804856f
   0x080484ac <+81>:	call   0x8048320 <[email protected]>
   0x080484b1 <+86>:	add    esp,0x4
   0x080484b4 <+89>:	mov    eax,0x0
   0x080484b9 <+94>:	leave  
   0x080484ba <+95>:	ret    
End of assembler dump.

Let’s start off by reversing the disassembly above using the techniques we learnt in the last two articles.

0x0804845b <+0>:	push   ebp
0x0804845c <+1>:	mov    ebp,esp
0x0804845e <+3>:	sub    esp,0x4
0x08048461 <+6>:	mov    DWORD PTR [ebp-0x4],0x0
int main()
{
	int i = 0;
}

This is the function prologue, followed by the initialization of a local variable to 0.

0x08048468 <+13>:	lea    eax,[ebp-0x4]
0x0804846b <+16>:	push   eax
0x0804846c <+17>:	push   0x8048540
0x08048471 <+22>:	call   0x8048340 <[email protected]>
0x08048476 <+27>:	add    esp,0x8
int main()
{
	int x = 0;

	scanf("%d", &x);
}

We load the address of the value at ebp-0x4 into eax and push it on the stack. We then push 0x8048540 (address of %d) on the stack, and call scanf().

Conditionals

0x08048479 <+30>:	mov    eax,DWORD PTR [ebp-0x4]
0x0804847c <+33>:	cmp    eax,0xa
0x0804847f <+36>:	jle    0x8048490 <main+53>

At +30, we move the value at ebp-0x4 into eax. Recall that this is the variable that was written to in the invocation of scanf earlier. As such, ebp-0x4 contains the integer that was entered by the user. After which, eax is compared (cmp) to 0xa which is 10 in decimal. This is followed by a jle instruction, which is an initialism for jump less than/equal.

At this point, it is a good idea to refer to the intel manual to get a better understanding of the cmp/jle instructions. The cmp instuction actually sets some status registers which jump-type instructions such as jle will check before deciding on whether or not to perform the jump.

As such, jle 0x8048480 translates to: jump to address 0x8048480 if the comparison (based on status registers) is true, true being less than or equal to in this case. Knowing this, we can continue to reverse the code.

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	}

0x8048490:

}

Let’s take a look at the new few lines of disassembly,

0x08048481 <+38>:	push   0x8048543
0x08048486 <+43>:	call   0x8048320 <[email protected]>
0x0804848b <+48>:	add    esp,0x4
0x0804848e <+51>:	jmp    0x80484b4 <main+89>

Seems like 0x8048543 (Input is greater than 10) is passed into puts, followed by an unconditional jump (jmp) to 0x80484b4.

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	} else {
		puts("Input is greater than 10");
		goto 0x80484b4;
	}

0x8048490:

0x80484b4:

}

Let’s continue with the next few lines. Take note that we’re now looking at 0x08048490 which where the if-block that we reversed earlier jumps to.

0x08048490 <+53>:	mov    eax,DWORD PTR [ebp-0x4]
0x08048493 <+56>:	cmp    eax,0x9
0x08048496 <+59>:	jg     0x80484a7 <main+76>

The value at ebp-0x4 is moved into eax and if it is greater than (jg) 9, we will jump to 0x80484a7.

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	} else {
		puts("Input is greater than 10");
		goto 0x80484b4;
	}

0x8048490:

	if (x > 9) {
		goto 0x80484a7;
	}

0x80484a7:

0x80484b4:

}

The next few lines of disassembly continues in wthin the same block.

0x08048498 <+61>:	push   0x8048559
0x0804849d <+66>:	call   0x8048320 <[email protected]>
0x080484a2 <+71>:	add    esp,0x4
0x080484a5 <+74>:	jmp    0x80484b4 <main+89>

The above translates to,

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	} else {
		puts("Input is greater than 10");
		goto 0x80484b4;
	}

0x8048490:

	if (x > 9) {
		goto 0x80484a7;
	}
	puts("Input is less than to 10");
	goto 0x80484b4;

0x80484a7:

0x80484b4:

}

We’ll now move on to the next few lines of disassembly, taking note that 0x080484a7 is within the if (x > 9) block.

0x080484a7 <+76>:	push   0x804856f
0x080484ac <+81>:	call   0x8048320 <[email protected]>
0x080484b1 <+86>:	add    esp,0x4

This translates to,

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	} else {
		puts("Input is greater than 10");
		goto 0x80484b4;
	}

0x8048490:

	if (x > 9) {
		goto 0x80484a7;
	}
	puts("Input is less than to 10");
	goto 0x80484b4;

0x80484a7:

	puts("Input is equal to 10");

0x80484b4:

}

The next block of disassembly starts at 0x080484b4, which also happens to be our function epilogue.

0x080484b4 <+89>:	mov    eax,0x0
0x080484b9 <+94>:	leave
0x080484ba <+95>:	ret

This translates to,

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x <= 10) {
		goto 0x8048490;
	} else {
		puts("Input is greater than 10");
		goto 0x80484b4;
	}

0x8048490:

	if (x > 9) {
		goto 0x80484a7;
	}
	puts("Input is less than to 10");
	goto 0x80484b4;

0x80484a7:

	puts("Input is equal to 10");

0x80484b4:
	return 0;

}

If we understand the flow of the code, we can actually remove the goto commands to clean things up.

int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x < 10) {
		puts("Input is less than to 10");
	} else if (x == 10) {
		puts("Input is equal to 10");
	} else {
		puts("Input is greater than 10");
	}

	return 0;
}

Note that code that is reversed is not always structrually the same as the original source code. However, it should be equivalent in terms of what it achieves.