Tersine Mühendisliğe Giriş
Reverse Engineering
Assembly ile C Programlama Dili Analizi
Günümüzde Tersine Mühendislik popüler olan ifadelerden biri.
Peki ne ifade ediyor ?
Vikipediye göre ; bir aygıtın, objenin veya sistemin; yapısının, işlevinin veya çalışmasının, çıkarımcı bir akıl yürütme analiziyle keşfedilmesi işlemidir. Makine, mekanik alet, elektronik komponent ve yazılım programının parçalarına ayrılması ve çalışma prensiplerinin detaylı şekilde analizini içerir.
Biz bu noktada basit bir C programının derleme ve debug işlemlerinin ardından assembly diline çevirip analizi ile ilgileniyor olacağız. Yani işin temelinde neler var buna bakacağız.
Direkt giriş yapıp uygulama üzerinden anlatımı tercih ediyorum.
Ve gdb komutu ile debug (uygulama işleyişini anlamak ve takip etmek için) işlemi gerçekleştiriyoruz.
Sebebi ise şöyle; biz bir C dosyası derledik fakat bir yere yazdırmadık yani çalıştırılabilen bir dosya formatında değil.
Yine ‘ls’ komutu ile listeleyip derlediğimiz dosyayı da çalıştırılabilir bir dosya şeklinde (cCode*) görebiliyoruz.
Assembly haline bakmadan önce gdb kısmına bir plugini olan ‘peda’ yı yükleyeceğiz. Bu işlemi gdb yi daha kullanışlı hale getirmek için yapıyoruz.
Daha fazla bilgi için https://github.com/longld/peda bakabilirsiniz.
Şimdi derleme işlemini tekrar yapalım.
‘gdb-peda’ şeklinde geldiğini görüyoruz. Ve asıl görmek istediğimiz yer olan assembly kısmına geldik.
‘disas main’ komutu ile C kodumuzun main kısmı assembly şeklinde karşımızda…
Ayrıntılı analiz işlemine geçecek olursak ;
Asıl anlamamız gereken yani assembly dilini anlamlandırmaya yarayan komutlarından bahsedelim;
push → Stack olarak adlandırılan hafıza bölgesine verileri iter.
mov → İki registerın içeriğini birbirine kopyalamamızı sağlar. Bir registera sabit bir değer de yazılabilir.
lea → (Load Effective Address -Etkin Adresi Yükle) offset adreslerini hedef operandına yükler.
call → Fonksiyon çağırırız.
pop → Stack olarak adlandırılan hafıza bölgesinden veri almada kullanılır.
ret → (return-geri dönüş) Stackten okunan dönüş adresinden itibaren program çalışmasına devam eder.
Diğer komutlar için ayrıntılı bilgiye http://www.csharpnedir.com/articles/read/?id=556 sitesinden ulaşabilirsiniz.
Bu anlamsız gibi görünen fakat assemblynin temelini oluşturan ifadelere göz atalım;
rbp, rsp, rdi, rip, eax yukarıda gördüğünüz bu ifadelere ‘register’ diyoruz.
Registerlar, işlemci çalışması sırasında değişik amaçlar için kullanılan değişkenlerdir.
Registerların ilk harfi olan E extended, 16 bitlik mimariden extend edilerek, 32 bit olduğu manasına gelmektedir. İlk harfi R olanlar ise register ifadesinden gelir.
- AX: Akümülatör. Aritmetik işlemlerde bolca kullanılır. Ek olarak fonksiyonların geri dönüş değerleri de bu register’da saklanır.
— EAX şeklindeki bir register 32 bittir, fakat bu registerin ilk 16 bitlik bölümü AX şeklinde ifade edilir. Bu AX şeklindeki 16 bitlik bölüm ise kendi içinde ilk 8 bitlik bölüm AL, sonraki 8 bitlik bölüm AH olmak üzere yine alt bölümleri bulunur. Yani AX,AH,AL registerleri sadece EAX registerinin alt bölümleridir.
- CX: Sayaç (counter). Döngülerde sayaç değişkeni olarak kullanılır. Ayrıca Object Oriented dillerinde geliştirilmiş yazılımlarda this pointer’inin değerini tutar.
- IP: Instruction pointer. CPU’nun an itibariyle code segment’i içerisindeki hangi instruction’i çalıştıracağını gösterir.
- BP: Base pointer. Stack veri yapısının tabanını (ilk giren, son çıkacak elemanı) gösterir.Yani genelde içeriği sıfırdır.
- SP: Stack pointer. Daha sonra açıklayacağımız stack veri yapısının son giren (ya da diğer bir deyiş ile ilk çıkacak) elemanını gösterir.
- SI: Source Index, Data segment veya istenirse başına küçük bir tanımlama eklenerek diğer data segmentlerdeki verileri de göstermek için kullanılan bir index (işaretçi) registerdir.
- DI: Destination Index, SI ile tamamen aynı özelliklere sahiptir. Fakat SI ve DI bazı string komutları tarafından kaynak ve hedef işaretçisi olarak da kullanılmaktadır.
Şimdi bu öğrendiğimiz komutları ve registerları birleştirip neler yapıldığına bakalım;
İlk satırdaki push rbp ile register base pointerı kullanıyoruz yani stacke push edip -atıp- saklamış oluyoruz.
mov rbp, rsp → kısmında stack pointer(sp), base pointera(bp) atanıyor.
Bu ilk iki satır genelde bulunur ve stack frame(çerçevesini)’i oluşturur.
lea rdi, [rip+0x9f] #0x6f4
#0x6f4 offset adresini rip registerında+9f'te bulunan rdi registerına yükler. rdi, bir sonraki talimatın adresini içerecektir.
Yani burada yaptığı şey C kodunda yazdığımız ‘printf’ işlemini yaptırmak.
mov eax, 0x0 → eax registerını 0 yapar.
call 0x520 <printf@plt> → printf fonksiyonunu çağırır ve ekrana yazdırır.
mov eax, 0x0→ eax registerını 0 yapar.
pop rbp → rbp registerında tutulan main kopyasını stackten geri yükler.
ret →stackten okunan dönüş adresinden itibaren program çalışmasına devam eder. ‘return’ kodumuzun çalışması da bu kısımda gerçekleşiyor.
İşte en temeli ile Assembly dünyası..
Dünyaya bir Merhaba demek bile ne kadar uzun işlemlerden geçiyormuş değil mi? :)
İlk defa görenlerden için biraz karmaşık gelebilir hak veriyorum. Zamanla çeşitli analizler yaptığınızda daha iyi anlaşılacaktır.
İyi çalışmalar diliyorum…