我刚学习格式字符串漏洞让我问这个问题
考虑以下简单程序:
#include<stdio.h>
void main(int argc, char **argv)
{
char *s="SomeString";
printf(argv[1]);
}
现在显然,此代码容易受到格式String漏洞的攻击。即当命令行参数为%s时,则打印值SomeString,因为printf弹出堆栈一次。
我不理解的是当调用printf时堆栈的结构
在我脑海中,我想象堆栈如下:
从左到右增长-----&gt;
main() ---> printf()-->
RET to libc_main | address of 's' | current registers| ret ptr to main | ptr to format string|
如果是这种情况,如何输入%s到程序,导致s的值被弹出?
(或)如果我对堆栈结构完全错误,请纠正我
答案 0 :(得分:4)
堆栈内容在很大程度上取决于以下内容:
这是我使用gcc stk.c -S -o stk.s
编译x86 mingw的小程序所得到的:
.file "stk.c"
.def ___main; .scl 2; .type 32; .endef
.section .rdata,"dr"
LC0:
.ascii "SomeString\0"
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB6:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $32, %esp
call ___main
movl $LC0, 28(%esp)
movl 12(%ebp), %eax
addl $4, %eax
movl (%eax), %eax
movl %eax, (%esp)
call _printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE6:
.def _printf; .scl 2; .type 32; .endef
这就是我使用gcc stk.c -S -O2 -o stk.s
获得的,即启用了优化:
.file "stk.c"
.def ___main; .scl 2; .type 32; .endef
.section .text.startup,"x"
.p2align 2,,3
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
LFB7:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
andl $-16, %esp
subl $16, %esp
call ___main
movl 12(%ebp), %eax
movl 4(%eax), %eax
movl %eax, (%esp)
call _printf
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc
LFE7:
.def _printf; .scl 2; .type 32; .endef
正如您所看到的,在后一种情况下,堆栈上没有指向“SomeString”的指针。实际上,字符串甚至不在编译代码中。
在这个简单的代码中,堆栈中没有保存寄存器,因为没有任何变量分配给需要在printf()
调用期间保留的寄存器。
所以,你在这里得到的唯一东西是字符串指针(可选),由于堆栈对齐而导致的未使用空间(andl $-16, %esp
+ subl $32, %esp
对齐堆栈并为局部变量分配空间,这里没有),printf()
的参数,从printf()
返回main()
的返回地址。
在前一种情况下,指向“SomeString”的指针和printf()
的参数(argv[1]
的值)相距很远:
movl $LC0, 28(%esp) ; address of "SomeString" is at esp+28
movl 12(%ebp), %eax
addl $4, %eax
movl (%eax), %eax
movl %eax, (%esp) ; address of a copy of argv[1] is at esp
call _printf
要使两个地址在堆栈中一个接一个地存储,如果这是你想要的,你需要使用代码,编译/优化选项或使用不同的编译器。
或者您可以在argv[1]
中提供格式字符串,以便printf()
能够到达它。例如,您可以在格式字符串中包含许多伪参数。
例如,如果我使用gcc stk.c -o stk.exe
编译这段代码并将其作为stk.exe %u%u%u%u%u%u%s
运行,我将从中获得以下输出:
4200532268676042006264200532880015253SomeString
所有这一切都非常糟糕,让它正常工作并非易事。
答案 1 :(得分:0)
在x86上,函数调用的堆栈可能类似于:
: :
+--------------+
: alignment :
+--------------+
12(%ebp) | arg2 |
+--------------+
8(%ebp) | arg1 |
+--------------+
4(%ebp) | ret | -----> return address
+--------------+
(%ebp) | ebp | -----> previous ebp value
+--------------+
-4(%ebp) | local1 | -----> local vars, sometimes they can overflow ;-)
+--------------+
: alignment :
+--------------+
: :
如果使用-fomit-frame-pointer
ebp
则不会保存在堆栈中。在不同的优化级别,一些变量可能会消失(优化出来),......
其他ABI在寄存器上存储函数参数,而不是将它们保存在堆栈中。之后,在调用另一个函数之前,实时寄存器可能会溢出到堆栈中。