文字列の脆弱性のフォーマット
11005 ワード
概要
文字列のフォーマットは、比較的一般的な脆弱性のタイプでもあります.この脆弱性をトリガーする関数は限られています.主に
int printf(const char* format,…)
これはc言語を学んだすべての人が必ず知っていて、使う関数です.まず、format文字列を指す文字列ポインタです.後ろには可変数のパラメータがあります.
普通の人はそう使うかもしれません
このプログラムは問題ありません.そしてサボるためにこんな風に書く人もいます
このプログラムは
脆弱性の発生原理
ここでは
まず、アセンブリのソースコードを見てみましょう.額はしばらくできません.やはり手書きでしょう.
差は少ないですが、このままです.この時の桟はこんな感じになります.
(えーと、ツッコミを入れない原始的なスタック構造の表現は、IDAを使ったことがあるのは知っているはずです).
cdeclの関数呼び出し規定に従って、関数の最右側のパラメータから、スタックを1つずつ押します.入力する文字列が文字列である場合は、文字列のポインタをスタックに圧縮します.このすべてはきちんと行われている.一般的な関数の場合、関数の呼び出し元と呼び出し元は、関数のパラメータの個数と各パラメータのタイプを知る必要があります.異なるタイプの場合、コンパイラは自動的にタイプの変換を行い、コンパイルエラーが発生したりして、プログラムの作成者に注意します.しかし、
ここでは
ここでは、
脆弱性の利用
誰かが(私のようなスラグ)この脆弱性の危害に疑問を感じるのは、無駄なゴミデータを印刷するだけのようだからだ.実は、その危害はスタックオーバーフロー脆弱性の危害よりも小さくなく、適切に使えばスタックオーバーフローよりも効果的だ.スタックオーバーフローが乱暴なカーペット爆撃であれば、フォーマット文字列脆弱性は恐ろしい狙撃手一撃で致命的だ.
この脆弱性の利用方法は主に2種類ある
印刷メモリ
さっきも
formatに十分なパラメータを入力すれば、
例えば、formatは
さらに、フォーマット文字列には
メモリの変更
文字列の脆弱性をフォーマットしてメモリ情報を印刷できるのはおかしくないかもしれません.しかし、フォーマット文字列は、メモリ内のデータを変更することもできます.次のコードを見てみましょう.
これは少し不思議なコードで、その実行結果を見てみましょう.
aの値は
もちろん、修正が必要なデータがかなりの数値であれば、%02333 dという形式を使用することができます.印刷値の右側に不足桁数を0で揃える.
フォーマット文字列が変更できるメモリの範囲がより広いことがわかります.ポインタを作成すれば、メモリ内の任意の数値を書き換えることができます.桟にあふれたじゅうたん爆撃とは違います.一度に1つのdwordサイズのメモリしか書き換えられない攻撃は、より精巧で致命的です.
じっこう
最も良い学習方法は実践であり、フォーマット文字列の脆弱性の効果を実験してみましょう.まず、コード
gccコンパイルを使用します.ここ
次にIDAにドラッグしてスタック構造を解析し、
修正が必要な変数はflagであり、ポインタpはflagを指すポインタである.だからpでflagの値を2000に修正して、goodを印刷することができます!!ターゲット
%010x%010x%010x%01970x%n
これは私が作ったpocです.短いですが、強いです(→→).では、効果を見てみましょう
oh yeah!!!!
文字列のフォーマットは、比較的一般的な脆弱性のタイプでもあります.この脆弱性をトリガーする関数は限られています.主に
printf
やsprintf
fprintf
などのcライブラリのprintファミリーの関数です.まずprintf
の関数宣言を見てみましょうint printf(const char* format,…)
これはc言語を学んだすべての人が必ず知っていて、使う関数です.まず、format文字列を指す文字列ポインタです.後ろには可変数のパラメータがあります.
普通の人はそう使うかもしれません
char str[100];
scanf("%s",str);
printf("%s",str);
このプログラムは問題ありません.そしてサボるためにこんな風に書く人もいます
char str[100];
scanf("%s",str);
printf(str)
このプログラムは
printf
でサボった書き方を使った.これは何の問題もないように見えます.しかし、非常に深刻な脆弱性が発生した.printf
のformat文字列の操作権をユーザーに渡さないでください.保証printf
関数の最初のパラメータは、プログラマーの把握において可変である.脆弱性の発生原理
ここでは
printf
の動作原理について詳しく説明します.64ビット上のprintf
関数の挙動に多くの変化が生じたからである.ここではしばらく説明しません.しかし、脆弱性の発生原因が明らかであれば、この脆弱性を使用することができます.まず正常な状況を見てみましょう#include
int main(void)
{
printf("%d%d%d%d%s",5,6,8,0x21,"test");
return 0;
}
まず、アセンブリのソースコードを見てみましょう.額はしばらくできません.やはり手書きでしょう.
.data
str db "test",0
format db "%d%d%d%d%s",0
.code
push str
push 21h
push 8
push 6
push 5
push format
call printf
差は少ないですが、このままです.この時の桟はこんな感じになります.
-00000003 db ? ;
-00000002 db ? ;
-00000001 db ? ;
+00000000 s db 4 dup(?)
+00000004 r db 4 dup(?)
+00000008 format db 4 ;"%d%d%d%c"
+0000000c %d db 4 ; 4
+00000010 %d db 4 ; 6
+00000014 %d db 4 ; 8
+00000018 %x db 4 ; 0x21
+0000001c %s db 4 ; "test"
+0000001c ; end of stack variables
(えーと、ツッコミを入れない原始的なスタック構造の表現は、IDAを使ったことがあるのは知っているはずです).
cdeclの関数呼び出し規定に従って、関数の最右側のパラメータから、スタックを1つずつ押します.入力する文字列が文字列である場合は、文字列のポインタをスタックに圧縮します.このすべてはきちんと行われている.一般的な関数の場合、関数の呼び出し元と呼び出し元は、関数のパラメータの個数と各パラメータのタイプを知る必要があります.異なるタイプの場合、コンパイラは自動的にタイプの変換を行い、コンパイルエラーが発生したりして、プログラムの作成者に注意します.しかし、
printf
関数になると、すべてが違います.printf
はc言語では珍しい可変パラメータをサポートするライブラリ関数であるからである.可変パラメータの関数では、すべてがぼやけてしまいます.関数の呼び出し元は、関数パラメータの数とタイプを自由に指定することができ、呼び出し元は、関数呼び出し前にスタックフレームにどのくらいのパラメータが押し込まれたか分からない.したがって、printf
関数は、formatパラメータを入力して、どれだけのパラメータが入力され、どのようなパラメータが入力されるかを指定する必要がある.その後、関数の呼び出し者が入力したフォーマットに従ってデータを1つずつ印刷します.もちろんこれは深刻な問題を生みます.もし私たちが何気なく、あるいは意図的にformatの中で、あるいはprintf
に印刷するデータの数が私たちが与えた数より大きいことを要求したらどうなりますか?printf
関数は、スタックフレーム内のどのデータがそのパラメータに入力され、関数呼び出し者に属するデータであるかを知ることはできない.次のコードを見てください#include
int main(void)
{
printf("%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x,%08x");
return 0;
}
ここでは
printf
のパラメータしか与えられていませんが、12個のintタイプのデータを印刷させ、コンパイルして実行してどのような結果が出るかを見てみましょう.ここでは、
printf
が私たちの意思に従って12個の数値を忠実に印刷していることがわかります.これらの数値は、私たちが入力したパラメータではなく、スタックに保存されている他の数値です.この特性により、ハッカーたちは文字列をフォーマットする脆弱性を生み出した.脆弱性の利用
誰かが(私のようなスラグ)この脆弱性の危害に疑問を感じるのは、無駄なゴミデータを印刷するだけのようだからだ.実は、その危害はスタックオーバーフロー脆弱性の危害よりも小さくなく、適切に使えばスタックオーバーフローよりも効果的だ.スタックオーバーフローが乱暴なカーペット爆撃であれば、フォーマット文字列脆弱性は恐ろしい狙撃手一撃で致命的だ.
この脆弱性の利用方法は主に2種類ある
印刷メモリ
さっきも
printf
が呼び出しスタックフレームの情報を印刷できるのを見ました.0 day攻撃では、相手のメモリのデータをどのように取得するかが重要なテクニックですが、文字列の脆弱性をフォーマットする方法の1つは、メモリの中で私たちが知るべきではないデータを取得することです.このプロセスをleakメモリと呼びます.0 day攻撃の重要な方法ret to libcはleakベースアドレスを前提としている.formatに十分なパラメータを入力すれば、
printf
はスタックに格納されている、知られていない情報を出すことができます.スタック内のformatのアドレスとleakを必要とする情報アドレスの差を計算すればよい.欲しいデータが手に入る例えば、formatは
0x20
であり、destデータは0x00
である.彼らは全部で32バイト差があるので、"%f%f%f%d,%x"
のような文字列を構築することができます.カンマの前にある"%f%f%f%d"
はforamtよりも高い28バイトのデータを印刷することができます.もちろん、これは私たちが望んでいるものではありません.そして最後の%x
は、私たちが望んでいるデータを16進数で印刷することができます.さらに、フォーマット文字列には
%s
パラメータがあることがわかります.では、スタックに興味のあるデータを指すポインタが保存されている場合、ポインタを印刷するときに%s
を使用して他の場所の内容を印刷することができます.また、一般的なプログラムでは、ユーザーが入力したデータがスタックに格納されます.これにより、ポインタを構築する機会が与えられ、フォーマット文字列の脆弱性と組み合わせて、ほとんどのメモリデータが得られます.メモリの変更
文字列の脆弱性をフォーマットしてメモリ情報を印刷できるのはおかしくないかもしれません.しかし、フォーマット文字列は、メモリ内のデータを変更することもできます.次のコードを見てみましょう.
#include
int main(void)
{
int a;
printf("aaaaaaa%n
",&a);
printf("%d
",a);
return 0;
}
これは少し不思議なコードで、その実行結果を見てみましょう.
aの値は
printf
関数によって7に変更されることが分かった.これが%n
の効果です.これはよく使われないパラメータです.%n
より前のprintf
で印刷された文字の個数を入力ポインタに割り当てる機能です.%n
でメモリの値を変更できます.%s
leakメモリと同様に、スタックに変更するメモリのアドレスがあれば、フォーマット文字列の脆弱性を使用して変更できます.もちろん、修正が必要なデータがかなりの数値であれば、%02333 dという形式を使用することができます.印刷値の右側に不足桁数を0で揃える.
フォーマット文字列が変更できるメモリの範囲がより広いことがわかります.ポインタを作成すれば、メモリ内の任意の数値を書き換えることができます.桟にあふれたじゅうたん爆撃とは違います.一度に1つのdwordサイズのメモリしか書き換えられない攻撃は、より精巧で致命的です.
じっこう
最も良い学習方法は実践であり、フォーマット文字列の脆弱性の効果を実験してみましょう.まず、コード
#include
int main(void)
{
int flag = 0;
int *p = &flag;
char a[100];
scanf("%s",a);
printf(a);
if(flag == 2000)
{
printf("good!!
");
}
return 0;
}
gccコンパイルを使用します.ここ
次にIDAにドラッグしてスタック構造を解析し、
printf
関数を呼び出すときのスタック構造はこうです.-00000010 r dd ? -00000010 format dd ?
-00000010 dd ?
-00000010 dd ?
-00000010 dd ?
-0000000C flag dd ?
-00000008 p dd ?
-00000004 a db ?
-00000000 db ? ;
修正が必要な変数はflagであり、ポインタpはflagを指すポインタである.だからpでflagの値を2000に修正して、goodを印刷することができます!!ターゲット
%010x%010x%010x%01970x%n
これは私が作ったpocです.短いですが、強いです(→→).では、効果を見てみましょう
oh yeah!!!!