1

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.

GhostSparkles
  • 101
  • 2
  • 10
  • 32b or 64b? Why don't you post, what bytes are stored at `0x400500`? In 32b mode `call $` is assembled to `e8 fb ff ff ff`, the `call` opcode is `e8`, and following 4 bytes are relative 32 bit offset to `eip` (which points to the next instruction at the moment of address calculation, i.e. the offset is -5 to have `call` to the itself in infinite loop). Guessing from your disassembly the opcode is probably `e8 fb fb ff ff`. – Ped7g Oct 15 '17 at 14:54
  • 64 bit, sorry. Will update the question. Printout of the address where the call happens, e.g 0xfffe66e8. – GhostSparkles Oct 15 '17 at 14:56
  • Not sure what you posted, but I don't see machine code there (`e8` call opcode specifically) That's the address you want to achieve? Or where the `call` resides? Anyway, from a quick test with assembler it looks that even in 64b mode the `call imm32` is calculated the same way as in 32b, i.e. 32 bit signed offset to the current `rip` (which points beyond `call`, i.e. +5). – Ped7g Oct 15 '17 at 14:58
  • Ah, so to change the call address, I would have the change the offset against eip. That makes sense, but wasn't easy to spot in GDB. Thank you. – GhostSparkles Oct 15 '17 at 15:00
  • Pasted the correct output now – GhostSparkles Oct 15 '17 at 15:01
  • `0xfffe66e8` is just 4 bytes, the `call` opcode is 5 bytes as I have in my first comment example. But it starts with `e8` and then let's guess the missing byte is `0xff` too, so the offset is `0xfffffe66`. Add that to the address of call+5, and you have target address. – Ped7g Oct 15 '17 at 15:05

2 Answers2

7

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.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
4

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • Thanks for your answer. With regards to jumping after the call, I'm not sure without looking at the program, but that would depend on a jump instruction being available, right? I can only alter 8 bytes. P.s it SIGSEGVs when it tries to run the first instruction in non executable memory. – GhostSparkles Oct 16 '17 at 07:00
  • @GhostSparkles: No, I'm talking about modifying program state with a debugger, instead of modifying the instructions at all. You can directly set `RIP` to anything you want while stopped at a breakpoint / single-stepping. – Peter Cordes Oct 16 '17 at 07:04