From 1ebf87dd8c990af06c695bda7acece2fba36897e Mon Sep 17 00:00:00 2001 From: Paul Kalauner Date: Thu, 16 Jan 2020 17:40:54 +0100 Subject: [PATCH] Add OTW writeup and spent time to seccon writeup --- .../toaster/overthewire-advent-bonanza.md | 305 ++++++++++++++++++ writeups/toaster/seccon19.md | 17 +- 2 files changed, 317 insertions(+), 5 deletions(-) create mode 100644 writeups/toaster/overthewire-advent-bonanza.md diff --git a/writeups/toaster/overthewire-advent-bonanza.md b/writeups/toaster/overthewire-advent-bonanza.md new file mode 100644 index 0000000..c9b5b0e --- /dev/null +++ b/writeups/toaster/overthewire-advent-bonanza.md @@ -0,0 +1,305 @@ +# OverTheWire Advent Bonanza 2019 + +#### day-01 7110 (~3h) +##### Overview +There were some `.csv` and `.txt` files given. The `.csv` files contained timestamps, I assumed (which turned out to be correct) and the keycodes of the keys which were pressed at the corresponding timestamps. The `.txt` files contained the translated SMS, but only for three of the four `.csv` files. The challenge was to translate the last `.csv` file. Also, there was a `.c` file, which explained how the keycodes work. +With the help of the challenge description I knew that the keys were pressed on an old phone, where you had to press one button multiple times to get a single letter and the letter changes depending on how many times you press the button. Now I could write a simple python program, which parses the `.csv` file. + +##### Exploit +Here is the script I wrote, explanation is below: +```python +keys= [" 0", ".,'?!\"1-()@/:", "abc2", "def3", "ghi4", "jkl5", "mno6", "pqrs7", "tuv8", "wxyz9", "@/:_;+&%*[]{}"] +filepath = 'sms4.csv' + +with open(filepath) as fp: + last_keycode = -1 + last_keycount = 0 + last_timestamp = 0 + line = fp.readline().strip() + out = "" + while line: + arr = line.split(",") + timestamp = int(arr[0]) + keycode = int(arr[1]) + # Either another key was pressed or the time between the key presses was high enough that two separate characters should be typed + if (last_keycode != keycode or timestamp - last_timestamp > 1200) and last_keycode != -1: + out += keys[last_keycode][last_keycount % len(keys[last_keycode])] + last_keycount = 0 + elif last_keycode == keycode: + last_keycount += 1 + last_keycode = keycode + last_timestamp = timestamp + if last_keycode < 0 or last_keycode > 10: + if last_keycode == 101: # Backspace + out = out[:-1] + last_keycode = -1 + last_timestamp = 0 + + line = fp.readline().strip() + print(out) +``` +The script reads the file line by line. In every line, it checks if the keycode is other than the previous one. If so, the letter (according to the amount of presses of this button) is added to the output string. The same thing happens when the time which passed since the last button was pressed exceeds a threshold (1200 in this case). This means that a new character is meant to be typed. We need to use modulo to get the correct character, as there is a "wrap-around" when you press a button more often than there are characters. For example, if you press "2" five times it should result in the letter a. +If the last keycode does not differ from the previous one, we simply add one to the current keycount. +Then, the last keycode and last timestamp variables are set. After this, it is checked if the keycode is one of the standard keys (like "1", "2", etc.). If not, ignore it. There is an exception with keycode "101", as this code represents the "Backspace" or "Delete character" button. If this button was pressed we simply delete the last character of our output string. At the end the output string is printed. + +The output of the script is the following: `alright pal herse ye flag gnd lucj enteking it with those hnves lol its aotw{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}0m.. .l ,p` +I don't think I got the message 100% correct but it was enough for the flag: + +Flag: `AOTW{l3ts_dr1nk_s0m3_eggn0g_y0u_cr4zy_d33r}` + +#### day-03 Northpole Airwaves (~3h) +##### Overview +This is a forensics challenge. Given was a ~700MB large file where you probably have to extract some information. + +##### Attempts +First I looked for some strings in the file, but of course this would have been too easy, so nothing useful here. +I then used `binwalk` to get an idea of the files which hide in here. I got the following output: + +``` +DECIMAL HEXADECIMAL DESCRIPTION +-------------------------------------------------------------------------------- +25161462 0x17FEEF6 Windows Script Encoded Data (screnc.exe) +30103466 0x1CB57AA Windows Script Encoded Data (screnc.exe) +104543102 0x63B337E Windows Script Encoded Data (screnc.exe) +220329450 0xD21F5EA Windows Script Encoded Data (screnc.exe) +492700718 0x1D5E042E Windows Script Encoded Data (screnc.exe) +``` + +I did some research on what this "Windows Script Encoded Data (screnc.exe)" is and found out that `screnc.exe` is some tool to obfuscate different types of scripts, including Visual Basic Script, for example. +After this, I was looking for a way to decode such an encrypted script. I found a script: https://www.interclasse.com/scripts/decovbe.php +This looked very promising, so I tried it out. Unfortunately, I got only gibberish as an output. + +My next idea was to split the large file up into the different files it contains (I could tell this by the binwalk output). To do so, I used `binwalk` again: +``` +binwalk --dd='.*' d6d0f728ac3ff4848f849b8cf222fb38a14aee870184cc22f2efe01336b2ec17-northpole-airwaves.bin +``` +The `--dd` option extracts the given type signatures. In this case (`.*`) all of them. This creates a new directory containing all the files. I then used the decoding script I found before on all the files separately. Again only gibberish... + +Well, turned out that what I did was nonsense and I should've read the description. I didn't get any further though. + +#### day-04 mooo (~4h) +##### Overview +Given was a simple webpage with one textfield, one dropdown and one button. The textfield lets you specify a message and you can choose a "Cow" in the dropdown. The whole thing is powered by `cowsay` as the footer states which is a tool to generate ASCII pictures of a cow saying the message you provided. + +##### Attempts and Solution +First, I looked up the man pages of cowsay [here](https://linux.die.net/man/1/cowsay). There is a very interesting section: + +``` +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. Should you wish to customize the eyes or the tongue of the cow, then the variables $eyes and $tongue may be used. The trail leading up to the cow's message balloon is composed of the character(s) in the $thoughts variable. Any backslashes must be reduplicated to prevent interpolation. The name of a cowfile should end with .cow, otherwise it is assumed not to be a cowfile. Also, at-signs (''@'') must be backslashed because that is what Perl 5 expects. +``` + +"A cowfile is made up of perl code" sounds really promising. I thought that maybe we can execute arbitrary perl code somehow which would also allow us to execute system commands like `ls` or something. + +In the dropdown you can choose different cows which are all predefined except one. The "custom" entry lets you create your own cow. I expected that this is done by using cowfiles. +So here I could change the cow's appereance, but was not able to inject perl code yet. I took a look at an example cowfile from [here](https://github.com/piuccio/cowsay/blob/master/cows/default.cow): + +```perl +$the_cow = <<"EOC"; + $thoughts ^__^ + $thoughts ($eyes)\\_______ + (__)\\ )\\/\\ + $tongue ||----w | + || || +EOC +``` + +I don't know perl, but `EOC` remembered me of `EOF` or something, so I just tried to use `EOC` as a separator. I quickly looked up how to execute commands in perl (`system()`) and tried it with the following input. + +```perl +$thoughts ^__^ + $thoughts ($eyes)\\_______ + (__)\\ )\\/\\ + $tongue ||----w | + || || +EOC +system("ls") +``` + +It worked! I got the directory listing and the only thing I had to do now is to execute `cat flag` in the same way and I got the flag. + +Flag: `AOTW{th3_p3rl_c0w_s4ys_M0oO0o0O}` + +#### day-05 Sudo Sudoku (~3h) +The solution was already solved by our team when I got to it, but I used the chance to take a look at Z3 which I never did before. + +##### Overview +Given was a `.txt` file which contained a special sudoku. It had very few prefilled numbers but instead there were some formulas given which must be fullfilled in addition to the standard sudoku rules. The formulas where basically just additions of different cells where the result has to be a specific number. + +##### Solution +The solution is pretty straight forward - just solve the sudoku. Of course this would take ages to do by hand, so I used Z3 in python. To do so, I used the sudoku example from the [Z3Py Guide](https://ericpony.github.io/z3py-tutorial/guide-examples.htm) and added the additional constraints: + +```python +from z3 import * + +# 9x9 matrix of integer variables +X = [ [ Int("x_%s_%s" % (i+1, j+1)) for j in range(9) ] + for i in range(9) ] + +# each cell contains a value in {1, ..., 9} +cells_c = [ And(1 <= X[i][j], X[i][j] <= 9) + for i in range(9) for j in range(9) ] + +# each row contains a digit at most once +rows_c = [ Distinct(X[i]) for i in range(9) ] + +# each column contains a digit at most once +cols_c = [ Distinct([ X[i][j] for i in range(9) ]) + for j in range(9) ] + +# each 3x3 square contains a digit at most once +sq_c = [ Distinct([ X[3*i0 + i][3*j0 + j] + for i in range(3) for j in range(3) ]) + for i0 in range(3) for j0 in range(3) ] + +# formulas from challenge specification +formulas = [X[1][8] + X[1][7] + X[2][0] + X[7][3] + X[7][3] == 23, + X[0][4] + X[3][6] + X[8][4] + X[6][7] + X[1][2] + X[0][4] == 19, + X[8][1] + X[8][2] + X[5][1] + X[4][8] == 15, + X[8][6] + X[7][7] + X[2][1] + X[3][8] == 26, + X[8][5] + X[0][4] + X[8][2] + X[1][7] + X[2][2] == 20, + X[8][6] + X[3][8] + X[1][5] + X[0][7] + X[0][2] + X[2][3] == 27, + X[2][6] + X[7][8] + X[8][6] + X[1][1] + X[7][7] + X[6][2] == 31, + X[3][2] + X[8][7] + X[0][3] + X[8][5] == 27, + X[5][4] + X[1][7] + X[5][7] + X[8][6] + X[5][0] == 33, + X[0][1] + X[0][7] + X[3][6] + X[4][3] == 21, + X[2][0] + X[8][3] + X[2][1] + X[8][0] + X[0][3] == 20, + X[5][7] + X[2][0] + X[5][5] + X[3][2] + X[1][5] == 25] + +sudoku_c = cells_c + rows_c + cols_c + sq_c + formulas + +# sudoku instance, we use '0' for empty cells +instance = ((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)) + +instance_c = [ If(instance[i][j] == 0, + True, + X[i][j] == instance[i][j]) + for i in range(9) for j in range(9) ] + +s = Solver() +s.add(sudoku_c + instance_c) +if s.check() == sat: + m = s.model() + r = [ [ m.evaluate(X[i][j]) for j in range(9) ] + for i in range(9) ] + print_matrix(r) + + # Print flag + print("AOTW{", end='') + for i in r[0]: + print(i, end='') + for i in range(1,len(r)): + print(r[i][len(r)-1], end='') + for i in range(len(r[len(r)-1])-2, 0, -1): + print(r[len(r)-1][i], end='') + for i in range(len(r)-1, 0, -1): + print(r[i][0], end='') + print("}") +else: + print("failed to solve") +``` + +This takes a while to solve but then the solved sudoku is printed. Also, the flag is printed which can be obtained by reading the numbers of the solved sudoku clockwise. + +Flag: `AOTW{86472953189247356794813521457639}` + +#### day-16 Musical Stegano (~10h) +This challenge was particularly interesting for me because I play guitar and the piano, so I know how to read sheet music which was helpful for this challenge. Also, this challenge was very special compared to other CTF challenges which was a nice variety. + +##### Overview +Given was a midi file. This can be opened with a music notation program, like [Musescore](https://musescore.org/) for example, to get the corresponding sheet music. The piece is written in G major. + +##### Attempts and Solution +My first attempt was to somewhow get a character based on the note and its duration. So for example, if there is a A as a half note, I calculated 2 (second note in G major) * 4 (one half note contains 4 eighth notes). Don't know how I came to this, just tried some random things out. + +After a few unsuccessful attempts, I was trying a more systematical approach. We know that the flag starts with `A` and ends with `}`. So the `}` character has to be encoded somewhere at the end of the sheet music. In the challenge description there is something written like "BASS SEVEN TRIGRAPHS". I interpreted "Bass" as "Base" and this could make sense (base of number system, 7 in this case), as we have 7 different notes in the G major scale. Also, I knew that G should be translated to 0, A to 1, B to 2, C to 3, D to 4, E to 5 and F# to 6 based on a hint. +Now I looked up the `}` in an ASCII table. The decimal ASCII value for `}` is 125. I converted this decimal value into base 7 which results in 236. After this, I translated this value to its "note value" based on the mapping I mentioned before. This results in BCF. (We can ommit the # in F# as it is not relevant for this challenge) +Now I was looking for the notes B, C and F somewhere at the end of the sheet music - and I found them. It turned out that every second sixteenth note is relevant (doesn't matter if it's in treble or bass clef). Now also the term "trigraph" in the challenge description makes sense. Three relevant notes next to each other represent one character of the flag. I also tried this with the character `A` at the beginning of the sheet music to confirm my assumption. + +In summary you have to do the following: + - Take every second sixteenth note (Those are the relevant notes) + - Form groups of three out of those relevant notes (Each group represents one character) + - Convert the notes of each group to the corresponding number representation (as mentioned above, G = 0, A = 1, etc.) + - Convert this 3-digit number to base 10 and look up the corresponding ASCII character in an ASCII table + +I wrote a very small python script to do so: + +```python +notes = ['G', 'A', 'B', 'C', 'D', 'E', 'F'] + +# Every second sixteenth note from the sheet music +music = 'ABB ADB AEG AEC BCD ADG BBE BBC AGG ABD AFF BAC AFD BGD AEA ADA BCF'.replace(' ', '') + +# Groups of three +for i in range(0, len(music), 3): + # Maps the notes to the number representations and converts it to a decimal integer + char_code = int(str(notes.index(music[i])) + str(notes.index(music[i+1])) + str(notes.index(music[i+2])), 7) + print(chr(char_code), end='') # Print the char with this character code + +print() +``` + +Flag: `AOTW{Mus1Cal_fUN}` + + +#### day-19 santa's signature (~4h) +##### Overview +Given was a service to connect to. Upon connecting, it outputs a public key and it says we should sign 3 different messages with our private key (which belongs to the given public key, we don't have this one obviously). Additionaly, the python source code of this service is given. + +##### Attempts and Solution +After taking a look at the source code, I googled what crypto library was used. Turned out it was `pycrypto`. I also looked for the source code and found it on GitHub [here](https://github.com/dlitz/pycrypto). +After analyzing the source code a bit further, it was obvious that the function we have to exploit is `verify`. I looked up the source code of this function in the GitHub repo. + +```python + def verify(self, M, signature): + """Verify the validity of an RSA signature. + :attention: this function performs the plain, primitive RSA encryption + (*textbook*). In real applications, you always need to use proper + cryptographic padding, and you should not directly verify data with + this method. Failure to do so may lead to security vulnerabilities. + It is recommended to use modules + `Crypto.Signature.PKCS1_PSS` or `Crypto.Signature.PKCS1_v1_5` instead. + + :Parameter M: The expected message. + :Type M: byte string or long + :Parameter signature: The RSA signature to verify. The first item of + the tuple is the actual signature (a long not larger than the modulus + **n**), whereas the second item is always ignored. + :Type signature: A 2-item tuple as return by `sign` + :Return: True if the signature is correct, False otherwise. + """ + return pubkey.pubkey.verify(self, M, signature) +``` + +As you can see above, this says that this method uses textbook RSA (without padding), which means we can exploit the weaknesses of it. I looked up how the verifying of signatures works in textbook RSA. I found [this](https://people.eecs.berkeley.edu/~luca/cs276/lecture20.pdf) document of Berkely University. +It basically says that a message is verified by checking if the following equation holds: $$S^e~mod~n = M~mod ~n$$ where $$S$$ is the signature, $$M$$ the message and $$n$$ and $$e$$ the integers of the public key. +Trivially, this equation holds for $$M=S=0$$ and $$M=S=1$$. So now we have to find a third (different) message and signature which fulfills the equation. This isn't a problem tho, as we can extract $$n$$ and $$e$$ out of the public key (`key.e`/`key.n` in python). Now we can basically take a message (like `-1` for example) take it to the power of $$e$$ and mod $$n$$ ($$S=M^e~mod~n$$). The result of this formula is a signature $$S$$ which is valid for the message $$M$$. + +Finally, I wrote a python script to calculate this and also to solve the challenge (using `pwntools`): + +```python +from pwn import * +from Crypto.PublicKey import RSA + +public_key = RSA.importKey(open('key.pub', 'r').read()) # key.pub contains the public key which I copied from the output when connecting to the service +msg = hex(pow(-1, public_key.e, public_key.n)) # pow(a, b, c) = a^b % c + +r = remote("3.93.128.89", 1219) +r.recvuntil("Message 1 you signed (hex encoded):") +r.sendline("0") +r.sendline("0") +r.sendline("1") +r.sendline("1") +r.sendline(msg) +r.sendline("-1") +r.interactive() +``` + +Flag: `AOTW{RSA_3dg3_c4s3s_ftw}` diff --git a/writeups/toaster/seccon19.md b/writeups/toaster/seccon19.md index 71ad579..f511d0c 100644 --- a/writeups/toaster/seccon19.md +++ b/writeups/toaster/seccon19.md @@ -1,12 +1,16 @@ # SECCON 2019 -Unfortunately I didn't have much time on this weekend so I couldn't play many challenges. Also, I was playing on saturday evening, so both of the challenges I solved where already solved by team mates. -I was looking for some (more more less) short and easy challenges and I found two which I will describe in this writeup. I also tried some others (`Option-Cmd-U` and `Beeeeeeeeeer`) but not long enough to be mentioned in this writeup. +Unfortunately I didn't have much time this weekend so I couldn't play many challenges. Also, I was playing on saturday evening, so both of the challenges I solved where already solved by team mates. +I was looking for some (more or less) short and easy challenges and I found two which I will describe in this writeup. I also tried some others (`Option-Cmd-U` and `Beeeeeeeeeer`) but not long enough (~1h) to be mentioned in this writeup. All in all I had fun solving the smaller challenges and was happy that there were some easier challenges too. It was very satisfying getting those flags, allthough it wasn't that hard. I also found out some new tricks how to bypass filters against SQL injection. -#### coffee_break +#### coffee_break (~3h) +##### Overview I am usually not into crypto challenges (Still have bad memories about that mitm-crypto challenge of the InetSec course. Just kidding, was a nice challenge @cluosh), however this challenge had many solves in comparison to the others, so I tried my luck. -Turned out that this was quite an easy challenge, as you had the source code of an encryption script, where you could simply do the steps the other way round: +In this challenge you had an encrypted message/flag given in the challenge description and the source code of an encryption script. + +##### Exploit +For the exploit I simply reversed the steps taken in the encryption script to write a decryption script: ```python import sys @@ -36,8 +40,11 @@ My script only works with python 2 and adds gibberish at the end of the output, Flag: `SECCON{Success_Decryption_Yeah_Yeah_SECCON}` -#### web_search +#### web_search (~8h) +##### Overview This web page displays some RFCs with the possibility to filter them. You can enter a search term and the page filters the entries accordingly. + +##### Exploit When I saw the page for the first time, I tried out the search function. I entered something like "random" because I saw some RFC with the text "randomly". After I pressed "Search", the text in the text field changed to "rom" which was really weird. After a few seconds and with the thought that there could be a SQL injection vulnerability, I grasped that the SQL keyword `AND` was removed in my search term. I confirmed that by trying a few other search terms containing the `OR` and `AND` keywords. Now I tried SQL commands like `'OR '1'='1` but I had to find a way to bypass the removal of SQL keywords. I entered something like `'OORR '1'='1` and somehow the middle `OR` gets removed but then one `OR` is still remaining, so the first step was made. -- 2.43.0