From 3bdb82d1fb88f328ffa656b6ba67902d9d0c0f51 Mon Sep 17 00:00:00 2001 From: michael Date: Mon, 13 Jan 2020 23:06:14 +0100 Subject: [PATCH] Add writeups --- writeups/tortagel/36C3_hxp19.md | 203 ++++++++++++++++++++++++++++++ writeups/tortagel/otw_advent19.md | 187 +++++++++++++++++++++++++++ 2 files changed, 390 insertions(+) create mode 100644 writeups/tortagel/36C3_hxp19.md create mode 100644 writeups/tortagel/otw_advent19.md diff --git a/writeups/tortagel/36C3_hxp19.md b/writeups/tortagel/36C3_hxp19.md new file mode 100644 index 0000000..84b590b --- /dev/null +++ b/writeups/tortagel/36C3_hxp19.md @@ -0,0 +1,203 @@ +# 36C3 hxp CTF 2019 + +## Retrospective + +I played on Saturday and Sunday, on both days about 7 hours, but I found the challenges either quit difficult or much guessing. So I lost a lot of time to find an interesting challenge for me. The `catch the flag` challenge was interesting, but I just started it a view hours before the end of the CTF and had not time to finish it. + +### catch the flag (not solved) + +Given was a client (`client.py`) and a server (`game.py`) program which communicated over sockets. There was another python script (`world_generator.py`) which generated a random world for the game. This script was executed only once at the beginning, so the _world_ was every time the same. The `flag_char.py` script was only a helper script for the server (`game.py`). + +A world looks something like that: + +``` +00100000100000000000 +00000000000000000000 +00000101000000100000 +00100000100000000000 +00000000100000000000 +00002222222222001000 +01010000000000000000 +11000000010000000000 +00100010000000000000 +00000010000000000000 +``` + +The real world was much bigger! We can navigate through this world with the directions **up**, **down**, **left** and **right**. We always start on the top left corner. If we _crash_ into a **1**, we lost. The **twos** are the flag which we have to _collect_. We cannot leave the game world. + +Additionally we receive as response from the server the following _information_: + +- `i\x00breeze`: horizontally or vertically next to us is a **1**. +- `i\x00smell`: horizontally or vertically next to us is a **2** (flag char). +- `iw`: we move onto a wall. +- `e\x00`: a message. +- `f\x00`: we found a flag char. +- `d:` we lost. + +The client program is just a wrapper which does some fancy stuff on the different responses from the server - we do not really need it. + +The tricky part now is that each character of the flag, initially in the center of the world, moves every second turn randomly one field up, down, left or right. So we cannot just easy navigate to the flag and collect all the characters from it. + +I wrote the following script to explore the world a little bit: + +``` python +#!/usr/bin/env python3 + +import socket + +HOST = "78.47.17.200" +PORT = 7888 +WIDTH = 80 +HEIGHT = 80 + +pos = (0, 0) + +flag_chars = [] + +def read_map(): + mapp = [[9 for i in range(WIDTH)] for j in range(HEIGHT)] + with open("exploit_map", "r") as f: + lines = f.read().strip().split('\n') + for y, line in enumerate(lines): + for x, c in enumerate(line): + mapp[y][x] = int(c) + return mapp + +def write_map(mapp): + with open("exploit_map", "w") as f: + for line in mapp: + f.write(''.join(map(str, line)) + "\n") + +def receive_until_prompt(sock, prompt=b"\n"): + received = b"" + buf_size = len(prompt) + while True: + new = sock.recv(buf_size) + received += new + for i in range(1, len(prompt) + 1): + if received.endswith(prompt[-i:]): + if i == len(prompt): + return received + else: + buf_size = len(prompt) - i + 1 + if not new: + raise Exception(f"Connection closed before {prompt} was found.") + +def recv(sock): + data = receive_until_prompt(sock) + data = data.strip() + data = data.split(b"\x00") + return data + +def set_curr_pos(mapp, val): + x, y = pos + mapp[y][x] = val + write_map(mapp) + +def parse(data, mapp): + res = None + if data[0] == b"c": + print(f"c-text={data[1]}") + elif data[0] == b"e": + print(f"e-text={data[1]}") + elif data[0] == b"i": + last = data + set_curr_pos(mapp, 0) + if len(data) == 1: + #print("nothing") + None + elif len(data) == 2: + if data[1] == b'breeze': + print("breeze") + res = "breeze" + elif data[1] == b'smell': + print("smell") + elif len(data) == 3: + print(f"breeze and smell: {data}") + res = "breeze" + else: + print(f"???? {data}") + elif data[0] == b"iw": + print("wall") + elif data[0] == b"d": + print("dead") + set_curr_pos(mapp, 1) + res = "dead" + elif data[0] == b"f": + set_curr_pos(mapp, 0) + print(f"flag char={data[1]}") + flag_chars.append(data[1]) + write_map(mapp) + return res + +def set_pos(direction): + global pos + x, y = pos + if direction == "s": + pos = (x, y + 1) + elif direction == "w": + pos = (x, y - 1) + elif direction == "a": + pos = (x - 1, y) + elif direction == "d": + pos = (x + 1, y) + x, y = pos + if x < 0: + pos = (0, y) + if x >= WIDTH: + pos = (WIDTH - 1, y) + if y < 0: + pos = (x, 0) + if y >= HEIGHT: + pos = (x, HEIGHT - 1) + +def move(s, direction, mapp): + set_pos(direction) + s.sendall(f"{direction}\n".encode()) + data = recv(s) + return parse(data, mapp) + +def connect(mapp): + global pos + pos = (0, 0) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((HOST, PORT)) + parse(recv(s), mapp) + return s + +mapp = read_map() + +s = connect(mapp) + +for d in range(WIDTH): + way = "d"*d + "s"*HEIGHT + #way = "s"*d + "d"*WIDTH + for direction in way: + res = move(s, direction, mapp) + if res == "dead": + s = connect(mapp) + break + elif res == "breeze": + res = move(s, "a", mapp) + if res == "dead": + s = connect(mapp) + break + +print(f"flag_chars={flag_chars}") +``` + +But obviously this does not really help us, because even if we find every flag character, we do not know the correct order. So I looked again to the scripts and found the following line in the `flag_char.py` file: `random.seed(int(time.time()))`. So we know the seed and can thus predict the position of the flag characters. + +So a solution could be the following: + +1. Explore the whole world to know where the **ones** are. +2. Move randomly around to find all the flag characters to know the length of the flag. +3. Predict the position of each character with the known seed. + +As I said at the beginning, the time was over after I found out all this information. + +### flag concat (not solved) + +Given was a program which took two flags as input and concatenated them. If a flag included the string `hxp{`, it ignored the characters before this part. So e.g. if flag 1 is `aaahxp{flag1}\n` and flag 2 is `bbbhxp{flag2}\n` the output is `hxp{flag1}\nhxp{flag2}\n`. + +I looked for a while at the source code and to the man pages to find something for a buffer overflow. With gdb I found out, we have to change the return address from `0x4009cf` to `0x4007b6`. The behavior with the packed_strings struct was somehow weird to me. Everything I tried to overwrite the return address on the stack did not work. diff --git a/writeups/tortagel/otw_advent19.md b/writeups/tortagel/otw_advent19.md new file mode 100644 index 0000000..25ff8e0 --- /dev/null +++ b/writeups/tortagel/otw_advent19.md @@ -0,0 +1,187 @@ +# OverTheWire Advent CTF 2019 + +## Retrospective + +It was interesting to see that there exists a CTF advent calendar. So at first I was motivated to look at some challenges, but I did not have as much time as I had hoped for in December. I looked at many challenges until i realized they are much more complicated than I thought. + +## Challenges + +### 7110 (solved) + +On the first day I started right at the beginning and solved the first challenge in 3 hours. + +After looking at the given files, especially to the `key.h` file, I found out, the challenge is about to decode a key logger file. There where some example key logger files and the decoded text given. The structure of the csv logging files is the following: `,` + +At the beginning I started do decode the file _by hand_, but after decoding some letters, it was too time consuming and I started to write a script for it. + +I did not cover all the special cases, only those who where necessary. E.g. the MENU_RIGHT key was for deleting characters. Another problem I ran into was that it is possible to _click over all the characters_ an restart at the beginning. The solution was simply the modulo operation. + +The (not beautiful) exploit script: + +``` python +#!/usr/bin/env python3 + +# N7110_KEYPAD_KEYS +N7110_KEYPAD_ZERO = 0 +N7110_KEYPAD_ONE = 1 +N7110_KEYPAD_TWO = 2 +N7110_KEYPAD_THREE = 3 +N7110_KEYPAD_FOUR = 4 +N7110_KEYPAD_FIVE = 5 +N7110_KEYPAD_SIX = 6 +N7110_KEYPAD_SEVEN = 7 +N7110_KEYPAD_EIGHT = 8 +N7110_KEYPAD_NINE = 9 +N7110_KEYPAD_STAR = 10 +N7110_KEYPAD_HASH = 11 +N7110_KEYPAD_MENU_LEFT = 100 +N7110_KEYPAD_MENU_RIGHT = 101 +N7110_KEYPAD_MENU_UP = 102 +N7110_KEYPAD_MENU_DOWN = 103 +N7110_KEYPAD_CALL_ACCEPT = 104 +N7110_KEYPAD_CALL_REJECT = 105 + +N7110_KEYPAD_ZERO_ABC_CHARS = " 0" +N7110_KEYPAD_ONE_ABC_CHARS = ".,'?!\"1-()@/:" +N7110_KEYPAD_TWO_ABC_CHARS = "abc2" +N7110_KEYPAD_THREE_ABC_CHARS = "def3" +N7110_KEYPAD_FOUR_ABC_CHARS = "ghi4" +N7110_KEYPAD_FIVE_ABC_CHARS = "jkl5" +N7110_KEYPAD_SIX_ABC_CHARS = "mno6" +N7110_KEYPAD_SEVEN_ABC_CHARS = "pqrs7" +N7110_KEYPAD_EIGHT_ABC_CHARS = "tuv8" +N7110_KEYPAD_NINE_ABC_CHARS = "wxyz9" +N7110_KEYPAD_STAR_ABC_CHARS = "@/:_;+&%*[]{}" + +KEY_DICT = {N7110_KEYPAD_ZERO: N7110_KEYPAD_ZERO_ABC_CHARS, + N7110_KEYPAD_ONE: N7110_KEYPAD_ONE_ABC_CHARS, + N7110_KEYPAD_TWO: N7110_KEYPAD_TWO_ABC_CHARS, + N7110_KEYPAD_THREE: N7110_KEYPAD_THREE_ABC_CHARS, + N7110_KEYPAD_FOUR: N7110_KEYPAD_FOUR_ABC_CHARS, + N7110_KEYPAD_FIVE: N7110_KEYPAD_FIVE_ABC_CHARS, + N7110_KEYPAD_SIX: N7110_KEYPAD_SIX_ABC_CHARS, + N7110_KEYPAD_SEVEN: N7110_KEYPAD_SEVEN_ABC_CHARS, + N7110_KEYPAD_EIGHT: N7110_KEYPAD_EIGHT_ABC_CHARS, + N7110_KEYPAD_NINE: N7110_KEYPAD_NINE_ABC_CHARS, + N7110_KEYPAD_STAR: N7110_KEYPAD_STAR_ABC_CHARS + } + +FILENAME = "sms4.csv" +TIME_DIFF = 830 + +plaintext = "" +with open(FILENAME, 'r') as file: + line = file.readline() + last_timestamp = -1 + last_key_pressed = -1 + click_count = 0 + while line != '': + timestamp, key_pressed = line.split(',') + timestamp = int(timestamp) + key_pressed = int(key_pressed) + if key_pressed <= N7110_KEYPAD_STAR: + if last_key_pressed == key_pressed: + if (timestamp - last_timestamp) >= TIME_DIFF: + plaintext += KEY_DICT[key_pressed][click_count % len(KEY_DICT[key_pressed])] + click_count = 0 + else: + click_count += 1 + else: + if last_key_pressed <= N7110_KEYPAD_STAR: + plaintext += KEY_DICT[last_key_pressed][click_count % len(KEY_DICT[last_key_pressed])] + click_count = 0 + else: + if key_pressed == N7110_KEYPAD_MENU_RIGHT: + plaintext = plaintext[:-1] + elif last_key_pressed >= 0 and last_key_pressed <= N7110_KEYPAD_STAR: + plaintext += KEY_DICT[last_key_pressed][click_count % len(KEY_DICT[last_key_pressed])] + click_count = 0 + + last_timestamp = timestamp + last_key_pressed = key_pressed + line = file.readline() + +print(plaintext) +``` + +Flag: `AOTW{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}` + +### Sudoku (not solved) + +I tried this challenge only until i saw `@georg` solved it in a much better way, so for about one hour. + +Given was a simple sudoku with some extra conditions which must be hold. + +The developed script just tries naively all the possible solutions. I let it run for a while, but it did not terminate after a few minutes. But seriously, z3 is here the muuuch better way like `@georg` did it. + +``` python +#!/usr/bin/env python3 + +def findNextCellToFill(grid, i, j): + for x in range(i,9): + for y in range(j,9): + if grid[x][y] == 0: + return x,y + for x in range(0,9): + for y in range(0,9): + if grid[x][y] == 0: + return x,y + return -1,-1 + +def isValid(grid, i, j, e): + rowOk = all([e != grid[i][x] for x in range(9)]) + if rowOk: + columnOk = all([e != grid[x][j] for x in range(9)]) + if columnOk: + secTopX, secTopY = 3 *(i//3), 3 *(j//3) + for x in range(secTopX, secTopX+3): + for y in range(secTopY, secTopY+3): + if grid[x][y] == e: + return False + return True + return False + +def solveSudoku(grid, i=0, j=0): + i,j = findNextCellToFill(grid, i, j) + if i == -1: + if check(grid): + return True + else: + for e in range(1,10): + if isValid(grid,i,j,e): + grid[i][j] = e + if solveSudoku(grid, i, j): + return True + grid[i][j] = 0 + return False + +def check(grid): + if grid[1][8] + grid[1][7] + grid[2][0] + grid[7][3] + grid[7][3] == 23: + if grid[0][4] + grid[3][6] + grid[8][4] + grid[6][7] + grid[1][2] + grid[0][4] == 19: + if grid[8][1] + grid[8][2] + grid[5][1] + grid[4][8] == 15: + if grid[8][6] + grid[7][7] + grid[2][1] + grid[3][8] == 26: + if grid[8][5] + grid[0][4] + grid[8][2] + grid[1][7] + grid[2][2] == 20: + if grid[8][6] + grid[3][8] + grid[1][5] + grid[0][7] + grid[0][2] + grid[2][3] == 27: + if grid[2][6] + grid[7][8] + grid[8][6] + grid[1][1] + grid[7][7] + grid[6][2] == 31: + if grid[3][2] + grid[8][7] + grid[0][3] + grid[8][5] == 27: + if grid[5][4] + grid[1][7] + grid[5][7] + grid[8][6] + grid[5][0] == 33: + if grid[0][1] + grid[0][7] + grid[3][6] + grid[4][3] == 21: + if grid[2][0] + grid[8][3] + grid[2][1] + grid[8][0] + grid[0][0] == 20: + if grid[5][7] + grid[2][0] + grid[5][5] + grid[3][2] + grid[1][5] == 25: + return True + return False + +sudoku = [[0,0,0,0,0,0,0,0,1], + [0,1,2,0,0,0,0,0,0], + [0,0,0,0,0,0,2,0,0], + [0,0,0,0,0,0,0,0,2], + [0,2,0,0,0,0,0,0,0], + [0,0,0,0,0,0,0,0,0], + [0,0,0,0,0,0,1,2,0], + [1,0,0,0,0,2,0,0,0], + [0,0,0,1,0,0,0,0,0]] + +solved = solveSudoku(sudoku) +for s in solved: + print(s) +``` -- 2.43.0