
Этот репозиторий предоставляет учебную среду для понимания того, как работает эксплойт RCE в Exim для CVE-2018-6789.
Это набор файлов, скриптов, заметок и т.д. для создания среды, в которой можно исследовать Exim RCE (CVE-2018-6789). Его можно использовать для отладки Exim, написания эксплойтов, трассировки вызовов функций Exim, изучения пользовательской системы управления памятью Exim (storeblocks), понимания того, как работает реальный эксплойт, и т.д.
Он предназначен только для академических целей!
vagrant-libvirt)Загрузите исходный код Exim, выполнив
$ git submodule update --init
В корневом каталоге находится Vagrantfile. В качестве провайдера виртуализации он использует libvirt. Образ загружается напрямую с зеркала Fedora через box_url, поскольку загрузки из Vagrant Cloud сейчас не работают (миграция HCP); Fedora публикует там только образы libvirt и VirtualBox (без VMware), а прямой box_url привязан к конкретному провайдеру, поэтому данная настройка рассчитана только на libvirt.
# -*- mode: ruby -*-
# vi: set ft=ruby :
memory = 8192 # in MiB
cpus = 4
Vagrant.configure("2") do |config|
# Vagrant Cloud downloads are broken; pull the box from Fedora's mirror
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
Вы можете изменить конфигурацию как угодно, но имейте в виду, что, например, скрипт setup_vm.sh использует dnf для установки пакетов. Если вы хотите использовать Ubuntu, вам придётся заменить строки dnf install на apt-get install и соответствующим образом скорректировать имена пакетов. Однако нет гарантии, что настройка будет работать корректно.
Когда конфигурация вас устроит, просто выполните:
$ vagrant up
для настройки машины, а затем
$ vagrant ssh
для подключения к ней. Если вы не знаете, как пользоваться Vagrant, загляните сюда: https://www.vagrantup.com/intro/getting-started/
Vagrant отображает текущий каталог (т.е. только что клонированный репозиторий) как общий каталог в /vagrant. Чтобы создать и запустить Docker-образ для Exim, выполните следующие команды внутри вашей VM (vagrant ssh)
[vagrant@localhost ~]$ cd /vagrant
[vagrant@localhost vagrant]$ ./scripts/reset_docker.sh
Первый запуск займёт гораздо больше времени, потому что Exim будет собран из исходного кода. Если вы изменяете отладочные скрипты или другие файлы, которые будут скопированы в Docker-контейнер, вы всегда можете использовать ./scripts/reset_docker.sh для пересборки Docker-образа. Разумеется, вы также можете просто вырезать нужные строки из скрипта и выполнить их как отдельные команды.
Когда всё будет готово, вы должны увидеть консоль root:
Successfully tagged exim:latest
787f310ef922a1e519cf8bb47f1c4fed5f510da705e7ceefd48f160c980e969c
root@787f310ef922:/opt#
Странные строки на вашей машине могут выглядеть иначе, но теперь вы находитесь в Debian Docker-контейнере, работающем в Fedora VM на вашем хосте.
Сначала вы можете создать две SSH-сессии с помощью vagrant ssh в двух окнах терминала. Одна может использоваться для запуска эксплойтов и взаимодействия с Exim по SMTP. Другая — для запуска, выполнения, отладки и т.д. Exim внутри Docker-контейнера. В VM ASLR отключён, поэтому вы можете устанавливать надёжные точки останова, которые не меняются во время сеансов отладки.
Пример сеанса:
Первый терминал:
$ vagrant ssh
[vagrant@localhost vagrant]$
Второй терминал:
$ 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
Скрипт run_exim.sh завершается, и Exim продолжает работать в фоновом режиме. Скрипт ./attach_exim.sh должен подключить gdb к работающему процессу демона Exim и выдать результат, похожий на этот:
...
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 запущен и ожидает запросы. Установленные точки останова берутся из файла debugging/breakpoints. Вы можете использовать Ctrl+C, чтобы прервать процесс и передать управление gdb. Вы также можете запустить один из предоставленных скриптов эксплойтов, чтобы проверить, всё ли работает как ожидается:
Первый терминал:
[vagrant@localhost ~]$ cd /vagrant/sploits/
[vagrant@localhost sploits]$ ./sploit_0.py
[+] Opening connection to localhost on port 25: Done
Второй терминал:
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>
Вы можете удалить все точки останова командой d и продолжить командой c, чтобы скрипт sploit_0.py выполнился до завершения:
Второй терминал:
Breakpoint smtp_reset
pwndbg> d
pwndbg> c
Continuing.
[Inferior 2 (process 42) exited with code 01]
Первый терминал:
...
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
Если порождённый процесс, к которому вы только что подключились для отладки, завершится (например, [Inferior 2 (process 42) exited with code 01]), вы можете выйти из gdb и снова запустить ./attach_exim.sh.
В каталоге debugging вы можете найти несколько скриптов, которые могут быть полезны. Один из них — showmem.py. Он позволяет просматривать storeblocks Exim и соответствующие им heap-чанки. Вы можете запустить его внутри gdb с помощью команды 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:
Области памяти с отступом — это storeblocks, остальные области — heap-чанки (glibc). В настоящее время это приблизительная оценка, поскольку я не сверял используемые чанки со списками свободных блоков glibc, поэтому возможны неверные указания на используемые/свободные блоки. Вы всегда можете использовать команды pwndbg bins, heap, ... как дополнительный источник информации!
Примечание: при использовании libvirt/KVM папка /vagrant синхронизируется через rsync, поэтому после редактирования файлов на хосте выполните vagrant rsync (или держите vagrant rsync-auto запущенным), чтобы скопировать изменения в VM перед пересборкой контейнера.
.
├── 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
Скрипты эксплойтов можно найти в каталоге sploits. Они построены инкрементально, чтобы было легче понять отдельные шаги. Они почти идентичны шагам, опубликованным @straightblast426 на medium.com.
sploit_10.py — финальный скрипт, который должен продемонстрировать RCE в одном единственном скрипте. Этот скрипт не запускает reverse shell, а создаёт файл в /tmp. Вы можете изменить это, отредактировав следующую строку:
cmd = '/bin/bash -c "touch /tmp/pwned"'
Обратите внимание, что предоставленный скрипт sploit_10.py — не единственный способ эксплуатации уязвимости. Например, существуют и другие способы организовать heap!
Подготовка кучи (grooming) чувствительна к окружению: держите host_lookup = отключённым (обратное DNS-имя клиента засоряет кучу, которую эксплойт подготавливает), а контейнер — на debian:bullseye (подготовка работает только с glibc 2.31 из Debian, но не с Ubuntu или bookworm). Это также означает, что данный репозиторий будет работать (в текущем виде) только до тех пор, пока доступен дистрибутив с glibc 2.31 (впервые выпущенной в 2020 году)!
Указатель next будет заменён на заранее известный постоянный адрес (это должно сработать и у вас, если вы используете идентичную настройку). Если вы примените этот эксплойт к другому запущенному экземпляру Exim, он не сработает (вероятность очень мала). Вы могли бы обойти ASLR с помощью перебора. Это работает довольно хорошо, поскольку Exim порождает (клонирует) сам себя для обработки клиентских запросов. Это означает, что общая раскладка памяти остаётся прежней, и у вас появляется реальный шанс подобрать указатель next перебором. Если вы не знаете, что такое указатель next, сначала прочитайте ссылки.
Если вы соберёте exim немного иначе, расположение указателя next может отличаться. Вам нужно найти адрес storeblock, содержащего acl_smtp_rcpt. Выполните следующие шаги и скорректируйте указатель в соответствующих файлах sploit_xx.py:
# 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
...
Storeblock с адресом 0x55555562e6c0 также содержит acl_smtp_rcpt:
pwndbg> p acl_smtp_rcpt
$1 = (uschar *) 0x55555562e7f0 "acl_check_rcpt"
Поэтому в скриптах эксплойтов замените его на адрес, выводимый командой smem:
# ...
# address of the ACL strings storeblock (not the chunk)
address_of_acl_storeblock = 0x55555562E6C0
# ...