mayoko’s diary

プロコンとかいろいろ。

picoctf 2014 Format

すごいっすなぁ。

全然わからなかったので調べてみたらわかりました(最初のほう読むだけで解けます)。
Introduction to format string exploits

printf は, char* s を使って, printf("%s", s) と書くのは大丈夫ですが, printf(s) と書くと危険なようです。これはなぜかと言うと, 例えば s に "%p" のような変換指定文字列(% がついてるやつ。あと %d とか %f とか)をそのまま解釈してしまうので, プログラムで printf("%p"); と書いたのと同じように働いてしまうからだそうです。

%p を指定するのがないけどどうなってるんだ, という感じですが, 素直に次のスタックに入っているアドレスを次々と呼び出すようです(ここら辺あんまりわかってないけど多分 printf の仕様見ればわかるんだろうか)。

今回の場合は secret の値を書き換えたいわけなので, secret が printf から見てどのスタックに入っているのかを確かめましょう。
まず secret のアドレスの確認ですが, これは gdb を使えば簡単です。gdb の中で

print &secret

とすればアドレスが 0x804a030 とわかります。

次に, ./format の printf での secret の場所を確認します。

./format の後に %p をたくさん書きます(./format %p.%p.%p.%p.(もっと続ける) という感じ)。

すると, 7 個目のアドレスが secret のアドレスと一致していることがわかります。

ところで, printf には便利な機能があって, 7 個目の変換してい文字列だけ表示したいときは %7$p とかやれば良いらしいです。例えば printf("%2$d\n", 1, 2, 3); とか書くと 2 と表示されます(2 つ目の整数は 2 なので)。

今回の場合は, ./format '%7$p' と書くと 0x804a030 と出てきます(出てこねぇよ!と言ってる人はシェルの左側をよく見ましょう)。

これだとまだ意味ないんですが, printf にある %n という機能を使うとさらに強いことができます。%n は printf に今まで書いた文字を, 指定したポインタの場所に書き込む, ということをやるらしいです。例えば

printf("unko%n", &a);

とやると a には 4 が入ります(unko は 4 文字なので)。

今, printf の 7 個先のスタックが secret のアドレスと一致していることが分かっているので, ./format 'unko%7$n' とやれば secret には 4 が入るはずです。今回は secret に 1337 を入れたいので, 適当な 1337 文字を…書くのは面倒なのですが, printf にはまた便利な機能があって, %1337c と書くと何らかの文字を勝手に 1337 文字入れてくれます。ということで結局,

./format '%1337c%7$n'

と書くとフラグを取れます。やったね。