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

Posted

技术标签:

【中文标题】如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用【英文标题】:how to do a relative jump/call with inline assembly in GCC (x86_64) 【发布时间】:2015-03-16 20:11:15 【问题描述】:

我正在编写一个从头开始生成 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 等语法,但这将导致绝对跳转而不是相对跳转。

【问题讨论】:

【参考方案1】:

当您将jmpcall 指向标签时,您使用的是相对地址而不是绝对地址。您在 GDB 中看到的反汇编可能具有欺骗性,请尝试 objdump -D &lt;ELF file&gt; 并查找 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

0x4004b8 上的 jmp 可能不是您想要的。它跳转到内存位置0x4004e6引用的地址;尝试在0x622fffffffd215ff 执行指令可能会引发页面错误。同样,0x4004e6 处的 call 实际上正在将程序计数器移动到 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 的指令编码,也希望很明显,汇编程序为0x4004b80x4004e2 处的指令生成了相对地址。

您的程序仍然存在段错误,但希望这可以帮助您找出原因。

【讨论】:

【参考方案2】:

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

jmp 0x28(%rip)

在汇编程序中,它会(大约)写成

jmp   $+0x28

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

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

【讨论】:

在 GAS 语法中,jmp . + 0x22 组装成 eb 20。 dot 是 NASM $ 的 GAS 等价物。 jmp 0x28(%rip) 仍然有太多间接性:gas 警告:Warning: indirect jmp without '*' 并组装 ff 25 28 00 00 00 jmp QWORD PTR [rip+0x28] 当然好的解决方案是使用本地标签,甚至编号标签,如0: .../call 0b(寻找第一个0:标签倒退1b and 1f in GNU assembly。或者使用.Lfoo%=: 在 inline-asm 块中获取唯一标签。

以上是关于如何在 GCC (x86_64) 中使用内联汇编进行相对跳转/调用的主要内容,如果未能解决你的问题,请参考以下文章

GCC内联汇编中的C数组?

gcc 内联汇编行为异常

x86_64 汇编器的 gcc 错误(操作码 0x83 cmp m64/imm8)

最牛X的GCC 内联汇编

最牛X的GCC 内联汇编

使用 GNU 汇编器在 x86_64 中调用 printf