缓冲区溢出在 x86 上需要 16 个字节,但在 x64 上需要 29 个字节

Buffer overflow needs 16 bytes on x86 but 29 bytes on x64

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

int main(int argc, char **argv) {
  char buff[15];
  int auth = 0;

  printf("\nEnter password: ");
  gets(buff);

  if (strcmp(buff, "password") != 0) {
    printf("\nAccess denied\n");
  } else {
    auth = 1;
  }

  if (auth) {
    printf("\nAccess granted\n");
  }

  return 0;
}

这段代码需要16个字节(用户输入的字符)才能在x86上溢出auth并打印"Access granted"。在 x64 上,需要 29 个字节才能执行相同的操作。为什么是这样?似乎我的变量之间有一些填充,或者它们之间有其他东西的地址。我不相信这是影子 space 的效果(这也适用于 *nix 吗?)因为只保留前 32 个字节 https://en.wikipedia.org/wiki/X86_calling_conventions#Microsoft_x64_calling_convention

请注意,我没有通过任何优化来编译它以避免寄存器内部的东西。

我在 OS X 上使用 GCC 6.2.0。这是 x86 版本的汇编输出:

    .cstring
LC0:
    .ascii "Enter password: [=11=]"
LC1:
    .ascii "password[=11=]"
LC2:
    .ascii "Access denied[=11=]"
LC3:
    .ascii "Access granted[=11=]"
    .text
    .globl _main
_main:
LFB1:
    pushl   %ebp
LCFI0:
    movl    %esp, %ebp
LCFI1:
    pushl   %ebx
    subl    , %esp
LCFI2:
    call    ___x86.get_pc_thunk.bx
L1$pb:
    movl    [=11=], -12(%ebp)
    subl    , %esp
    leal    LC0-L1$pb(%ebx), %eax
    pushl   %eax
    call    _printf
    addl    , %esp
    subl    , %esp
    leal    -27(%ebp), %eax
    pushl   %eax
    call    _gets
    addl    , %esp
    subl    , %esp
    leal    LC1-L1$pb(%ebx), %eax
    pushl   %eax
    leal    -27(%ebp), %eax
    pushl   %eax
    call    _strcmp
    addl    , %esp
    testl   %eax, %eax
    je  L2
    subl    , %esp
    leal    LC2-L1$pb(%ebx), %eax
    pushl   %eax
    call    _puts
    addl    , %esp
    jmp L3
L2:
    movl    , -12(%ebp)
L3:
    cmpl    [=11=], -12(%ebp)
    je  L4
    subl    , %esp
    leal    LC3-L1$pb(%ebx), %eax
    pushl   %eax
    call    _puts
    addl    , %esp
L4:
    movl    [=11=], %eax
    movl    -4(%ebp), %ebx
    leave
LCFI3:
    ret
LFE1:
    .section __TEXT,__textcoal_nt,coalesced,pure_instructions
    .weak_definition    ___x86.get_pc_thunk.bx
    .private_extern ___x86.get_pc_thunk.bx
___x86.get_pc_thunk.bx:
LFB2:
    movl    (%esp), %ebx
    ret
LFE2:
    .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
    .set L$set[=11=],LECIE1-LSCIE1
    .long L$set[=11=]
LSCIE1:
    .long   0
    .byte   0x1
    .ascii "zR[=11=]"
    .byte   0x1
    .byte   0x7c
    .byte   0x8
    .byte   0x1
    .byte   0x10
    .byte   0xc
    .byte   0x5
    .byte   0x4
    .byte   0x88
    .byte   0x1
    .align 2
LECIE1:
LSFDE1:
    .set L$set,LEFDE1-LASFDE1
    .long L$set
LASFDE1:
    .long   LASFDE1-EH_frame1
    .long   LFB1-.
    .set L$set,LFE1-LFB1
    .long L$set
    .byte   0
    .byte   0x4
    .set L$set,LCFI0-LFB1
    .long L$set
    .byte   0xe
    .byte   0x8
    .byte   0x84
    .byte   0x2
    .byte   0x4
    .set L$set,LCFI1-LCFI0
    .long L$set
    .byte   0xd
    .byte   0x4
    .byte   0x4
    .set L$set,LCFI2-LCFI1
    .long L$set
    .byte   0x83
    .byte   0x3
    .byte   0x4
    .set L$set,LCFI3-LCFI2
    .long L$set
    .byte   0xc4
    .byte   0xc3
    .byte   0xc
    .byte   0x5
    .byte   0x4
    .align 2
LEFDE1:
LSFDE3:
    .set L$set,LEFDE3-LASFDE3
    .long L$set
LASFDE3:
    .long   LASFDE3-EH_frame1
    .long   LFB2-.
    .set L$set,LFE2-LFB2
    .long L$set
    .byte   0
    .align 2
LEFDE3:
    .subsections_via_symbols

对于 x64 版本:

    .cstring
LC0:
    .ascii "Enter password: [=12=]"
LC1:
    .ascii "password[=12=]"
LC2:
    .ascii "Access denied[=12=]"
LC3:
    .ascii "Access granted[=12=]"
    .text
    .globl _main
_main:
LFB1:
    pushq   %rbp
LCFI0:
    movq    %rsp, %rbp
LCFI1:
    subq    , %rsp
    movl    %edi, -36(%rbp)
    movq    %rsi, -48(%rbp)
    movl    [=12=], -4(%rbp)
    leaq    LC0(%rip), %rdi
    movl    [=12=], %eax
    call    _printf
    leaq    -32(%rbp), %rax
    movq    %rax, %rdi
    call    _gets
    leaq    -32(%rbp), %rax
    leaq    LC1(%rip), %rsi
    movq    %rax, %rdi
    call    _strcmp
    testl   %eax, %eax
    je  L2
    leaq    LC2(%rip), %rdi
    call    _puts
    jmp L3
L2:
    movl    , -4(%rbp)
L3:
    cmpl    [=12=], -4(%rbp)
    je  L4
    leaq    LC3(%rip), %rdi
    call    _puts
L4:
    movl    [=12=], %eax
    leave
LCFI2:
    ret
LFE1:
    .section __TEXT,__eh_frame,coalesced,no_toc+strip_static_syms+live_support
EH_frame1:
    .set L$set[=12=],LECIE1-LSCIE1
    .long L$set[=12=]
LSCIE1:
    .long   0
    .byte   0x1
    .ascii "zR[=12=]"
    .byte   0x1
    .byte   0x78
    .byte   0x10
    .byte   0x1
    .byte   0x10
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .byte   0x90
    .byte   0x1
    .align 3
LECIE1:
LSFDE1:
    .set L$set,LEFDE1-LASFDE1
    .long L$set
LASFDE1:
    .long   LASFDE1-EH_frame1
    .quad   LFB1-.
    .set L$set,LFE1-LFB1
    .quad L$set
    .byte   0
    .byte   0x4
    .set L$set,LCFI0-LFB1
    .long L$set
    .byte   0xe
    .byte   0x10
    .byte   0x86
    .byte   0x2
    .byte   0x4
    .set L$set,LCFI1-LCFI0
    .long L$set
    .byte   0xd
    .byte   0x6
    .byte   0x4
    .set L$set,LCFI2-LCFI1
    .long L$set
    .byte   0xc
    .byte   0x7
    .byte   0x8
    .align 3
LEFDE1:
    .subsections_via_symbols

我又在想这个问题,想起了我在聊天中发给你的编译器标志:

-mpreferred-stack-boundary=num

特别是以下段落:

Attempt to keep the stack boundary aligned to a 2 raised to num byte boundary. If -mpreferred-stack-boundary is not specified, the default is 4 (16 bytes or 128 bits).

Warning: When generating code for the x86-64 architecture with SSE extensions disabled, -mpreferred-stack-boundary=3 can be used to keep the stack boundary aligned to 8 byte boundary. Since x86-64 ABI require 16 byte stack alignment, this is ABI incompatible and intended to be used in controlled environment where stack space is important limitation.

因此,15 字节数组需要 16 字节 stack-slot 才能将其保存在堆栈边界内。

int auth,假设一个 4 字节 int,也需要一个 16 字节 stack-slot 来尊重 gccs 堆栈边界标志。用gdb调试程序,我注意到int auth地址是BSP-0x04BSP-0x04BSP-0x10 是填充,所以它适合 stack-slot。因此,要溢出缓冲区直到到达 int auth,您需要 15 个字节(缓冲区大小)+ 1 个字节(缓冲区填充)+ 12 个字节(int auth 填充)+ 1 个字节才能到达 int.

最后,我跟你提过,我在调试的时候发现了数组和int之间的地址。这可能是内存中残留的一些垃圾:由于程序不关心 int 之前的额外 12 个字节,它可能不会清理它并且它可能被用来存储一些内存指针。

下面是 x64 代码程序堆栈的表示。

主堆栈

-------------------------------------------- --------
保存的 RBP
---------------------------------------------- ---- 新的 RBP = 0x7FFFFFFFFDD10
授权
---------------------------------------------- ---- RBP - 0x04 = 0x7FFFFFFFFDD0C
auth 12 字节填充
---------------------------------------------- ---- RBP - 0x10 = 0x7FFFFFFFDD00
buff 1字节填充
---------------------------------------------- ---- RBP - 0x11 = 0x7FFFFFFFDCFF
增益
---------------------------------------------- ---- RBP - 0x20 = 0x7FFFFFFFDCF0
EDI 值
---------------------------------------------- ---- RBP - 0x24 = 0x7FFFFFFFDCEC
RSI 值
---------------------------------------------- ---- RBP - 0x30 = 0x7FFFFFFFDCE0 栈顶