【问题标题】:Stack View when printf is called?调用 printf 时的堆栈视图?
【发布时间】:2012-08-26 17:31:50
【问题描述】:

我刚刚了解了让我提出这个问题的格式字符串漏洞

考虑以下简单程序:

#include<stdio.h>
void main(int argc, char **argv)
{
char *s="SomeString";
printf(argv[1]);
}

现在很明显,这段代码容易受到格式字符串漏洞的影响。 IE。当命令行参数为 %s 时,会打印 SomeString 的值,因为 printf 会弹出堆栈一次。

我不明白的是调用printf时栈的结构

在我的脑海中,我想象堆栈如下:

从左到右增长----->

main()                                                                  ---> printf()-->
RET to libc_main | address of 's' | current registers| ret ptr to main | ptr to format string| 

如果是这样的话,在程序中输入%s如何导致s的值被弹出?

(或)如果我对堆栈结构完全错误,请纠正我

【问题讨论】:

    标签: c assembly stack calling-convention


    【解决方案1】:

    在 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 将函数参数存储在寄存器中,而不是将它们保存在堆栈中。稍后,在调用另一个函数之前,活动寄存器可能会溢出到堆栈中。

    【讨论】:

      【解决方案2】:

      堆栈内容很大程度上取决于以下内容:

      • CPU
      • 编译器
      • 调用约定(即参数如何在寄存器和堆栈中传递)
      • 编译器执行的代码优化

      这是我通过使用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
      

      所有这些都非常老套,要让它正常工作并非易事。

      【讨论】:

      • 这是一个绝妙的答案,谢谢!我在 GCC 中使用 `-mpreferred-stack-boundary=2' 编译它。有了这个,'%s' as argv[1] 只会给我“SomeString”。我正在使用 GDB,看到堆栈跟踪,在我的编译中,ptr 到 %s (argv[1]) 和字符串的 ptr 被存储,然后 RET ptr 在进入 printf 之前被存储。
      猜你喜欢
      • 2021-03-03
      • 1970-01-01
      • 1970-01-01
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-26
      • 1970-01-01
      相关资源
      最近更新 更多