我正在尝试编写一个示例程序来理解堆栈缓冲区溢出,我有以下程序。
overflow.s:
.section .data
.section .text
.globl _start
_start:
call sum
movl %eax, %ebx
movl $15, %ebx
movl $1, %eax
int $0x80
.type sum, @function
sum:
pushl %ebp # save the current base pointer
movl %esp, %ebp # store current stack pointer to %ebp
subl $4, %esp # inc the stack pointer by 4 bytes for local variable
movl $5, -8(%ebp) # store value 5 from 8 bytes of %ebp 4 bytes beyond stack pointer
addl $5, -8(%ebp) # add 5 to the value store beyond of stack pointer
movl -8(%ebp), %eax # store the value in %eax
movl %ebp, %esp
popl %ebp
ret
汇编并链接该程序:
as -gstabs+ overflow.s -o oveflow.o
ld overflow.o -o overflow
./overflow
echo $?
15 <============= the result
我预计我会得到一些垃圾或段错误。但似乎按预期工作。所以在sum函数中,当我将堆栈指针递增4个字节时,当我从基址指针存储5个8字节的值时,我期待这是溢出的模拟。以上程序错误地使用了堆栈缓冲区溢出的示例。 ?
答案 0 :(得分:2)
低于%esp
的内存可能被异步破坏(通过信号处理程序 1 ),但程序的行为并不取决于您读取的值/使用addl $5, -8(%ebp)
或movl -8(%ebp), %eax
写入ESP正下方的4字节堆栈插槽。
在Linux上,触摸ESP下方的内存并不是错误,一直到最大堆栈大小的限制(ulimit -s
,默认为8192 kiB)。如果内存尚未映射,则堆栈映射会自动扩展,以映射其中所有页面与当前映射的堆栈页面之间的最低地址。 (或者,对于线程堆栈,它已经完全分配,而不是动态增长。但是初始进程堆栈是特殊的。)
如果您在循环中运行push
(没有pop
或其他任何东西来平衡它),堆栈将会增长,直到ESP减少到指向未映射的页面超出内核为您增加堆栈的程度。然后,下一个push
(或call
或其他)会出现段错误,我们称之为堆栈溢出。
缓冲区溢出将是sub $12, %esp
为int arr[3]
保留空间,但随后写入int arr[5]
:这将覆盖您的返回地址,最终ret
会跳到攻击者想要你跳的地方。
# arg in EAX: how many array elements to store into arr[3]
vulnerable_function:
sub $12, %esp
mov %esp, %ecx
.Lloop:
mov %eax, (%ecx)
add $4, %ecx
dec %eax
jnz
add $12, %esp
ret
脚注1 : 您还没有安装任何信号处理程序,因此(在Linux上)没有任何东西可以异步使用堆栈内存,并且您可以在ESP下方无限red-zone。
但是当你编写一个完整的程序而不使用任何库时,这是一个特例,通常你应该只是内联小函数,如果预留和释放堆栈空间的额外指令有非常重要的成本。