Tersine Mühendisliğe Giriş

Reverse Engineering

İrem Boyukısa
5 min readMar 6, 2020

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.

Öncelikle nano ile .c uzantılı bir dosya açalım.
Ve kodumuzu içine yazalım.
‘ls’ komutu ile dosyamızın oluştuğunu görebiliriz.
Ardından kodumuzu gcc komutu ile derleyelim.

Ve gdb komutu ile debug (uygulama işleyişini anlamak ve takip etmek için) işlemi gerçekleştiriyoruz.

Bu noktada bir hata bizi bekliyor olacak :)

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.

-o komutu ile derlediğimiz kodları bir dosyayı yazdırıyoruz.

Yine ‘ls’ komutu ile listeleyip derlediğimiz dosyayı da çalıştırılabilir bir dosya şeklinde (cCode*) görebiliyoruz.

Eveet derleme işlemimiz bu sefer tamam.

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.

‘peda’ yükleme işlemleri

Ş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…

r (run) ile programı çalıştırıp çıktımızı görebiliriz.

Ayrıntılı analiz işlemine geçecek olursak ;

İlk kısım CPUdaki OFFSET adı verilen adresleme için kullanılan 16 bitlik veri yoludur. İkinci kısım ise kodlarınızın satırlarını ifade eder.

Asıl anlamamız gereken yani assembly dilini anlamlandırmaya yarayan komutlarından bahsedelim;

pushStack 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…

--

--

İrem Boyukısa
İrem Boyukısa

Written by İrem Boyukısa

Cyber Security & Information Security Researcher

No responses yet