簡単な数値計算をするプログラム、calcが渡されます。
デコンパイルを試す
RecStudioっていう逆コンパイラを試したら、分岐がif-elseやswitch-caseになって見やすくなった以外に嬉しいことはあまりない感じの出力で、しかも、目的とすべき場所が逆コンパイルできてませんでした。(使い方がわかってないだけかも)
https://retdec.com/のRetargetable Decompilerっていうのはサーバ上で逆コンパイルしてくれるし結構速いし出すコードもいくぶんかわかりやすいので今後はこちらを使います。しかし5分以上かかるファイルは扱えません。そのときはたぶん公開されているIDA用のプラグインを使えば良いです。
// // This file was generated by the Retargetable Decompiler // Website: https://retdec.com // Copyright (c) 2018 Retargetable Decompiler <info@retdec.com> // #include <signal.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> // ------------------------ Structures ------------------------ struct _IO_FILE { int32_t e0; }; // ------------------- Function Prototypes -------------------- int32_t calc(void); int32_t eval(int32_t * a1, int32_t a2); int32_t parse_expr(int32_t a1, int32_t * a2); // --------------------- Global Variables --------------------- int32_t g1 = 0; // ebp char * g2 = "0"; // 0x80bf7a8 struct _IO_FILE * g3 = (struct _IO_FILE *)0x80ec200; // 0x80ec4c0 // ------------------------ Functions ------------------------- // Address range: 0x8048ee1 - 0x8048ff7 int32_t eval(int32_t * a1, int32_t a2) { int32_t result = (int32_t)a1; int32_t v1 = 0x1000000 * a2; if (v1 == 0x2b000000) { int32_t v2 = result - 4; // 0x8048f2d int32_t v3 = *(int32_t *)(v2 + 4 * *a1); // 0x8048f2d int32_t v4 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048f3c *(int32_t *)(v2 + 4 * *a1) = v4 + v3; // branch -> 0x8048fe4 // 0x8048fe4 *a1 = *a1 - 1; return result; } // 0x8048ef8 if (v1 <= 0x2b000000) { // 0x8048efd if (v1 == 0x2a000000) { int32_t v5 = result - 4; // 0x8048f92 int32_t v6 = *(int32_t *)(v5 + 4 * *a1); // 0x8048f92 int32_t v7 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048fa1 *(int32_t *)(v5 + 4 * *a1) = v7 * v6; // branch -> 0x8048fe4 } // 0x8048fe4 *a1 = *a1 - 1; return result; } // 0x8048f07 switch (v1 / 0x1000000) { case 45: { int32_t v8 = result - 4; // 0x8048f61 int32_t v9 = *(int32_t *)(v8 + 4 * *a1); // 0x8048f61 int32_t v10 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048f70 *(int32_t *)(v8 + 4 * *a1) = v9 - v10; // branch -> 0x8048fe4 break; } case 47: { int32_t v11 = *a1; // 0x8048fb4 int32_t v12 = result - 4; // 0x8048fc4 uint32_t v13 = *(int32_t *)(v12 + 4 * *a1); // 0x8048fc4 int32_t v14 = *(int32_t *)(result + 4 + 4 * (*a1 - 1)); // 0x8048fd3 *(int32_t *)(v12 + 4 * v11) = (int32_t)((0x100000000 * (int64_t)(v13 / 0x80000000) | (int64_t)v13) / (int64_t)v14); // branch -> 0x8048fe4 break; } } // 0x8048fe4 *a1 = *a1 - 1; return result; } // Address range: 0x804902a - 0x8049378 int32_t parse_expr(int32_t a1, int32_t * a2) { int32_t v1 = *(int32_t *)20; // 0x8049047 int32_t v2; int32_t v3 = &v2; // 0x804906c_0 function_8048240(v3, 100); int32_t v4 = 0; // 0x804935449 int32_t v5 = a1; // 0x80491a747 int32_t v6 = 0; // branch -> 0x8049081 while (true) { int32_t v7 = v6 + a1; // 0x804908d char * v8 = (char *)v7; int32_t v9 = v4; // 0x804935450 int32_t v10 = v5; // 0x80491a746 if ((int32_t)*v8 >= 58) { int32_t v11 = v7 - v5; // 0x80490b7 char * mem = malloc(v11 + 1); // 0x80490c7 int32_t v12 = (int32_t)mem; // 0x80490c7_3 memcpy(mem, (char *)v5, v11); *(char *)(v12 + v11) = 0; function_80482a0(v12, (int32_t)&g2); int32_t v13; if (v12 == 0) { // 0x804910d puts("prevent division by zero"); fflush((struct _IO_FILE *)*(int32_t *)0x80ec4c0); // branch -> 0x804935f } else { uint32_t str_as_i = atoi(mem); // 0x8049136 if (str_as_i >= 1) { int32_t v14 = *a2; // 0x804914a *a2 = v14 + 1; *(int32_t *)((int32_t)a2 + 4 + 4 * v14) = str_as_i; // branch -> 0x8049164 } int32_t v15 = a1 + 1 + v6; if (*v8 != 0) { // 0x8049179 if ((int32_t)*(char *)v15 > 57) { // 0x80491c0 puts("expression error!"); fflush(g3); // branch -> 0x804935f // 0x804935f if (*(int32_t *)20 != v1) { // 0x804936b __stack_chk_fail(); // branch -> 0x8049370 } // 0x8049370 g1 = v13; return 0; } } char * v16 = (char *)(v4 + v3); char v17 = *v8; // 0x80491f1 int32_t v18; // 0x804935451 if (*v16 == 0) { // 0x80491e3 *v16 = v17; v18 = v4; // branch -> 0x804930c lab_0x804930c_5: // 0x804930c if (*v8 == 0) { // 0x8049354 if (v18 <= 0) { // 0x804935a // branch -> 0x804935f // 0x804935f if (*(int32_t *)20 != v1) { // 0x804936b __stack_chk_fail(); // branch -> 0x8049370 } // 0x8049370 g1 = v13; return 1; } for (int32_t i = v18; i >= 1; i--) { // 0x8049330 eval(a2, (int32_t)*(char *)(i + v3)); // continue -> 0x8049330 } // branch -> 0x804935f // 0x804935f if (*(int32_t *)20 != v1) { // 0x804936b __stack_chk_fail(); // branch -> 0x8049370 } // 0x8049370 g1 = v13; return 1; } // 0x8049324 v4 = v18; v5 = v15; v6++; // branch -> 0x8049081 continue; } else { // 0x8049203 switch ((int32_t)v17) { default: { // 0x80492e8 eval(a2, (int32_t)*v16); v18 = v4 - 1; // branch -> 0x804930c goto lab_0x804930c_5; } case 37: { // 0x804926c if (*v16 == 43) { lab_0x804928a_4:; int32_t v19 = v4 + 1; // 0x804928a *(char *)(v19 + v3) = *v8; v18 = v19; // branch -> 0x804930c goto lab_0x804930c_5; } break; } case 42: { // 0x804926c if (*v16 == 43) { goto lab_0x804928a_4; } lab_0x804927b: // 0x804927b if (*v16 == 45) { goto lab_0x804928a_4; } lab_0x80492ab: // 0x80492ab eval(a2, (int32_t)*v16); *v16 = *v8; v18 = v4; // branch -> 0x804930c goto lab_0x804930c_5; break; } case 43: { // 0x804922c eval(a2, (int32_t)*v16); *v16 = *v8; v18 = v4; // branch -> 0x804930c goto lab_0x804930c_5; } case 45: { // 0x804922c eval(a2, (int32_t)*v16); *v16 = *v8; v18 = v4; // branch -> 0x804930c goto lab_0x804930c_5; } case 47: { // 0x804926c if (*v16 == 43) { goto lab_0x804928a_4; } goto lab_0x804927b; } } // 0x804927b if (*v16 == 45) { goto lab_0x804928a_4; } goto lab_0x80492ab; } } // 0x804935f if (*(int32_t *)20 != v1) { // 0x804936b __stack_chk_fail(); // branch -> 0x8049370 } // 0x8049370 g1 = v13; return 0; } // 0x8049324 v4 = v9; v5 = v10; v6++; // branch -> 0x8049081 } } // Address range: 0x8049379 - 0x8049433 int32_t calc(void) { int32_t v1 = *(int32_t *)20; // 0x8049383 int32_t v2; int32_t v3 = &v2; // 0x8049395_0 function_8048240(v3, 1024); int32_t v4; // 0x80493c2 if (get_expr(v3, 1024) == 0) { // 0x80493bd v4 = *(int32_t *)20; if (v4 != v1) { // 0x804942d __stack_chk_fail(); // branch -> 0x8049432 } // 0x8049432 return v4 ^ v1; } // 0x80493cc // branch -> 0x80493cc while (true) { // 0x80493cc int32_t v5; init_pool((int32_t)&v5); if (parse_expr(v3, &v5) != 0) { // 0x80493f6 printf("%dn", *(int32_t *)(g1 - 1436 + 4 * (v5 - 1))); fflush(g3); // branch -> 0x804938d } // 0x804938d function_8048240(v3, 1024); if (get_expr(v3, 1024) == 0) { // break -> 0x80493bd break; } // continue -> 0x80493cc } // 0x80493bd v4 = *(int32_t *)20; if (v4 != v1) { // 0x804942d __stack_chk_fail(); // branch -> 0x8049432 } // 0x8049432 return v4 ^ v1; } // Address range: 0x8049452 - 0x80494af int main(int argc, char ** argv) { // 0x8049452 signal(SIGALARM, (void (**)(int32_t))timeout); alarm(60); puts("=== Welcome to SECPROG calculator ==="); fflush(g3); calc(); return puts("Merry Christmas!"); } // --------------- Statically Linked Functions ---------------- // void __stack_chk_fail(void); // unsigned int alarm(unsigned int seconds); // int atoi(const char * nptr); // int fflush(FILE * stream); // void * malloc(size_t size); // void * memcpy(void * restrict dest, const void * restrict src, size_t n); // int printf(const char * restrict format, ...); // int puts(const char * s); // __sighandler_t signal(int sig, __sighandler_t handler); // --------------------- Meta-Information --------------------- // Detected compiler/packer: gcc (4.8.2) // Detected functions: 4 // Functions selected to be decompiled but not found: initpool // Decompiler release: v2.2.1 (2016-09-07) // Decompilation date: 2018-02-21 06:47:59
とこんな感じになります。試しにやっただけなので、読みやすく書き換えたりしてません。後述の参考URLのソースコードを読んだ方がわかりやすいです。
ちなみに逆コンパイルは、逆アセンブルと同じで、人間がコメントを追加したり変数名・関数名などを書き換えて読みやすいようにする必要があります。変数がv1とかv2とかそのままだと理解しにくいので…。
逆アセンブルを試す
あわせてIDAで逆アセンブルしますが…
無理なので他人のWriteupにて再現されたソースコードを見る
プログラムの流れ
parse_expr関数で入力の内容を判断します。
入力を、数字以外の文字が来るまで読みます。
数字の場合は被演算子スタック(expr->buf配列)に追加し、演算子スタック(op_s配列)の中身があればeval関数で計算します。
演算子の場合は、’+’や’-‘の場合は出現次第eval関数に被演算子スタックと演算子スタックのトップを渡して計算します。つまり、一つ前の演算子の計算を行います。その後で、今読んだ演算子を演算子スタックのトップとすげ替えます。
バグのありか
すると
expr->buf[expr->bufsize-2] += expr->buf[expr->bufsize-1]
では、インデックスの指定にexpr->bufsizeを用いているため、好きな場所を書き換えられます。
バグを利用した動作例
検証
上の流れでいくと、「+n」とだけ入力したときは、expr->bufsizeが=nになり、答えとしてexpr->buf[n-1]が表示されます。
試してみると、「+1」でexpr->buf[0]の1が出力されています。expr->buf[]は長さ100の配列なので、expr->buf[100]は範囲外のはずです。「+101」にはちゃんとカナリアが入っており、正しそうです。後述しますが「+357」もカナリアが表示されてます
ちなみに「+0」は0で割るなというメッセージが出てしまいます。なぜか0が被演算子だと、除数のとき以外でもこうなる実装なので「+00」で回避しましょう。
user1@user1-VirtualBox:~/ダウンロード$ ./calc === Welcome to SECPROG calculator === +00 0 +1 1 +2 0 +3 0 +4 0 +100 0 +101 825241899 +102 0 +356 0 +357 1082368768 +358 0 Merry Christmas!
解法
セキュリティ機構に関して
スタックカナリア
NX
ランダマイズ関係
具体的な解法
単純にメモリに書き込めるのではなく、その一つ前にも加算されてしまうという(→前述の太字のところを読んでください)ことに注意して書き込みます。
以上です。
コメント