2

I was just learning about format string vulnerabilities that makes me ask this question

Consider the following simple program:

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

Now clearly, this code is vulnerable to a format String vulnerability. I.e. when the command line argument is %s, then the value SomeString is printed since printf pops the stack once.

What I dont understand is the structure of the stack when printf is called

In my head I imagine the stack to be as follows:

grows from left to right ----->

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

if this is the case, how does inputting %s to the program, cause the value of s to be popped ?

(OR) If I am totally wrong about the stack structure , please correct me

4

2 に答える 2

4

スタックの内容は、次のものに大きく依存します。

  • CPU
  • コンパイラ
  • 呼び出し規則 (つまり、パラメーターがレジスターおよびスタックでどのように渡されるか)
  • コンパイラによって実行されるコードの最適化

これは、次を使用して x86 mingw で小さなプログラムをコンパイルすることで得られるものですgcc stk.c -S -o stk.s

        .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

2 つのアドレスをスタックに次々に格納するには、それが必要な場合は、コード、コンパイル/最適化オプションを操作するか、別のコンパイラを使用する必要があります。

または、それに到達するargv[1]ようなフォーマット文字列を指定することもできます。printf()たとえば、フォーマット文字列にいくつかの偽のパラメーターを含めることができます。

たとえば、このコードを を使用してコンパイルしgcc stk.c -o stk.exe、 として実行するとstk.exe %u%u%u%u%u%u%s、次の出力が得られます。

4200532268676042006264200532880015253SomeString

これはすべてかなりハックであり、正しく機能させるのは簡単ではありません。

于 2012-09-01T04:52:54.390 に答える
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 は、関数の引数をスタックに保存するのではなく、レジスタに保存します。後で、別の関数を呼び出す前に、ライブ レジスタがスタックにスピルすることがあります。

于 2012-09-01T10:15:09.060 に答える