Merhaba,
Bu rehberde temel olarak packing ve unpacking nedir, ne için yapılır değinmeye çalışacağım. Ayrıca konuyu daha anlaşılır kılmak adına UPX ile packlenmiş bir yazılımı basitçe manuel nasıl unpack edebileceğinizi göstereceğim.
Ama öncelikle işin mantığını anlamamız gerekiyor. Bunun için öncelikle kendimize soralım. Packing nedir, programlar neden packlenir ve tersine mühendisler tarafından unpack edilmeye çalışılır? Aslında bunun cevabını kendi kendimize de verebiliriz.
Packing adından da anlaşılacağı üzere genel itibariyle bir programı başka bir program ile paketleyerek yaptığımız şeye deniliyor. Genellikle bunu yapanlar programının kırılmasını istemeyen kimseler, çeşitli string bilgilerini gizlemek isteyenler, boyut düşürenler, programın hangi dil ile yazıldığının ortaya çıkmasını engellemek isteyenler, decompile edilmesini engellemek isteyenler, zararlı yazılım analizinde süreci sekteye uğratma ve anti analiz çabaları şeklinde daha uzun uzun çeşitli kullanımları sayabiliriz.
Packerlar genel itibariyle iki şekilde çalışır. Ana programınız özel bir algoritma yardımıyla sıkıştırılır ve bunu açabilecek bir diğer programın içerisine dahil edilir. Çalıştırma esnasındaysa öncelikle çıkartıcı program devreye girer, programın kendisini belleğe çıkartıp kendisi aradan çıkar. Daha sonra program çalıştırılır. Bu tüm packerlarda aynı olmasa da genel itibariyle UPX gibi basit mantıkla çalışanlarda böyle işler. Dikkat, çıkarma işlemi diskte değil bellekte gerçekleşiyor. Bunu da karıştırılmaması için belirtmiş olalım.
Velhasıl kelam, durumu daha iyi anlatmak için bir örnek vermek istiyorum. C dilinde printf kullanarak basitçe konsol mesajı görüntüleyen bir program yazalım. Ben burada Embarcadero Dev C++ kullandım IDE olarak. Siz sormadan söylemiş olalım Programın içinde ayrıca “gizlibilgi” adında bir değişken tanımlayıp içine birkaç şey yazdım.
Programı derleyip çalıştırdım, gizlibilgi içindeki string ifadeyi ekrana bastırmadığımız için yazmadı gördüğünüz gibi. Şu anlık gizli bilgimiz gerçekten de gizli sanki? Peki bu bilgi programdan bir şekilde elde edilebilir mi? İsterseniz haydi ona bakalım.
Ta daa. Aşırı gizli bilgimiz programı sıradan “HxD” gibi bir Hex Editör aracılığıyla açınca ortaya çıkıyor. Neden mi? Çünkü program bunu derlenince sandığınız gibi buhar edip yok etmiyor, içinde bir yerde illa ki saklıyor. Bir de isterseniz bir sonraki görselde dosyamızı basit bir packer olan UPX ile packleyip bakalım.
Bunun için basitçe upx.exe –best unpacketbeni.exe komutunu veriyorum. Bütün bunları anlatmamın nedeni, bir program neden packleniyor onu anlamanız.
Şimdi packlenmeyen program ile packlenmiş olan programı DiE (DetectItEasy) üzerine atıp string değerlerini karşılaştıralım.
Soldaki packlenmeyen programın stringlerine baktığımızda oldukça temiz ve olması gerektiği gibi olduğunu görüyoruz. Gizli bilgimiz tamamen ufak bir göz atmayla elde edilebiliyor. Sağda ise UPX ile packlediğimiz hali yer alıyor. Stringler afedersiniz ama yamulmuş. Gördüğünüz gibi gizli bilgimizin ufak bir kısmı ortada, bazı noktaları ise eksik. Hafif daha aşağı kaydırıp biraz daha bakalım.
Durumu daha fazla izah etmeme gerek var mı bilmiyorum.
Basitçe packing, unpacking nedir ve neden yapılır anladıysak yavaşça UPX ile packlenen bir program “manuel” nasıl unpack edilir detaylıca değinmeye başlayalım. Normalde UPX’in yine kendisini kullanarak bunu yapabilirsiniz ama yüksek boyutlu dosyalar söz konusu olduğunda sıkıntı olabiliyor. Bazenleri de her türlü manuele başvurmak zorunda kalabiliyorsunuz.
Bir programı unpack etmeye çalışmadan önce dosyanın kendisinden elde edebileceğiniz kadar bilgiyi elde etmelisiniz. Zira unpack işlemini buna uygun şekilde yapacaksınız. Genel itibariyle birçoğu konunun başında anlattığım mantıkla çalışıyor fakat yine de zaman kaybetmemek adına kontrol etmelisiniz.
Şimdi ameliyata geçebiliriz.
Dosyamız 32-bit. Yani 32-bit bir debugger kullanmamız gerekiyor. Ben bu iş için x32DBg isimli debugger hoşuma gittiği için onu kullanacağım. Programımızı sürükleyip atalım. X64/X32DBg yazılımları programı debug etmeden önce her zaman ntdll.dll modülünden başlar. Bizim işimiz ntdll.dll ile değil, programın kendisiyle.
Bu nedenle yukarıdaki ok işaretini kullanarak veya kısayolları kullanarak programı bir kere basıp çalıştırıyoruz.
Evet nihayet programa düştük. İş göreceğim yer burası ve karşımızda bazı Assembly kodlarını görüyorum. Burada şimdi dosyamızı unpack edebilmek için packerların nasıl çalıştığını aklımıza getirmemiz gerekiyor.
Ne demiştik bir packer genelde öncelikle bir programı alır sıkıştırıp diğerinin içine dahil eder, daha sonra çıkartıcı bizim programımızı belleğe çıkartıp aradan çıkar.
X86 Assembly de stack’e veri girişini ve çıkışını ne ile sağlıyoruz hatırlayalım. Evet, pushad (Push All General Registers-Double) ve popad (Pop All General Registers Double) ile. Demek ki UPX, öncelikle verileri stack alanına pushluyor, daha sonra popad ile çıkartıp çalıştırıyor. Kendisi de RET ile bize elveda edip süreci asıl programa devrediyor.
Şu lanet siyah pencereyi aşağıya atıp detaylıca bakmaya başlayalım şimdi.
Şu anda gördüğünüz gibi program çalışmaya başladığında bizi x32dbg
Bir kere daha yukarıdaki ok işaretine basıp devam edelim.
Hopp, işte burası yani
E pushlandığına göre, çıkarılması yani poplanması da lazım bunun. Yani programın bittiği noktayı bulmalıyız. Bunun için fazla saçmalamadan aşağıya doğru hafif hafif kaydırıyoruz.
Evet popad’ı bulduk. Aslında hiç kaybetmemiştik, hatırlarsanız buraya iki resim önce bakıyorduk. Adreslere bakarsanız yine oraya geldiğimizi anlayabilirsiniz. Bundan sonrasına dikkatli bakalım şimdi zira ilk program burada bitiyor. Daha sonra asıl programımıza illa bir yerde atlanması gerekiyor.
Bahsi geçen
Bunun için ne yapıyoruz? Direkt şimdi ok işaretinden çalıştırırsak program çalışmasını bitirir, debugger da kapanır. Bu yüzden
Daha sonra da ok işaretinden çalıştıralım.
Ta daaa, breakpoint attığımız yerde programımız durdu ve istediğimiz yere atladı. Artık özgürüz. Gördüğünüz gibi
İşte gerçek programımız. Şimdi yapmamız gereken x32/x64dbg aracının en sevdiğim yanı olan Scylla’sını kullanarak burayı dump etmek.
CTRL+I diyoruz ve Scylla’mızı açıyoruz. Dump diyerek kaydediyoruz.
Şimdi programı açmaya gidiyy.
NE AÇMASI MÜBAREK İNSAN. Import table ayarlamadın, DLL’leri içeri aktarmadın. Programı bu haliyle çalıştırırsan aha da aşağıdaki gibi bir şeyle karşılaşırsın. Windows sana hayırlı işler diyip yolu gösterir.
Ne yapıyoruz bunun için, Scylla’ya tekrar gidiyoruz ve önce IAT Autoresearch sonra Get Imports ve Fix Dump diyip dump ettiğimiz dosyayı seçerek programımıza API’lerimizi dahil edip import table ayarını yapmış oluyoruz.
Programımız unpacketbeni_dump_SCY.exe adıyla kaydoluyor. Çalıştırıp bakalım artık hata veriyor mu?
Vermiyor. Tebrikler, kendi başınıza ele güne minnet etmeden UPX’i manuel unpack ettiniz.
Bir de Detect It Easy üzerine atıp bakalım isterseniz. Gördüğünüz gibi tertemiz. Artık debugger’ı kapatabilirsiniz.
Umarım faydalı olmuştur diye düşünüyorum.
Bu yazıyı eğer sonuna kadar sabırla okuyup uyguladıysanız bir nebze packerların çalışma mantığını kavramışsınızdır. Bu sadece işin kaymağı, basit kısmı. Daha gelişmiş packerlar söz konusu olduğunda unpack etmesi de zorlaşabiliyor. Fakat zamanla kurcalaya kurcalaya, araştıra araştıra olacak ve öğrenilecek şeyler bunlar.
Selametle kalın
Bu rehberde temel olarak packing ve unpacking nedir, ne için yapılır değinmeye çalışacağım. Ayrıca konuyu daha anlaşılır kılmak adına UPX ile packlenmiş bir yazılımı basitçe manuel nasıl unpack edebileceğinizi göstereceğim.
Ama öncelikle işin mantığını anlamamız gerekiyor. Bunun için öncelikle kendimize soralım. Packing nedir, programlar neden packlenir ve tersine mühendisler tarafından unpack edilmeye çalışılır? Aslında bunun cevabını kendi kendimize de verebiliriz.
Packing adından da anlaşılacağı üzere genel itibariyle bir programı başka bir program ile paketleyerek yaptığımız şeye deniliyor. Genellikle bunu yapanlar programının kırılmasını istemeyen kimseler, çeşitli string bilgilerini gizlemek isteyenler, boyut düşürenler, programın hangi dil ile yazıldığının ortaya çıkmasını engellemek isteyenler, decompile edilmesini engellemek isteyenler, zararlı yazılım analizinde süreci sekteye uğratma ve anti analiz çabaları şeklinde daha uzun uzun çeşitli kullanımları sayabiliriz.
Packerlar genel itibariyle iki şekilde çalışır. Ana programınız özel bir algoritma yardımıyla sıkıştırılır ve bunu açabilecek bir diğer programın içerisine dahil edilir. Çalıştırma esnasındaysa öncelikle çıkartıcı program devreye girer, programın kendisini belleğe çıkartıp kendisi aradan çıkar. Daha sonra program çalıştırılır. Bu tüm packerlarda aynı olmasa da genel itibariyle UPX gibi basit mantıkla çalışanlarda böyle işler. Dikkat, çıkarma işlemi diskte değil bellekte gerçekleşiyor. Bunu da karıştırılmaması için belirtmiş olalım.
Velhasıl kelam, durumu daha iyi anlatmak için bir örnek vermek istiyorum. C dilinde printf kullanarak basitçe konsol mesajı görüntüleyen bir program yazalım. Ben burada Embarcadero Dev C++ kullandım IDE olarak. Siz sormadan söylemiş olalım Programın içinde ayrıca “gizlibilgi” adında bir değişken tanımlayıp içine birkaç şey yazdım.
Programı derleyip çalıştırdım, gizlibilgi içindeki string ifadeyi ekrana bastırmadığımız için yazmadı gördüğünüz gibi. Şu anlık gizli bilgimiz gerçekten de gizli sanki? Peki bu bilgi programdan bir şekilde elde edilebilir mi? İsterseniz haydi ona bakalım.
Ta daa. Aşırı gizli bilgimiz programı sıradan “HxD” gibi bir Hex Editör aracılığıyla açınca ortaya çıkıyor. Neden mi? Çünkü program bunu derlenince sandığınız gibi buhar edip yok etmiyor, içinde bir yerde illa ki saklıyor. Bir de isterseniz bir sonraki görselde dosyamızı basit bir packer olan UPX ile packleyip bakalım.
Bunun için basitçe upx.exe –best unpacketbeni.exe komutunu veriyorum. Bütün bunları anlatmamın nedeni, bir program neden packleniyor onu anlamanız.
Şimdi packlenmeyen program ile packlenmiş olan programı DiE (DetectItEasy) üzerine atıp string değerlerini karşılaştıralım.
Soldaki packlenmeyen programın stringlerine baktığımızda oldukça temiz ve olması gerektiği gibi olduğunu görüyoruz. Gizli bilgimiz tamamen ufak bir göz atmayla elde edilebiliyor. Sağda ise UPX ile packlediğimiz hali yer alıyor. Stringler afedersiniz ama yamulmuş. Gördüğünüz gibi gizli bilgimizin ufak bir kısmı ortada, bazı noktaları ise eksik. Hafif daha aşağı kaydırıp biraz daha bakalım.
Durumu daha fazla izah etmeme gerek var mı bilmiyorum.
Basitçe packing, unpacking nedir ve neden yapılır anladıysak yavaşça UPX ile packlenen bir program “manuel” nasıl unpack edilir detaylıca değinmeye başlayalım. Normalde UPX’in yine kendisini kullanarak bunu yapabilirsiniz ama yüksek boyutlu dosyalar söz konusu olduğunda sıkıntı olabiliyor. Bazenleri de her türlü manuele başvurmak zorunda kalabiliyorsunuz.
Bir programı unpack etmeye çalışmadan önce dosyanın kendisinden elde edebileceğiniz kadar bilgiyi elde etmelisiniz. Zira unpack işlemini buna uygun şekilde yapacaksınız. Genel itibariyle birçoğu konunun başında anlattığım mantıkla çalışıyor fakat yine de zaman kaybetmemek adına kontrol etmelisiniz.
Şimdi ameliyata geçebiliriz.
Dosyamız 32-bit. Yani 32-bit bir debugger kullanmamız gerekiyor. Ben bu iş için x32DBg isimli debugger hoşuma gittiği için onu kullanacağım. Programımızı sürükleyip atalım. X64/X32DBg yazılımları programı debug etmeden önce her zaman ntdll.dll modülünden başlar. Bizim işimiz ntdll.dll ile değil, programın kendisiyle.
Bu nedenle yukarıdaki ok işaretini kullanarak veya kısayolları kullanarak programı bir kere basıp çalıştırıyoruz.
Evet nihayet programa düştük. İş göreceğim yer burası ve karşımızda bazı Assembly kodlarını görüyorum. Burada şimdi dosyamızı unpack edebilmek için packerların nasıl çalıştığını aklımıza getirmemiz gerekiyor.
Ne demiştik bir packer genelde öncelikle bir programı alır sıkıştırıp diğerinin içine dahil eder, daha sonra çıkartıcı bizim programımızı belleğe çıkartıp aradan çıkar.
X86 Assembly de stack’e veri girişini ve çıkışını ne ile sağlıyoruz hatırlayalım. Evet, pushad (Push All General Registers-Double) ve popad (Pop All General Registers Double) ile. Demek ki UPX, öncelikle verileri stack alanına pushluyor, daha sonra popad ile çıkartıp çalıştırıyor. Kendisi de RET ile bize elveda edip süreci asıl programa devrediyor.
Şu lanet siyah pencereyi aşağıya atıp detaylıca bakmaya başlayalım şimdi.
Şu anda gördüğünüz gibi program çalışmaya başladığında bizi x32dbg
00450F28
adresine atmış. Bu adreste JMP yani Assembly’de atla komutunu ifade eden bir kod var. jmp unpacketbeni.450F44
ile programımız 00450F44
adresindeki ret c
komutuna atlıyor. Stack’e baktığımızda halen ntdll.dll de olduğumuzu görüyoruz. Yani özetle programımız bir sonraki çalıştırmamızda bizi istediğimiz yere, yani verileri pushladığı yere ulaştıracak.Bir kere daha yukarıdaki ok işaretine basıp devam edelim.
Hopp, işte burası yani
00450D80
aradığımız yer. Programın çalışmadan önce pushlandığı yeri bulduk. İşte bizim programımız burada içe aktarılıyor. Stack’e baktığımızda kernel32.dll ile iş görüldüğünü fark ediyoruz.E pushlandığına göre, çıkarılması yani poplanması da lazım bunun. Yani programın bittiği noktayı bulmalıyız. Bunun için fazla saçmalamadan aşağıya doğru hafif hafif kaydırıyoruz.
Evet popad’ı bulduk. Aslında hiç kaybetmemiştik, hatırlarsanız buraya iki resim önce bakıyorduk. Adreslere bakarsanız yine oraya geldiğimizi anlayabilirsiniz. Bundan sonrasına dikkatli bakalım şimdi zira ilk program burada bitiyor. Daha sonra asıl programımıza illa bir yerde atlanması gerekiyor.
00450F1A
adresindeki push 0
benim dikkatimi çekiyor. Ve onun altındaki JNE unpacketbeni.450F1A
. Bu tarz debuggerlarda çalışırken program aşağıdan yukarıya çalışır her zaman. JNE yani Jump Not Equal, eşit değilse atla diyor. Push 0 olana kadar atlama ve çalışması devam edecek programın, 0 olduğunda da atlamayıp düz devam edecek.Push 0
, yani bir sonraki çalıştırmamızda program 00450F1A adresine atlama yapmayacak aksine 00450F23
’da yer alan jmp unpacketbeni.4014C0
komutu ile 4014C0
'dan devam edecek.Bahsi geçen
00450F23
adresinin üstüne gelelim. Görüyoruz ki içi boş, yani hava civa. Program henüz pushad kısmında duruyor onu da unutmayalım. Demek ki çalışınca burayı dolduracak. Yani UPX aradan çekilecek 004014C0
adresinde gerçek has program çalışacak.Bunun için ne yapıyoruz? Direkt şimdi ok işaretinden çalıştırırsak program çalışmasını bitirir, debugger da kapanır. Bu yüzden
SUB ESP
yazan satıra masaya yumruğumuzu vurarak bir breakpoint koyalım. Yavaş vurun ama sonra eliniz falan kırılır sorumluluk kabul etmiyorum.Daha sonra da ok işaretinden çalıştıralım.
Ta daaa, breakpoint attığımız yerde programımız durdu ve istediğimiz yere atladı. Artık özgürüz. Gördüğünüz gibi
004014C0
artık boş değil, içinde gerçek programımız var. Hemen iki tık ile 004014C0
adresine gidelim. Mouse tekerleğini kaydırarak gitmeye çalışmayın, yamulursunuz benden bilmeyin.İşte gerçek programımız. Şimdi yapmamız gereken x32/x64dbg aracının en sevdiğim yanı olan Scylla’sını kullanarak burayı dump etmek.
CTRL+I diyoruz ve Scylla’mızı açıyoruz. Dump diyerek kaydediyoruz.
Şimdi programı açmaya gidiyy.
NE AÇMASI MÜBAREK İNSAN. Import table ayarlamadın, DLL’leri içeri aktarmadın. Programı bu haliyle çalıştırırsan aha da aşağıdaki gibi bir şeyle karşılaşırsın. Windows sana hayırlı işler diyip yolu gösterir.
Ne yapıyoruz bunun için, Scylla’ya tekrar gidiyoruz ve önce IAT Autoresearch sonra Get Imports ve Fix Dump diyip dump ettiğimiz dosyayı seçerek programımıza API’lerimizi dahil edip import table ayarını yapmış oluyoruz.
Programımız unpacketbeni_dump_SCY.exe adıyla kaydoluyor. Çalıştırıp bakalım artık hata veriyor mu?
Vermiyor. Tebrikler, kendi başınıza ele güne minnet etmeden UPX’i manuel unpack ettiniz.
Bir de Detect It Easy üzerine atıp bakalım isterseniz. Gördüğünüz gibi tertemiz. Artık debugger’ı kapatabilirsiniz.
Umarım faydalı olmuştur diye düşünüyorum.
Bu yazıyı eğer sonuna kadar sabırla okuyup uyguladıysanız bir nebze packerların çalışma mantığını kavramışsınızdır. Bu sadece işin kaymağı, basit kısmı. Daha gelişmiş packerlar söz konusu olduğunda unpack etmesi de zorlaşabiliyor. Fakat zamanla kurcalaya kurcalaya, araştıra araştıra olacak ve öğrenilecek şeyler bunlar.
Selametle kalın
Son düzenleme: