缓冲区溢出在 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 来尊重 gcc
s 堆栈边界标志。用gdb
调试程序,我注意到int auth
地址是BSP-0x04
。 BSP-0x04
到 BSP-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 栈顶
#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 来尊重 gcc
s 堆栈边界标志。用gdb
调试程序,我注意到int auth
地址是BSP-0x04
。 BSP-0x04
到 BSP-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 栈顶