2012-08-31 44 views
2

我只是瞭解格式化字符串漏洞,這讓我問這個問題調用printf時的堆棧視圖?

考慮下面的簡單程序:

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

現在顯然,這個代碼很容易受到格式化字符串漏洞。即當命令行參數爲%s時,則由於printf彈出一次堆棧,所以會打印值SomeString。

什麼我不明白是當printf的被稱爲

在我腦子裏,我想象中的堆棧是如下的堆棧結構:

增長從左至右----->

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

如果是這種情況,如何向程序中輸入%s,導致s的值被彈出?

(OR)如果我完全錯了層疊結構,請大家指正

回答

4

棧內容很大程度上取決於以下幾點:

  • 的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()「 s參數,從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 

所有這一切是相當哈克和它的不平凡的使其正確工作。

+0

這是一個輝煌的答案,謝謝!我在GCC中用'-mpreferred-stack-boundary = 2'編譯它。有了這個,'%s'作爲argv [1]會給我只是「SomeString」。我正在使用GDB,看到堆棧跟蹤,並且在我的編譯中,%s(argv [1])的ptr和字符串的ptr被存儲,然後在進入printf之前存儲RET ptr。 – asudhak

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 : 
      +--------------+ 
      :    : 

如果您使用 ebp不會被保存在堆棧上。在不同的優化級別上,某些變量可能會消失(被優化),...

其他ABI將函數參數存儲在寄存器中,而不是將它們保存在堆棧上。之後,在調用另一個函數之前,活寄存器可能會溢出到堆棧中。