┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Fuzzing Radare2 For 0days In About 30 Lines Of Code │ █ │ ~ Architect & S01den └───────────────────█ ──┘ [ Çeviri @echel0n tarafından yapılmıştır ] --- Özet --- Radare2 çoğu kişinin de bildiği üzere, açık kaynak bir tersine mühendislik ve analiz aracıdır. Böyle araçlar, aslında diğer yazılımları araçları incelemek için kullandıldığından, kendisinde güvenlik açığı bulmak için analiz etmek oldukça ilginçtir. Bu yazıda CVE-2020-16269 ve CVE-2020-17487 kodlu zafiyetleri kendi aptal fuzzer'ımız ve ufak tefek tersine mühendislik ile nasıl keşfettiğimizi anlatacağız. İlk parçasında, radare2'yi nasıl fuzz'ladığımızı ve ikinci parçasında fuzzer tarafından bulunan zafiyetleri, ELF ile ilişkili hatayı örnek alarak, analiz etmek ve yeniden üretebilmek için nasıl kullandığımızı açıklayacağız. --- Fuzzing --- Bahsi geçen iki güvenlik açığını bulmak için hedefimize aptal fuzzing uyguladık. Buradaki anahtar faktör, kod kapsamı yüzdesi yüksek olmasıdır. Bunu yaparken de, radare2'nin testbins adlı repo'sundaki örnekleri kullanmayı seçtik[0]. İlk çökmeleri, farklı dosya formatlarında ve ilk 30 dakikada bulduk. Bu dosya formatlarının içerisinde bizim ilgimizi çeken ve ayrıca en çok kullanılan PE ve ELF formatları oldu. Daha fazla gevelememek adına, aşağıda fuzzer'ımızın küçük versiyonunu inceleyebilirsiniz. ----------------------------------- CUT-HERE ------------------------------------- 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)) ---------------------------------------------------------------------------------- --- Zafiyeti Sömürme --- radare2'yi çökerten örneklerle birlikte, bu çökmelerin nedenlerine bakalım. İlk çökme ELF formatı olan bir dosyadan kaynaklanıyor. Bu örnek DWARF bilgilerini tutan, dwarftest adlı dosyanın mutasyona uğramış hali. ================================================================================== $ file dwarftest ---> dwarftest: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, ...,with debug_info, not stripped ================================================================================== Hatayı hangi bayt tetiklediğini keşfetmek için radare2'ye çökmeye neden olan örneği yükleyip ayıklayıcıyı çalıştırabiliriz. Alternatif olarak orijinal dwarftest dosyasıyla mutasyona uğramış versiyonunu karşılaştırarak radiff2 ile bunu keşfedebiliriz: ================================================================================== $ radiff2 bins/src/dwarftest mutated_dwarftest 0x000010e1 00 => 01 0x000010e1 ================================================================================== Bu konum DWARF yapısının bir parçası. Bu sadece DWARF bilgisi eklenmiş çalıştırılabilinir dosyalar için geçerli ama yine de bozulmuş DWARF bilgisini hazırlayıp herhangi bir ELF dosyasına enjekte edebiliriz. Bu bozuk DWARF bilgisi neden radare2'yi kızdırıyor sorusunun cevabını objdump ile almak mümkün: ================================================================================== $ 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> ... ================================================================================== Ha gayret, az kaldı. Şimdi tek yapmamız gereken şey bunu nasıl sömürebileceğimizi bulmak. Bunu yapabilmenin yolu, bu çökmeyi gdb'de inceleyip, çökmeye neden olan fonksiyonunu kaynak kodunda bulmak. (neyse ki radare2 açık kaynak) parse_typedef fonksiyonunda hatalı satır şu şekilde: ================================================================================== name = strdup (value->string.content); ================================================================================== Bu satır, kopyalanmış string NULL olduğunda, null pointer dereference zafiyetine neden oluyor. Çok da ayrıntıya girmeden, tersine mühendisliğin yasaklanmış gücüyle keşfettiğimiz bu durum DW_AT_name'teki kaydırmanın çok büyük olduğunda gerçekleştiğini tespit ettik. Şimdi herhangi bir ELF dosyasını bu hatayı tetikleyecek hale getirecek kodu yazma zamanı. Ekler bölümünde, PE hatasını da kapsayan kodun tam halini bulabilirsiniz. (CVE-2020-17487 kodlu zafiyet de, radare2'nin dosyayı yükleyememesine yol açıyor) --- Sonuç --- Umuyoruz ki bu yazıyı okumaktan keyif almışsınızdır. Artık biliyorsunuz ki kullanımı çok yüksek olan araçlarda bile zafiyet bulmak çok zor değil! Bulunan hataların DoS'tan başka bir zafiyete yol açmasa da, tersine mühendislik aracını bir dosyayı yüklerken çökmesine neden olmak yine de yararlı... --- Referanslar --- [0] https://github.com/radareorg/radare2-testbins --- Ekler --- - Exploit POC