ARM64 缓冲区溢出 - 无法覆盖 $pc

标签 arm gdb buffer-overflow arm64

这是源代码。

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

void win()
{
  printf("code flow successfully changed\n");
}

int main(int argc, char **argv)
{
  char buffer[64];

  gets(buffer);
}
main 的汇编代码
0x0000000000400604 <+0>:    stp x29, x30, [sp, #-96]!
0x0000000000400608 <+4>:    mov x29, sp
0x000000000040060c <+8>:    str w0, [sp, #28]
0x0000000000400610 <+12>:   str x1, [sp, #16]
0x0000000000400614 <+16>:   add x0, sp, #0x20
0x0000000000400618 <+20>:   bl  0x4004d0 <gets@plt>
0x000000000040061c <+24>:   mov w0, #0x0                    // #0
0x0000000000400620 <+28>:   ldp x29, x30, [sp], #96
0x0000000000400624 <+32>:   ret
win的汇编代码
0x00000000004005e4 <+0>:    stp x29, x30, [sp, #-16]!
0x00000000004005e8 <+4>:    mov x29, sp
0x00000000004005ec <+8>:    adrp    x0, 0x400000
0x00000000004005f0 <+12>:   add x0, x0, #0x6e0
0x00000000004005f4 <+16>:   bl  0x4004c0 <puts@plt>
0x00000000004005f8 <+20>:   nop
0x00000000004005fc <+24>:   ldp x29, x30, [sp], #16
0x0000000000400600 <+28>:   ret
源代码来自 protostar-stack4。据我所知,您必须覆盖 $pc 才能运行 win 功能。所以我试图覆盖 $pc,但我不能。我在 main+32 处设置了一个断点并用 100*a 运行它,但是 $pc 和 $x30 都没有被覆盖。我该怎么做才能覆盖 $pc?我是否走在正确的道路上?请帮忙。
$x0  : 0x0               
$x1  : 0x0000fffff7fb1290  →  0x0000000000000000
$x2  : 0xfbad2288        
$x3  : 0x0000fffff7fae8d0  →  0x00000000fbad2288
$x4  : 0x6161616161616161 ("aaaaaaaa"?)
$x5  : 0x0000fffffffff384  →  0x0000000000000000
$x6  : 0x6161616161616161 ("aaaaaaaa"?)
$x7  : 0x6161616161616161 ("aaaaaaaa"?)
$x8  : 0x6161616161616161 ("aaaaaaaa"?)
$x9  : 0x6161616161616161 ("aaaaaaaa"?)
$x10 : 0x6161616161616161 ("aaaaaaaa"?)
$x11 : 0x6161616161616161 ("aaaaaaaa"?)
$x12 : 0x6161616161616161 ("aaaaaaaa"?)
$x13 : 0x6161616161616161 ("aaaaaaaa"?)
$x14 : 0x6161616161616161 ("aaaaaaaa"?)
$x15 : 0x6161616161616161 ("aaaaaaaa"?)
$x16 : 0x1               
$x17 : 0x6161616161616161 ("aaaaaaaa"?)
$x18 : 0x0               
$x19 : 0x0000000000400630  →  <__libc_csu_init+0> stp x29,  x30,  [sp,  #-64]!
$x20 : 0x0               
$x21 : 0x00000000004004e0  →  <_start+0> mov x29,  #0x0                     // #0
$x22 : 0x0               
$x23 : 0x0               
$x24 : 0x0               
$x25 : 0x0               
$x26 : 0x0               
$x27 : 0x0               
$x28 : 0x0               
$x29 : 0x0000fffffffff360  →  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
$x30 : 0x0000fffff7e62218  →  <__libc_start_main+232> bl 0xfffff7e77d00 <__GI_exit>
$sp  : 0x0000fffffffff360  →  "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
$pc  : 0x0000000000400624  →  <main+32> ret 
$cpsr: [negative ZERO CARRY overflow interrupt fast]
$fpsr: 0x0               
$fpcr: 0x0               
我该怎么办?

最佳答案

从代码中可以看出,编译器已经把返回地址放在了栈上缓冲区的下方,所以不管你写多少字节都不可能覆盖它。
具体来说,stp x29, x30, [sp, #-96]!是 pre-decrement,所以它存储 x29[sp] 的新地址, 和 x30 ,其中包含返回地址,位于 [sp, 8] .另一方面,缓冲区位于 [sp, 32] (注意 add x0, sp, 0x20 )。这是 ARM64 的典型特征;预递减寻址模式可以方便地将返回地址存储在堆栈帧的底部,在所有函数的局部变量下方。
您可以改写的是由任何名为 main 的函数保存的返回地址。 (在 C 启动代码中的某处,例如 glibc 的 __libc_start_main() )。只有当其他函数返回到它自己的调用者时才会发生这种情况,因此您过早地停止了程序。
不幸的是,在许多系统上,该函数根本不返回;它叫 exit()反而。 That's what glibc's __libc_start_main does. .因此,除非我遗漏了什么,当 gets 时,这种缓冲区溢出在这样的系统上将不起作用。从内部调用 main .如果您想使用它,请尝试编写一个不同的程序,从某个子例程调用它:

void other_func(void) {
    char buf[64];
    gets(buf);
}

int main(void) {
    other_func();
    return 0;
}
现在你的溢出不会覆盖 other_func() 存储的返回地址(再次位于缓冲区下方),但它可以覆盖 main() 存储的返回地址(这是堆栈的进一步)。您将获得控制权,而不是在 other_func() 时返回,但当 main()返回。 (即便如此,那是在其 ret 指令执行之后;在来自 retmain 指令上放置断点还为时过早。)
看起来这个练习是为 x86 设计的,其中返回地址通常位于堆栈帧的顶部,因为它由 call 保存。在堆栈帧建立之前执行的指令。在 x86 系统上,溢出确实会覆盖 main 保存的返回地址。 ,并让您控制程序计数器。

关于ARM64 缓冲区溢出 - 无法覆盖 $pc,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68774522/

相关文章:

c++ - 使用调试器单步执行程序需要很长时间

c - 关于缓冲区溢出

c - 如何防止字符串读取 C 中的 CTF 标志

assembly - ARM 组装难题

assembly - 为什么这段汇编代码在管道中有 2 个停顿而不是 1 个?

assembly - 使用 gdb 比较汇编跟踪

gdb - 手动生成 elf 核心转储

arm - 在 M0+ 设备上除以零异常

c - 在基于arm32镜像的容器中,当目录为空时,readdir返回EOVERFLOW

c - 利用 jmp_buf 结构中的缓冲区溢出