diff --git a/tic-tac-toes/.gdb_history b/tic-tac-toes/.gdb_history new file mode 100644 index 0000000..06a9e70 --- /dev/null +++ b/tic-tac-toes/.gdb_history @@ -0,0 +1,29 @@ +disassemble main +break print_win_message +d 1 +break main +r +disassemble print_win_message +break *0x00005555555554c2 +c +r +c +c +r +c +c +r +c +c +r +c +r +c +c +r +c +c +r +c +c +disassemble print_win_message diff --git a/tic-tac-toes/Dockerfile b/tic-tac-toes/Dockerfile new file mode 100644 index 0000000..3844f3f --- /dev/null +++ b/tic-tac-toes/Dockerfile @@ -0,0 +1,19 @@ +FROM debian:bullseye + +RUN apt update -y && apt install -y python3 build-essential + +RUN useradd -m ctf + +WORKDIR /app +COPY src /app/src +COPY Makefile /app/Makefile +COPY spawner.py /app/spawner.py + +ENV FLAG="\"\\\"IGCTF{W3ll_y0u_st1ll_l0st_BuT_at_l3ast_yoU_g0t_th3_fl4g}\\\"\"" + +RUN make clean +RUN make + +CMD ["python3", "spawner.py", "--host", "0.0.0.0", "--port", "3005", "./tic-tac-toes"] + +EXPOSE 3005 \ No newline at end of file diff --git a/tic-tac-toes/Makefile b/tic-tac-toes/Makefile new file mode 100644 index 0000000..26be712 --- /dev/null +++ b/tic-tac-toes/Makefile @@ -0,0 +1,49 @@ +CC=gcc +LD=gcc + +# I'm using GCC to link code. This is prefered over using LD directly since +# it will handle all the GLIBC linkage for you. + +SRCS = $(wildcard src/*.c) +OBJS = ${SRCS:.c=.o} + +DEST = tic-tac-toes + +CC_FLAGS = -Wall -Wextra -Werror -Wno-unused-parameter -Wno-unused-variable -Wno-unused-function +LD_FLAGS = +LIBS = + +ifeq ($(FLAG),) +FLAG := "\"IGCTF{REDACTED}\"" +endif + +# This is to let make know these are not actually file targets but rather +# command targets. +.PHONY = all clean format debug run + +all: $(DEST) + +# Link all objects together into one binary +$(DEST): ${OBJS} + $(LD) ${LD_FLAGS} $^ $(LIBS) -o $(DEST) + +# For every c file, compile it to an object file +%.o: %.c + $(CC) ${CC_FLAGS} -c $< -o $@ -DFLAG=$(FLAG) + +# Run the formatter +format: + clang-format -i src/*.c src/*.h + +# Run the created binary +run: $(DEST) + ./$(DEST) + +# Run the created binary using the debugger +debug: $(DEST) + gdb ./$(DEST) -ex "set disassembly-flavor intel" + +# Clean the project of all build files +clean: + $(info [INFO] Cleaning directory) + rm -rf ${OBJS} $(DEST) diff --git a/tic-tac-toes/README.md b/tic-tac-toes/README.md new file mode 100644 index 0000000..80ddb01 --- /dev/null +++ b/tic-tac-toes/README.md @@ -0,0 +1,15 @@ +# TicTacToes +## Text +Everyone has that one weird friend, mine just sent me a game they made. +They bet me that I can't beat their game and if I can't, I have to send +them a pic of myself in sandals??? Anyway I really don't want to, so please +just win this for me. + +## Author +Robbe De Greef + +## Files +tic-tac-toes (the binary) + +## How to Deploy +Deploy docker compose. diff --git a/tic-tac-toes/SOLUTION.md b/tic-tac-toes/SOLUTION.md new file mode 100644 index 0000000..96c9275 --- /dev/null +++ b/tic-tac-toes/SOLUTION.md @@ -0,0 +1,36 @@ +## Difficulty +50/100 | MEDIUM + +Simple format string attack, this is not groundbreaking stuff. + +## Category +Exploitation +## How To Solve +There is a format string vulnerability in the `print_win_message` function. +The name the user inputs at the beginning of the game is printed here using +`printf` without any validation. This means we can leak data from the stack. +It also happens that the flag is on the stack. +To leak the flag we are going to use the `%s` format string specifier. The +problem is that there are a couple of values on the stack before that, for +example the stack looks like this right before the `printf` call: + +``` +00:0000│ rsp 0x7fffffffd250 —▸ 0x5555555596b0 ◂— '%x%x%x%s' +01:0008│-018 0x7fffffffd258 —▸ 0x55555555670b ◂— 'Xx_TicTacToesKing69_xX' +02:0010│-010 0x7fffffffd260 —▸ 0x555555559720 —▸ 0x55555555670b ◂— 'Xx_TicTacToesKing69_xX' +03:0018│-008 0x7fffffffd268 —▸ 0x55555555667f ◂— 'IGCTF{REDACTED}' +04:0020│ rbp 0x7fffffffd270 —▸ 0x7fffffffd2b0 —▸ 0x7fffffffd2e0 —▸ 0x7fffffffd380 —▸ 0x7fffffffd3e0 ◂— ... +05:0028│+008 0x7fffffffd278 —▸ 0x555555555b64 (play_game+240) ◂— mov rax, qword ptr [rbp - 0x18] +06:0030│+010 0x7fffffffd280 —▸ 0x555555559740 —▸ 0x5555555596b0 ◂— '%x%x%x%s' +07:0038│+018 0x7fffffffd288 —▸ 0x555555559720 —▸ 0x55555555670b ◂— 'Xx_TicTacToesKing69_xX' +``` + +So we need to pop off, 3 64 bit ints before we reach the flag. Which means we would +need the following payload `%p%p%p%s`. This would work on a 32 bit machine but since +this is a 64 bit binary, the first 6 arguments are passed via register. This means +we need to "pop off" an additional 5 arguments (the first argument is the format +string itself) giving us the final payload string: +`%p%p%p%p%p%p%p%p%s` + +## Flag +IGCTF{W3ll_y0u_st1ll_l0st_BuT_at_l3ast_yoU_g0t_th3_fl4g} \ No newline at end of file diff --git a/tic-tac-toes/docker-compose.yaml b/tic-tac-toes/docker-compose.yaml new file mode 100644 index 0000000..e16f08c --- /dev/null +++ b/tic-tac-toes/docker-compose.yaml @@ -0,0 +1,6 @@ +version: '3.9' +services: + tic-tac-toes: + build: . + ports: + - 3005:3005 \ No newline at end of file diff --git a/tic-tac-toes/spawner.py b/tic-tac-toes/spawner.py new file mode 100644 index 0000000..feecba5 --- /dev/null +++ b/tic-tac-toes/spawner.py @@ -0,0 +1,47 @@ +import socket +from threading import Thread +import subprocess +import sys +import argparse + +# How to use: +# Set the host and port above +# Invoke the spawner like this +# python3 spawner.py -h -p + +def on_new_client(conn, addr, cmd): + print(f"New connection with {addr}") + file = conn.makefile('w') + p = subprocess.Popen(cmd, stdout=file, stdin=file) + conn.close() + +def main(): + parser = argparse.ArgumentParser(description="Spawn some processes over a socket") + parser.add_argument('--host', type=str, required=True, nargs=1, help='The host ip to listen on') + parser.add_argument('--port', type=int, required=True, nargs=1, help='The host port to listen on') + parser.add_argument('rest', nargs=argparse.REMAINDER) + args = parser.parse_args() + + s = socket.socket() + host = args.host[0] + port = args.port[0] + cmd = args.rest + print(f"Command spawn each time: {cmd}") + + s.bind((host, port)) + s.listen(5) + + print(f"Now listening for connections on {host}:{port}") + try: + while True: + conn, addr = s.accept() + t = Thread(target=on_new_client, args=(conn,addr, cmd)) + t.run() + except KeyboardInterrupt: + pass + + s.close() + print("Goodbye") + +if __name__ == '__main__': + main() diff --git a/tic-tac-toes/src/board.c b/tic-tac-toes/src/board.c new file mode 100644 index 0000000..5fdf2d8 --- /dev/null +++ b/tic-tac-toes/src/board.c @@ -0,0 +1,87 @@ +#include +#include +#include "board.h" + +struct board *board_init() +{ + struct board *board = calloc(1, sizeof(struct board)); + return board; +} + +void board_destruct(struct board *board) +{ + free(board); +} + +enum cell_state board_get_cell(struct board *board, int x, int y) +{ + return board->cells[y * WIDTH + x]; +} + +void board_set_cell(struct board *board, int x, int y, enum cell_state state) +{ + board->cells[y * WIDTH + x] = state; +} + +static void board_print_decoration_line() +{ + for (int x = 0; x < WIDTH; x++) + { + printf("+---"); + } + printf("+\n"); +} + +char board_cell_char_repr(enum cell_state cell) +{ + switch (cell) + { + case CROSS: + return 'X'; + case NOUGHT: + return 'O'; + default: + return '_'; + } +} + +void board_print(struct board *board) +{ + for (int y = 0; y < HEIGHT; y++) + { + board_print_decoration_line(); + for (int x = 0; x < WIDTH; x++) + { + enum cell_state cell = board_get_cell(board, x, y); + char c = board_cell_char_repr(cell); + printf("| %c ", c); + } + printf("|\n"); + } + board_print_decoration_line(); +} + +int board_is_full(struct board *board) +{ + for (int y = 0; y < HEIGHT; y++) + { + for (int x = 0; x < WIDTH; x++) + { + if (board_get_cell(board, x, y) == NOPLAYER) + return 0; + } + } + return 1; +} + +enum cell_state get_opponent(enum cell_state player) +{ + if (player == CROSS) + { + return NOUGHT; + } + else + { + return CROSS; + } +} \ No newline at end of file diff --git a/tic-tac-toes/src/board.h b/tic-tac-toes/src/board.h new file mode 100644 index 0000000..4cb1d74 --- /dev/null +++ b/tic-tac-toes/src/board.h @@ -0,0 +1,26 @@ +#ifndef BOARD_H +#define BOARD_H + +#define WIDTH 3 +#define HEIGHT 3 + +enum cell_state { + NOPLAYER = 0, + CROSS, + NOUGHT, +}; + +struct board { + enum cell_state cells[WIDTH * HEIGHT]; +}; + +struct board* board_init(); +void board_destruct(struct board* board); +enum cell_state board_get_cell(struct board* board, int x, int y); +void board_set_cell(struct board* board, int x, int y, enum cell_state state); +void board_print(struct board* board); +int board_is_full(struct board* board); +enum cell_state get_opponent(enum cell_state player); +char board_cell_char_repr(enum cell_state cell); + +#endif /* BOARD_H */ \ No newline at end of file diff --git a/tic-tac-toes/src/board.o b/tic-tac-toes/src/board.o new file mode 100644 index 0000000..491cdd4 Binary files /dev/null and b/tic-tac-toes/src/board.o differ diff --git a/tic-tac-toes/src/flag.c b/tic-tac-toes/src/flag.c new file mode 100644 index 0000000..e69de29 diff --git a/tic-tac-toes/src/flag.o b/tic-tac-toes/src/flag.o new file mode 100644 index 0000000..77a426a Binary files /dev/null and b/tic-tac-toes/src/flag.o differ diff --git a/tic-tac-toes/src/player.c b/tic-tac-toes/src/player.c new file mode 100644 index 0000000..981279c --- /dev/null +++ b/tic-tac-toes/src/player.c @@ -0,0 +1,23 @@ +#include +#include + +#include "player.h" + +struct player* player_init(char* name, enum cell_state color, player_move_fptr move) { + struct player* player = malloc(sizeof(struct player)); + player->name = name; + player->color = color; + player->move = move; + return player; +} + +void player_destruct(struct player* player) +{ + free(player); +} + +struct player* player_swap(struct player* cur, struct player* first, struct player* second) +{ + if (cur->color == first->color) return second; + return first; +} \ No newline at end of file diff --git a/tic-tac-toes/src/player.h b/tic-tac-toes/src/player.h new file mode 100644 index 0000000..b494976 --- /dev/null +++ b/tic-tac-toes/src/player.h @@ -0,0 +1,18 @@ +#ifndef PLAYER_H +#define PLAYER_H + +#include "board.h" + +typedef void (*player_move_fptr)(struct board*, enum cell_state); + +struct player { + char* name; + enum cell_state color; + player_move_fptr move; +}; + +struct player* player_init(char* name, enum cell_state color, player_move_fptr move); +void player_destruct(struct player* player); +struct player* player_swap(struct player* cur, struct player* first, struct player* second); + +#endif /* PLAYER_H */ \ No newline at end of file diff --git a/tic-tac-toes/src/player.o b/tic-tac-toes/src/player.o new file mode 100644 index 0000000..75c7db9 Binary files /dev/null and b/tic-tac-toes/src/player.o differ diff --git a/tic-tac-toes/src/tic-tac-toes.c b/tic-tac-toes/src/tic-tac-toes.c new file mode 100644 index 0000000..15fd99c --- /dev/null +++ b/tic-tac-toes/src/tic-tac-toes.c @@ -0,0 +1,246 @@ +#include +#include +#include +#include "board.h" +#include "player.h" + +#ifndef FLAG +#define FLAG "IGCTF{REDACTED}" +#endif + +#define max(x, y) (((x) > (y)) ? (x) : (y)) +#define min(x, y) (((x) < (y)) ? (x) : (y)) + +const char *toes = + " @@@@@@@@@@ @@@#@@@@*@@ \n" + " @@%:::::::::-%@@ @#::#@#+*%@#@@ \n" + " @%::::--==--::::%@ @@+::=@:....:@+@@ @@@@@ \n" + " @*:*@%+-....:+%@*:*@ @%::::#@-..:%#:%@%*%%##@@@ \n" + " @#:*#............%=:*@ @%::::::=##+:::@++@...-%%@@ \n" + " @@-:+#............@-::%@@@=:::::::::::=@-+@:...+@*@ \n" + " @%::-@...........=@:::-@ @@-::::::::::#*:::*%%%*:*@ \n" + " @+:::##.........-@-::::%@ @@-:::::::::@=:::::::::%@ @@@@@@ \n" + " @=::::+@*-...:+@*::::::*@ @=:::::::::%+:::::::::@@@=::+#%@@ \n" + " @=:::::::=***+:::::::::*@ @*:::::::::*#:::::::::@+::-@-..:%@@ \n" + " @*:::::::::::::::::::::#@ @*:::::::::*%:::::::::%+:::*%:.:@#@ \n" + " @@:::::::::::::::::::::@@ @*:::::::::*#:::::::::%*:::::+*+::@ \n" + " @@-:::::::::::::::::::#@ @+:::::::::#+:::::::::%#::::::::::@ \n" + " @+::::::::::::::::::-@@ @-:::::::::%=:::::::::%*::::::::::@ \n" + " @@::::::::::::::::::*@ @@-:::::::::%=:::::::::@*::::::::::@@@@@@ \n" + " @%:::::::::::::::::@@ @%::::::::::#*::::::::+@@:::::::::-@::-#@@@ \n" + " @@-::: _____ _ :@ _____ :::: _____ ::::+@:::::::::: :-@..=@ \n" + " @+::: |_ _(_)__ |_ _|_ _ __ |_ _|__ ___ ___ :::::::*@@@@@ \n" + " @+::: | | | / _| | |/ _` / _| | |/ _ \\/ -_|_-< :::::::::::*@ \n" + " @+::: |_| |_\\__| |_|\\__,_\\__| |_|\\___/\\___/__/ ::::::::::*@ \n" + " @+::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::*@ \n"; + +void print_banner() +{ + puts(toes); + sleep(1); +} + +void print_win_message(char *winner, char *loser) +{ + char* flag = FLAG; + puts("And the flag goes tooo..."); + puts(winner); + puts("Guess who lost..."); + printf(loser); +} + +int has_player_won(struct board *board, enum cell_state player) +{ + // Rows + for (int y = 0; y < HEIGHT; y++) + { + enum cell_state x0 = board_get_cell(board, 0, y); + enum cell_state x1 = board_get_cell(board, 1, y); + enum cell_state x2 = board_get_cell(board, 2, y); + + if (x0 == x1 && x1 == x2 && x0 == player) + return 1; + } + + // Cols + for (int x = 0; x < WIDTH; x++) + { + enum cell_state y0 = board_get_cell(board, x, 0); + enum cell_state y1 = board_get_cell(board, x, 1); + enum cell_state y2 = board_get_cell(board, x, 2); + + if (y0 == y1 && y1 == y2 && y0 == player) + return 1; + } + + // Diags + enum cell_state p0 = board_get_cell(board, 0, 0); + enum cell_state p1 = board_get_cell(board, 1, 1); + enum cell_state p2 = board_get_cell(board, 2, 2); + + if (p0 == p1 && p1 == p2 && p0 == player) + return 1; + + p0 = board_get_cell(board, 2, 0); + p1 = board_get_cell(board, 1, 1); + p2 = board_get_cell(board, 0, 2); + + if (p0 == p1 && p1 == p2 && p0 == player) + return 1; + + return 0; +} + +int minimax(struct board *board, int depth, int isMaximizingPlayer, enum cell_state player) +{ + if (has_player_won(board, CROSS)) + { + return 10; + } + else if (has_player_won(board, NOUGHT)) + { + return -10; + } + else if (board_is_full(board)) + { + return 0; + } + + if (isMaximizingPlayer) + { + int bestScore = -1000; + + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < HEIGHT; y++) + { + if (board_get_cell(board, x, y) == NOPLAYER) + { + board_set_cell(board, x, y, player); + int score = minimax(board, depth + 1, 0, player); + board_set_cell(board, x, y, NOPLAYER); + bestScore = max(score, bestScore); + } + } + } + + return bestScore; + } + else + { + int best_score = 1000; + + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < HEIGHT; y++) + { + if (board_get_cell(board, x, y) == NOPLAYER) + { + board_set_cell(board, x, y, get_opponent(player)); + int score = minimax(board, depth + 1, 1, player); + board_set_cell(board, x, y, NOPLAYER); + best_score = min(score, best_score); + } + } + } + + return best_score; + } +} + +void ai_do_move(struct board *board, enum cell_state player) +{ + int best_score = -1000; + int best_move_x = -1; + int best_move_y = -1; + + for (int x = 0; x < WIDTH; x++) + { + for (int y = 0; y < HEIGHT; y++) + { + if (board_get_cell(board, x, y) == NOPLAYER) + { + board_set_cell(board, x, y, player); + int score = minimax(board, 0, 0, player); + board_set_cell(board, x, y, NOPLAYER); + + if (score > best_score) + { + best_score = score; + best_move_x = x; + best_move_y = y; + } + } + } + } + + board_set_cell(board, best_move_x, best_move_y, player); +} + +void user_do_move(struct board *board, enum cell_state player) +{ + int x, y; + + while (1) + { + printf("x: "); + scanf("%i", &x); + printf("y: "); + scanf("%i", &y); + + x -= 1; + y -= 1; + + if (x < 0 || x > 2 || y < 0 || y > 2) + continue; + + if (board_get_cell(board, x, y) != NOPLAYER) + continue; + + board_set_cell(board, x, y, player); + break; + } +} + +void play_game(struct player *player1, struct player *player2) +{ + struct board *board = board_init(); + struct player *player = player2; + + while (!has_player_won(board, player->color)) + { + player = player_swap(player, player1, player2); + + printf("%s can now move\n", player->name); + board_print(board); + + player->move(board, player->color); + } + board_print(board); + + // Laigh + char *winner = player->name; + player = player_swap(player, player1, player2); + char *loser = player->name; + print_win_message(winner, loser); + + board_destruct(board); +} + +int main() +{ + setbuf(stdout, NULL); + + char *username = NULL; + + print_banner(); + + printf("Before we start playing, what is your name?\n> "); + scanf("%ms", &username); + + struct player *player1 = player_init("Xx_TicTacToesKing69_xX", CROSS, ai_do_move); + struct player *player2 = player_init(username, NOUGHT, user_do_move); + + play_game(player1, player2); + return 0; +} \ No newline at end of file diff --git a/tic-tac-toes/src/tic-tac-toes.o b/tic-tac-toes/src/tic-tac-toes.o new file mode 100644 index 0000000..e4fea0e Binary files /dev/null and b/tic-tac-toes/src/tic-tac-toes.o differ diff --git a/tic-tac-toes/tic-tac-toes b/tic-tac-toes/tic-tac-toes new file mode 100755 index 0000000..a2332db Binary files /dev/null and b/tic-tac-toes/tic-tac-toes differ