文字列の脆弱性のフォーマット

11005 ワード

概要
文字列のフォーマットは、比較的一般的な脆弱性のタイプでもあります.この脆弱性をトリガーする関数は限られています.主にprintfsprintffprintfなどの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!!!!