Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/martinclauss/exim-rce-cve-2018-6789

This repository provides a learning environment to understand how an Exim RCE exploit for CVE-2018-6789 works.
https://github.com/martinclauss/exim-rce-cve-2018-6789

binary-exploitation cve docker educational exim exim-exploit exploit exploit-development gdb learning-by-doing pwndbg pwntools rce vagrant

Last synced: 7 days ago
JSON representation

This repository provides a learning environment to understand how an Exim RCE exploit for CVE-2018-6789 works.

Awesome Lists containing this project

README

        

# Exim RCE (CVE-2018-6789) Learning Environment

## Description

This is a set of files, scripts, notes, ... to set up an environment to investigate the Exim RCE (CVE-2018-6789). It can be used to debug Exim, write exploits, trace Exim function calls, learn about Exim's custom memory management (storeblocks), find out how a real-world exploit works, ...

It should only be used for academic purposes!

## Requirements

- Vagrant
- Docker (only if you decide to run Docker on your host and not inside the Vagrant VM)

## Setup
Download Exim's source code by executing
```
$ git submodule update --init
```

### VM

There is a `Vagrantfile` in the root directory that currently supports libvirt, virtualbox and vmware as virtualization providers.

```ruby
# -*- mode: ruby -*-
# vi: set ft=ruby :

memory = 8192 # in MiB
cpus = 4

Vagrant.configure("2") do |config|
config.vm.box = "fedora/39-cloud-base"
config.vm.box_version = "39.20231031.1"

config.vm.provider "libvirt" do |lv|
lv.memory = memory
lv.cpus = cpus
end
config.vm.provider "virtualbox" do |lv|
lv.memory = memory
lv.cpus = cpus
end
config.vm.provider "vmware_fusion" do |lv|
lv.memory = memory
lv.cpus = cpus
end

config.vm.provision "shell", inline: <<-SHELL
/vagrant/scripts/setup_vm.sh
SHELL
end
```

You can change the configuration as you like but keep in mind that, for example, the `setup_vm.sh` script uses `dnf` to install packages. If you want to use Ubuntu you must replace the `dnf install` lines with `apt-get install` and adjust the package names accordingly. However, there is no guarantee that the setup will work correctly.

When you are happy with your configuration just run:

```
$ vagrant up
```

to set up the machine and after that

```
$ vagrant ssh
```

to connect to it. If you don't know how to use Vagrant have a look here: https://www.vagrantup.com/intro/getting-started/

### Docker container

Vagrant maps the current directory (i.e. the repository you just cloned) as a shared directory to `/vagrant`. To create and run the Docker image for Exim enter the following commands **inside your VM** (`vagrant ssh`)

```
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
```

The first time will take much longer because Exim will be built from source. If you modify debugging scripts or other files that will be copied into the docker container you can always use `./scripts/reset_docker.sh` to rebuild the Docker image. Surely, you can also just cut out necessary lines from the script and run them as single commands.

When everything is done you should see a root console:

```
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
```

The weird strings may look different on your machine but you are now in a Debian Docker container running in a Fedora VM on your host machine.

### Usage of the VM and the container

First, you can create **two** SSH sessions with `vagrant ssh` in two terminal windows. One can be used to run exploits and interact with Exim via SMTP. The other one is used to start, run, debug, ... Exim within the Docker container. *ASLR is disabled* in the VM so you can set reliable breakpoints that do not change during debugging sessions.

Example session:

First terminal:

```
$ vagrant ssh
[vagrant@localhost vagrant]$
```

Second terminal:

```
$ vagrant ssh
[vagrant@localhost vagrant]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
...
# now you are inside the Debian Docker container
root@99296cf63016:/opt# ./run_exim.sh
root@99296cf63016:/opt# ./attach_exim.sh
```

The `run_exim.sh` script exits and Exim runs in the background. The `./attach_exim.sh` script should attach `gdb` to the running Exim daemon process and give you an output like this:

```
...
pwndbg: loaded 170 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)

Attaching to process 14
Reading symbols from /usr/exim/bin/exim-4.89_1-1-fc6d6586-XX-1...done.
...
0x00007ffff6b7f5e3 in __select_nocancel () at ../sysdeps/unix/syscall-template.S:84
84 ../sysdeps/unix/syscall-template.S: No such file or directory.
Breakpoint 1 at 0x5555555c03d2: file smtp_in.c, line 1762.
Breakpoint 2 at 0x5555555c051d: file smtp_in.c, line 1884.
Breakpoint 3 at 0x55555556a2c8: file base64.c, line 154.
Breakpoint 4 at 0x5555555c6aca: file smtp_in.c, line 3690.
```

Exim is running and waiting for requests. The breakpoints that were set come from the `debugging/breakpoints` file. You can use `Ctrl+C` to interrupt the process and give control to `gdb`. You could also run one of the provided exploit scripts to test if everything is working as expected:

First terminal:

```
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
```

Second terminal:

```
Thread 2.1 "exim" hit Breakpoint 2, smtp_reset (reset_point=reset_point@entry=0x555555843078) at smtp_in.c:1884
1884 {
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────[ REGISTERS ]───────────────────────────────────────────────
RAX 0x555555843078 ◂— 0x0
RBX 0x0
RCX 0x555555824b40 (store_last_get) —▸ 0x555555843078 ◂— 0x0
RDX 0x555555820b30 (yield_length) ◂— 0x15800001c38
RDI 0x555555843078 ◂— 0x0
RSI 0x0
R8 0x3
R9 0x52
R10 0x73
R11 0x246
R12 0x5555555ec7fa ◂— 'daemon.c'
R13 0x555555843078 ◂— 0x0
R14 0x0
R15 0x0
RBP 0x5555555ee3db ◂— and byte ptr [rax], ah /* ' %s\n' */
RSP 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
RIP 0x5555555c051d (smtp_reset) ◂— push rbp
────────────────────────────────────────────────[ DISASM ]────────────────────────────────────────────────
► 0x5555555c051d push rbp
0x5555555c051e push rbx
0x5555555c051f sub rsp, 8
0x5555555c0523 mov rbp, rdi
0x5555555c0526 mov qword ptr [rip + 0x263657], 0 <0x555555823b88>
0x5555555c0531 mov dword ptr [rip + 0x263645], 0 <0x555555823b80>
0x5555555c053b mov dword ptr [rip + 0x26364f], 0 <0x555555823b94>
0x5555555c0545 mov dword ptr [rip + 0x263699], 0 <0x555555823be8>
0x5555555c054f mov dword ptr [rip + 0x263687], 0 <0x555555823be0>
0x5555555c0559 mov dword ptr [rip + 0x263679], 0 <0x555555823bdc>
0x5555555c0563 mov dword ptr [rip + 0x263677], 0 <0x555555823be4>
────────────────────────────────────────────[ SOURCE (CODE) ]─────────────────────────────────────────────
In file: /opt/exim/src/src/smtp_in.c
1879 Returns: nothing
1880 */
1881
1882 static void
1883 smtp_reset(void *reset_point)
► 1884 {
1885 recipients_list = NULL;
1886 rcpt_count = rcpt_defer_count = rcpt_fail_count =
1887 raw_recipients_count = recipients_count = recipients_list_max = 0;
1888 cancel_cutthrough_connection("smtp reset");
1889 message_linecount = 0;
────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────
00:0000│ rsp 0x7ffffffbe528 —▸ 0x5555555c31d1 (smtp_setup_msg+67) ◂— mov dword ptr [rip + 0x260b6d], 0
01:0008│ 0x7ffffffbe530 —▸ 0x7ffffffbe600 ◂— 0x0
02:0010│ 0x7ffffffbe538 —▸ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
03:0018│ 0x7ffffffbe540 —▸ 0x555555605f1a ◂— add byte ptr [rip + 0x25203a73], ah
04:0020│ 0x7ffffffbe548 —▸ 0x555555843078 ◂— 0x0
05:0028│ 0x7ffffffbe550 ◂— 0x0
06:0030│ 0x7ffffffbe558 —▸ 0x7ffff6b7f5e3 (__select_nocancel+10) ◂— cmp rax, -0xfff
07:0038│ 0x7ffffffbe560 ◂— 0x7ffffffbe560
──────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────
► f 0 5555555c051d smtp_reset
f 1 5555555c31d1 smtp_setup_msg+67
f 2 55555556de43 daemon_go+10909
f 3 55555556de43 daemon_go+10909
f 4 555555583ca5 main+21601
f 5 7ffff6abe2e1 __libc_start_main+241
──────────────────────────────────────────────────────────────────────────────────────────────────────────
Breakpoint smtp_reset
pwndbg>
```

You can delete all breakpoints with `d` and continue with `c` to let the `sploit_0.py` script run until it exists:

Second terminal:

```
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
```

First terminal:

```
...
220 787f310ef922 ESMTP Exim 4.89_1-1-fc6d6586-XX Mon, 02 Mar 2020 14:47:24 +0000
250-787f310ef922 Hello test.example.org [172.17.0.1]
250-SIZE 52428800
250-8BITMIME
250-PIPELINING
250-AUTH PLAIN
250-CHUNKING
250-PRDR
250 HELP

501 Invalid base64 data
[*] Closed connection to localhost port 25
```

If the forked process you just debugged (attached to) exits (e.g. `[Inferior 2 (process 42) exited with code 01]`) you can quit `gdb` and run `./attach_exim.sh` again.

## GDB Scripts

Under `debugging` you can find some scripts that could be useful. One of the scripts is `showmem.py`. It allows you to inspect Exims storeblocks and the corresponding heap chunks. You can run it inside `gdb` with the `smem` command:

```
pwndbg> smem
...
[SHOWMEM]: 0x5555558402b0: heap chunk of size 0x000004b0 (used) / data:
[SHOWMEM]: 0x555555840760: heap chunk of size 0x00000030 (used) / data: /lib/x86_64-linux-gnu
[SHOWMEM]: 0x555555840790: heap chunk of size 0x00000050 (used) / data: ...UUU
[SHOWMEM]: 0x5555558407e0: heap chunk of size 0x000000e0 (used) / data:
[SHOWMEM]: 0x5555558408c0: heap chunk of size 0x00000370 (free) / data: .~....
[SHOWMEM]: 0x555555840c30: heap chunk of size 0x00000040 (used) / data:
[SHOWMEM]: 0x555555840c70: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555840c80: storeblock of size 0x00002000 / data:
[SHOWMEM]: 0x555555842c90: heap chunk of size 0x00002020 (used) / data:
[SHOWMEM]: 0x555555842ca0: storeblock of size 0x00002000 / data: root
[SHOWMEM]: 0x555555844cb0: heap chunk of size 0x00008010 (used) / data:
[SHOWMEM]: 0x55555584ccc0: heap chunk of size 0x00002010 (used) / data:
[SHOWMEM]: 0x55555584ecd0: heap chunk of size 0x00001010 (used) / data: 220 99296cf63016 ESMTP Exim 4.89_1-1-fc6d6586-XX M
[SHOWMEM]: 0x55555584fce0: heap chunk of size 0x0001d320 (free) / data:
```

The indented memory regions are the storeblocks the other regions are heap chunks (glibc). **Currently this is an approximation since I did not cross-check the in-use chunks with glibc's free lists, so there might be some wrong indications of used/free blocks**. You can always use pwndbg's `bins`, `heap`, ... commands as an additional source of information!

Note: If you edit the scripts and you are using libvirt / KVM as a provider you should use `vagrant rsync-auto` to copy your changes from the host (not the VM and not the Docker container) to the VM. If you use, for example, VirtualBox you don't have to take care of this manually.

## Structure

```
.
├── debugging # GDB related scripts
│   ├── breakpoints
│   ├── gdbinit
│   ├── showmem.py
│   └── trace.py
├── Dockerfile # Dockerfile to build and debug Exim
├── Exim # Source code for the vulnerable Exim version
├── exim_code_backup # backup of the Exim's vulnerable source code
│   └── exim-fc6d65867e82009a6e0671771728d41d3423a790.zip
├── exim_files # Patched Exim files to build Exim correctly
│   ├── configure
│   ├── eximon.conf
│   ├── Makefile
│   └── Makefile-Linux
├── README.md
├── scripts # Helper scripts to debug Exim
│   ├── attach_exim.sh
│   ├── reset_docker.sh
│   ├── run_exim.sh
│   └── setup_vm.sh
├── sploits # Incremental exploit scripts and a script to find the 0xf1 byte
│   ├── exim_0xf1.py
│   ├── smtp.py
│   ├── sploit_0.py
│   ├── sploit_1.py
│   ├── sploit_2.py
│   ├── sploit_3.py
│   ├── sploit_4.py
│   ├── sploit_5.py
│   ├── sploit_6.py
│   ├── sploit_7.py
│   ├── sploit_8.py
│   └── sploit_9.py
│   ├── sploit_10.py
└── Vagrantfile # Vagrantfile to create the VM that hosts the Docker container

```
## Exploit Scripts

The exploit scripts can be found under `sploits`. They are built incrementally to make it easier to understand the different steps. They are almost identical to the steps provided by @straightblast426 on medium.com.

`sploit_10.py` is the final script that should demo the RCE in one single script. This script does not spawn a reverse shell but create a file under `/tmp`. You can modify this by editing the following line:

```python
cmd = '/bin/bash -c "touch /tmp/pwned"'
```

**Please note that the provided `sploit_10.py` script is not the only way to exploit the vulnerability. There are, for example, other ways to arrange the heap!**

## Limitations

The `next` pointer will be changed with a constant previously known address (that should also work for you if you use the identical setup). If you would throw this exploit against another running Exim it won't work (chances are very small). You could bypass ASLR with some brute-forcing. This works quite good since Exim forks (clones) itself to handle client requests. That means that the overall memory layout stays the same and you get a realistic chance to brute-force the `next` pointer. If you don't know what the `next` pointer is you should read the references first.

If you build `exim` slightly different, the `next` pointer location may be different. You have to find the address of the storeblock that contains `acl_smtp_rcpt`. Follow these steps and adjust the pointer in the respective `sploit_xx.py` files:

```
# connect to VM with: vagrant ssh
./run_exim.sh
./attach_exim.sh

# pwndbg starts, then Ctrl-C
pwndbg> smem
[SHOWMEM]: 0x55555562e6b0: heap chunk of size 0x00002020 (used) / data: .2cUUU
[SHOWMEM]: 0x55555562e6c0: storeblock of size 0x00002000 / data: /usr/exim/configure
...
```

The storeblock with address `0x55555562e6c0` also contains `acl_smtp_rcpt`:

```
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
```

So in the exploit scripts, replace it with the address as displayed with the `smem` command:

```python
# ...
# address of the ACL strings storeblock (not the chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...
```

## References

- https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/ ([backup](https://web.archive.org/web/20231207170848/https://devco.re/blog/2018/03/06/exim-off-by-one-RCE-exploiting-CVE-2018-6789-en/))
- https://medium.com/@straightblast426/my-poc-walk-through-for-cve-2018-6789-2e402e4ff588 ([backup](https://web.archive.org/web/20221025073740/https://straightblast.medium.com/my-poc-walk-through-for-cve-2018-6789-2e402e4ff588))