[[FrontPage]] > [[Armadillo-9]] > [[exploit xpdf_arm]]
*xpdf(changed)(on ARM) [#x7ff4fc8]
*環境 [#i7530842]
-CPU: ARM9
-OS: Debian Sarge(2.4.31-a9-2)
--アットマークテクノのページで配布されているものを使った
-target: xpdf-3.00
--この脆弱性は xpdf-3.00pl2.patch で対応された。
--Debian のは既にパッチが当たってるので、脆弱性を埋め込んだ独自ビルドを行う。
-独自ビルドについて
--lesstif を使った
--configure に書いてあるコンパイラに渡すフラグ -O2 を解除する。
--maskColors を宣言する位置を変える。詳細は後述する。
--Debian Sarge の xpdf.3.00-orig.tar.gz を参考に、Debian Sarge 版のソースにオリジナルの脆弱性を埋め込んだ。
パッチが当たると、以下のように境界チェックが入るので、境界チェックを排除する。
//Gfx.cc:2660 行目付近
if (maskObj.isArray()) {
for (i = 0;
i < maskObj.arrayGetLength() && i < 2*gfxColorMaxComps;
++i) {
maskObj.arrayGet(i, &obj1);
maskColors[i] = obj1.getInt();
-----------------------------------------------------------------
// get the mask
haveMask = gFalse;
dict->lookup("Mask", &maskObj);
if (maskObj.isArray()) {
for (i = 0; i < maskObj.arrayGetLength(); ++i) {
maskObj.arrayGet(i, &obj1);
maskColors[i] = obj1.getInt();
obj1.free();
}
haveMask = gTrue;
}
***変数の宣言順序について [#d123cf86]
-maskColors というバッファをあふれさせて攻撃を行うが、maskColors を取得するには maskObj の情報が必要である。
-そのため maskColors をあふれさせたときに、ともに maskObj が壊れてしまわないように、変数の宣言順序を変更する。
デフォルトだと、以下のような配置になっている。上にある方が 0x00000000 側のアドレスである。
[size] [declaration]
4 int i;
12 Object obj2;
12 Object obj1;
256 int[64] maskColors; //[!]
4 GBool haveMask;
12 Object maskObj;
4 GfxImageColorMap *colorMap;
4 GfxColorSpace *colorSpace;
4 GBool invert;
4 GBool mask;
4 int bits;
4 int height;
4 int width;
4 Dict *dict;
----------------------------------
4 (arg2)GBool inlineImg;
4 (arg1)Stream *str;
4 (arg0)Object *ref;
4 this pointer;
----------------------------------
84 (backup area for registers)
----------------------------------
4 ret_addr
変更後は、maskColors が後ろに来るので、以下のようになる。
[size] [declaration]
(...snip...)
4 int height;
4 int width;
4 Dict *dict;
256 int[64] maskColors; //[!]
----------------------------------
4 (arg2)GBool inlineImg;
4 (arg1)Stream *str;
4 (arg0)Object *ref;
4 this pointer;
----------------------------------
84 (backup area for registers)
----------------------------------
4 ret_addr
具体的には以下のような変更を行う。
//original
Dict *dict;
(...snip...)
Object maskObj;
GBool haveMask;
int maskColors[2*gfxColorMaxComps]; //[!]
Object obj1, obj2;
int i;
---------------------------------------
//changed
int maskColors[2*gfxColorMaxComps]; //[!]
Dict *dict;
(...snip...)
Object maskObj;
GBool haveMask;
Object obj1, obj2;
int i;
**攻撃対象 [#j518dd21]
-バッファオーバーフロー脆弱性のある部分は、Gfx.cc 2660行目付近である。
-x86
--リターンアドレスを書き換えて、攻撃コードにリターンさせる。
-ARM
--リターンアドレスを書き換える方法は使えない。
---Gfx オブジェクトの this ポインタを偽者のデータへのポインタに書き換える。
---さらに out というデータを書き換える事により、vtable を攻撃コードへのアドレスで上書きする。
---以上の操作によって、仮想関数の呼び出し先を書き換えることができる。書き換えた仮想関数が呼び出されると、攻撃コードにジャンプする。
***リターンアドレス書き換えが使えない理由 [#f55f755c]
ARM では引数とリターンアドレスの位置関係によって(libpngと同様の理由)リターンアドレスだけ書き換える、という手法が使えない。
**ARMでの攻撃手法 [#be13abf2]
バッファオーバーフローを起こした後、out->drawImage という仮想関数が呼び出される。この部分を利用する。
// get the mask
haveMask = gFalse;
dict->lookup("Mask", &maskObj);
if (maskObj.isArray()) {
for (i = 0; i < maskObj.arrayGetLength(); ++i) {
maskObj.arrayGet(i, &obj1);
maskColors[i] = obj1.getInt();
obj1.free();
}
haveMask = gTrue;
}
// draw it
out->drawImage(state, ref, str, width, height, colorMap,
haveMask ? maskColors : (int *)NULL, inlineImg); //[!]
delete colorMap;
maskObj.free();
**攻撃の詳細 [#xb3594e9]
-リターンアドレスは使わない。
-書き換えるのは Gfx オブジェクトの this ポインタ(arg0 の後ろにある)である。
-仮想関数の呼び出し先が、攻撃コードのアドレスを指すようにする。
--this ポインタ、this->out ポインタ、this->out の vtable が攻撃データで埋め尽くした領域を指すようにする。
--this(0x00112233) -> 0x00112233 [本来のデータ領域]~
これを書き換えて~
this(0xdeadbeaf) -> 0xdeadbeaf [攻撃コードの開始アドレスで埋め尽くした領域]~
とする。this->out や、vtable に関してもそう。これによって関数ポインタが攻撃コードの開始アドレスを指すようになり、プログラムを騙して、攻撃コードを call させることができる。
*計算すべきアドレス [#f3f514cb]
-必要なもの
--maskColors(オーバーフローさせるバッファ)の開始地点
-計算するもの
--this(Area A へのポインタで書き換える)
--Area A(Area B へのポインタで埋められている領域): this の実体
--Area B(Area C へのポインタで埋められている領域): this->out の実体
--Area C(Ared D へのポインタで埋められている領域): this->out の vtable の実体(?)
--Area D(攻撃コードが記述されている領域)
*攻撃作成の際の注意点 [#fff4d84f]
-スタックの開始位置が多少ずれても平気なように、各エリアは 512バイトくらいは欲しいところである。
-同様に攻撃データの開始地点も余裕を持って、nop(ARM だと mov r0, r0)で埋めた領域内を指すようにすべき。
-攻撃データのサイズをあまりにもでかくすると、スタック領域(〜 0xbfffffff まで)を超えてしまって、SIGSEGV を食らう。
*攻撃データの詳細 [#l9adbe10]
今回の攻撃には、仮想関数の関数ポインタを書き換えるために必要なエリアが 3つ、攻撃コード記述用のエリアが 1つ必要である。
**スタックのレイアウト(再掲) [#i55bdcc8]
先述した変数の宣言位置の変更を行った後のスタックのレイアウトは以下のとおりである。
[size] [declaration]
(...snip...)
4 int height;
4 int width;
4 Dict *dict;
256 int[64] maskColors; //[!]
----------------------------------
4 (arg2)GBool inlineImg;
4 (arg1)Stream *str;
4 (arg0)Object *ref;
4 this pointer;
----------------------------------
84 (backup area for registers)
----------------------------------
4 ret_addr
**攻撃データのレイアウト [#a5f95d0e]
-節約のために、maskColors から this pointer までの領域を Area A として用いる。
-攻撃コード、攻撃データは libpng の使いまわしである。
-addr は攻撃用 pdf における位置ではなく、maskColors を書きつぶす攻撃用のデータにおける位置を表す。
[addr] [size] [declaration]
0x000 268 Area A
0x10c 4 this
0x110 384 Area B
0x290 384 Area C
0x410 384 Area D [nop]
0x590 <-- [mov r5, pc]
0x594 172 [sub r5, r5, #8]
0x598 [攻撃コード]
0x5f4 --> [攻撃データ]
***スタック開始位置のずれ [#j1fb81ff]
環境によって、ポインタで指す位置がずれる。
|----| スタックが予想していたよりも低い
| | ときに指される領域
| |
|----| <-- 予想とぴったりのときに指す位置
| |
| | スタックが予想していたよりも高い
|----| ときに指される領域
-今回は予想値を低目にとることで、予想よりも低い領域を減らした。
--スタックの開始位置は、0x30 ずつずれるようなので、その倍数で計算する。
--低め用の領域(96バイト、2段分)、高め用の領域(288バイト、6段分)で、計 384バイト取った。
--ただし Area A はスペースの都合上、高め用の領域が少ない。
***今回使ったオフセット値 [#v66401f6]
今回使ったデータでは、以下のような位置関係にした。
maskColors の予想開始位置をベースとしたオフセットで表す。手で計算してもいいけどcalc_address という補助プログラムを作ったので、それを使うほうが簡単。
[data] [offset] [why]
this: +96 (to Area A(n))
Area A: +368 (Area A(268) + this(4) + to Area B(n))
Area B: +752 (A(268) + this(4) + B(384) + to C(n))
Area C: +1136 (A(268) + this(4) + B(384) + C(384) + to D(n))
to Area X(n) は、今回の場合 n=96 である。ただしこの数値は「低め用の領域」をどのくらい取るかによって変わるので注意である。
**変換 [#o0942ede]
pdf に記述するときは「符号付の 10進数」に直さなければいけないので、converter を書いた。