ゾンビ狩りクラブ

Linux, Server, Network, Security 関連などをゆるーくテキトーに載せてます

School CTF 2015 writeup

今更ながらwriteupを書きます。

今回私はチームvulsで参加。結果はチームで6100点(私個人では1300点)で見事1位を獲得しました。 f:id:Garfields:20151113222958p:plain

私が解いた問題を以下に書きます。

Joy


Highly professional (Joy 100)

写真が与えられて、写っている人の雇い主を答える問題。
f:id:Garfields:20151113223853j:plain

とりあえず画像に写っている女性の名前を特定しようとするが、画像がぼやけてて名前がわからない。
ここで最強の手段を使う(google画像検索)
するとこの女性はMr.robotという海外ドラマに出てくるAllsafeで働くNoreen Wardをいう女性らしいことが判明。
そこでAllsafeで調べ、このページからGideon Goddardという男が会社のオーナーだと分かった。
flag: gideon_goddard

Rev


Simple Check (Rev 100)

ELFファイルが渡される。
check_key関数で入力を検査し、戻り値が1だったらinteresting_function関数を実行、戻り値が0だったらputs("Wrong")を実行だったので、gdbで戻り値を操作すればok。
flag: flag_is_you_know_cracking!!!

Parallel Comparator (Rev 200)

c言語が記述されているファイルが渡される。

     1 #include <stdlib.h>
     2 #include <stdio.h>
     3 #include <pthread.h>
     4 
     5 #define FLAG_LEN 20
     6 
     7 void * checking(void *arg) {
     8     char *result = malloc(sizeof(char));
     9     char *argument = (char *)arg;
    10     *result = (argument[0]+argument[1]) ^ argument[2];
    11     return result;
    12 }
    13 
    14 int highly_optimized_parallel_comparsion(char *user_string)
    15 {
    16     int initialization_number;
    17     int i;
    18     char generated_string[FLAG_LEN + 1];
    19     generated_string[FLAG_LEN] = '\0';
    20 
    21     while ((initialization_number = random()) >= 64);
    22     
    23     int first_letter;
    24     first_letter = (initialization_number % 26) + 97;
    25 
    26     pthread_t thread[FLAG_LEN];
    27     char differences[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
    28     char *arguments[20];
    29     for (i = 0; i < FLAG_LEN; i++) {
    30         arguments[i] = (char *)malloc(3*sizeof(char));
    31         arguments[i][0] = first_letter;
    32         arguments[i][1] = differences[i];
    33         arguments[i][2] = user_string[i];
    34 
    35         pthread_create((pthread_t*)(thread+i), NULL, checking, arguments[i]);
    36     }
    37 
    38     void *result;
    39     int just_a_string[FLAG_LEN] = {115, 116, 114, 97, 110, 103, 101, 95, 115, 116, 114, 105, 110, 103, 95, 105, 116, 95, 105, 115};
    40     for (i = 0; i < FLAG_LEN; i++) {
    41         pthread_join(*(thread+i), &result);
    42         generated_string[i] = *(char *)result + just_a_string[i];
    43         free(result);
    44         free(arguments[i]);
    45     }
    46 
    47     int is_ok = 1;
    48     for (i = 0; i < FLAG_LEN; i++) {
    49         if (generated_string[i] != just_a_string[i])
    50             return 0;
    51     }
    52 
    53     return 1;
    54 }
    55 
    56 int main()
    57 {
    58     char *user_string = (char *)calloc(FLAG_LEN+1, sizeof(char));
    59     fgets(user_string, FLAG_LEN+1, stdin);
    60     int is_ok = highly_optimized_parallel_comparsion(user_string);
    61     if (is_ok)
    62         printf("You win!\n");
    63     else
    64         printf("Wrong!\n");
    65     return 0;
    66 }

要約すると、 59行目: user_stringに入力を代入
21行目: rand()の値が最初に64以下になったら、その値を initialization_numberの値にする。
24行目: first_letterの値を initialization_number%26+97 にする。
35行目: checking関数で (first_letter+difference[i])user_string[i] をresultに代入。resultのアドレスを返す。
42行目: (char)result+just_a_string[i] を generated_string[i] に代入。
48行目: generated_string[i] と just_a_string[i] を比較し、全て同じだったら printf("You win!\n") を実行。

*(char*)result+just_a_string[i] を generated_string[i] に代入したものと just_a_string[i] が同じということは、resultの値がゼロになるようにすればよい。
resultは、(first_letter+difference[i])user_string[i] なので、ゼロにするには、user_string[i]を first_letter+difference[i] と同じにすればよい。
つまり、first_letterを求めて、difference[i]を足せばフラグが出る。

以上を踏まえてプログラムを書く

     1  #include <stdio.h>
     2 #define FLAG_LEN 20
     3 
     4 char dif[FLAG_LEN] = {0, 9, -9, -1, 13, -13, -4, -11, -9, -1, -7, 6, -13, 13, 3, 9, -13, -11, 6, -7};
     5 
     6 int main() {
     7     int initialization_number, first_letter, i, n, result;
     8         
     9         while((initialization_number = rand()) >= 64);
    10         first_letter = (initialization_number % 26) + 97;
    11         for(i=0; i<FLAG_LEN; i++) {
    12         result = (first_letter+dif[i]);
    13         printf("%c", result);
    14     }
    15     printf("\n");
    16 
    17     return 0;
    18 }
    19 

flag: lucky_hacker_you_are

Secret Galaxy (Rev 300)

ELFファイルが渡される。
実行すると何やら以下のようなものが出力される。

--------------GALAXY DATABASE-------------  
Galaxy name | Existence of life | Distance from Earth  
-------------------------------------------  
   NGS 2366 |  IS NOT INHABITED | 1804289383  
  Andromeda |  IS NOT INHABITED | 846930886  
    Messier |  IS NOT INHABITED | 1681692777  
   Sombrero |  IS NOT INHABITED | 1714636915  
 Triangulum |  IS NOT INHABITED | 1957747793

なんかよくわからないので、IDAで解析する。
Cで書きなおしたら多分おそらく思うにこんな感じになる。

char *galaxy_name[] = {"NGS 23", "Andromeda", "Messier", "Sombrero", "Triangulum", "DARK SECRET GALAXY"};
char starbase[40*5];
char *sc;

__attribute__((constructor))
    void __libc_csu_gala() {
        //scの初期化やらなんやら
    }

void fill_starbase(char *sb) {
    int i;
    void *a;

    a = 0;
    for(i=0; i<5; i++) {
        *((long int*)sb+40*i) = galaxy_name[i];
        *((int*)sb+40*i+8) = random();
        *((int*)sb+40*i+12) = 0;
        *((long int*)sb+40*i+16) = 0;
        *((long int*)sb+40*i+24) = 40*(i+1)+sb;
        *((long int*)sb+40*i+32) = a;
        a = 40*i+sb;
    }   
}

print_starbase(char *sb) {
    int i;
    char *life;

    puts("--------------GALAXY DATABASE-------------");
    printf("%10s | %s | %s\n", "Galaxy name", "Existence of life", "Distance from Earth");
    puts("-------------------------------------------");
    for(i=0; i<5; i++) {
        if(*(sb+40*i+12) == 1) {
            life = "INHABITED";
        } else {
            life = "IS NOT INHABITED";
        }
        
        printf("%11s | %17s | %d\n", (char*)(*((long int*)sb+40*i)), life, *((int*)sb+40*i+8));
    }
    
}

int main() {
    fill_starbase(&starbase);
    print_starbase(&starbase);

    return 0;
}

fill_starbase()は、銀河情報を繰り返し格納する関数であり、print_starbase()はfill_starbase()で格納した銀河情報を表示する関数である。
fill_starbase関数を見てみると、(sb+40*N+24)の中に次の銀河情報のアドレスを格納しているのがわかる。
表示される銀河は5個だが、5個目に格納されている次の銀河情報のアドレスを見れば謎の6個目の銀河がわかるはず。

gdb-peda$ x/gx 0x601294+40*4+24
0x60134c <starbase+184>:  0x000000000060135c
gdb-peda$ x/4gx 0x000000000060135c
0x60135c <sc>:    0x0000000000400a5f  0x0000000100007a69
0x60136c <sc+16>: 0x0000000000601384  0x0000000000000000
gdb-peda$ x/s 0x0000000000601384
0x601384 <sc+40>: "aliens_are_around_us"

flag: aliens_are_around_us

Exploit

Heartles types (Exploit 100)

C言語が記述されたファイルが渡される。

     1 #include <stdio.h>
     2 #include <stdlib.h>
     3 #include <string.h>
     4 
     5 char stored_pass[] = "***";
     6 char flag[] = "***";
     7 
     8 int main()
     9 {
    10     unsigned char password_len;
    11     puts("Enter password len :");
    12     scanf("%d\n", &password_len);
    13     
    14     if (password_len == 0) {
    15         puts("Password can't be 0 characters long");
    16         return 1;
    17     }
    18 
    19     char *pass;
    20     pass = malloc((int)password_len + 1);
    21     int i;
    22     for (i = 0; i < password_len; ++i) {
    23         pass[i] = fgetc(stdin);
    24     }
    25     
    26     int stored_pass_len = strlen(stored_pass) + 1;
    27     ++password_len;
    28     int min_len = (password_len < stored_pass_len) ? password_len : stored_pass_len;
    29     for (i = 0; i < min_len; ++i) {
    30         if (pass[i] != stored_pass[i]) {
    31             puts("Wrong pass!");
    32             return 1;
    33         }
    34     }
    35     puts(flag);
    36     return 0;
    37 }

なにやらチェックしているが、負の値はチェックしていない。
なので、負の値を入力して、あとは適当に文字を入力するだけ。
flagは忘れた。。。

Try to guess (Exploit 500)

なぜこれがExploitなのか、なぜ500点なのか分からない問題。
ELFファイルが渡される。
C言語にしたらこんな感じ。

char *hello = "Hello, in order to get a flag thou must";
char *farewell = "Thank you for your time, sir!\n";
char *gen = "Generating number... Your guess?\n";
char *wrong = "Wrong number, it was ";
char *flag = "****\n";
int always_random_number, first_try, second_try, unguessable;

int next_rand(int num) {
    int i, result;

    if(num == 2) {
        for(i=0; i<first_try; i++) {
            unguessable ^= rot(second_try, i+1);
        }
        result = unguessable;
    } else {
        result = rand();
    }

    return result;
}

int try_guess(int fd, int num) {
    int gen_len, flag_len, wrong_len, l, i;
    char buf[256];

    for(i=0; i<32; i++) {
        buf[i] = 0;
    }
    gen_len = strlen(gen);
    write(fd, gen, gen_len);
    read(fd, buf, 11);
    l = strtol(buf, 0, 10);
    always_random_number = next_rand(num);
    if(l == always_random_number) {
        flag_len = strlen(flag);
        write(fd, flag, flag_len);
        exit(1);
    }
    
    memset(buf, 0x00, 256);
    sprintf(buf, "%s%u\n\n", wrong, (unsigned int)always_random_number);
    wrong_len = strlen(buf);
    write(fd, buf, wrong_len);
}

int play_guess_game(int fd) {
    int seed;
    size_t hello_len, farewell_len;

    seed = time(0);
    srand(seed);
    hello_len = strlen(hello);
    write(fd, hello, hello_len);

    first_try = try_guess(fd, 0);
    second_try = try_guess(fd, 1);
    try_guess(fd, 2);
    farewell_len = strlen(farewell);

    write(fd, farewell, farewell_len);
}

next_rand関数で乱数を生成しているが、引数が0と1の時は、種をtime(0)にしたrand()を使う。
よって同じようにtime(0)を種にしrand()を実行した結果を入力すればよい。

% nc sibears.ru 12007
Hello, in order to get a flag thou must guess a number. Thou have 3 tries.
Generating number... Your guess?
700999430
Welp, can't argue with that. Here's your flag{y0uD4B35tnuMb3Rgenerator3veR!!1}

flag: y0uD4B35tnuMb3Rgenerator3veR!!1

感想

最初はサーバーが落ちたりして最悪なCTFだったが、サーバーが復旧してからは、どんどん問題が解けて楽しかった。
短時間CTFということで簡単な問題が多かったが、たまにはこういうCTFも良いのではと思った。
あと、一人で3000点とった志穂氏はやはりプロだと思った。