Published

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

How a function's prologue, return value, and epilogue look in disassembly, through a program that only returns 0.

reversing

1 min read

New Concepts Covered

  • Function prologue
  • Return value
  • Function epilogue

A minimal C program

We will start off this series by looking at the simplest of cases — a program that does nothing apart from returning 0.

Source Code

C
int main()
{
	return 0;
}

Disassembly

Text
0x080483db <+0>:	push   ebp
0x080483dc <+1>:	mov    ebp,esp
0x080483de <+3>:	mov    eax,0x0
0x080483e3 <+8>:	pop    ebp
0x080483e4 <+9>:	ret

Understanding the function lifecycle

Function Prologue

Text
0x080483db <+0>:	push   ebp
0x080483dc <+1>:	mov    ebp,esp

This is what we call a function prologue. It prepares the stack and registers for use within the current function. In this scenario, the old frame pointer ebp is being saved by pushing it onto the stack. After which, ebp is set up for use as main’s frame pointer by copying the value of esp into it.

Return value

Text
0x080483de <+3>:	mov    eax,0x0

This sets the value of eax to 0. By convention, eax is the register that stores the return value of a function. As such, this sets the return value of main to 0.

Function epilogue

Text
0x080483e3 <+8>:	pop    ebp
0x080483e4 <+9>:	ret

Similar to how we set up the stack and registers in the function prologue at the start of main, we need to perform cleanup at the end. This is done in what is called a function epilogue. In this scenario, the value of ebp is restored, and we execute a ret instruction to return to the parent function of main.

main's stack and registers after each instructionSix snapshots from left to right, higher addresses at the top. push ebp stores the caller's ebp below the return address and moves esp down 4. mov ebp, esp points ebp at that slot. mov eax, 0x0 changes only eax. pop ebp restores the caller's ebp and moves esp up 4. ret pops the return address into eip, leaving esp 4 above its value on entry.↑ higheron entryesp → ret addrcallerret addrebpespeax ?push ebp+0 · esp −4callerret addrsaved ebpebpespeax ?mov ebp, esp+1 · ebp = espcallerret addrsaved ebpebpespeax ?mov eax, 0x0+3 · eax = 0callerret addrsaved ebpebpespeax = 0pop ebp+8 · esp +4callerret addrsaved ebpebpespeax = 0ret+9 · esp +4callerret addrsaved ebpebpespeax = 0eip ← ret addrrestored
Figure 1. The prologue and epilogue mirror each other. Everything main pushes it pops, so after ret, esp and ebp are back where they were before the caller's call.