GetPC (get Program Counter, also known as GetEIP on x86) code is code that determines its own location in a process' address space. It is commonly used in code that needs to reference itself, for instance in self-decoding and self-modifying code.
The easiest way to implement GetPC code on x86 is using the
CALL instruction, like so:
$+0: E8 00000000 CALL $+5 ; PUSH $+5 onto the stack $+5: 59 POP ECX ; ECX = $+5 $+6: ...shellcode...
Because the argument to the CALL instruction is the offset of the code to call, in this case 0, this code contains null bytes. Unfortunately, it is often not possible or practical to inject null bytes into a target process during exploitation. A common trick to get around this is to make the
CALL point to a lower address address, making the offset negative, like so:
$+0 EB XX JMP SHORT $+N ; Jump to the call instruction $+5: 59 POP ECX ; ECX = $+N+5 $+6: ...shellcode... $+N: E8 FFFFFFXX CALL $+5 ; PUSH $+N+5 onto the stack and jump back to $+5
As you can see, this code is null free but this code has two drawbacks:
- It is 2 bytes larger than the first example.
- The shellcode between the POP and CALL instruction can only be 126 bytes, if you need more room, you will have to jump over the call and put the remainder after it.
There is a second trick to get around these two problems:
$+0 EB FFFFFFFF CALL $+4 ; PUSH $+5 onto the stack and jump to $+4 $+5: C8 59XX XX ENTER XX59,XX ; Does not get executed like this; see below.
When this code executes, the CALL will jump to the last byte of its own instruction. The code executed after the CALL will therefore be:
$+4: FFC8 DEC ECX ; Does nothing useful; can be considered a NOP. $+6: 59 POP ECX ; ECX = $+5 $+7: ...shellcode...
The above code is only 1 byte larger than the first GetPC and puts no constraints of the shellcode following it.
A way to retrieve the value of EIP on x86 systems is to use the x87
FSTENV instruction to store the state of the x87 floating point chip after issuing a
FLDZ instruction. The structure store in memory by
FSTENV will then contain the address of the
FLDZ instruction at offset 0x0C of the structure:
$+0 D9EE FLDZ ; Floating point stores $+0 in its environment $+2 D974E4 F4 FSTENV SS:[ESP-0xC] ; Save environment at ESP-0xC; now [ESP] = $+0 $+6 59 POP ECX ; ECX = $+0
This method is also null free and uses the same amount of bytes as the last
A third way to write GetPC code is to use the Windows specific Structure Exception Handler (SEH). When an exception happens in a process on Windows, a structured exception is created by the operating system and passed to the first SEH handler to see if it can handle it. If not, the second SEH handler is asked, etc... if no SEH handler handles the exception, the program is terminated because of an unhandled exception. Whenever a structure exception handler is called, it is passed (pointers to) information about the exception on the stack. This information includes the address in memory of the instruction that caused the exception.
It is relatively simple to register a structured exception handler with the OS. If a shellcode can generate a small structured exception handler at a known location in memory and register it before causing an exception, this small structured exception handler would get executed. It could determine the address of the instruction that caused the exception and jump back to the next instruction (the one immediately following it). The code would then continue as normal, only now knowing exactly where it is.[todo:Write an example]
Because the SEH was abused heavily by exploits for various purposes, Microsoft has introduced additional checks on the SEH as mitigation against this abuse with each service pack of Windows XP. These have made writing working SEH GetPC increasingly harder. On Windows Vista, it seems these mitigation make it impractical of not impossible to write working SEH GetPC code. However, it is still possible to write SEH GetPC code on Windows XP sp3. It's even possible to do so using only alphanumeric instructions, which is used in ALPHA3 to create alphanumeric SEH GetPC code.