Emulazione di Router - Riproduzione di una PoC di una CVE tramite l'Emulazione del Firmware ASUS

Altin (tin-z) · July 21, 2023

I device embedded e il mondo IoT diventano ogni anno più presenti nella vita quotidiana, e di conseguenza sempre più rilevanti nel mondo IT Sec. Prima di AI, prima del MetaVerso e ancora prima dei protocolli su Blockchain, l’argomento che più era trattato nel mondo IT era quello di Smart device e IoT. Potremmo dire che tutti gli argomenti sopra citati potrebbero essere in un futuro non troppo remoto, sempre più interconnessi tra di loro. Senza entrare nei meriti e demeriti, aspettivi positivi e negativi, ma solo oggettivamente parlando, tutto ciò sembra molto interessante.

Nel seguente blog viene mostrato come emulare una parte del firmware dei router ASUS per replicare la vulnerabilità CVE-2020-36109 e CVE-2023-35086.



Start

Nella seguente sezione viene introdotta una panoramica sugli argomenti Embedded devices, QEMU e GDB. Per saltare subito alla sezione di emulazione CVE cliccare quì: CVE-2020-36109.

Embedded devices

I sistemi embedded con montato sopra Linux si differenziano da un normale pc principalmente dall’hardware montatovi che è specifico per un certo dominio tramite l’ausilio di sensori ed attuatori. Il tradeof tra costi e performance è molto più rilevante rispetto ai PC, per questo motivo la memoria e CPU hanno caratteristiche limitate. Dato l’hw specifico sono necessari moduli driver specifici del produttore e quindi anche software lato user per comunicare col resto. La soluzione è quella di fornire assieme al device anche un firmware, ovvero un file compresso contente il Kernel e i file col filesystem. Il kernel può essere basato su Linux oppure altro OS. Il formato del file compresso dipende anch’esso dal vendor e può essere anche distribuito in formato non plain e quindi cifrato.

Quindi sostanzialmente per un device embedded Linux abbiamo gli stessi step di acensione per un normale PC.



QEMU and GDB

QEMU è un emulatore che permette di emulare la parte CPU e hardware tramite software, e di conseguenza è possibile emulare un sistema operativo come un software normale. Le architetture di CPU (ISA) supportate da QEMU sono diverse, in particolare abbiamo Intel x86, ARM, e MIPS.

QEMU fornisce i seguenti modi di emulazione :

  • QEMU system emulation: Emulazione sistema operativo


  • QEMU system emulation with KVM: Emulazione sistema operativo senza traduzione ISA, quindi l’ISA della macchina guest dev’essere uguale a quella della CPU su host


  • QEMU user-mode emulation: Emulazione lato userspace, mentre esecuzione systemcall viene inoltrata al Kernel di host


QEMU permette di debuggare anche il sistema emulato attraverso il componente Gdbstub che espone il protocollo GDB. GDB (GNU Project Debugger) è un debugger che supporta un’esecuzione controllata di eseguibili su Linux. Offre delle API in python che permettono di scrivere delle estensioni, le più note sono Peda, GEF, pwndbg. Nel nostro caso siamo particolarmente interessati a GDB Multiarch, il quale permette di debuggare un programma da remoto che è stato compilato per un’architettura CPU diversa da quella del nostro host.

Gdb-multiarch + QEMU user-mode

Tramite l’ausilio di GDB-multiarch e QEMU user-mode è possibile monitorare l’emulazione e quindi l’esecuzione di un programma dalla stessa macchina host. Per evitare di riflettere le operazioni di syscall sul nostro filesystem usiamo chroot, il quale ci permette di cambiare il punto di mounting di “/”. Infine usiamo qemu static che essendo compilato staticamente non richiede librerie esterne.



CVE-2020-36109

La vulnerabilità CVE-2020-36109 viene descritta come un buffer overflow dentro il file/funzione blocking_request.cgi esposta dal servizio httpd che permetterebbe di ottenere remote code execution. Notiamo la data di disclosure 2021-01-04 ed usiamo questa informazione per ritrovare la versione del firmware corretta.


Firmware extraction

Facendo riferimento alla data del disclosure CVE scarichiamo due versioni del firmware: versione unpatched, e versione patched. A questo punto prendiamo la versione unpatched ed estraiamo il firmware che in questo caso risulta essere in chiaro e quindi estraiamo il tutto usando binwalk e ubidump.


Il file blocking_request.cgi non risulta presente nel filesystem estratto, però facendo grep -r blocking_request.cgi vediamo che la stringa è soltanto presente dentro il file httpd. Questo significa che il file cgi viene direttamente implementato dal servizio httpd. Notiamo quindi l’architettura per cui è stato compilato il device essere ARM.


String analysis

Apriamo il binario httpd (non patchato) usando il decompilatore di Ghidra. Siccome il servizio dovrà in qualche modo rispondere alle request HTTP dei client allora dovrà anche fornire risposte differenti a seconda del URI richiesto, e nel caso dei CGI integrati verrà usato una function table, ovvero una lista dove ogni entry contiene un puntatore ad una stringa su cui fare il string compare del URI e la funzione da invocare nel caso di compare con esito positivo. Per ricostruire già la function table e avere una versione migliore del disassemblato/decompilato usiamo lo script codatify (versione ghidra fix_code).




In particolare notiamo la stringa "do_blocking_request_cgi" che ci conduce alla funzione 0x48df4. A questo punto seguiamo lo stesso iter per la versione del firmware patchato e confrontiamo i due decompilati. La funzione del decompilato patched risulta più corretta rispetto allo stack layout quindi facciamo fede ad essa come versione del decompilato più veritiera al codice sorgente. Quello che notiamo ad occhio è che nella versione patchata ci sono diverse strlcpy e che sembrano centrare coi field ‘CName’, ‘mac’, ‘interval’, ‘timestap’. Oltre ciò notiamo delle ulteriori validazioni sui field, ciò suggerisce che probabilmente sono gli argomenti passati dalla request http del client.


Binary diffing

Per avere un quadro più preciso su quali modifiche siano state applicate sulla versione del binario patched usiamo bindiff, un tool che espone una serie di tecniche per fare binary diffing ed ottenere le differenze tra i due binari in termini di codice assembly e control flow graph. Per utilizzare bindiff con ghidra abbiamo bisogno di generare l’input adatto tramite binexport, quì vi è una guida su come fare ciò link.


Un’altro metodo per trovare punti interessanti per il task di binary diffing è quello di vedere se sono state importate nuove funzioni di libreria oppure se vengono chiamate un numero diverso di volte dentro il codice rispetto alla versione unpatched. Viene quindi quì mostrato uno script in python per svolgere tale task tramite radare2 (link).


Emulation

Seguiamo quindi la rotta Gdb-multiarch + QEMU user-mode + chroot.


Per esporre gdb stub su qemu usiamo il flag -g <porta> lanciamo quindi i seguenti comandi in due terminali separati: sudo chroot ${PWD} ./qemu-arm-static -g 12345 bin/sh, sudo gdb-multiarch ./bin/busybox -q --nx -ex "source ./.gdbinit-gef.py" -ex "target remote 127.0.0.1:12345"


Tuttavia quando proviamo a lanciare httpd vengono fuori una moltitudine di eccezioni conviene quindi tracciare quali syscall vengono effettuate in modo da sistemare il filesystem chrooted. Viene usato il seguente comando: sudo chroot ${PWD} ./qemu-arm-static -strace -D logstrace.log ./usr/sbin/httpd. Dal file di log estraiamo i file, folder, link simbolici e librerie mancanti che dovranno poi essere fixate.



Dopo aver risolto i vari errori di file mancanti è necessario emulare anche la parte nvram, infatti i router asus caricano e scrivono le modifiche su questa memoria per tenere traccia delle configurazioni del router, come per esempio la password del wifi e il nome del modello del device. Come è possibile leggera dal log di strace, la nvram dovrebbe essere montata sul path /jffs/nvram_war.

Per comunicare lato client con la nvram viene usato libnvram, il quale espone in particolare le seguenti funzioni:

Function Name Description
nvram_init() Initializes the libnvram library.
nvram_get(key) Retrieves the value associated with key.
nvram_set(key, value) Sets the value for the specified key.
nvram_unset(key) Removes the entry associated with key.
nvram_save() Saves changes made to the NVRAM configuration.
nvram_load() Loads the NVRAM configuration.
nvram_list_all() Lists all entries in the NVRAM configuration.
nvram_reset() Resets the NVRAM configuration to defaults.

Per ovviare questo problema viene usata la tecnica di hooking tramite LD_PRELOAD facendo cross-compilation di questa libreria: nvram-faker Le entry chiave:valore di default vengono prese dal file nvram.ini, file generato da leak di utenti asus su forum e simili (e.g. “nvram show” site:pastebin.com). Lanciamo quindi i seguenti comandi sudo chroot <path-rootfs_ubifs> ./qemu-arm-static -E LD_PRELOAD=./libnvram-faker.so -g 12345 ./usr/sbin/httpd -p 12234 e sudo gdb-multiarch ./usr/sbin/httpd -q --nx -ex 'source ./.gdbinit-gef.py' -ex 'target remote 127.0.0.1:12345'.


A questo punto iniziamo la fase di reversing con l’ausilio del debugger. Inserendo vari breakpoint in particolare sulle funzioni str* estraiamo il control flow che segue il binario. Ripetendo questo processo di reversing ibridio estraiamo le seguenti note:


  • 0x018dcc legge la request ed esegue il parsing iniziale (solo sezione HEAD)


  • 0x1b79c estrae il campo dato come primo argomento dal payload della post request
  • 0x1ccb0 data una stringa in argomento ritorna il valore corrispettivo salvato prima nella nvram
  • La POST request su blocking_request.cgi serve per aggiungere i mac address ad una blacklist probabilmente per la connessione LAN
  • I campi CName, mac, interval, e timestap devono essere passati nella POST request
  • Per eseguire l’if corretto le seguenti condizioni devono verificarsi: la request deve avere il campo timestap di un valore non distante più di 21 secondi dal timestamp del router, il campo mac dev’essere una sottostringa del valore nvram MULTIFILTER_MAC


Exploitation

La vulnerabilità risiede nella possibile esecuzione di due strcat verso un buffer su stack di dimensione fissa e l’input è dato dal client tramite i parametri POST mac e timestap.


Per triggerare la vulnerabilità dobbiamo craftare una request con le seguenti caratteristiche:

  • Il parametro timestap dev’essere valido, infatti viene tradotto in int tramite chiamata atol
  • Il parametro mac dev’essere substring di MULTIFILTER_MAC e guardando su internet questo valore sembra essere inizialmente di NULL (?), supponendo ciò sia vero, il parametro mac dovrà essere fissato a ‘%00’
  • L’overflow può solo accadere dal parametro timestap che però dovrà anche essere un valore valido per atol, risolviamo ciò usando questo valore "<int-valido>%0a<payload>"



Considerazioni CVE-2020-36109

Gli script per replicare l’ambiente di test e la PoC vengono forniti ai seguenti link:

Limitazioni exploit:

  • L’overflow accade tramite str* quindi non possiamo usare null dentro il payload
  • Il payload si trova subito sotto all’epilogo dello stack, quindi non possiamo corrompere altre strutture dati se non il return address
  • C’è stack canary, e per i motivi di prima, anche guessando in qualche modo il canarino se esso ha il nullbyte allora diventa impossibile sovrascrivere il return address
  • Lo stack canary contiene null

Make exploit great again:

  • Tuttavia nel caso in cui il valore nvram MULTIFILTER_MAC contiene un qualsiasi char ascii, ciò permetterebbe di sbufferare prima con parametro mac e poi timestap, unendo ciò possiamo sovrascrivere il return address e creare una ROP
  • I router asus lanciano httpd come demone, quindi il processo che assiste il client è un child di parent, questo significa che eredita lo stesso space address e quindi possiamo facilmente fare bruteforce di canary e base addresses per la ROP

Patch:

  • Come descritto prima, la patch consiste nel limitare la size dei parametri POST tramite strlcpy.



CVE-2023-35086

La CVE-2023-35086 identifica una vulnerabilità di format string presente nei modelli di router e versioni RT-AX56U V2: 3.0.0.4.386_50460 e RT-AC86U: 3.0.0.4_386_51529. Dal report si legge che la vulnerabilità è presente nella funzione do_detwan_cgi del servizio httpd. La vulnerabilità però è presente anche in altri modelli di router ASUS.

Seguendo gli stessi step illustrati nella sezione CVE-2020-36109 siamo in grado di emulare una parte del firmware. La parte di string analysis rivela dove si trova la funzione che risponde alle request GET e POST verso l’uri /detwan.cgi e cioè 0x49258


Proviamo a decompilare la funzione con il decompilatore di ghidra che non mostra la funzione nella sua interezza, ma già notiamo i seguenti punti:

  • FUN_0001b70c estrae il parametro passato dal client action_mode
  • Il parametro viene usato come terzo argomento nella call logmessage_normal che è una funzione esterna esposta da libshared


Cercando su github troviamo che il codice sorgente della funzione logmessage_normal è presente nel progetto asuswrt-merlin, in particolare nel file asuswrt-merlin/release/src/router/shared/misc.c. Come possiamo notare la funzione salva il contenuto del parametro http action_mode dentro la variabile locale buf, che a sua volta viene usata come secondo argomento nella call syslog


Come si nota dal manuale però la syslog esposta da libc supporta le format string e quindi passando una format string sul parametro http action_mode siamo in grado triggerare questa vulnerabilità.


Per replicare la poc lanciamo i seguenti comandi in due terminali separati: sudo chroot <path-rootfs_ubifs> ./qemu-arm-static -E LD_PRELOAD=./libnvram-faker.so -g 12345 ./usr/sbin/httpd -p 12234 e sudo gdb-multiarch ./usr/sbin/httpd -q --nx -ex 'source ./.gdbinit-gef.py' -ex 'target remote 127.0.0.1:12345'.


Considerazioni CVE-2023-35086

Gli script per replicare l’ambiente di test e la PoC vengono forniti ai seguenti link:

Twitter, Facebook