GhostSparkles
GhostSparkles

Reputation: 121

Change a 'call' address in memory?

This is for a 64 bit x86 Intel processor.

In GDB I can see:

0x400500 <main+50>: call 0x400100 <somefunc>

Yet, when I inspect the memory at 0x400500 I can't see any references to '0x400100', nor is there anything obvious in the registers nearby.

How does it 'know' where to call. It seems simple but I haven't been able to find the answer.

I'm trying to call, e.g. system with the arguments of the above function, with only limited access to memory. Note that this just for fun, part of a challenge exercise.

Upvotes: 1

Views: 1724

Answers (2)

Peter Cordes
Peter Cordes

Reputation: 364248

Normal call instructions are encoded as call rel32, with a relative displacement instead of absolute. As always, check the instruction-set reference manual entry to learn how instructions are encoded.

To get GDB's built-in disassembly to include the raw machine-code bytes, use disas /r. (Not sure how to get that behaviour for the asm window in layout asm / layout reg.) objdump includes the machine code (in hex) by default. I use alias disas='objdump -drwC -Mintel' in my .bashrc.


In GDB, you don't need to modify the relative displacement encoded in the machine instruction: you could just stepi into the call and them use a jump *0x1234567 GDB command to continue execution somewhere else. Or set $pc = 0x1234567 to change EIP / RIP without also doing a "continue".

See Is it possible to "jump"/"skip" in GDB debugger?

So you let the call instruction push a return address and jump, but then jump somewhere else to pretend like you jumped there in the first place. This might only work if the original target of the call was accessible; I'm not sure if x86 faults just from setting RIP to non-executable memory, or of it won't fault until it tries to run the first instruction.

Upvotes: 4

Employed Russian
Employed Russian

Reputation: 213556

How does it 'know' where to call.

The address to call is an offset from the instruction after the call.

Example:

int foo() { return 42; }
int main() { return foo(); }

gcc -g t.c
gdb -q ./a.out

(gdb) disas/r main
Dump of assembler code for function main:
   0x00000000004004f8 <+0>: 55  push   %rbp
   0x00000000004004f9 <+1>: 48 89 e5    mov    %rsp,%rbp
   0x00000000004004fc <+4>: b8 00 00 00 00  mov    $0x0,%eax
   0x0000000000400501 <+9>: e8 e7 ff ff ff  callq  0x4004ed <foo>
   0x0000000000400506 <+14>:    5d  pop    %rbp
   0x0000000000400507 <+15>:    c3  retq
End of assembler dump.

(gdb) p &foo
$1 = (int (*)()) 0x4004ed <foo>

(gdb) p/x 0x4004ed - 0x0000000000400506
$2 = 0xffffffe7

Note the e7 ff ff ff bytes which are part of the callq. That's the offset, spelled in little-endian.

Upvotes: 7

Related Questions