]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/whazzp/hxp_36C3_CTF02019.md
phager
[pub/jan/ctf-seminar.git] / writeups / whazzp / hxp_36C3_CTF02019.md
1 # hxp 36C3 CTF
2
3 ## General
4 In a retrospective look at the CTF and its challenges I have to say that I should have taken more time investigating the challenges as I think both challenges I had a look at were definitely solvable (the second challenge was actually solved).
5
6 ## md15
7 This challenge was basically a reverse engineering challenge and the introduction was an abstract for a paper describing a new preimage attack on md5 that is still going through a peer-review. It ended with a note stating: "3 x md5 = md15".
8 The archive provided simply contained a binary, named `md15`.
9
10 ### Investigating the md15 binary
11 The first thing I did was having a look at the security measurements of the file. But there was nothing obvious to find, since it was not a pwn challenge. After that I started Ghidra and let it reverse-engineer the binary. The author(s) of the challenge definitely did not want it to be too easy since the binary had been stripped of any meaningful variable names and symbols, making the reversed code harder to read.
12 The program flow was pretty linear:
13 - check the arguments and bail out if something is not right
14 - take the input and hash it 3 times
15 - compare each hash with a different checksum stored within the binary
16
17 Furthermore the program would exit with different status codes, depending on which check went wrong. For example the program would exit with code -1 if no argument had been provided, -2 if the argument did not have the correct length and so on. The input itself would actually be the flag.
18 Through the program argument check I was able to identify a small part of the flag.
19
20 <pre>
21 <code>
22 if (sVar2 == 0x15) {
23 if ((*__s == 0x7b707868) && (*(char *)(__s + 5) == '}')) {
24     ...
25 </code>
26 </pre>
27
28 This statement actually checks if the program argument is of the form: "hxp{xxxxxxxxxxxxxxxx}" where x means the unknown part. Furthermore the unknown part must be 16 characters long and therefore limiting the possible solutions.
29 The binary also contained a `MD5` hash function, which was called three times with different inputs.
30
31 ### Running the binary
32 My first idea was to run the binary in gdb, allowing me to understand what was going on a little better. But the debugger found no symbols, therefore making it not possible to examine some variables the easy way.
33 My next idea was to run the binary and print its exit code. Therefore I wrote a small python script.
34
35 <pre>
36 <code>
37 import subprocess
38
39 flag = xxxxxxxxxxxxxxxx
40 arg = "hxp{" + flag + "}"
41
42 print("arg: " + arg)
43 process = subprocess.Popen(["./md15", arg], stdout=subprocess.PIPE)
44 out = process.communicate()[0]
45 out = bytes(out)
46 print(out.decode())
47 print("exit: " + str(process.returncode))
48
49 </code>
50 </pre>
51
52 ### The end of the line
53 In the end I was not able to make any progress and decided to look for an easier challenge.
54
55 ### A possible solution
56 As of the time writing the report I got a better understanding of what I probably missed about the challenge. Each time before the md5 function is called the input is actually altered through xoring.
57
58 <pre>
59 <code>
60 __ptr = strdup((char *)__s);
61 lVar4 = 4;
62 do {
63     __ptr[lVar4] = *(byte *)((long)__s + lVar4) ^ 0x68;
64     lVar4 = lVar4 + 1;
65 } while (lVar4 != 0x14);
66 n = __ptr + 4;
67 MD5((uchar *)&local_f8,(size_t)n,(uchar *)0x10);
68 </code>
69 </pre>
70
71 The code fragment above describes the procedure applied before calculating the md5. Together with some better knowledge about handling gdb and where to find these damn program arguments it should be possible to solve the challenge.
72
73
74 ## bacon
75 Since the first challenge stated its diffuculty with 'medium'. So I thought I should look for a challenge with the difficulty 'easy'. The only one left with this difficulty was a crypto challenge named `bacon`.
76 The archive provided only contained a python file, called `vuln.py`. The challenge also povided an ip and port, which could be called by netcat.
77
78 ### Examining the `vuln.py`
79 The python file was pretty short (only 39 lines of code) and made no fuss about the flag. If the correct input was provided it would simply output it. To achieve that, the condition:
80 <code>
81 H(s) == h
82 </code>
83 had to be met, where `h` was random 6 bytes sequence and `H(s)` a function taking the users input `s`. The random sequence `h` was outputted at the beginning of the program and the matching input had to be provided.
84 At first I thought that the function `H()` was some kind of hash function and I got stuck understanding the algorithm.
85
86 ### A desperate attempt
87 As a reaction of despair I decided to let my computer do some work while investigating the algorithm further. So I wrote a small python script that would simply use the function `H()` and compute as many outputs as possible. That was obviously not bound to success, because there are basically 2^48 (~3 trillion) different outputs of the function. But I thought its at least something and with enough output I would be able to luckily look up the solution (altough chances are very low). I stopped the attempt after realizing how many possibilities there are with about 20 million precomputed input/output pairs generated at the time.
88
89 ### The solution
90 After the rather ridiculous attempt of precomputing possible outputs, @georg eventually also had a look at the challenge and enlightened everyone working on it, that it's actually the Speck-Algorithm (yes, the name of the challenge was no joke actually) outputting a cipher. In the end he also solved the challenge (and I simply refer to his writeup for the solution). Spoiler: The key was provided as part of the user input, so if one can choose key and plaintext it should be possible to reverse the Speck-Algorithms output.