
Este repositório fornece um ambiente de aprendizado para entender como um exploit RCE do Exim para CVE-2018-6789 funciona.
Este é um conjunto de ficheiros, scripts, notas, ... para configurar um ambiente para investigar o Exim RCE (CVE-2018-6789). Pode ser utilizado para depurar o Exim, escrever exploits, rastrear chamadas de funções do Exim, aprender sobre a gestão de memória personalizada do Exim (storeblocks), descobrir como funciona um exploit do mundo real, ...
Deve ser utilizado apenas para fins académicos!
vagrant-libvirt)Descarregue o código fonte do Exim executando
$ git submodule update --init
Existe um Vagrantfile no diretório raiz. Utiliza libvirt como fornecedor de virtualização. A box é obtida diretamente do espelho do Fedora via porque as transferências do Vagrant Cloud estão atualmente quebradas (migração HCP); o Fedora publica apenas boxes libvirt e VirtualBox lá (sem VMware), e um direto é específico do fornecedor, portanto esta configuração visa apenas libvirt.
box_urlbox_url# -*- mode: ruby -*-
# vi: set ft=ruby :
memory = 8192 # em MiB
cpus = 4
Vagrant.configure("2") do |config|
# Transferências do Vagrant Cloud estão quebradas; puxar a box do espelho do Fedora
config.vm.box = "fedora-44-cloud-base"
config.vm.box_url = "https://download.fedoraproject.org/pub/fedora/linux/releases/44/Cloud/x86_64/images/Fedora-Cloud-Base-Vagrant-libvirt-44-1.7.x86_64.vagrant.libvirt.box"
config.vm.provider "libvirt" do |lv|
lv.memory = memory
lv.cpus = cpus
end
config.vm.provision "shell", inline: <<-SHELL
/vagrant/scripts/setup_vm.sh
SHELL
end
Pode alterar a configuração conforme desejar, mas tenha em mente que, por exemplo, o script setup_vm.sh usa dnf para instalar pacotes. Se quiser usar Ubuntu, deve substituir as linhas dnf install por apt-get install e ajustar os nomes dos pacotes em conformidade. No entanto, não há garantia de que a configuração funcione corretamente.
Quando estiver satisfeito com a sua configuração, basta executar:
$ vagrant up
para configurar a máquina e depois
$ vagrant ssh
para se conectar a ela. Se não souber como usar o Vagrant, veja aqui: https://www.vagrantup.com/intro/getting-started/
O Vagrant mapeia o diretório atual (ou seja, o repositório que acabou de clonar) como um diretório partilhado para /vagrant. Para criar e executar a imagem Docker para o Exim, insira os seguintes comandos dentro da sua VM (vagrant ssh)
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
A primeira vez demorará muito mais porque o Exim será compilado a partir do código fonte. Se modificar scripts de depuração ou outros ficheiros que serão copiados para o contentor docker, pode sempre usar ./scripts/reset_docker.sh para reconstruir a imagem Docker. Certamente, também pode extrair apenas as linhas necessárias do script e executá-las como comandos individuais.
Quando tudo estiver concluído, deverá ver uma consola root:
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
As strings estranhas podem parecer diferentes na sua máquina, mas agora está num contentor Debian Docker a executar dentro de uma VM Fedora na sua máquina host.
Primeiro, pode criar duas sessões SSH com vagrant ssh em duas janelas de terminal. Uma pode ser usada para executar exploits e interagir com o Exim via SMTP. A outra é usada para iniciar, executar, depurar, ... o Exim dentro do contentor Docker. ASLR está desativado na VM, para que possa definir pontos de interrupção fiáveis que não mudem durante as sessões de depuração.
Exemplo de sessão:
Primeiro terminal:
$ vagrant ssh
[vagrant@localhost vagrant]$
Segundo terminal:
$ vagrant ssh
[vagrant@localhost vagrant]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
...
# agora está dentro do contentor Debian Docker
root@99296cf63016:/opt# ./run_exim.sh
root@99296cf63016:/opt# ./attach_exim.sh
O script run_exim.sh termina e o Exim executa em segundo plano. O script ./attach_exim.sh deve anexar gdb ao processo do daemon Exim em execução e dar-lhe um resultado como este:
...
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.
O Exim está em execução e à espera de pedidos. Os pontos de interrupção que foram definidos vêm do ficheiro debugging/breakpoints. Pode usar Ctrl+C para interromper o processo e dar controlo ao gdb. Também pode executar um dos scripts de exploit fornecidos para testar se tudo está a funcionar como esperado:
Primeiro terminal:
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
Segundo 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 <smtp_reset> push rbp
0x5555555c051e <smtp_reset+1> push rbx
0x5555555c051f <smtp_reset+2> sub rsp, 8
0x5555555c0523 <smtp_reset+6> mov rbp, rdi
0x5555555c0526 <smtp_reset+9> mov qword ptr [rip + 0x263657], 0 <0x555555823b88>
0x5555555c0531 <smtp_reset+20> mov dword ptr [rip + 0x263645], 0 <0x555555823b80>
0x5555555c053b <smtp_reset+30> mov dword ptr [rip + 0x26364f], 0 <0x555555823b94>
0x5555555c0545 <smtp_reset+40> mov dword ptr [rip + 0x263699], 0 <0x555555823be8>
0x5555555c054f <smtp_reset+50> mov dword ptr [rip + 0x263687], 0 <0x555555823be0>
0x5555555c0559 <smtp_reset+60> mov dword ptr [rip + 0x263679], 0 <0x555555823bdc>
0x5555555c0563 <smtp_reset+70> 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>
Pode apagar todos os pontos de interrupção com d e continuar com c para deixar o script sploit_0.py executar até terminar:
Segundo terminal:
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
Primeiro 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
Se o processo bifurcado que acabou de depurar (ao qual se anexou) terminar (por exemplo, [Inferior 2 (process 42) exited with code 01]), pode sair do gdb e executar ./attach_exim.sh novamente.
Em debugging pode encontrar alguns scripts que podem ser úteis. Um dos scripts é showmem.py. Permite inspecionar os storeblocks do Exim e os chunks de heap correspondentes. Pode executá-lo dentro do gdb com o comando smem:
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:
As regiões de memória indentadas são os storeblocks; as outras regiões são chunks de heap (glibc). Atualmente, isto é uma aproximação, pois não verifiquei os chunks em uso com as listas livres da glibc, portanto pode haver algumas indicações erradas de blocos usados/livres. Pode sempre usar os comandos bins, heap, ... do pwndbg como fonte adicional de informação!
Nota: Com libvirt / KVM, a pasta /vagrant é sincronizada via rsync, portanto, após editar ficheiros no host, execute vagrant rsync (ou mantenha vagrant rsync-auto em execução) para copiar as suas alterações para a VM antes de reconstruir o contentor.
.
├── debugging # Scripts relacionados com GDB
│ ├── breakpoints
│ ├── gdbinit
│ ├── showmem.py
│ └── trace.py
├── Dockerfile # Dockerfile para compilar e depurar o Exim
├── Exim # Código fonte para a versão vulnerável do Exim
├── exim_code_backup # cópia de segurança do código fonte vulnerável do Exim
│ └── exim-fc6d65867e82009a6e0671771728d41d3423a790.zip
├── exim_files # Ficheiros do Exim corrigidos para compilar o Exim corretamente
│ ├── configure
│ ├── eximon.conf
│ ├── Makefile
│ └── Makefile-Linux
├── README.md
├── scripts # Scripts auxiliares para depurar o Exim
│ ├── attach_exim.sh
│ ├── reset_docker.sh
│ ├── run_exim.sh
│ └── setup_vm.sh
├── sploits # Scripts de exploit incrementais e um script para encontrar o byte 0xf1
│ ├── 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 para criar a VM que hospeda o contentor Docker
Os scripts de exploit podem ser encontrados em sploits. Eles são construídos incrementalmente para facilitar a compreensão dos diferentes passos. São quase idênticos aos passos fornecidos por @straightblast426 no medium.com.
sploit_10.py é o script final que deve demonstrar o RCE num único script. Este script não abre uma shell reversa, mas cria um ficheiro em /tmp. Pode modificar isto editando a seguinte linha:
cmd = '/bin/bash -c "touch /tmp/pwned"'
Por favor, note que o script sploit_10.py fornecido não é a única forma de explorar a vulnerabilidade. Existem, por exemplo, outras formas de organizar o heap!
O grooming é sensível ao ambiente: mantenha host_lookup = desativado (um nome de cliente DNS reverso polui o heap que o exploit prepara) e o contentor em debian:bullseye (o grooming só sobrevive na glibc 2.31 do Debian, não na do Ubuntu ou do bookworm). Isto também significa que este repositório só funcionará (como está) enquanto existir uma distro disponível que forneça glibc 2.31 (lançada pela primeira vez em 2020)!
O ponteiro next será alterado com um endereço constante previamente conhecido (que também deve funcionar para si se usar a configuração idêntica). Se lançasse este exploit contra outro Exim em execução, não funcionaria (as probabilidades são muito pequenas). Poderia contornar o ASLR com algum brute-force. Isto funciona bastante bem, pois o Exim bifurca (clona) a si próprio para lidar com pedidos de clientes. Isso significa que o layout geral da memória permanece o mesmo e obtém uma hipótese realista de fazer brute-force do ponteiro next. Se não sabe o que é o ponteiro next, deve ler primeiro as referências.
Se compilar o exim de forma ligeiramente diferente, a localização do ponteiro next pode ser diferente. Deve encontrar o endereço do storeblock que contém acl_smtp_rcpt. Siga estes passos e ajuste o ponteiro nos respetivos ficheiros sploit_xx.py:
# conectar à VM com: vagrant ssh
./run_exim.sh
./attach_exim.sh
# pwndbg inicia, depois Ctrl-C
pwndbg> smem
[SHOWMEM]: 0x55555562e6b0: heap chunk of size 0x00002020 (used) / data: .2cUUU
[SHOWMEM]: 0x55555562e6c0: storeblock of size 0x00002000 / data: /usr/exim/configure
...
O storeblock com endereço 0x55555562e6c0 também contém acl_smtp_rcpt:
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
Então, nos scripts de exploit, substitua-o pelo endereço conforme exibido com o comando smem:
# ...
# endereço do storeblock das strings ACL (não do chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...