Table of Contents

Question

Given what you learned about CALL and RET, explain how you would read the value of EIP? Why can’t you just do MOV EAX, EIP?

Answer

mov eax, eip is seen as an invalid instruction(not encodable) by any assembler since EIP is not a General Purpose Register(GPR); it is a special purpose register that is used as a pointer to the next instruction to execute(hence known as, extended instruction pointer).

However, there are a couple of tricks to get EIP register value by reading it from the stack following the execution of the call instruction.

Consider the following assembly subroutine(I’m using MASM as the assembler):

; Obtain instruction pointer(EIP) register value
OPTION LANGUAGE: SYSCALL
@get_eip@0 PROC PUBLIC
  ; Function prologue - save the non-volatile registers onto the stack and perform explicit stack frame linkage
  push ebp                      ; ESP = ESP - 0x4 and [ESP] = EBP, preserve the current base frame pointer on the stack
  mov ebp, esp                  ; EBP = ESP, create a new local stack frame within the callee by setting the base frame pointer to point to the current top of the stack

  ; Get the address of the instruction in the calling function immediately after the call instruction that will be executed after control returns to the caller
  mov eax, dword ptr [ebp + 4h] ; EAX = [EBP + 0x4], obtain the return address from the stack and store it into EAX

  ; Function epilogue - perform cleanup and return EAX register value to the calling procedure
  ;leave
  ;mov esp, ebp                 ; ESP = EBP, restore the stack by releasing the local stack frame
  pop ebp                       ; EBP = [ESP] and ESP = ESP + 0x4, restore the caller's base frame pointer
  ret                           ; EIP = [ESP] and ESP = ESP + 0x4, return from procedure
@get_eip@0 ENDP
OPTION LANGUAGE: C

And the corresponding function declaration in a header file to be able to call it from C/C++ code:

extern "C" __declspec(noinline) DWORD __fastcall get_eip(
  void
);

Note that the same function could also be written in C instead of assembly like so:

DWORD __fastcall get_eip(void) {
  return (DWORD)_ReturnAddress();
}

To get EIP register value, we call the above subroutine:

before-call

After the call instruction is executed, retaddr is pushed onto the stack and EIP value is changed to the call target.

after-call

Note that the top of the stack now contains the return address. Also, note that WinDbg stack view displays lower memory addresses higher and higher memory addresses lower and that’s why the stack appears to grow in the upward direction here.

We can read the retaddr from the stack by reading memory at address (base frame pointer + 0x4) and storing it into EAX.

get-retaddr

Finally, when the ret instruction is executed, the retaddr is popped from the stack into EIP.

eip

Note that after returning from the subroutine, EAX now contains the same value as EIP.

Another important thing to note is that using the call $+5; pop eax technique should be avoided altogether to prevent messing up the Return Address Stack(RAS) and causing branch mispredictions on older Pentium Pro processors.