Bu sitede bulunan bütün bilgi ve araçlar SADECE eğitim amaçlıdır, ne niyetle kullandığınız sizi bağlar ve sizin sorumluluğunuzdadır! [Demedi deme]

Hacker'ın Olmazsa Olmazı - Assembly #1 Unutulmayan İlkler

Tarih 27 Şubat 2013. KATEGORI Bilisim

İlk yazıyı okudunuz ve Assembly öğrenmekte hala ısrar ediyorsunuz.Peki o zaman serinin ikinci yazısında, ilk yazıda atladığımız daha doğrusu çok fazla uzamasın diye sonraki yazılara bıraktığımız bazı detayları açıklayıp ilkleri yaşamaya başlıyoruz.Konu başlıklarına bir göz atalım:

Bu yazıyı okuduktan sonra hazırlanan videoyu da izleyerek ilklerimizi bitiriyoruz.

Sayı sayan Harf de sayar (ASCII)

Önce parantez içerisinde belirttiğimiz kısaltmayı bir uzatalım.Vikipedi'den aldığımız bilgileri göre ASCII American Standard Code for Information Interchange yani Bilgi Değişimi için Amerikan Kodlama Sistemi imiş.Latin alfabesi üzerine kurulu 7 bitten oluşan [yani kaç adet? Sayı saymayı öğrenmiştik!] bu karakter seti sonradan 1 bit daha eklenerek 8 bit yani 1 byte uzunluğunda toplam [arife tarif gerekmez] karakter sayısına ulaşıp genişletilmiştir.

Bu listede A, B, C gibi okunabilen karakterler (harfler + sayılar + semboller) olduğu gibi bazıları gözle görülebilen bazıları ise görünemeyen karakterler de vardır. Dokunup hissedemeyeceğimize göre bu karakterlerle başa çıkmanın bir yolunu bulmamız lazım.Hemen basit bir örnek verelim:

ka@site ~ $ ./merhaba
Merhaba Televole 
ka@site ~ $

Bilgisayarda bir program çalıştırdınız ve ekrana böyle bir mesaj geldi.Peki harfleri sayalım Merhaba, 7 adet Televole 8 adet toplam 15 harf yani karakter var.Başka gözle gördüğümüz bir boşluk var etti 16 bir de göremesek de yeni satır karakteri var toplam 17 karakter oldu.Şimdi bu 17 karakteri ASCII ile yani bilgisayarın anladığı dilde (hex) yazalım.

ka@site ~ $ ./merhaba
0x4d(M) 0x65(e) 0x72(r) 0x68(h) 0x61(a) 0x62(b) 0x61(a) 0x20( ) 0x54(T) 0x65(e) 0x6c(l) 0x65(e) 0x76(v) 0x6f(o) 0x6c(l) 0x65(e) 0xa(\n) 
ka@site ~ $

Bizim gördüğümüz mesaj aslında 0x4d6572686162612054656c65766f6c65a diye bir hex sayıymış.Hex olarak yazdığımız bu sayıyı bir de binary olarak yazdığınızı düşünün!Boşuna niye kasalım ki o işi zaten işlemci yapıyor,biz de onun ne yaptığını az çok(!) öğrenmiş olduk.Buradan çıkarabileceğimiz bir sonuç var mı?Var ki soruyoruz:
Bir programın ["cat merhaba" dediniz mesela] içeriğini görmek isterseniz karşınıza saçma sapan karakterler çıkarabilir.Peki bu durumda ne yapıyoruz, hex editor kullanıyoruz.

Okuyan ve şu konular hakkında bilgi sahibi olmayan herkese Unicode ve Hex Editor konularını ödev olarak verip bir sonraki başlığımıza geçiyoruz.

Assembly Yemeğinin Malzemeleri

Yemeğin adı kurufasulye olsa kimsenin malzeme bilgisine ihtiyacı olmayabilirdi ama yemek Assembly olunca önce malzemeleri iyice bir öğrenmek lazım.Bir program yazmak istediğimiz zaman hangi kodu nereye yazıyoruz?İşte bunu öğrenmek için ilk malzememiz:

Assembly Segments (Kodlama Bölümleri):

Code Segment (.text veya .code):
Bir programın çalıştırılacak bütün kodları bu bölüme yazılır.Bu bölüm çoğunlukla sadece okunabilir (read-only) bir alandır ve program çalışırken bu alana müdahele edemezsiniz.[Anlamadım?! Canını bile okuucaazz da hack konusuna gelince,az daha sabır]

Data Segment (.data):
İsminden de anlaşılacağı üzere bu bölüme programı ilgilendiren data yani verileri yazıyoruz.Bu bölümde çoğunlukla statik (çalışma sırasında değişmeyecek) verileri ve global variables (genel değişkenleri) yazıyoruz.Bu bölümde belirtilen herşey program başlar başlamaz hafızada yerini alır.
Eğer .data yerine .rodata kullanılırsa verileri kesinlikle değiştiremezsiniz.Örnek verelim:
Program başladığı anda hafızada sifre = 123 sifre için 123 değeri atandı.

BSS (.bss):
Aslında Data Segment kısmına ait olan bu bölümün .data'dan farkı, veri için istemiş olduğunuz alan program çalışır çalışmaz hafızada yerini alsa da içeriği boş olur.Program çalışma esnasında bu alana farklı değerler atayabilir.Bunu da örneklendirelim:
Program başladığı anda hafızada sifre için yer ayrıldı fakat mov sifre, 123 komutu gelene kadar herhangi bir değer atanmadı.

Çok detaylı olmasa da ilk malzememiz hakkında bilgilendikten sonra ikinci malzememize geçiyoruz:

Derleyiciler:

Yazılan kodları ve belirtilen verileri,makine diline çeviren programlara derleyici yani compiler denir.Detaya girmeden önce bilinen Assembly derleyicilerini listeyelim:

  • FASM
  • GAS
  • MASM
  • NASM

Bu derleyicilerden GAS (Linux) ve MASM (Microsoft) işletim sistemlerinde standart olarak kullanılırken, NASM ve FASM özel yazılmış derleyicilerdir ve her birinin kendine has tarzı ve özelliği bulunmaktadır.Küçük bir karşılaştırma yapalım:

NASM tarzı

section .data
mesaj: db "Merhaba Televole",10

GAS tarzı

 
section .data
mesaj: .asciz "Merhaba Televole\n"

Yazdığımız programı derlemek için derleyiciye doğru parametreleri vermemiz gerekiyor.Bu seride kullanılan NASM için vermeniz gereken en temel parametre programın hangi formata uygun derleneceğini belirten -f elf parametresidir.Elf yani Executable and Linkable Format, yaniyani Çalıştırılabilir ve Bağlanabilir Format manasına gelmektedir.
Derleyicilere de değindikten sonra üçüncü malzememize geçelim.

Bağlayıcılar:

Programımızı yazdık,derledik ve artık elimizde .o uzantılı bir object dosyamız oldu.İstersek bu dosyayı kendi başına bağlayabilir ve çalıştırabiliriz VEYA bu dosyayı başka object dosyaları ile birlikte bağlayıp tek program altında çalıştırabiliriz.
80li yıllarda doğanlar çok iyi hatırlayacaktır,meşhur bir çizgi film serimiz vardı,Voltran.Kötülere karşı savaşan bu aslanlar tek başınayken hep dayak yer,birleşip Voltranı oluşturur sonra da kötüleri alt ederdi.Yazdığımız object dosyalarını aslanlar kabul edersek Voltranı oluşturmak işte bu bağlayıcının görevi oluyor.[Ne örnek oldu ama :)]

Spotlight Image
Merak edenler için websitesi bu adreste.

Derlenen programı bağlamak için (Linux'te) ld derlenen_objenin_adı.o -o çalıştırabilir_dosya_adı diyor ve artık programımız çalışmaya hazır hale geldiğine göre malzemelerimiz de bitmiş oluyor.

Assembly Yemeğinin Tarifi

Malzemeler tamamlandığına göre yemeğimizin tarifine geçebiliriz.Bir yemeğin [iyi kaptırdık bu yemek muhabbetine] yani programın amacı hesaplama yapmak,bilgi giriş-çıkışı,vb kullanıcıyı ilgilendiren görevleri yerine getirmektir.Peki kafanızda bir program taslağı oluştu, sıra kod yazmaya geldi.Komutu ve komutla ilgili detayları nereden öğrenicez?Tarifimize başlayalım:

En basitinden ekrana birşey [mesaj olur,uyarı olur,oluroğluolur] yazdırmak istersek hangi komutu kullanıcaz,tabii ki write. Tamam ama bu write nasıl bir komuttur,hangi detayları ister,işlemi bitirdikten sonra geri-bildirim yapar mı bunu öğrenmek için Programcı Kılavuzumuza bakıyoruz.Linxute bu kılavuz man 2 komut_ismi şeklinde çağrılır ve karşınızda İngilizce olsa da detaylı bir açıklama gelir.Tamamını görmek isteyenler komutu çalıştırıp bakabilir.

Spotlight Image
En önemli kısım işaretlense de mükemmellik detaylarda gizlidir.

İncelemeye SYNOPSIS başlığından başlayalım.Karşımıza gelen bilgiler şöyle:

man 2 write
...
SYNOPSIS
	...
	ssize_t write(int fd, const void *buf, size_t count);
...
RETURN VALUE
	On success, the number of bytes written is returned ...

Karşımızda C programlama diliyle yazılmış bir fonksiyon duruyor,peki Assembly ile nasıl alakalandırıyoruz anlatalım.Öncelikle ssize_t fonksiyon geri bildiriminin işaretli (negatif ya da pozitif) bir sayı değeri olduğunu söylüyor,write fonksiyon ismi oluyor,int fd işlemin nereye yapılacağı ile ilgili bizden bir sayı değeri istiyor,const void *bufmesajın bulunduğu hafıza adresini istiyor ve son olarak size_t count ise mesaj uzunluğu istiyor diyebiliriz.

RETURN VALUE kısmını okuyunca anlıyoruz ki fonksiyon başarıyla görevini tamamlayınca yazılan byte miktarını yani mesaj uzunluğunu geri bildirim yapıyormuş.Peki bunları Assembly programına nasıl aktarıyoruz [çek bibuçuk yoortlu]:

Önce fonksiyon ismini unistd_32.h dosyasında arayıp çağrı numarasını buluyor ve bunu eax registerına yazıyoruz.

ka@site ~$ locate unistd_32.h
/usr/include/i386-linux-gnu/asm/unistd_32.h
ka@site ~$ cat /usr/include/i386-linux-gnu/asm/unistd_32.h
...
#define __NR_write 4

Numaramızı da öğrendiğimize göre komutumuzu mov eax, 4 yazıp ikinci değerimize geçiyoruz.int fd kısmına fonksiyonun işlemi nereye yapacağını belirten sayısal değerimizi giriyoruz.Peki bu nereler neresi?

Standard Input (stdin) -> 0 #Genelde klavyeniz
Standard Output (stdout) -> 1 #Genelde ekranınız
Standard Error (stderr) -> 2 #Hata bilgisi

Artık bu standartları da öğrendiğimize göre mesajın ekranımıza yazılmasını istemek için mov ebx, 1 yazıyoruz.Üçüncü değer için ecx registerına mesajımızın bulunduğu adresi belirtiyoruz.Bunu belirtmek için kullandığımız derleyicinin formatına uygun olarak bir tanımlama yapıp bu tanımlanan ismi ecx registerına atıyoruz.Yazmamız gereken komut mov ecx, tanımlanan_isim.Bu kısmı da halletikten sonra son değer olan mesaj uzunluğunu edx registerına atıyor mov edx, mesaj_uzunlugu ve kerneli sahneye davet ediyoruz.[Komutu zaten öğrenmiştiniz]

Herşey yolunda gitti ve mesaj ekranımızda ise eax registerı değişmiş oluyor.Neden mi? Çünkü write fonksiyon sorunsuz çalışınca yazılan byte miktarını geri bildirim yapıyordu. Demek ki ekrana mesaj yazılmadan önce 4 olan eax değerimiz, yazıldıktan sonra mesaj uzunluğumuz olan edx ile aynı olmak zorunda.

Son olarak ince bir detayı da anlatıp tarifimizi bitirelim.Write fonksiyon geri bildirim olarak negatif veya pozitif bir değer veriyorsa,negatif değeri nasıl alıyoruz.Hiçbir şey yazmayınca 0 değeri aldığımızı varsayarsak -1 değeri nasıl geliyor?Tabii ki yazma işleminde sorun çıkınca.Bu yüzden geri bildirim için size_t (işaretsiz,daima pozitif bir değer) yerine ssize_t kullanılmış.
Burada amaç sizi programcı yapmak değil,tersine mühendislik yapabilmek için yazılan programları incelemek olduğundan, bu detay çok da önemli sayılmaz.Bilmekte her zaman fayda var diyor ve bir sonraki başlığımıza geçiyoruz.

Yemeğimizin Tadını Kontrol Ediyoruz

Yemeklerin tadını kontrol etmek çok basit olsa da aynı durum programlar için geçerli olmuyor.Diyelim ki çok uzun bir program yazdınız.Hatta kendinizi aştınız ve kendime özel bir işletim sistemi yazıcam dediniz,bilmem-kaç milyon satır.
Hemen bir bilgi girelim araya,2012 yılında çıkarılan Linux 3.2 kernel 14,998,651 satır koddan oluşuyordu :) [Kaynak:Wikipedia]
Nedendir bilinmez yazdığınız program birçok yerde hatalar verdi ve bunları düzeltmek istiyorsunuz.Bu durumda ayıklayıcı yani debugger kullanıyoruz.Yine yeniden birçok debugger olmasına rağmen biz GDB kullanmayı tercih ediyoruz.Hatayı nokta atışı bulabilmek için programı istediğiniz yerde durdurabilmeli, registerları,değerleri kısaca ihtiyacınız olan herşeyi kontrol edebilmelisiniz ki ayıklayıcının yaptığı da zaten bu.

Burada tek tek neyi nasıl hangi komutla yaptığımızı anlatabiliriz ama bu yazı için hazırlanan video bunu detaylıca anlatıyor zaten.Yazının sonunda videoyu izlemeyi unutmayın diyor ve mutfak temizliğine geçiyoruz.

Mutfak Temizliği ve Son

Eğer ilk iki yazıyı okuyup,ben YAPMAK için değil BOZMAK için geldim bu dünyaya gelen tiplerdenseniz,son bir testimiz kaldı.Eğer bu testi de geçerseniz kesinlikle vazgeçmeyin sizden HACKER olur.

Bu yazının videosunda bahsi geçen basit (!) programı bilmem kaç satır ile yazarken aynı işi bakın Python programlama diliyle nasıl yapıyoruz.

ka@site ~$ ./merhaba
Merhaba Televole
ka@site ~$ python -c 'print "Merhaba Televole"'
Merhaba Televole
ka@site ~$

Satır satır kodlar yazmaca yok,derleme derdi yok,bağla uğraş derdi yok,al bu komutu git istediğin işletim sisteminde (Python kurulu) çalıştır, 32 bit 64 bit kaç bit olursa olsun farketmez.Assembly yokuşun en dik olduğu yol ama mevzu burada kardeşim! [Bundan sonraki yazılar daha da karmaşık olacağı için bir sonraki yazıya geçmeden biraz gaz vermek şarttı :)]

Yazıyı okudunuz,bir kısmını anladınız,bazı soru işaretleriniz var gidip şu videoyu da izleyince bir sonraki yazıya geçebilirsiniz.