簡単な数値計算をするプログラム、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
ランダマイズ関係
具体的な解法
単純にメモリに書き込めるのではなく、その一つ前にも加算されてしまうという(→前述の太字のところを読んでください)ことに注意して書き込みます。
以上です。


コメント