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

问题描述 投票:0回答:2

这是源代码。

#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               

我该怎么办?

arm gdb buffer-overflow arm64
2个回答
5
投票

从代码中可以看到,编译器已将返回地址低于放在堆栈上的缓冲区中,因此无论你写入多少字节,都不可能覆盖它。

具体来说,

stp x29, x30, [sp, #-96]!
是预减的,因此它将
x29
存储在
[sp]
的新地址,并将包含返回地址的
x30
存储在
[sp, 8]
。 另一方面,缓冲区位于
[sp, 32]
(注意
add x0, sp, 0x20
)。 这是 ARM64 的典型情况;预自减寻址模式可以方便地将返回地址存储在堆栈帧的底部,位于所有函数的局部变量下方。

您可以覆盖的是由任何名为

main
的函数(C 启动代码中的某个位置,例如 glibc 的
__libc_start_main()
)保存的返回地址。 只有当其他函数返回到其自己的调用者时才会发生这种情况,因此您太早停止了程序。

不幸的是,在许多系统上,该函数根本不返回;它改为调用

exit()
这就是 glibc 的
__libc_start_main
所做的。
。 因此,除非我遗漏了一些东西,否则当从
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
保存的返回地址,并让您控制程序计数器。
    


0
投票
the_function_called_by_main()

的返回地址,但实际上如果你想模拟缓冲区溢出攻击,我们可以覆盖

main()
的返回地址。
这是我的 BOF.c 代码:

#include <stdlib.h> #include <stdio.h> #include <string.h> int bufferOverflow(const char * str) { char buffer[12]; /* This line has a buffer overflow vulnerability. */ strcpy(buffer, str); return 1; } int main(int argc, char ** argv) { char aString[512]; FILE *badfile; printf("Buffer overflow vulnerability starting up...\n"); badfile = fopen("badfile", "r"); fread(aString, sizeof(char), 512, badfile); bufferOverflow(aString); printf("bufferOverflow() function returned\n"); return 1; }

输入 badfile 由以下几行创建:

for (int i = 0; i < 512; i++) { buffer[i] = i+1; } // to locate the return address buffer[24] = 0x01; buffer[25] = 0x23; buffer[26] = 0x45; buffer[27] = 0x67;

现在我们使用gdb(这里我使用
gef

)来看看如果我运行这个程序会发生什么。我设置了break main,然后设置了

run
,我们可以看到现在$x30包含了
main()
的返回地址,并且它已经被存储到$sp+8,在我的例子中是
0x0000ffffffffe908
。每次调用另一个函数时都会执行此存储方法,并且返回地址将在返回给调用者之前加载回 $x30。

截图1:破坏main

进入

bufferOverflow()

,我们可以看到该函数的返回地址已被存储到

0x0000ffffffffe8f8
中,并且缓冲区从
0x0000ffffffffe8f0
开始向更高的地址写入。结果,
bufferOverflow()
的返回地址不会被覆盖,但
main()
的返回地址会被覆盖。我们可以在截图中看到我们的
0x0000ffffffffe908
已经变成了badfile中指定的
0x201f1e1d67452301

截图2:bufferOverflow()内部

© www.soinside.com 2019 - 2024. All rights reserved.