From 3fc5b60f01464b858e866fcdc64f82971bee5816 Mon Sep 17 00:00:00 2001 From: Christoph Werner Date: Thu, 26 Dec 2019 18:20:13 +0100 Subject: [PATCH] Add OTW Advent 2019 CTF writeup --- writeups/chrztoph/overTheWire2019.md | 145 ++++++++++++++++++ .../overTheWire2019/challenge1/challenge1.js | 52 +++++++ .../overTheWire2019/challenge5/challenge5.js | 104 +++++++++++++ .../overTheWire2019/challenge5/package.json | 6 + .../overTheWire2019/challenge5/yarn.lock | 13 ++ 5 files changed, 320 insertions(+) create mode 100644 writeups/chrztoph/overTheWire2019.md create mode 100644 writeups/chrztoph/overTheWire2019/challenge1/challenge1.js create mode 100644 writeups/chrztoph/overTheWire2019/challenge5/challenge5.js create mode 100644 writeups/chrztoph/overTheWire2019/challenge5/package.json create mode 100644 writeups/chrztoph/overTheWire2019/challenge5/yarn.lock diff --git a/writeups/chrztoph/overTheWire2019.md b/writeups/chrztoph/overTheWire2019.md new file mode 100644 index 0000000..0726261 --- /dev/null +++ b/writeups/chrztoph/overTheWire2019.md @@ -0,0 +1,145 @@ +# OverTheWire Advent Bonanza 2019 + +## Challenge 1 + +Time: about 4h without writeup + +### Task description + +``` +Santa is stranded on the Christmas Islands and is desperately trying to reach his trusty companion via cellphone. We've bugged the device with a primitive keylogger and have been able to decode some of the SMS, but couldn't make much sense of the last one. Can you give us a hand? +``` + +### Overview + +The challenge was really fun because it was a nice coding challenge and reminded me about the good old times. You are given four `.csv` files produced by the keylogger but only three are already decoded and the task is to decode the last sent message. In a given C header file is information about the keys and their meaning. + +### Exploitation + +At first I was very confused about the `keys.h` header file. Why do different characters and numbers map to a specific keypad number? After some time I noticed that this keylogger is from a phone like back in the old days where you have to press a button multiple times to get the desired letter. Remember the phones that have real physical buttons. For example if you press the button `2` three times fast enough in a row you get according to `N7110_KEYPAD_TWO_ABC_CHARS "abc2"` the letter `c`. This is nearly everything that is needed to solve the challenge. + +One thing to put attention at while coding is if you have the same character multiple times in a row. This is the reason why there is a timestamp along with the pressed button. If the time between two button presses is too high but the pressed button is the same as the button pressed before it should be counted as a new button click and therefore a new character. + +The special button `N7110_KEYPAD_MENU_RIGHT` has also been used which deletes on button press the last character entered. + +To execute the code which decrypts the message just run `node challenge1.js` which outputs the following: + +``` +alright pal herse ye flag good lucj enteking it with those hooves lol its aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}0m.. .l ,p +``` + +The solution for the challenge is `l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r`. + + + + +## Challenge 4 + +Time: about 4h without writeup + +### Task description + +``` +'Moo may represent an idea, but only the cow knows.' - Mason Cooley +Service: http://3.93.128.89:1204 +``` + +### Overview + +Opening the service located at the given IP you are given an interface where you can enter a message, choose a type of cow and then click `say`. This prints an ASCII cow which says your message. At the bottom of the site the used `cowsay` version is linked (https://packages.ubuntu.com/bionic/cowsay). + +### Exploitation + +After opening the site and printing the first cow on to the site I noticed that there is an option to select the desired type of cow. The repository of cowsay at https://github.com/schacon/cowsay/tree/master/cows shows that there are per default some cow "skins" available but `custom` is none of them. + +Choosing the `custom` cow you will be presented with a text field to enter your cow. In the manual of `cowsay` (see https://man.cx/cowsay if not installed on your system) is the following written: + +``` +A cowfile is made up of a simple block of perl(1) code, which assigns a picture of a cow to the variable $the_cow. +``` + +This can also be seen in the example cows previously linked. So I just tried to early end the string with `EOC` and doing a `print system("ls");` to list all the files which worked. + +So the complete code for exploitation is: + +``` +EOC +print system("ls"); +``` + +to list all the the files and then + +``` +EOC +print system("cat flag"); +``` + +which prints the flag `AOTW{th3_p3rl_c0w_s4ys_M0oO0o0O}`. + + + + +## Challenge 5 + +Time: about 6h without writeup + +### Task description + +``` +Santa's little helpers are notoriously good at solving Sudoku puzzles, so they made a special variant... +``` + +### Overview + +After downloading an archive you are given a `.txt` file with the challenge description. The task is to solve a given Sudoku with 12 equations that must hold for the solved Sudoku. The equations all look similar to this one: `B9 + B8 + C1 + H4 + H4 = 23`. + +### Exploitation + +To solve the challenge I wrote at first a simple JS function that checks if a valid Sudoku also fulfills the equations given. Therefore I installed a small library which solves a given Sudoku on a random basis. This solution might work but would take forever because the possiblity is high that the generated solutions of the library are often the same as already solved ones. + +I stopped continuing working on the challenge because someone else already solved it with `z3py` by just adopting the Sudoku example (see https://ericpony.github.io/z3py-tutorial/guide-examples.htm). A theorem prover is definitely a better, easier and faster way to solve this challenge. + +My try can still be run with `yarn` and `node challenge5.js`. + + + + +## Challenge 17 + +Time: about 6h without writeup + +### Task description + +``` +It's not just leprechauns that hide their gold at the end of the rainbow, elves hide their candy there too. +Service: http://3.93.128.89:1217/ +``` + +### Overview + +Opening the website presents you with a simple clicker game where you at first enter your name and are then presented with an UI where you can do the following things: + +- `collect` +- `melt` +- `upgrade collection speed` +- `buy a flag` +- `show growth chart` +- `erase progress and restart adventure` + +The goal is to collect as many snowflakes as possible or at least this is usually the goal of a clicker game. + +### Exploitation + +At first the `buy a flag` button got my attention but the button is disabled and it says that you need `10^63` snowflakes. This is too much that you can make it by playing the game. So I tried sending a custom set amount to the server at `http://3.93.128.89:1217/client` with data `{"action": "collect", "amount": 1e63}` and some variations but unfortunately this is checked on the server side and you can only increase the amount of snowflakes by the amount you are supposed to. Also the other endpoints are not very helpful. Another interesting endpoint is `http://3.93.128.89:1217/history/client` where all previously sent requests are shown. But also with this endpoint the amount of snowflakes can not be increased. I also thought that it's maybe possible to do an underflow with the `melt` action but as far as I found out you can only decrease the amount of snowflakes by one at a time and there server is rate limited because if you send too much requests in a short period of time you get the message: + +``` +{ + "error": "Throttled at one request per second. Please note that this challenge does NOT require brute forcing, and does NOT require sending an excessive number of requests." +} +``` + +I tried sending different data to all the different endpoints in the website uses but no luck. Often the server just throws a `500` but I didn't managed to find a way for exploiting this. The server used is `Werkzeug/0.16.0 Python/2.7.17` as it can be found in the response header but unfortunately the debugging mode is not enabled. + +The challenge is also marked as `crypto` and the only thing that points into that direction is the use of the cookie. To buy the flag there is maybe the cookie of an account needed which has enough snowflakes to buy the flag. But I didn't find a way to get more out of the cookie and haven't found any other cookie in the server responses. + +Later the Mattermost people discovered by bruteforcing that there is another endpoint located at `http://3.93.128.89:1217/history/control`. diff --git a/writeups/chrztoph/overTheWire2019/challenge1/challenge1.js b/writeups/chrztoph/overTheWire2019/challenge1/challenge1.js new file mode 100644 index 0000000..0441de7 --- /dev/null +++ b/writeups/chrztoph/overTheWire2019/challenge1/challenge1.js @@ -0,0 +1,52 @@ +var fs = require('fs'); +const content = fs.readFileSync('sms4.csv', 'utf8'); +const lines = content.split("\n"); +const validLines = lines.filter(line => line.trim()); + +const keypadChars = { + 0: " 0", + 1: ".,'?!\"1-()@/:", + 2: "abc2", + 3: "def3", + 4: "ghi4", + 5: "jkl5", + 6: "mno6", + 7: "pqrs7", + 8: "tuv8", + 9: "wxyz9", + 10: "@/:_;+&%*[]{}" +}; + +let lastInput = -1; +let lastTime = -1; +let sameInputCount = 0; +let result = ""; +const NEW_KEY_TIMEDIFF_THRESHOLD = 1000; +const N7110_KEYPAD_ZERO = 0; +const N7110_KEYPAD_STAR = 10; +const N7110_KEYPAD_MENU_RIGHT = 101 + +for (let i = 0; i < validLines.length; i++) { + const splitted = validLines[i].split(","); + const time = Number.parseInt(splitted[0]); + const input = Number.parseInt(splitted[1]); + + if (lastInput === input && time - lastTime < NEW_KEY_TIMEDIFF_THRESHOLD) { + sameInputCount++; + } else if (input === N7110_KEYPAD_MENU_RIGHT) { + result = result.substr(0, result.length - 1); + + sameInputCount = 0; + } else { + if (lastInput >= N7110_KEYPAD_ZERO && lastInput <= N7110_KEYPAD_STAR) { + result += keypadChars[lastInput][sameInputCount % keypadChars[lastInput].length]; + } + + sameInputCount = 0; + } + + lastInput = input; + lastTime = time; +} + +console.log(result); diff --git a/writeups/chrztoph/overTheWire2019/challenge5/challenge5.js b/writeups/chrztoph/overTheWire2019/challenge5/challenge5.js new file mode 100644 index 0000000..87317ed --- /dev/null +++ b/writeups/chrztoph/overTheWire2019/challenge5/challenge5.js @@ -0,0 +1,104 @@ +var sudoku = require('sudoku'); +var _ = require("lodash") + +const A = 0; +const B = 1; +const C = 2; +const D = 3; +const E = 4; +const F = 5; +const G = 6; +const H = 7; +const I = 8; + +const N1 = 0; +const N2 = 1; +const N3 = 2; +const N4 = 3; +const N5 = 4; +const N6 = 5; +const N7 = 6; +const N8 = 7; +const N9 = 8; + +const isValidMap = (map) => { + // B9 + B8 + C1 + H4 + H4 = 23 + if (map[B][N9] + map[B][N8] + map[C][N1] + map[H][N4] + map[H][N4] !== 23) { + return false; + } + + // A5 + D7 + I5 + G8 + B3 + A5 = 19 + if (map[A][N5] + map[D][N7] + map[I][N5] + map[G][N8] + map[B][N3] + map[A][N5] !== 19) { + return false; + } + + // I2 + I3 + F2 + E9 = 15 + if (map[I][N2] + map[I][N3] + map[F][N2] + map[E][N9] !== 15) { + return false; + } + + // I7 + H8 + C2 + D9 = 26 + if (map[I][N7] + map[H][N8] + map[C][N2] + map[D][N9] !== 26) { + return false; + } + + // I6 + A5 + I3 + B8 + C3 = 20 + if (map[I][N6] + map[A][N5] + map[I][N3] + map[B][N8] + map[C][N3] !== 20) { + return false; + } + + // I7 + D9 + B6 + A8 + A3 + C4 = 27 + if (map[I][N7] + map[D][N9] + map[B][N6] + map[A][N8] + map[A][N3] + map[C][N4] !== 27) { + return false; + } + + // C7 + H9 + I7 + B2 + H8 + G3 = 31 + if (map[C][N7] + map[H][N9] + map[I][N7] + map[B][N2] + map[H][N8] + map[G][N3] !== 31) { + return false; + } + + // D3 + I8 + A4 + I6 = 27 + if (map[D][N3] + map[I][N8] + map[A][N4] + map[I][N6] !== 27) { + return false; + } + + // F5 + B8 + F8 + I7 + F1 = 33 + if (map[F][N5] + map[B][N8] + map[F][N8] + map[I][N7] + map[F][N1] !== 33) { + return false; + } + + // A2 + A8 + D7 + E4 = 21 + if (map[A][N2] + map[A][N8] + map[D][N7] + map[E][N4] !== 21) { + return false; + } + + // C1 + I4 + C2 + I1 + A4 = 20 + if (map[C][N1] + map[I][N4] + map[C][N2] + map[I][N1] + map[A][N4] !== 20) { + return false; + } + + // F8 + C1 + F6 + D3 + B6 = 25 + if (map[F][N8] + map[C][N1] + map[F][N6] + map[D][N3] + map[B][N6] !== 25) { + return false; + } + + return true; +} + +var puzzle = [ + null, null, null, null, null, null, null, null, 1, + null, 1, 2, null, null, null, null, null, null, + null, null, null, null, null, null, 2, null, null, + null, null, null, null, null, null, null, null, 2, + null, 2, null, null, null, null, null, null, null, + null, null, null, null, null, null, null, null, null, + null, null, null, null, null, null, 1, 2, null, + 1, null, null, null, null, 2, null, null, null, + null, null, null, 1, null, null, null, null, null]; + +let solution = _.chunk(sudoku.solvepuzzle(puzzle), 9); +while (!isValidMap(solution)) { + solution = _.chunk(sudoku.solvepuzzle(puzzle), 9); +} + +console.log(solution) diff --git a/writeups/chrztoph/overTheWire2019/challenge5/package.json b/writeups/chrztoph/overTheWire2019/challenge5/package.json new file mode 100644 index 0000000..b745083 --- /dev/null +++ b/writeups/chrztoph/overTheWire2019/challenge5/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "lodash": "^4.17.15", + "sudoku": "^0.0.3" + } +} diff --git a/writeups/chrztoph/overTheWire2019/challenge5/yarn.lock b/writeups/chrztoph/overTheWire2019/challenge5/yarn.lock new file mode 100644 index 0000000..253f75e --- /dev/null +++ b/writeups/chrztoph/overTheWire2019/challenge5/yarn.lock @@ -0,0 +1,13 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +lodash@^4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +sudoku@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/sudoku/-/sudoku-0.0.3.tgz#975f0c7f01b7b046bb187a1b72b3a22582f1b489" + integrity sha512-8UU9b39qCJzncdgGT2jbSjAaNdFNJasoZcgKWt7pZ5zVA6Way8yIP9kR8aS2TA/KOwehu0a5uS43eQxrW4hCIA== -- 2.43.0