web-dev-qa-db-ja.com

スタックを表示する

私は最近、バッファオーバーフローとその機能について学び始めました。誰かがバイナリを共有して練習しました(VMでは心配しないでください)。私はバイナリが開くソケットに文字列を供給してきましたが、ある長さでは、文字列が原因でプログラムが本来のメッセージで応答しないことに気付きました。また、特定の長さの別の文字列をフィードすると、メッセージの一部がサーバーからソケットを介して返されますが、他の部分はサーバー側のコンソールに出力されるだけです。これの原因は完全にはわかりません(これはこの投稿の公式の質問ではありませんが、コメントで回答を聞きたいです)。

そして、それが私の質問につながります:スタックのイメージを生成したり、それをダンプしたりできるアプリケーションはありますか、そして一般的に何が書き込まれますか?異なる長さのソケット文字列をフィードしたときに何が起こっているかを確認するのに本当に役立つと思います。スタックの各「セクション」のサイズ(それが何と呼ばれるかわからない)が他のセクションに相対的なサイズで画像に表されている場合は、それが好きです(したがって、スタックのサイズを視覚化できます) 、または読みやすい方法で。

あなたの答えが画像の生成に関するものであれば、このようなものは素晴らしいでしょうが、書き込まれた量が示されていればそれはいいでしょう(あふれているときに確認できるように)。

stack image

プログラムを起動したときに画像を生成し、ソケットに巨大な値を供給した後でしょう。次に、比較します。他の方法やより良い学習方法がある場合は、ぜひ聞いてください。

編集#1:私はブラックボックステストです。

編集#2:この質問への回答は既に認められていますが、他の回答にも感謝します。より多くの情報と反応、より多くを学ぶことができます。したがって、私は(当然の場合)賞金で新しい答えに報酬を与えます。感謝します!

14
Aaron Esau

簡単な方法でメモリのダンプを取得する

脆弱なプロセスにSIGSEGV(kill -SEGV pid)を送信するだけで、コアダンプが許可されている場合(ulimit -c unlimited)、すべてのメモリが含まれているNiceコアダンプファイルを取得できます。

例:

ターミナル#1:

/tmp$ ./test 
idling...
idling...
Segmentation fault <---- HERE I SEND THE 1st SIGSEGV
/tmp$ ulimit -c unlimited
/tmp$ ./test 
idling...
idling...
Segmentation fault (core dumped) <---- HERE IS THE 2d SIGSEGV
/tmp$ ls test
test    test.c  
/tmp$ ls -lah core 
-rw------- 1 1000 1000 252K Oct 10 17:42 core

ターミナル#2

/tmp$ ps aux|grep test
1000  6529  0.0  0.0   4080   644 pts/1    S+   17:42   0:00 ./test
1000  6538  0.0  0.0  12732  2108 pts/2    S+   17:42   0:00 grep test
/tmp$ kill -SEGV 6529
/tmp$ ps aux|grep test
1000  6539  0.0  0.0   4080   648 pts/1    S+   17:42   0:00 ./test
1000  6542  0.0  0.0  12732  2224 pts/2    S+   17:42   0:00 grep test
/tmp$ kill -SEGV 6539

これにより、バイナリがSIGSEGVを取得した時点での状態のダンプが得られることに注意してください。したがって、バイナリがmain()とevil_function()で構成されていて、SIGSEVの受信中にプログラムがevil_function()を実行していた場合、evil_function()のスタックを取得します。しかし、main()スタックに戻るために周りを調べることもできます。

アレフワンペーパーに関するすべての良い指針: http://insecure.org/stf/smashstack.html

自分で「マッピング」を推測する

このコードスニペットのように、バイナリが基本的なバッファオーバーフローを実装していると想像すると:

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


int evil_function(char *evil_input)
{
    char stack_buffer[10];
    strcpy(stack_buffer, evil_input);
    printf("input is: %s\n", stack_buffer);
    return 0;
}


int main (int ac, char **av)
{
    if (ac != 2) 
    {
        printf("Wrong parameter count.\nUsage: %s: <string>\n",av[0]);
        return EXIT_FAILURE;
    }
    evil_function(av[1]);

    return (EXIT_SUCCESS);
}

Gdbを使用するだけでバッファアドレスをどこに書き込むかを推測するのは非常に簡単です。上記のサンプルプログラムを試してみましょう:

/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x10")
input is: AAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x11")
input is: AAAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x12")
input is: AAAAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x13")
input is: AAAAAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x14")
input is: AAAAAAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x15")
input is: AAAAAAAAAAAAAAA
/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x16")
input is: AAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

では、6文字追加してスタックが乱れ始めます...スタックを見てみましょう:

/tmp/bo-test$ gdb test-buffer-overflow core
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
[...]
Core was generated by `./test-buffer-overflow AAAAAAAAAAAAAAAA'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00007f2cb2c46508 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007f2cb2c46508 in ?? () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x0000000000000000 in ?? ()
(gdb) Quit

さらに追加のcharをフィードしていきましょう。

/tmp/bo-test$ ./test-buffer-overflow $(Perl -e "print 'A'x26")
input is: AAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)
/tmp/bo-test$ gdb test-buffer-overflow core
GNU gdb (Debian 7.7.1+dfsg-5) 7.7.1
[...]
Core was generated by `./test-buffer-overflow AAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000004141 in ?? ()
(gdb) 

ねえ...このアドレスを見て:0x0000000000004141! 0x41は...の16進ASCIIコードです。 'A':p RETアドレスを書き直しました:)さて、最後の試み、ただ見ます:

/tmp/bo-test$ ./test-buffer-overflow AAAAAAAAAAAAAAAAAAAAAAAAABCDEFGHI
input is: AAAAAAAAAAAAAAAAAAAAAAAAABCDEFGHI
Segmentation fault (core dumped)
/tmp/bo-test$ gdb test-buffer-overflow core GNU gdb 
Core was generated by `./test-buffer-overflow AAAAAAAAAAAAAAAAAAAAAAAAABCDEFGHI'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000400581 in evil_function (
    evil_input=0x7fff7e2712a6 'A' <repeats 25 times>, "BCDEFGHI")
    at test-buffer-overflow.c:12
12  }
(gdb) bt
#0  0x0000000000400581 in evil_function (
    evil_input=0x7fff7e2712a6 'A' <repeats 25 times>, "BCDEFGHI")
    at test-buffer-overflow.c:12
#1  0x4847464544434241 in ?? ()
#2  0x00007fff7e260049 in ?? ()
#3  0x0000000200000000 in ?? ()
#4  0x0000000000000000 in ?? ()

今回は、アドレスをもう一度見てください:0x4847464544434241...これで、どこに書き込むかが正確にわかりました...

9
binarym

@binarymの答えはかなり良いです。彼はすでにバッファオーバーフローの背後にある理由、単純なオーバーフローを見つける方法、およびコアファイルやGDBを使用してスタックを確認する方法を説明しています。 2つの詳細を追加したいだけです。

  1. より詳細なブラックボックステストの例、つまり次のようになります。

バッファオーバーフローを一貫して検出する方法の説明(ブラックボックステスト)

  1. コンパイラの癖、つまり、ブラックボックステストが失敗する場合(多かれ少なかれ、ブラックボックスで生成されたペイロードが失敗する場合に似ています)。

使用するコードはもう少し複雑です。

_#include <stdio.h>
#include <string.h>

void do_post(void)
{
    char curr = 0, message[128] = {};
    int i = 0;
    while (EOF != (curr = getchar())) {
        if ('\n' == curr) {
            message[i] = 0;
            break;
        } else {
            message[i] = curr;
        }
        i++;
    }
    printf("I got your message, it is: %s\n", message);
    return;
}

int main(void)
{
    char curr = 0, request[8] = {};
    int i = 0;
    while (EOF != (curr = getchar())) {
        request[i] = curr;
        if (!strcmp(request, "GET\n")) {
            printf("It's a GET!\n");
            return 0;
        } else if (!strcmp(request, "POST\n")) {
            printf("It's a POST, get the message\n");
            do_post();
            return 0;
        } else if (5 < strlen(request)) {
            printf("Some rubbish\n");
            return 1;
        }  /* else keep reading */
        i++;
    }
    printf("Assertion error, THIS IS A BUG please report it\n");
    return 0;
}
_

POSTおよびGETリクエストでHTTPを楽しんでいます。そしてgetchar()を使用して文字ごとにSTDINを読み取っています(これは不適切な実装ですが、それは教育的なものです) 。コードはGET、POSTと「ごみ」(その他))を区別し、多かれ少なかれ適切に記述されたループを使用して(オーバーフローなしで)区別します。

しかし、POSTメッセージを解析すると、_message[128]_バッファにオーバーフローが発生します。残念ながら、そのバッファはプログラムの奥深くにあります(実際、それほど深くはありませんが、単純な長い引数です)それは見つかりません)コンパイルして、長い文字列を試してみましょう:

_[~]$ gcc -O2 -o over over.c
[~]$ Perl -e 'print "A"x2000' | ./over 
Some rubbish
_

ええ、それはうまくいきません。コードがわかっているので、先頭に「POST\n」を追加すると、オーバーフローがトリガーされることがわかります。しかし、コードがわからない場合はどうなりますか?それともコードが複雑すぎますか?ブラックボックステストを開始します。

ブラックボックステスト

最も一般的なブラックボックステスト手法はファジングです。他のほとんどすべての(ブラックボックス)テクニックは、そのバリエーションです。ファジングとは、面白いものが見つかるまで、ランダムにプログラムに入力することです。このプログラムを確認するための簡単なファジングスクリプトを作成しました。見てみましょう。

_#!/usr/bin/env python3

from itertools import product
from subprocess import Popen, PIPE, DEVNULL

prog = './over'
valid_returns = [ 0, 1 ]

all_chars = list(map(chr, range(256)))
# This assumes that we may find something with an input as small as 1024 bytes,
# which isn't realistic.  In the real world several megabytes of need to be
# tried.
for input_size in range(1,1024):
    input = [p for p in product(all_chars, repeat=input_size)]
    for single_input in input:
        child = Popen(prog, stdin=PIPE, stdout=DEVNULL)
        byte_input = (''.join(single_input)).encode("utf-8")
        child.communicate(input=byte_input)
        child.stdin.close()
        ret = child.wait()
        if not ret in valid_returns:
            print("INPUT", repr(byte_input), "RETURN", ret)
            exit(0)

# The exit(0) is not realistic either, in the real world I'd like to have a
# full log of the entire search space.
_

それは単にそれを行います:プログラムへのますます大きなランダム入力をフィードします。 (警告:スクリプトには大量のRAMが必要です)これを実行し、数時間後に興味深い出力が得られます。

_INPUT b"POST\nXl_/.\xc3\x93\xc3\x90\xc2\x87\xc3\xa6dh\xc3\xaeH\xc2\xa0\xc2\x836\x16.\xc3\xb7\x1be\x1e,\xc3\x98\xc3\xa4\xc2\x81\xc2\x83 su\xc2\xb1\xc3\xb2\xc3\x8d^\xc2\xbc\xc2\xa11/\xc2\x9f\x12vY\x12[0\x0c]\xc3\xb6\x19zI\xc2\xb8\xc2\xb5\xc3\xbb\xc2\x9e\xc3\xab>^\xc2\x85\xc2\x91\xc2\xb5\xc2\xb5\xc3\xb6u\xc3\x8e).\xc3\xbcn\x1aM\xc3\xbb+{\x1c\xc3\x9a\xc3\x8b&\xc2\x93\xc2\xa1D\xc3\xad\xc3\xad\xc3\x81\xc2\xbd\xc2\x8d\xc2\xa3 \xc3\x87_\xc2\x82\xc3\x9asv\xc3\x92\xc2\x85IP\xc2\xb8\x1bS\xc3\xbe\xc3\x9e\\\xc2\x8e\xc3\x9f\xc2\xb1\xc3\xa4\xc2\xbe\x1fue\xc3\x81\xc3\x8a\xc2\x8b'\xc3\xaf\xc2\xa1\xc3\x95'\xc2\xaa\xc3\xa8P\xc2\xa7\xc2\x8f\xc3\x99\xc2\x94S5\xc2\x83\xc3\x85U" RETURN -11
_

プロセスは-11で終了しました。segfaultですか?どれどれ:

_kill -l | grep SIGSEGV
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
_

これは問題のないセグメンテーションフォールトです( 説明のためのこの回答 を参照してください)。これで、このsegfaultをシミュレートし、オーバーフローの場所を(GDBで)検出するために使用できる入力サンプルができました。

コンパイラの癖

上で奇妙な何かを見ましたか?省略した情報があります。下にあるスポイラータグを使用して、戻って理解できるようにします。答えはここにあります:

なぜ私は_gcc -O2 -o over over.c_を使用したのですか?単純な_gcc -o over over.c_では不十分なのはなぜですか?このコンテキストでのコンパイラの最適化(_-O2_)の特別な点は何ですか?

公平を期すために、私自身も、このような単純なプログラムでこの動作を見つけることができたのは驚くべきことでした。コンパイラーは、パフォーマンス上の理由から、コンパイル時に大量のコードを書き換えます。コンパイラーは、いくつかのリスク(たとえば、はっきりと見えるオーバーフロー)を軽減しようとします。多くの場合、最適化を有効にした場合と有効にしない場合で、同じコードが大きく異なるように見えることがあります。

この特定の癖を見てみましょうが、脆弱性はすでにわかっているので、Perlに戻りましょう。

_[~]$ gcc -O2 -o over over.c
[~]$ Perl -e 'print "POST\n" . "A"x2000' | ./over 
It's a POST, get the message
I got your message, it is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAins
Segmentation fault (core dumped)
_

はい、それはまさに私たちが期待していたことです。しかし今、最適化を無効にしましょう:

_[~]$ gcc -o over over.c
[~]$ Perl -e 'print "POST\n" . "A"x2000' | ./over 
It's a POST, get the message
I got your message, it is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAÿ}
$ echo $?
0
_

なんてこったい!コンパイラは、私が作成した脆弱性にとても愛情を込めてパッチを当てました。そのメッセージの長さを見ると、その長さが141バイトであることがわかります。バッファはオーバーフローしましたが、コンパイラは、オーバーフローが重要な何かに達した場合に書き込みを停止するために、ある種のアセンブリを追加しました。

懐疑論者のために、上記の動作を得るために私が使用しているコンパイラのバージョンは次のとおりです。

_[~]$ gcc --version
gcc (GCC) 6.2.1 20160830
Copyright (C) 2016 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
_

この話の教訓は、ほとんどのバッファーオーバーフローの脆弱性は、同じコンパイラーで同じ最適化(または他のパラメーター)でコンパイルされた場合にのみ、同じペイロードでのみ機能するということです。コンパイラーは、コードを高速に実行するためにコードに悪影響を及ぼすことを行います。ペイロードが2つのコンパイラーによってコンパイルされた同じプログラムで動作する可能性は十分ありますが、常にそうであるとは限りません。

追記

私は楽しみのために、そして自分のために記録をとるためにこの答えをしました。私はあなたの質問に完全には答えないので、賞金に値しません。賞金の定義に追加された余分な質問にのみ答えます。 bynarymの答えは、元の質問のより多くの部分に答えるため、賞金に値します。

3
grochmal