Published

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

How conditionals look in disassembly, through a program that tells whether its input is more than, less than, or equal to 10.

reversing

5 min read

New Concepts Covered

  • Conditionals

The comparison program

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

C
#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

Text
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 <__isoc99_scanf@plt>
   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 <puts@plt>
   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 <puts@plt>
   0x080484a2 <+71>:	add    esp,0x4
   0x080484a5 <+74>:	jmp    0x80484b4 <main+89>
   0x080484a7 <+76>:	push   0x804856f
   0x080484ac <+81>:	call   0x8048320 <puts@plt>
   0x080484b1 <+86>:	add    esp,0x4
   0x080484b4 <+89>:	mov    eax,0x0
   0x080484b9 <+94>:	leave
   0x080484ba <+95>:	ret
End of assembler dump.

Recovering input and the first branch

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

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

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

Text
0x08048468 <+13>:	lea    eax,[ebp-0x4]
0x0804846b <+16>:	push   eax
0x0804846c <+17>:	push   0x8048540
0x08048471 <+22>:	call   0x8048340 <__isoc99_scanf@plt>
0x08048476 <+27>:	add    esp,0x8
C
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

Text
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 0x8048490 translates to: jump to address 0x8048490 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.

C
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,

Text
0x08048481 <+38>:	push   0x8048543
0x08048486 <+43>:	call   0x8048320 <puts@plt>
0x0804848b <+48>:	add    esp,0x4
0x0804848e <+51>:	jmp    0x80484b4 <main+89>

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

C
int main()
{
	int x = 0;

	scanf("%d", &x);

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

0x8048490:

0x80484b4:

}

Tracing the remaining branches

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.

Text
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.

C
int main()
{
	int x = 0;

	scanf("%d", &x);

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

0x8048490:

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

0x80484a7:

0x80484b4:

}

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

Text
0x08048498 <+61>:	push   0x8048559
0x0804849d <+66>:	call   0x8048320 <puts@plt>
0x080484a2 <+71>:	add    esp,0x4
0x080484a5 <+74>:	jmp    0x80484b4 <main+89>

The above translates to,

C
int main()
{
	int x = 0;

	scanf("%d", &x);

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

0x8048490:

	if (x > 9) {
		goto 0x80484a7;
	}
	puts("Input is less than 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.

Text
0x080484a7 <+76>:	push   0x804856f
0x080484ac <+81>:	call   0x8048320 <puts@plt>
0x080484b1 <+86>:	add    esp,0x4

This translates to,

C
int main()
{
	int x = 0;

	scanf("%d", &x);

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

0x8048490:

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

0x80484a7:

	puts("Input is equal to 10");

0x80484b4:

}

Reconstructing the complete function

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

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

This translates to,

C
int main()
{
	int x = 0;

	scanf("%d", &x);

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

0x8048490:

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

0x80484a7:

	puts("Input is equal to 10");

0x80484b4:
	return 0;

}
Control flow of main in the comparison programSix blocks. The first reads x and runs cmp eax, 0xa; jle jumps to 0x8048490 when x is at most 10, otherwise the block at 0x8048481 prints Input is more than 10 and jumps to 0x80484b4. The block at 0x8048490 runs cmp eax, 0x9; jg jumps to 0x80484a7, which prints Input is equal to 10 and falls through, otherwise 0x8048498 prints Input is less than 10 and jumps. 0x80484b4 returns 0.mainscanf("%d", &x)cmp eax, 0xajle 0x80484900x8048481puts"Input is more than 10"jmp 0x80484b40x8048490cmp eax, 0x9jg 0x80484a70x8048498puts"Input is less than 10"jmp 0x80484b40x80484a7puts"Input is equal to 10"0x80484b4mov eax, 0x0leaveretx > 10x ≤ 10x ≤ 9x > 9jmpjmpjle jumps whenx > 10 is falsex < 10 = not (x > 9)
Figure 1. Each conditional jump tests the opposite of the source's if and skips the block that if guards; x < 10 compiles as not (x > 9). All three branches rejoin at 0x80484b4.

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

C
int main()
{
	int x = 0;

	scanf("%d", &x);

	if (x < 10) {
		puts("Input is less than 10");
	} else if (x == 10) {
		puts("Input is equal to 10");
	} else {
		puts("Input is more 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.