Rating:
### Original writeup can be found [here](https://blog.bushwhackers.ru/faust2018-diagon-alley/), this is a local copy and may be outdated
## Overview
This task is a service which is running on team's vulnbox (server). It can be seen in traffic that check system frequently connects to service and performs some actions but this traffic is almost unreadable (actually encrypted as we will see later). While performing initial overview of the service we noticed that it is working as systemd network socket and each connection is spawning a new instance of `/srv/diagon_alley/diagon_alley` which receives client socket as its stdin and stdout (inetd-style server).
There are two binaries inside `/srv/diagon_alley/` folder: main binary named `diagon_alley` and another one named `shops`. `diagon_alley` is statically linked library without symbols acting as a frontend, which uses dynamically linked binary `shops` as a backend to database. By analyzing disassembly code we can figure out that both binaries are “sandboxed” using seccomp which explains why such architecture has been chosen instead of performing database query from the main binary. `diagon_alley` uses pipe-fork-exec routine to start `shops` as a child process and communicate with it later on. We can communicate only with `diagon_alley` binary, there is no direct access to `shops` backend.
Service supports following actions:
* registration
* login
* creating, entering and listing shops
* creating, adding, buying and listing items
To login and enter the shop you need to provide a password. Flags are stored inside items so you need to enter the corresponding shop and list items in it in order to get flags.
## Communication protocol reverse engineering
No client is provided to communicate with server so let's open `diagon_alley` binary in disassembler and dig our way to complete understanding of used communication protocol.
One of the first functions called in `main` is a constructor of `IO_wrapper` class. This class provides two methods, `IO_read` and `IO_puts` as can be understood from their inner logic. We need to note that these functions not only perform data sending and receiving but are also doing some crypto stuff with that data. They heavily use `IO_wrapper` object variables so let’s see what are these variables initialized with.
In `IO_wrapper` constructor (`0x402750`) two function pointers are set to `IO_read` and `IO_puts` functions, input and output `FILE*` variables are set to `stdin` and `stdout` respectively. One DWORD value is set to zero and another is set to `rand()`. Additionally current time is saved for unknown purpose, it is updated in `IO_read` so we called it `last_op_time`.
After constructing `IO` object it is immediately used in function at address `0x402890`. A lot of things are going on there so we start to randomly inspect called functions. In function `0x408580` we can see calls that look like asserts:
```c
...
sub_406290("in != NULL", "src/pk/rsa/rsa_import.c", 32LL);
...
sub_406290("key != NULL", "src/pk/rsa/rsa_import.c", 33LL);
...
```
Using these messages we can quickly find used crypto library: [libtomcrypt](https://github.com/libtom/libtomcrypt). We can identify function at `0x402450` by assert that it contains: `perror("fread", a2, v7);`. Using this approach we can find most standard function names and obtain the following “beautified” form of function at address `0x402890`:
```c
_BOOL8 __fastcall set_check_rsa(IO_wrapper *IO)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]
v13 = __readfsqword(0x28u);
sub_402A00();
stdin = IO->stdin;
packet_size = 0LL;
fread(&packet_size, 1LL, 8uLL, stdin);
v2 = fread(&packet, 1LL, packet_size, IO->stdin);
user_rsa_key = (rsa_key *)calloc(1LL, 72LL);
if ( !user_rsa_key )
goto LABEL_12;
user_rsa_key& = user_rsa_key;
if ( (unsigned int)rsa_import(&packet, v2, user_rsa_key) )
return 1LL;
IO->user_rsa_key = user_rsa_key&;
server_rsa_key = (rsa_key *)calloc(1LL, 72LL);
server_rsa_key& = server_rsa_key;
if ( server_rsa_key )
{
LOBYTE(v9) = rsa_generate(server_rsa_key, 2048);
if ( v9 )
return 1LL;
IO->server_rsa_key = server_rsa_key&;
a3 = 2048LL;
if ( rsa_export((unsigned __int8 *)&packet, &a3, 0, server_rsa_key&) )
return 1LL;
fwrite(&a3, 1LL, 8uLL, IO->stdout);
fwrite(&packet, 1LL, a3, IO->stdout);
result = sub_402810(IO);
}
else
{
LABEL_12:
perror("calloc", 72LL, v4);
result = 1LL;
}
return result;
}
```
We identified two additional variables in `IO_wrapper` object used in this function: `user_rsa_key` and `server_rsa_key`. Full `IO_wrapper` structure:
```c
struct __attribute__((aligned(16))) IO_wrapper
{
QWORD init_random;
QWORD cnt;
FILE *stdin;
FILE *stdout;
rsa_key *user_rsa_key;
rsa_key *server_rsa_key;
QWORD (__cdecl *write)(IO_wrapper *, char *);
QWORD (__cdecl *read)(IO_wrapper *, _BYTE *);
time_t last_op_time;
};
```
Now we look back at `IO_puts` and see that it uses `IO->user_rsa_key`. Similarly, `IO_read` uses `IO->server_rsa_key`. Looks like we need to generate our RSA key, send it to server, receive server’s RSA key and after that our messages to server are encrypted using server’s key and incoming messages are encrypted with ours key.
After that we reverse engineer regular packet structure from both `IO_puts` and `IO_read` functions. It looks like this:
```c
struct __attribute__((aligned(8))) in_buffer
{
QWORD chunk_length; // including itself
QWORD init_random; // as determined in IO_wrapper::ctr
QWORD cnt; // increments in IO_puts
QWORD data_length; // size of data array
BYTE data[]; // actual data
};
```
At first `cnt` is zero. You may think that we don’t know server’s random but after exchanging RSA keys servers sends us main menu and since it is encoded in this format, it contains this random number. Overall, here is the process of establishing and maintaining connection:
1. Generate and send our RSA key
2. Receive server’s RSA key
3. Receive first encrypted packet with main menu, extract `init_random` from it
4. Send and receive data encoded as `in_buffer` structure, do not forget to update local `cnt` copy on each server’s `IO_puts` (i.e., our send).
> Side note: an interesting question is how exactly to use RSA keys. What encryption&decryption scheme is used? Of course you can reverse engineer `diagon_alley` binary further or google what scheme `libtomcrypt` is using by default but who needs these smart approaches in 2018? In our case we imported ours and server's keys as `Crypto.PublicKey.RSA` objects and wondered why their methods `encrypt` and `decrypt` are raising `NotImplemented` exception so we googled it and first answer suggested to use `PKCS1_OAEP` scheme with these keys (`good_key = PKCS1_OAEP.new(our_key)`). It worked.
>
> After the end of the game player from some other team in IRC said that in their version of library raw RSA key’s `encrypt` and `decrypt` methods were implemented but not as in `PKCS1_OAEP` scheme so they were stuck with fully understood but not working protocol. Bad luck.
Script that handles connection and makes encryption layer transparent for `pwntools` can be found [here](https://gist.github.com/vient/b1eb7480f9329d27b41c154599be8d9a).
## Exploitation
After busting out our static analysis tools (`strings` from `binutils`) we can find vulnerability in `shops` binary. Queries that are meant to check password during login and shop entering use *LIKE* operator instead of normal comparison. That means, by simply entering `%%` as a password you can enter any shop from the list and get the items. As you see importing server-generated private keys and establishing communication between client and the server is way harder than further exploitation.
It is also possible to achieve code execution at this service, which we didn’t manage to do due to the lack of time, but it can still be your homework. It is possible to enter item name large enough to trigger the stack overflow in `add_item` method. It happenes due to the “typo” which copies 0x20 bytes of the item name to the buffer with 20 bytes size.
Full exploit can be found [here](https://gist.github.com/vient/d6a9db783e983fa89ce6db4a0761a544).
## IRC logs
Here is what orgs said about this challenge after the end of CTF (saved for history):
```
23:07 <+mightymo> so in the frontend there was a simple buffer overflow, when you create an item
23:08 <+mightymo> it would check that the item name is less than 50 by using strlen but then copy using the length of the message which is not based on c-strings
23:14 <+mightymo> to get code execution in the backend then, you could trigger a SIGUSR, which would lead to a uaf or craft a malicious createShopReq that would lead to an overflow
```