DG'hAck 2022 - Sandchat

7 minute read

Here is the write up of the Sandchat challenge of the DG’hAck 2022 CTF. This challenge was in the system category but It more mix reverse and pwn skills, was fun to solve !

Vous disposez d’un acces ssh à une application de surveillance d’un serveur. Saurez vous en échapper ? Selon nos sources un service serait vulnérable sur cette machine. A vous de découvrir son point faible..

Part 1 - Sandbox shell escape

To start the challenge we have an ssh access to a service which is sandboxed. The goal of this service is to monitor a server with simple command. Our goal is to escape it to get a shell on the machine.

These are the commands allowed by the service :

We are blocked in the home of the user (sandbox), we could list files of this directory and backup them (print them in base64), these are the most interesting commands.

First thing is to backup the TODO file :

- Implement debugging.
- Implement logging verbosity level.
- Fix broken links in /bin.
- Remove questionable historical feature.
- Fix access to system log files.
- Add more monitoring features.
- Plan a pentest of the sandbox!

Those messages could appear as hints but no one was useful for me x).

I tried many path traversal to backup files which are in another directory that /home/sandbox but didn’t success. But the file in the sys/sandbox/PID directory could be dump, and it was the binary which act as the sandbox service. We can see more clearly on the service by reversing the binary !

Reverse time

Let’s use IDA to reverse it.

$ file exe
exe: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=b01189b32e76aba44e5c70eba813992fbfbb37e8, for GNU/Linux 3.2.0, stripped

This is a classic ELF binary but It is stripped :(

We could try to see if there was command injection or path traversal vulnerability on all commands but there is nothing.

Digging in to the code, I find those strings which question me because I didn’t find any commands which print those messages.

Is there an hidden command ?

This is the interesting comparison, I debugged it to see the value of check variable because It was quicker. I used the plugin decomp2dbg, very useful to get the symbol of the binary with connecting GDB with IDA. Also cool to have a more precise idea where I was in the main fonction which was big, thanks to the decompiler of IDA.

The check variable contains the string hash.

We are on the good way !

Perfect, we escape the sandbox with an hidden command !

Part 2 - Pwn IRC

Now we have a shell, we get the flag obviously ? wait .. “There is a vulnerable service in the machine”, we didn’t complete this part, this is the second step.

Struggling a lot with trying some kernel exploits and try to bypass, but It didn’t work, the debian machine was up to date.

Quick enumeration with netstat leak us a running service, the files of it where in /opt directory.

We have the documentation of the service, this is an IRC binary which act as an IRC server.

This is the documentation :

# Documentation serveur CompactIRC / CIRC
Ceci est la documentation de l'implémentation serveur de CIRC.
Ce projet implémente un serveur CIRC de base qui répond à un sous-ensemble du protocole Compact Internet Relay Chat.
Pour se connecter au serveur :
  `/connect <adresse_serveur> <port>`
Le serveur implémente les commandes IRC suivantes :
* PSEUDO : Change le pseudonyme d'un utilisateur.
  `/PSEUDO <pseudonyme>`
* SUJET : Change la description d'un canal. 
  `/SUJET #<canal> :<sujet>`
* UTILISATEUR : Spécifie le pseudonyme, le nom d'hôte, le nom de serveur, et le véritable nom d'un nouvel 
  `/UTILISATEUR <pseudonyme> 0 * :<nom_reel>`
* LISTE : Liste les canaux du serveur
* REJOINDRE : rejoint un canal
  `/REJOINDRE #<canal>`
* PARTIR : Quitte un canal
  `/PARTIR #<canal>`
* QUI : Liste les informations d'un utilisateur
  `/QUI <pseudonyme>`
* QUIEST : Lister plus d'informations sur un utilisateur.
  `/QUIEST <pseudonyme>`
* MSGPRIV : Envoie des messages à un canal ou à un utilisateur.
   `/MSGPRIV <pseudonyme | canal> <message>`
* QUITTER : Déconnecte l'utilisateur du serveur.
De plus, le serveur implémente partiellement certaines commandes pour l'interaction avec le client, qui assurent
la connexion est maintenue :
* PING : Répond avec un PONG ;
* NOTICE : envoie des notifications à un client.
$ checksec server
Arch:     amd64-64-little
Stack:    Canary found
NX:       NX enabled
PIE:      PIE enabled

PIE, canary, that can make us think at a Heap challenge, but we couldn’t be sure.

Fire up IDA again and let’s reverse the binary !

This is the main fonction which is runned for each different connection in a new thread. (important to notice this for the heap)

First I was thinking that there is a buffer overflow in the read fonction, read 0x1000 in a buffer of length 16 but this was a bug with IDA, I checked in GDB and there isn’t any buffer overflow.

This is a while loop which compare input with each possible commands. There are /UAFPRINT and UAFRESET commands which aren’t in the documentation. UAF stands for Use After Free, this hint us again on the heap, beacause UAF is a common attack in the heap.

I have long thought that we should obtain a shell, but this wasn’t the case. In the folder where the documentation and the server binary were, there is a config file named conf.ini which we couldn’t read.

We retreive this string on the binary in the uaf_changeNick fonction :

The code check if channel and *((_QWORD *)channel + 2) are definied, if they are, the binary read the content of the config file and print it. (We could deduce that the flag is going to be print here :) )

We need to understand the use of channel. The variable is stored on the .bss section and it contains a pointer of the channel name (string). There is also a second variable : userName which act as the same, but contains a pointer of the username (also a string).

Both string are stored on the heap in a special heap thread section (first time I see this).

We could set a channel value like this : /SUJET <canal>:<subject> and a username like this : /PSEUDO <name>.

I try hard to find an heap overflow in the functions which manage our input when we use these commands but there is no buffer overflow at all :(

Here is an example of initialization. We can see that an attribute IsIrCop is present when we create a topic, this is the check to see if we are administrator (and if we could get the flag !)

Here is in gdb, the values in the .bss sections and string associated. With pwndbg we could see the heap chunk allocated for our string with the vis command.

The chunk of the username could be bigger, but for the channel it’s blocked by the code. We couldn’t set a subject length bigger than 16 chars. And we want to write at the next 8 bytes to set *((_QWORD *)channel + 2).

What we want to set is in red.

Because there isn’t any buffer overflow, we need to find a use after free. Free the channel chunk, and overwrite it with username allocation to rewrite *((_QWORD *)channel + 2) and get the flag.

The flaw is on the topicfree fonction :

unsigned __int64 topicfree()
  unsigned __int64 v1; // [rsp+18h] [rbp-8h]

  v1 = __readfsqword(0x28u);
  if ( !channel || is_channel_freed )
    puts("\nNo channel to free");
    is_channel_freed = 1;
  return __readfsqword(0x28u) ^ v1;

This fonction is called when we use the /SUJET command withtout argument. It frees the channel chunk, but it doesn’t put the channel value to 0, so channel still points to the chunk / which now is a bin. So if we set a username with length which match this free bin, It would take his place and we overwrite the value that we want !

The address for the topic is the same, but there is no more string value because the chunk was free and string put at NULL.

Last step is to put our username like this :

We get a local flag :)

We only have to repeat this on the remote machine and get the flagz ! :



Was a very fun challenge in multiple parts, thanks to the chall makers !