┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Fuzzing Radare2 nach 0days in etwa 30 Codezeilen │ █ │ ~ Architect & S01den └───────────────────█ ──┘ [ Übersetzung von SaThaRiel ] --- Allgemein --- Radare2 ist ein sehr bekanntes Open-Source Framework für Reverse Engineering und zur Binäranalyse. Dieses Werkzeug ist sehr interessant zu analysieren, insbesondere zum Aufspüren von Schwachstellen, da es unter anderem auch bei Malware Analyse zum Einsatz kommt. Wir zeigen in diesem Artikel, wie wir die beiden Bugs (CVE-2020-16269 und CVE-2020-17487) ohne große Vorkenntnisse gefunden haben, indem wir unseren -einfachen- Fuzzer eingesetzt und ein wenig Reverse-Engineering durchführten. Im ersten Abschnitt werden wir erklären, wie wir radare2 gefuzzed haben und im zweiten werden wir sehen, wie wir diese Crashes analysiert, isoliert und repro- duziert haben, alles Anhand des ELF Bugs (CVE-2020-16269). --- Fuzzing --- Um die beiden Schwachstellen zu finden, haben wir einen einfachen Fuzzer bei unserem Ziel angewendet. Der Schlüsselfaktor beim einfachen Fuzzen ist, dass man eine breitgefächerte Basis für eine hohe Codeabdeckung hat. Wir haben uns entschieden, das testbins Repository von Radare2 zu nutzen[0]. Während des Fuzzens haben wir Crashes innerhalb von 30 Minuten in unterschied- liche Fileformaten gefunden. Unter diesen Formaten, für uns am interessantesten, waren auch PE und ELF - die beiden am häufigsten genutzten Executable-Formate. Ohne viele Umschweife, hier ist eine kleine Version unseres Fuzzers. ------------------------------------- SNIP --------------------------------------- import glob;import random;import subprocess;import hashlib def harness(d): tf = open("wdir/tmp", "wb") tf.write(d) tf.close() try: p = subprocess.run(['r2','-qq', '-AA','wdir/tmp'], stdin=None, timeout=10) except: return try: p.check_returncode() except: print(f"Proc exited with code {p.returncode}") fh = hashlib.sha256(d).hexdigest() dump = open(f'cdir/crash_{fh}', 'wb') dump.write(d);dump.close() def mutate(data): mutable_bytes = bytearray(data) for a in range(10): r = random.randint(0, len(mutable_bytes)-1) mutable_bytes[r] = random.randint(0,254) return mutable_bytes if __name__ == '__main__': fs = glob.glob("corpus/*") while True: f = open(random.choice(fs), 'rb').read() harness(mutate(f)) ---------------------------------------------------------------------------------- --- Exploitation --- Nachdem wir ein paar Beispiele gesammelt haben, die Radare2 zum crashen bringen, schauen wir mal, was die Ursache ist. Das erste Programm ist ein ELF, eine mutierte Version von dwarftest, ein Beispiel- file, das DWARF Informationen beinhaltet. ================================================================================== $ file dwarftest ---> dwarftest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, ...,with debug_info, not stripped ================================================================================== Um herauszufinden, welches Byte den Fehler hervorruft, analysieren wir das Beispielfile, in Radare2 geladen, in einem Debugger. Alternativ ist es ebenso nützlich, die Differenz zwischen dem Original und dem mutiertem File anzuschauen, um die fehlerhervorrufenden Byte(s) zu finden. Dies kann dank radiff2 ganz einfach durchgeführt werden: ================================================================================== $ radiff2 bins/src/dwarftest mutated_dwarftest 0x000010e1 00 => 01 0x000010e1 ================================================================================== Das Offset ist Teil der DWARF Struktur. Dies ist aber nur für ausführbare Dateien der Fall, die bereits die DWARF Informationen beinhalten. Allerdings sollten wir in der Lage sein, deformierte DWARF Infos zu erzeugen und in beliebige ELF Dateien einzuschleusen. Um herauszufinden, warum die DWARF Infos Radare2 aus dem Takt bringen, können wir uns das Ganze in objdump anschauen: ================================================================================== $ objdump --dwarf=info mutated_dwarftest ... <4c> DW_AT_name :objdump: WARNING: the DW_FORM_strp shift is too large: 164 (indirect string, shift: 0x164): <shift too large> ... ================================================================================== Damit sind wir fast fertig. Jetzt müssen wir nur noch sehen, wie wir das ausnutzen können. Dazu schauen wir uns den Backtrace des Crashes in gdb an und analysieren dann den Sourcecode, an dem der Fehler hervorgerufen wird (radare2 ist dankenswerter Weise ein Open-Source Projekt). Die fehlerhafte Zeile ist in der Funktion parse_typedef: ================================================================================== name = strdup (value->string.content); ================================================================================== Dies löst eine Null-Pointer Dereferenzierung aus, wenn der duplizierte String NULL ist. Ohne zu sehr in die Tiefe zu gehen, konnten wir Dank der verbotenen Kraft des Reverse Engineerings herausfinden, dass dies der Fall ist, wenn der Shift in DW_AT_name zu groß ist. Jetzt muss nur noch ein Script geschrieben werden, das jedes ELF manipulieren kann, um den Fehler auszulösen. Im Anhang ist der komplette Exploit, inklusive des PE Bugs (CVE-2020-17487, welcher dazu führt, dass radare2 das Binary nicht laden kann). --- Fazit --- Wir hoffen, dass Dir diese Abhandlung gefallen hat. Wie Du sehen konntest, ist es nicht so schwer, Fehler in weit verbreiteten Tools zu finden. Du kannst nun versuchen, selber welche zu finden (insbesondere in Reverse Engineering Tools)! Selbst wenn der Fehler nicht direkt für andere Sachen als DoS ausgenutzt werden kann, ist es doch sehr hilfreich, ein Reverse Engineering Tool beim Laden eines Binaries crashen zu lassen... --- Notizen & Referenzen --- [0] https://github.com/radareorg/radare2-testbins --- Appendix --- - Exploit POC