From f0e5411be2cfd9f6df0899bb486719fa0929c41f Mon Sep 17 00:00:00 2001 From: chgue Date: Sat, 18 Jan 2020 19:18:45 +0100 Subject: [PATCH] chgue add more writeups --- writeups/chgue/hxp36c3.md | 80 +++++++++++++++++ writeups/chgue/otw19.md | 181 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 writeups/chgue/hxp36c3.md create mode 100644 writeups/chgue/otw19.md diff --git a/writeups/chgue/hxp36c3.md b/writeups/chgue/hxp36c3.md new file mode 100644 index 0000000..dc48892 --- /dev/null +++ b/writeups/chgue/hxp36c3.md @@ -0,0 +1,80 @@ +# Retrospective +Sadly, I didn't have that much time to play the CTF. Therefore, after trying some challenges I decided to stick to one challenge even if I was making no useful progress at all since jumping between challenges seemed even less useful. The challenge I tried was very interesting but sadly I was stuck at one step and did not find anything useful. + +**Time spent:** 7 hours + +## compilerbot (misc) (unsolved) +### Overview +The following python service was accessible via a tcp + +```python +#!/usr/bin/env python3 + +import base64 +import subprocess + +code = base64.b64decode(input('> ')).decode() +code = 'int main(void) {' + code.translate(str.maketrans('', '', '{#}')) + '}' + +result = subprocess.run(['/usr/bin/clang', '-x', 'c', '-std=c11', '-Wall', + '-Wextra', '-Werror', '-Wmain', '-Wfatal-errors', + '-o', '/dev/null', '-'], input=code.encode(), + stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + timeout=15.0) + +print(result.stdout) +if result.returncode == 0 and result.stdout.strip() == b'': + print('OK') +else: + print('Not OK') +``` + +Basically, the program takes an base64 input, decodes it, and inserts it into the `main` function of a barebones C template. Additionally, it uses the functions `translate` and `maketrans`. `translate` applies a translation table to a string. `maketrans` takes 3 inputs and creates a translation table. E.g. `maketrans('abc', '123', 'x')` turns the string `abcXcba` to `123321`. That is it maps the characters from the first input to the second one character by character. All characters in the third input are removed. In the case of the given service that means that `{`, `}` and `#` are removed from the input. + +If the compilation is successful, the service outputs `OK`. Otherwise, it outputs `Not OK`. The compiled code is never run and immediately discarded into `/dev/null`. + +### Attempted solutions +Looking at the code, the inputs are passed to `clang` correctly and no additional flags or anything else could be passed. Therefore, the exploit is presumably to bypass the input escaping and turning compilation into an oracle for a local file (i.e. the flag file). + +The translation can easily be circumvented using digraphs. I created a script that reads a file, escapes the blacklisted characters using digraphs and encodes the results in base64. Additionally, a newline is prepended and `return 0;` appended since otherwise the compiler will complain. +```python +import sys +import base64 +with open(sys.argv[1], "r") as f: + data = f.read().replace("{", "<%").replace("}", "%>").replace("#", "%:") + data = "\n" + data + "\nreturn 0;" + print(base64.b64encode(data.encode()).decode()) +``` + +My first idea was to `#include "flag"` and storing it in a char array. However, `#include` treats the file contents as C code and not as a string! Therefore, this is not easily possible. Stackoverflow sadly offers no solution either https://stackoverflow.com/questions/1246301/c-c-can-you-include-a-file-into-a-string-literal. To be able to include the file directly, it would have to be of the form `MACRONAME(the_flag_goes_here)`. Then one could define the macro `#define MACRONAME(x) #x`. This macro turns the input into a string literal since `#x` is some sort of macro builtin. However, the flag file has the format `hxp{the_flag_goes_here}` so this approach does not seem viable. + +It is however possible to `#define hxp `. So I tried +```C +#define hxp enum t +#include "./flag" +; +``` +This essentially creates an enum with the flag as the only element. This worked on my local machine with a fake flag such as the one above. However, on the real service it returned an error so the flag included some illegal characters.Furthermore, I don't know how this could be exploited further. + +Meanwhile, I looked at [`clang` extensions](https://clang.llvm.org/docs/LanguageExtensions.html) since some might be useful and differ from the ubiquitous `gcc`. I found nothing useful that could help with including the flag. However, I found [`_Static_assert()`](https://clang.llvm.org/docs/LanguageExtensions.html#c11-static-assert) which would probably be a handy oracle function if the flag could somehow be included. Sadly, I never managed to include the flag. + +I spent the rest of the time reading C documentation and tried to get something working locally. However, I could not find a way to include the flag. + +### Solution (as per other writeups) +The [intended solution](https://ctftime.org/writeup/17952) provided by hxp used the following peculiarity of `clang`: +```C +#define _str(x) #x +#define str(x) _str(x) +#define hxp str( +#include "flag" +) +``` +This starts the macro inside the included file but closes the bracket in our file. Somehow, `clang` accepts this and parses the contents (i.e. the flag) as a string (`gcc` would not do that). + +Since they can include the flag they use this pattern to find the flag by using a binary search +```C +static const char thing[str(...)[0]]; +_Static_assert(sizeof(thing) == '{', "..."); +``` + +Other solutions used some weird inline assembly and `.incbin` interactions to yield the flag. However, the above is the intended solution according to hxp. diff --git a/writeups/chgue/otw19.md b/writeups/chgue/otw19.md new file mode 100644 index 0000000..91ad458 --- /dev/null +++ b/writeups/chgue/otw19.md @@ -0,0 +1,181 @@ +# General retrospective +In general, the CTF was very fun. The web challenges were a bit guess heavy but the other challenges were fine. Apart from the 4 challenges in this writeup I also looked at some other challenges, mostly Battle of the Galaxies, Summer Adventure Game and Lost in Maze. + +**Time spent:** 24 hours (incl. other challenges not part of the writeup) + +# EasterEgg 3 +## Retrospective +Not too interesting but I learned about AZTEC QR codes (which are apparently often used by railway companies, etc.). +**Time spent:** 1 hour + +## Overview +The organizers posted this image on twitter with no additional information +![mattermost login required](https://mattermost.w0y.at/api/v4/files/oksmhc5kcfdxmgzu4csnchqowc "challenge image") + +## Solution +Clearly, the first instinct is to use QR code scanner. This results in `137:64:137:154:171:146:63:175` which can be interpreted as ASCII in octal notation (the `:` being separators between the letters). This results in the string `_4_lyf3}`. Because of the `}` this part is presumably the end of the flag. However, some other information must be hidden somewhere. + +I cut out the QR code and used `zxing` and to try out different formats. `zxing --barcode_format=AZTEC qr.png` (Note the Aztec looking letters in the image!) resulted in `414f54577b6234726330643373`. Interpreting this as ASCII in hex notation results in `AOTW{b4rc0d3s`. + +The flag is then given by `AOTW{b4rc0d3s_4_lyf3}`. + +# christmaSSE keygen (day 10) +## Retrospective +The challenge was fun and a learning experience since I never heard of SIMD-instructions or used numpy before. An especially memorable lesson was to never forget to think about whether `\\` or `\` is appropriate... This error cost me like 2 hours. Funnily enough, @georg was working on the challenge in parallel. However, he also had one tiny error somewhere in his decoding (endianess was wrong iirc). Before fixing my error I was able to get the flag by combining the output of his matrix exponentiation and my decyption. Teamwork makes the dreamwork! ^^ + +**Time spent:** 6 hours + +## Overview +A program which uses SIMD-instructions is given. It takes an input string, does some calculations, transforms the input with the result of the calculations and then prints the transformed string (i.e. the flag). The catch is that the calculations contains loops, an outer and an inner loop. The outer loop has `1234567890123456789` iterations and the inner loop has `1000` iterations. Clearly, this does not terminate for a long time so it has to be optimized. + +## Solution +@HaH translated the assembly instructions to pseudocode. From this it is clear that the SIMD-instructions are used to emulate some sort of operations on matrices and vectors. To be more precise, the instructions can be interpreted as follows: + +The initial matrix is the identity matrix of size 4, i.e. +```math +A_0 = +\begin{pmatrix} +1 & 0 & 0 & 0\\ +0 & 1 & 0 & 0\\ +0 & 0 & 1 & 0\\ +0 & 0 & 0 & 1 +\end{pmatrix} +``` + +One iteration $`i`$ of the outer loop takes the matrix $`A_{i-1}`$ and computes the following matrix product: +```math +A_i' = A_{i-1} \cdot +\begin{pmatrix} +1 & 2 & 3 & 4\\ +5 & 6 & 7 & 8\\ +9 & 10 & 11 & 12\\ +13 & 14 & 15 & 16 +\end{pmatrix} +``` + +The inner loop implements a modulo operation on $`A_i'`$. Essentially, using some SIMD-instructions the prime modulus $`p`$ is subtracted repeatedly from every value in the matrix $`x`$ long as the $`x - n \geq 0`$. The inner loop runs a thousand times which is enough since the values are only 32 bit integers. Thus, after the inner loop the resulting matrix is given by +```math +A_i = A_i' \mod p +``` + +Therefore, the nested loops are just applying modular exponentiation on a matrix and the result after all outer loop iterations is given by +```math +\begin{pmatrix} +1 & 2 & 3 & 4\\ +5 & 6 & 7 & 8\\ +9 & 10 & 11 & 12\\ +13 & 14 & 15 & 16 +\end{pmatrix} ^ {1234567890123456789} \mod p +``` + +This can easily be calculated in e.g. Python. The result is then used to transform the input in some manner to yield the flag. However, this can be translated straight from the decompiled assembly into Python code without thinking to much about what it is doing. Below is a script that correctly computes the flag using numpy and a rather fast matrix modular exponentiation algorithm taken from [Wikipedia](https://en.wikipedia.org/wiki/Modular_exponentiation#Generalizations). However, @georg implemented a simpler algorithm which was fast enough as well. + +```python +import numpy as np +import struct +m = np.matrix("1 2 3 4; 5 6 7 8; 9 10 11 12; 13 14 15 16") +exponent = 1234567890123456789 +prime = 0x96433d +flag_pt1 = [0xfc, 0x14, 0xeb, 0x09, 0xbc, 0xae, 0xe7, 0x47, 0x4f, 0xe3, 0x7c, 0xc1, 0x52, 0xa5, 0x02, 0x8e] +flag_pt2 = [0x89, 0x71, 0xc8, 0x8d, 0x96, 0x23, 0x01, 0x6d, 0x71, 0x40, 0x5a, 0xea, 0xfd, 0x46, 0x1d, 0x23] +flag = flag_pt1 + flag_pt2 + +# Matrix modular exponentiation algorithm taken from wikipedia +def mmulmod(a, b, c): + return np.mod(np.matmul(a, b), c) + +def mexpm(a, b, c): + if b == 0: + return np.matrix("1 0 0 0; 0 1 0 0; 0 0 1 0; 0 0 0 1") + if b % 2 == 1: + return mmulmod(a, mexpm(a, b-1, c), c) + d = mexpm(a, b//2, c) + return mmulmod(d, d, c) + +# Matrix exponentiation and turn into list for transformation +pos = mexpm(m, exponent, prime).tolist() + +# Translate the assembly code to python +pos_added = [i for s in pos for i in s] +key = [i for s in [list(struct.pack(":` generates an address for the specified username since the code probably splits at `:` and checks the second value. + +### Putting it together +@lavish created a script to combine the two exploits. This resulted in an account getting `13` tokens from one account (note that 5000 are needed). However, registering new users was rate limited. The obvious idea is then to juggle tokens between two accounts using the race condition. I.e. the account with 13 tokens then uses the race condition to transfer 13 tokens as often as possible to the previous account and so forth. This generates enough tokens extremely quickly and resulted in the flag `AOTW{S4n7A_c4nT_hAv3_3lF-cOnTroL_wi7H0uT_eLf-d1sCipl1N3}`. + +# snowflake idle (day 17) +## Retrospective +Another web challenge where the guessing part was the most difficult. To be fair, the guessing part was way more obvious than in the previous challenge but we were too stupid to notice. Otherwise, the challenge was rather straightforward. + +**Time spent:** 5 hours + +## Overview +On navigating to the page one is asked to chose a username. After choosing a name one is presented with a website which essentially is some cookie clicker like thing but instead of cookies one can generate snowflakes. After having collected a certain amount, one can upgrade the collection speed by 1 (similar to cookie clicker games). The amount of snowflakes over time is visualized as a graph and there exists some button to buy the flag after having reached a large amount of snowflakes. + +The immediately visible endpoints are `/control` for registering a user, `/client` for actions related to the game (e.g. getting snowflakes) and `/history/client` which is used by the history graph and returns the history as a json. The history is composed of every action that was `POST`ed to `/client`. + +## Solution +The snowflake gathering cannot be scripted efficiently as it is checked server-side and the amount of snowflakes cannot be underflowed as negative snowflake values are possible. Many people in MM and myself tried to find something for quite a long time until @TwentyOneCool used dirbuster (I believe) to find the endpoint `/history/control` (this was the guessing part which is obvious in retrospect. Similar to `/history/client` this endpoint returns a history of the `/control` endpoint. Therefore, we saw that the endpoint `/control` offered the capabilities to `load` and `save` a client state, i.e. the amount of snowflakes! Furthermore, the client state was displayed for the `save` command. By registering a really long username consisting of `a...a` we noticed that the encrypted pattern repeats. Therefore, the client data was encrypted using some sort of poly-alphabetic shift cipher. Using a script it is easy to extract the key: + +```python +import base64 + +testblock = "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" + +testblock2 = "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" + +def xor(b1, b2): + return [a ^ b for a, b in zip(b1, b2)] + +def get_key(s): + s = list(base64.b64decode(s)) + key = xor([ord("a")]*60, s[36:96]) # repetition starts at index 36 + return key[-16:] + key[:-16] # the last 16 bytes are actually the start of the key + +def main(): + k = get_key(testblock) + print(k) + print("".join([chr(x) for x in xor(list(base64.b64decode(testblock))[:60], k)])) + print("".join([chr(x) for x in xor(list(base64.b64decode(testblock2))[:60], k)])) + +if __name__ == "__main__": + main() + +``` + +Running the script outputs the following: +``` +[249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60, 249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60, 249, 30, 132, 234, 14, 215, 113, 76, 15, 57, 182, 100, 166, 36, 156, 73, 117, 150, 141, 60] +{"money": 5.0, "speed": 1, "name": "aaaaaaaaaaaaaaaaaaaaaaaa +{"money": 1.0, "speed": 2, "name": "aaaaaaaaaaaaaaaaaaaaaaaa +``` +The first array is the key in decimal notation, the second is the beginning of the state which the key was derived from and the third line is the beginning of an earlier game state. Clearly, the decryption key stays the same and thus it is very easy to encrypt a custom json object with the required amount of money. Sadly, I can't try it anymore since the service is down but teammates solved the challenge without having the full key (which is a bit more cumbersome). + +After getting enough money one can buy the flag `AOTW{leaKinG_3ndp0int5}`. -- 2.43.0