# IDA-Pro steals RIP — introduction in relative addressing

intraarterial injection: i was involved into a project on design a software-level protection, based on anti-dbg tricks that should work in 32- and 64-bit environment causing no conflict with legal apps. also, my shell-code locator has to learn how to recognize x86-64 exploits, so… I took a deep breath and dived into 64-bit word. well, I’m not newbit here, but digging up the anti-dbg tricks working everywhere sounds like a challenge. ok, anti-dbg tricks, shell-codes… good point to begin with.

kotal: x86 does not allow to address EIP register directly (PDP-11 does), but supports relative addressing in the flow control commands (“the” means “all”), for example: CALL L1 it’s a relative call. in the machine representation it looks like: E8 61 06 00 00, where E8h is opcode of CALL and 61 06 00 00 – a relative 32bit signed offset of the target, calculated from the _end_ of the CALL.

it’s very important for shell-codes, because it gives them ability to work being loaded at any offset. for protections it’s useful well. to prevent dumping – just allocate the memory on the heap and copy your procedure there. no dumper is able to create a workable PE image out of heap!

drawbacks: aside of benefits of relative addressing it has its own disadvantages. guess, what happens if we copy our function which calls the function we can’t copy (for example, API). the delta between CALL and the target will be changed, forcing us to recalculate all relative addresses, or… (turn your mind on) start to use absolute addressing, for example: mov eax, offset API_func/CALL eax;

home and dry: x86-64 does not allow to use RIP (former EIP) as a general purpose register (MOV RAX, RIP does not work), but it supports relative addressing almost everywhere (let me to quite the Intel manuals:”RIP-relative addressing allows specific ModR/M modes to address memory relative to
the 64-bit RIP using a signed 32-bit displacement. This provides an offset range of -/+2GB from the RIP
“). what it does mean?! for shell-code writers it means a lot!!! from now on we don’t need in GetPC subroutine (usually, CALL L1/L1:POP r32) and can use RIP directly. and this is the part where we meet the problem of the stolen RIP.

anaphylactic shock: please, consider the following code. this is how IDA-Pro 5.5 disassembles it. remember: it’s a piece of a real shell-code, so, concentrate your mind into fuming acid and do not miss the point (see the picture bellow as well):

.code:0000000000401000 start proc near
.code:0000000000401000 mov ecx, 69h
.code:0000000000401005 jmp short loc_40100C
.code:0000000000401007 loc_401007:
.code:0000000000401007 nop
.code:0000000000401008 xor [eax+ecx], cl
.code:000000000040100C loc_40100C:
.code:000000000040100C lea rax, loc_401013
.code:0000000000401013 loc_401013:
.code:0000000000401013 loop loc_401007
.code:0000000000401015 mov r9d, 0

how do you like it?! ok, let me to be more specific. how do you like the line: “lea rax, loc_401013″?! what?! did you say: “looks clear!” hello no!!! look closely!!! Option -> Text representation -> Number of opcode bytes -> 9. do you see _now_ what IDA-Pro hides from us?!

.code:000000000040100C 48 8D 05 00 00 00 00 lea rax, loc_401013
.code:0000000000401013 loc_401013:

oh, my unholy cow!!! “LEA RAX, loc_401013” turns out to be “LEA RAX, [RIP]“, thus we’re dealing with position-independent code. in a way, IDA-Pro is correct. she is just calculates RIP on the fly and replaces it by the effective offset. but, we – hackers – want to know if the code is position independent or not!!!

breakdown: HIEW also replaces RIP by effective offset. please consider the following line: 0040100C: 488D05000000001 LEA RAX, [000401013]

ok, do you want to get high? well, let’s do it, ppl!

00000000: 488D0500000000 lea rax,[7]
00000007: 488D0500000000 lea rax,[00000000E]
0000000E: 488D0500000000 lea rax,[000000015]

the same opcodes produce different targets, how funny!!! of course, it’s an opcode of LEA RAX, [RIP] command and I would like to have an option which enables/disables showing RIP, because I do need in very much!!!

updated: Igor Skochinsky pointed out (see his comment below) that IDA-Pro allows us to show RIP (Options -> Analysis options -> Processor specific analysis options -> Explicit RIP-addressing). ok, lets enable it and see what happens:

.code:000000000040100C loc_40100C: ; CODE XREF: start+5j
.code:000000000040100C lea rax, [rip+0]

well, say hello to “RIP”! it’s explicated now, but… the rest of the code is almost damaged and unvoyageable (means: inconvenient for navigation):

.code:000000000040101B lea r8, [rip+0FDEh] ; “x86-64 program!”
.code:0000000000401022 lea rdx, [rip+0FEEh] ; “hello world!”
.code:0000000000401029 mov rcx, 0 ; hWnd
.code:0000000000401030 call qword ptr [rip+2016h]
.code:0000000000401036 mov ecx, eax ; uExitCode

we see relative offsets like 0FDEh, 0FEEh, 2016h, etc. they’re red colored (means: IDA-Pro does not recognize these offsets) and if we move cursor to the constant – we can’t jump by ENTER and we need to calculate the target address manually. so, the problem is still unsolved.

in passing: look at the encoder again. don’t you think that it damages the loop?! ok, lets trace the code with any debugger or with our own mind if we have no 64-bit debugger under our hands.

“loop loc_401007″ has E2h F2h opcode. in binary representation F2h is “011110010″, so the lowest bit is zero, thus, when ECX = = 1, the target of loop will change from 401007h to 401008h (401007h ^ 1 = 401008h). as result – NOP will be skipped. of course, it might be INC EBX (opcode 43h) – in that case, EBX would be increased not by ECX (as it’s expected), but by (ECX – 1). how interesting…

well, when ECX = = 0, LOOP just does not pass the control to the target, so everything works fine.

updated: Sol_Ksacap (from www.wasm.ru) pointed out that (let me to quote him): “the target of loop will indeed change, but there won’t be any loop – “loop” instruction first decreases RCX, and only then checks if it’s zero“. and he is definitely right. this post was written in hurry. sorry for the mistakes I made and big thanks all guys who pointed it out.

off the record: in normal shell-codes you probably meet something like LEA EAX, [RIP-1] (opcode: 8B05FFFFFFFF), since commands with the positive offsets have zeros in opcodes and shell-codes do not like zeros very much (because of ASCIIZ, where Zero is a string terminator).

updated on:
Wed, Juli-15: enable-RIP option in IDA-Pro, loop patching bugs;

an example of real 64bit shell-code with hidden RIP

an example of real 64bit shell-code with hidden RIP

Tags: , , , ,  


  1. You have to use a custom disassembler. Because IDA/HIEW are “static” disassemblers (different purpose), you need a “dynamic” one. I use also custom one for my investigations, primarily because of multi-platform support: Win, Lin, Mac, Sun.

  2. “I would like to have an option which enables/disables showing RIP, because I do need in very much!!!”
    …and you have it:

  3. Hi there. Seems like this post was written in a rush or something – there’re some glitches which just needs to be pointed out (ftgj, lol):

    >.code:0000000000401008 xor [eax+eax], cl
    You mean “xor [eax + eCx], cl”, of course.

    >401007h ^ 1 = 401008h
    Something just not right here, yep.

    >when ECX = = 1, the target of loop will change from 401007h to 401008h
    The target of loop will indeed change, but there won’t be any loop — “loop” instruction first decreases RCX, and only then checks if it’s zero.

Leave a comment

Comments are closed.