ARM64 缓冲区溢出 - 无法覆盖 $pc
ARM64 Buffer Overflow-Cannot overwrite $pc
这是源代码。
#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
我该怎么办?
从代码中可以看出,编译器已经将return地址放在缓冲区的下方,所以你不可能覆盖它,不管你写了多少字节。
具体来说,stp x29, x30, [sp, #-96]!
是预减,所以把x29
存放在[sp]
的新地址,x30
里面包含了return ] 地址,位于 [sp, 8]
。另一方面,缓冲区位于 [sp, 32]
(注意 add x0, sp, 0x20
)。这是 ARM64 的典型情况;预减寻址方式方便将return地址存储在栈帧底部,在所有函数局部变量的下方。
您可以改写的是由任何名为 main
的函数保存的 return 地址(在 C 启动代码中的某处,例如 glibc 的 __libc_start_main()
)。只有当其他函数 return 调用它自己的调用者时才会发生这种情况,因此您过早地停止了该程序。
不幸的是,在许多系统上,该功能根本没有 return;它改为调用 exit()
。 That's what glibc's __libc_start_main
does.。因此,除非我遗漏了什么,否则当从 main
中调用 gets
时,这种缓冲区溢出将不会在这样的系统上工作。如果你想玩它,试着写一个不同的程序,从一些子程序调用它:
void other_func(void) {
char buf[64];
gets(buf);
}
int main(void) {
other_func();
return 0;
}
现在你的溢出不会覆盖 other_func()
存储的 return 地址(再次位于缓冲区下方),但它可以覆盖 return 存储的地址 main()
(在堆栈的更上方)。您将获得控制权,不是 other_func()
return 时,而是 main()
return 时。 (即便如此,那是在它的ret
指令执行之后;在ret
指令上放置一个断点main
还为时过早。)
看起来这个练习是针对 x86 的,其中 return 地址通常位于堆栈帧的顶部,因为它由在堆栈帧之前执行的 call
指令保存已成立。在 x86 系统上,您的溢出确实会覆盖 main
保存的 return 地址,并让您控制程序计数器。
这是源代码。
#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
我该怎么办?
从代码中可以看出,编译器已经将return地址放在缓冲区的下方,所以你不可能覆盖它,不管你写了多少字节。
具体来说,stp x29, x30, [sp, #-96]!
是预减,所以把x29
存放在[sp]
的新地址,x30
里面包含了return ] 地址,位于 [sp, 8]
。另一方面,缓冲区位于 [sp, 32]
(注意 add x0, sp, 0x20
)。这是 ARM64 的典型情况;预减寻址方式方便将return地址存储在栈帧底部,在所有函数局部变量的下方。
您可以改写的是由任何名为 main
的函数保存的 return 地址(在 C 启动代码中的某处,例如 glibc 的 __libc_start_main()
)。只有当其他函数 return 调用它自己的调用者时才会发生这种情况,因此您过早地停止了该程序。
不幸的是,在许多系统上,该功能根本没有 return;它改为调用 exit()
。 That's what glibc's __libc_start_main
does.。因此,除非我遗漏了什么,否则当从 main
中调用 gets
时,这种缓冲区溢出将不会在这样的系统上工作。如果你想玩它,试着写一个不同的程序,从一些子程序调用它:
void other_func(void) {
char buf[64];
gets(buf);
}
int main(void) {
other_func();
return 0;
}
现在你的溢出不会覆盖 other_func()
存储的 return 地址(再次位于缓冲区下方),但它可以覆盖 return 存储的地址 main()
(在堆栈的更上方)。您将获得控制权,不是 other_func()
return 时,而是 main()
return 时。 (即便如此,那是在它的ret
指令执行之后;在ret
指令上放置一个断点main
还为时过早。)
看起来这个练习是针对 x86 的,其中 return 地址通常位于堆栈帧的顶部,因为它由在堆栈帧之前执行的 call
指令保存已成立。在 x86 系统上,您的溢出确实会覆盖 main
保存的 return 地址,并让您控制程序计数器。