7
votes

I'm working on writing an exploit to spawn a shell from scratch. (i.e. to be used in a buffer overflow). One of the problems I'm facing is getting the jmp statements to work. My understanding is that the jmp instruction is relative to the ip. However when I try to run the following in inline assembly I get a jump to an absolute address.

jmp 0x28 #in inline GCC will jump to address 0x28 not 0x28 relative to the ip

One way I've gotten around this is to use the IP as part of the instruction like so:

jmp *0x28(%rip) #will jump to address 0x28 relative to the ip

However when I do this I get a segmentation fault on jmp

The entire assembly code is below:

void main() {
    __asm__(
    "jmp  *0x28(%rip)                           \n"
    "popq %rax                              \n" 
    "movw $0x0, 0x0(%rax)       #add null termination           \n"
    "movq %rax,0x8(%rax)        #set up argv in memory          \n"
    "movq $0, 0x10(%rax)                            \n"
    "mov $0x0, %edx         #set up arg 3               \n"
    "mov %rax, %rsi                             \n"
    "add $0x8, %rsi                             \n"
    "mov %rax,%rdi                              \n"
    "mov $0x3b,%eax                             \n"
    "syscall                                \n"
    "call *-0x2e(%rip)                          \n"
    ".string \"/bin/sh\""
    );
}

The disassembled output from GDB is:

Dump of assembler code for function main:
   0x00000000004004ac <+0>: push   %rbp
   0x00000000004004ad <+1>: mov    %rsp,%rbp
   0x00000000004004b0 <+4>: jmpq   *0x28(%rip)        # 0x4004de <main+50>
   0x00000000004004b6 <+10>:    pop    %rax
   0x00000000004004b7 <+11>:    movw   $0x0,(%rax)
   0x00000000004004bc <+16>:    mov    %rax,0x8(%rax)
   0x00000000004004c0 <+20>:    movq   $0x0,0x10(%rax)
   0x00000000004004c8 <+28>:    mov    $0x0,%edx
   0x00000000004004cd <+33>:    mov    %rax,%rsi
   0x00000000004004d0 <+36>:    add    $0x8,%rsi
   0x00000000004004d4 <+40>:    mov    %rax,%rdi
   0x00000000004004d7 <+43>:    mov    $0x3b,%eax
   0x00000000004004dc <+48>:    syscall 
   0x00000000004004de <+50>:    callq  *-0x2e(%rip)        # 0x4004b6 <main+10>
   0x00000000004004e4 <+56>:    (bad)  
   0x00000000004004e5 <+57>:    (bad)  
   0x00000000004004e6 <+58>:    imul   $0x5d006873,0x2f(%rsi),%ebp
   0x00000000004004ed <+65>:    retq   
End of assembler dump.

I get a segfault on the first instruction jmp *0x28(%rip) despite the fact that GDB says it's going to go to the correct address.

What's interesting is that if I place a label before call *-0x2e(%rip) and jmp to that it works. The address will be absolute and the segmentation fault at jmp will not be produced.

C code using label:

void main() {
    __asm__(
    "jmp  my_hack                               \n"
    "popq %rax                              \n" 
    "movw $0x0, 0x0(%rax)       #add null termination           \n"
    "movq %rax,0x8(%rax)        #set up argv in memory          \n"
    "movq $0, 0x10(%rax)                            \n"
    "mov $0x0, %edx         #set up arg 3               \n"
    "mov %rax, %rsi                             \n"
    "add $0x8, %rsi                             \n"
    "mov %rax,%rdi                              \n"
    "mov $0x3b,%eax                             \n"
    "syscall                                \n"
    "my_hack:                               \n"
    "call *-0x2e(%rip)                          \n"
    ".string \"/bin/sh\""
    );
}

Resulting disassembly

Dump of assembler code for function main:
   0x00000000004004ac <+0>: push   %rbp
   0x00000000004004ad <+1>: mov    %rsp,%rbp
   0x00000000004004b0 <+4>: jmp    0x4004da <main+46>
   0x00000000004004b2 <+6>: pop    %rax
   0x00000000004004b3 <+7>: movw   $0x0,(%rax)
   0x00000000004004b8 <+12>:    mov    %rax,0x8(%rax)
   0x00000000004004bc <+16>:    movq   $0x0,0x10(%rax)
   0x00000000004004c4 <+24>:    mov    $0x0,%edx
   0x00000000004004c9 <+29>:    mov    %rax,%rsi
   0x00000000004004cc <+32>:    add    $0x8,%rsi
   0x00000000004004d0 <+36>:    mov    %rax,%rdi
   0x00000000004004d3 <+39>:    mov    $0x3b,%eax
   0x00000000004004d8 <+44>:    syscall 
   0x00000000004004da <+46>:    callq  *-0x2e(%rip)        # 0x4004b2 <main+6>
   0x00000000004004e0 <+52>:    (bad)  
   0x00000000004004e1 <+53>:    (bad)  
   0x00000000004004e2 <+54>:    imul   $0x5d006873,0x2f(%rsi),%ebp
   0x00000000004004e9 <+61>:    retq   
End of assembler dump.

The jump using the label in the above disassemble will not produce an segmentation fault. The call that's executed at 0x00000000004004da will.

Can somebody explain why using the rip in jmp causes the segmentation fault?

How can a relative jump/call be done with GCC inline assembly? I don't know how to check the assembler however I'm pretty sure I'm using GAS (on their wiki it says it's the default GCC assembler). There have been suggestions in related questions to use syntax such as jmp .+0x28 however this will result in being an absolute jump and not a relative jump to the pc.

2

2 Answers

2
votes

I think you have a bit too much indirection. Try

jmp 0x28(%rip)

In assembler, it would be written (approximately) as

jmp   $+0x28

I wrote approximately because the assembly instruction is relative to the instruction's beginning address. But rip is incremented to the next instruction by the time it executes. So to get the same effect something like

jmp   $+0x24     # maybe 0x23, maybe 0x25 depending on the instruction length
1
votes

When you jmp and call to a label, you are using a relative address and not an absolute address. The disassembly you're seeing in GDB may be deceptive, try objdump -D <ELF file> and look for the main segment.

Here's what objdump tells us about your first example.

00000000004004b4 <main>:
  4004b4:       55                      push   %rbp
  4004b5:       48 89 e5                mov    %rsp,%rbp
  4004b8:       ff 25 28 00 00 00       jmpq   *0x28(%rip)        # 4004e6 <main+0x32>
  4004be:       58                      pop    %rax
  4004bf:       66 c7 00 00 00          movw   $0x0,(%rax)
  4004c4:       48 89 40 08             mov    %rax,0x8(%rax)
  4004c8:       48 c7 40 10 00 00 00    movq   $0x0,0x10(%rax)
  4004cf:       00 
  4004d0:       ba 00 00 00 00          mov    $0x0,%edx
  4004d5:       48 89 c6                mov    %rax,%rsi
  4004d8:       48 83 c6 08             add    $0x8,%rsi
  4004dc:       48 89 c7                mov    %rax,%rdi
  4004df:       b8 3b 00 00 00          mov    $0x3b,%eax
  4004e4:       0f 05                   syscall 
  4004e6:       ff 15 d2 ff ff ff       callq  *-0x2e(%rip)        # 4004be <main+0xa>
  4004ec:       2f                      (bad)  
  4004ed:       62                      (bad)  
  4004ee:       69 6e 2f 73 68 00 5d    imul   $0x5d006873,0x2f(%rsi),%ebp
  4004f5:       c3                      retq

The jmp at 0x4004b8 is probably not what you want. It jumps to the address referenced at memory location 0x4004e6; attempts to execute instructions at 0x622fffffffd215ff are likely to throw a page fault. Likewise the call at 0x4004e6 is actually moving the program counter to 0x66580000002825ff resulting in another likely segfault.

I've slightly modified your second example

void main() {
    __asm__(
    "jmp  my_hack                               \n"
    "my_hack2:\n"
    "popq %rax                              \n" 
    "movw $0x0, 0x0(%rax)       #add null termination           \n"
    "movq %rax,0x8(%rax)        #set up argv in memory          \n"
    "movq $0, 0x10(%rax)                            \n"
    "mov $0x0, %edx         #set up arg 3               \n"
    "mov %rax, %rsi                             \n"
    "add $0x8, %rsi                             \n"
    "mov %rax,%rdi                              \n"
    "mov $0x3b,%eax                             \n"
    "syscall                                \n"
    "my_hack:                               \n"
    "call my_hack2                          \n"
    ".string \"/bin/sh\""
    );
}

...and the resulting disassembly from objdump

00000000004004b4 <main>:
  4004b4:       55                      push   %rbp
  4004b5:       48 89 e5                mov    %rsp,%rbp
  4004b8:       eb 28                   jmp    4004e2 <my_hack>

00000000004004ba <my_hack2>:
  4004ba:       58                      pop    %rax
  4004bb:       66 c7 00 00 00          movw   $0x0,(%rax)
  4004c0:       48 89 40 08             mov    %rax,0x8(%rax)
  4004c4:       48 c7 40 10 00 00 00    movq   $0x0,0x10(%rax)
  4004cb:       00 
  4004cc:       ba 00 00 00 00          mov    $0x0,%edx
  4004d1:       48 89 c6                mov    %rax,%rsi
  4004d4:       48 83 c6 08             add    $0x8,%rsi
  4004d8:       48 89 c7                mov    %rax,%rdi
  4004db:       b8 3b 00 00 00          mov    $0x3b,%eax
  4004e0:       0f 05                   syscall 

00000000004004e2 <my_hack>:
  4004e2:       e8 d3 ff ff ff          callq  4004ba <my_hack2>
  4004e7:       2f                      (bad)  
  4004e8:       62                      (bad)  
  4004e9:       69 6e 2f 73 68 00 5d    imul   $0x5d006873,0x2f(%rsi),%ebp
  4004f0:       c3                      retq

Even if you don't know the instruction encoding for jmp and call, hopefully it is obvious that the assembler generated relative addresses for the instructions at 0x4004b8 and 0x4004e2.

Your program still segfaults but hopefully this helps you figure out why.