__    __                      __                        __
  /  |  /  |                    /  |                      /  |
  $$ |  $$ |  ______    _______ $$ |   __   ______    ____$$ |
  $$ |__$$ | /      \  /       |$$ |  /  | /      \  /    $$ |
  $$    $$ | $$$$$$  |/$$$$$$$/ $$ |_/$$/ /$$$$$$  |/$$$$$$$ |
  $$$$$$$$ | /    $$ |$$ |      $$   $$<  $$ |  $$ |$$ |  $$ |
  $$ |  $$ |/$$$$$$$ |$$ \_____ $$$$$$  \ $$ \__$$ |$$ \__$$ |
  $$ |  $$ |$$    $$ |$$       |$$ | $$  |$$    $$/ $$    $$ |
  $$/   $$/  $$$$$$$/  $$$$$$$/ $$/   $$/  $$$$$$/   $$$$$$$/


               

Derlenmiş Programlarda İkili Yama Uygulama ve Değişiklik Tespiti

Derlenmiş programlara, kod veya veri içeriğini değiştirmek amacıyla ikili yamalar uygulanabilir. Programın, çalışma zamanında bütünlüğünü doğrulayabilmesi gerekmektedir. Bir ikilinin modifiye edilip edilmediğini tespit etmek, temelde bir hata tespit problemidir; bu nedenle, CRC32, MD5 veya SHA1 gibi bir kontrol toplamı algoritması, kod veya veri bloğu için özgün bir imza oluşturmak üzere kullanılabilir. Bu imza, çalışma zamanında herhangi bir değişikliğin gerçekleşip gerçekleşmediğini belirlemek için kontrol edilebilir.

Kullanılan kontrol toplamı algoritması, değişikliklerin doğru bir şekilde tespit edilmesini sağlamak için yeterince güvenilir olmalıdır. Bu algoritmalar, değişiklikleri hızlı ve etkin bir biçimde tespit edebilmek için tasarlanmıştır. Aşağıda, bu tür bir kontrol toplamı algoritmasının nasıl kullanılabileceğine dair bir örnek yer almaktadır:


// Örnek CRC32 Kullanımı
uint32_t CalculateCRC32(const void* data, size_t length) {
// CRC32 hesaplama işlevi burada yer alacaktır.
}
      

Bu yaklaşım, programın bütünlüğünü korumak ve yetkisiz değişiklikleri tespit etmek için etkili bir yöntem sunar. Uygulanan algoritmanın seçimi, programın ihtiyaçlarına ve güvenlik gereksinimlerine bağlı olarak değişiklik gösterebilir.

CRC32 algoritmasını, hem uygulama kolaylığı hem de hızı nedeniyle tercih ettik. Bu algoritma, kısa bayt dizilerindeki değişiklikleri tespit etmek için idealdir; ancak, yalnızca 232 olası kontrol toplamı değeri bulunması ve kriptografik olarak güvenli olmaması nedeniyle, çarpışma olasılığı yüksektir. Bu, saldırganın kontrol toplamını değiştirmeden kodu değiştirme şansına sahip olabileceği anlamına gelir. Bu tür bir uygulama için, kriptografik güç muhtemelen gereksizdir, zira kontrol toplamlarında bir çarpışma zorlamaktan daha kolay saldırı yöntemleri vardır (örneğin, sadece kontrol toplamı hesaplayan kodu değiştirmek).

Burada sunulan kontrol toplamı API'si, kontrol edilecek bloğun başlangıç ve bitişini işaretlemek için makrolar, ayrıca bloğun kontrol toplamını hesaplamak için bir fonksiyon içeren CRC32'nin bir uygulamasıdır. crc32_calc() fonksiyonu, bir tamponun kontrol toplamını hesaplamak için kullanılır.


#define CRC_START_BLOCK(label) void label(void) { }
#define CRC_END_BLOCK(label) void _##label(void) { }
#define CRC_BLOCK_LEN(label) (int)_##label - (int)label
#define CRC_BLOCK_ADDR(label) (unsigned char *)label
static unsigned long crc32_table[256] = {0};
#define CRC_TABLE_LEN 256
#define CRC_POLY 0xEDB88320L

static int crc32(unsigned long a, unsigned long b) {
  int idx, prev;
  prev = (a >> 8) & 0x00FFFFFF;
  idx = (a ^ b) & 0xFF;
  return (prev ^ crc32_table[idx] ^ 0xFFFFFFFF);
  }
    
static unsigned long crc32_table_init(void) {
  int i, j;
  unsigned long crc;
  for (i = 0; i < CRC_TABLE_LEN; i++) {
    crc = i;
    for (j = 8; j > 0; j--) {
      if (crc & 1) crc = (crc >> 1) ^ CRC_POLY;
      else crc >>= 1;
    }
    crc32_table[i] = crc;
  }
  return 1;
}
    
unsigned long crc32_calc(unsigned char *buf, int buf_len) {
  int x;
  unsigned long crc = 0xFFFFFFFF;
  if (!crc32_table[0]) crc32_table_init();
  for (x = 0; x < buf_len; x++) crc = crc32(crc, buf[x]);
    return crc;
}
      

Aşağıdaki program, kontrol toplamı uygulamasının nasıl kullanılacağını göstermektedir. Programın öncelikle main() içinde stdout'a kontrol toplamını yazdıracak bir printf() ile derlendiğine dikkat edin. main() kontrol edilen tampondan sonra programa bağlandığı sürece, bu printf() kaldırılabilir ve kontrol toplamı değeri değişmeden program yeniden derlenebilir. Kontrol toplamı bilindiğinde, kontrol toplamı değerini crc32_stored konumuna yamalamak için bir hex editörü kullanılabilir. Bu örnekte, kontrol toplamının dört baytı, ikili dağıtılmadan önce rastgele baytlarla üzerine yazılması gereken iki 0xFEEDFACE işaretçisi arasında saklanır. İşaretçilerin ikilide little-endian sırasında saklanacağını ve bu nedenle C kaynağındaki baytların ters sıralamasını unutmayın.


#include <stdio.h>
/* uyarı: "crc32_stored" gerçek kontrol toplamı ile değiştirilmeli! */
asm(".long 0xCEFAEDFE \n" /* 0xFEEDFACE işaretçileri için bakın */
"crc32_stored: \n"
".long 0xFFFFFFFF \n" /* bunu ikilide değiştirin! */
".long 0xCEFAEDFE \n" /* bitiş işaretçisi */
);

CRC_START_BLOCK(test)
int test_routine(int a) {
  while (a < 12) a = (a - (a * 3)) + 1;
  return a;
}

CRC_END_BLOCK(test)

int main(int argc, char *argv[]) {
  unsigned long crc;
  crc = crc32_calc(CRC_BLOCK_ADDR(test), CRC_BLOCK_LEN(test));
  #ifdef TEST_BUILD
  /* Bu printf(), programda saklanması gereken CRC değerini gösterir.
  * printf() kaldırılmalı ve program dağıtımdan önce yeniden derlenmelidir.
  */
  printf("CRC is %08X\n", crc);
  #else
  if (crc != crc32_stored) {
    printf("CRC32 %#08X does not match %#08X\n", crc, crc32_stored);
      return 1;
  }
  printf("CRC32 %#08X is OK\n", crc);
  #endif
    return 0;
}
      

main() içindeki printf() çağrısından hemen önceki yorumda belirtildiği gibi, bu programı önce TEST_BUILD tanımlı olarak derlemeli, ardından CRC değerini elde etmek için çalıştırmalısınız. Bu CRC değeri, ikilide crc32_stored için değiştirilmesi gereken değerdir. Daha sonra programı TEST_BUILD tanımsız olarak yeniden derleyin ve ilk çalıştırmadan alınan uygun CRC değeri ile ikiliyi değiştirin.

Tüm programın kontrol toplamını üretmek ve herhangi bir baytın değişip değişmediğini belirlemek için bunu kullanmak cazip gelebilir; ancak, bu, kontrol toplamının granülerliğini kaybetmenize ve performansın düşmesine neden olabilir. Bunun yerine, kodun hayati bölümleri için birden fazla kontrol toplamı üretmelisiniz. Bu kontrol toplamları şifrelenebilir ve önemsiz kod bloklarının kontrol toplamlarıyla bile tamamlanabilir, böylece hangi kod parçalarının önemli olduğu gizlenir.

main() içinde kullanılan kontrol, basit bir karşılaştırma, if (crc != crc32_stored) şeklindedir. Bu, bir kontrol toplamının temel kullanımını gösterse de, bu tür doğrudan bir karşılaştırmanın kullanımı şiddetle tavsiye edilmez. Çünkü, crc32() çağrısının ve ardından gelen karşılaştırmanın disassembled hali hemen fark edilir:


804842b: ff 75 fc pushl -4(%ebp)
804842e: ff 75 f8 pushl -8(%ebp)
8048431: e8 f2 fe ff ff call 8048328 <crc32> ;crc32() çağrısı
8048436: 83 c4 10 add $0x10,%esp
8048439: 89 45 f4 mov %eax,-12(%ebp)
804843c: 8b 45 f4 mov -12(%ebp),%eax
804843f: 3b 05 d0 83 04 08 cmp 0x80483d0,%eax ;sonucu karşılaştır
8048445: 74 22 je 8048469 ;eşitse atla
      

Bir saldırganın bu korumayı aşması için, 8048445 offset'indeki je talimatını (opcode 0x74) bir jmp talimatına (opcode 0xEB) değiştirmesi yeterlidir. Üretilen kontrol toplamı, geçerli bir kontrol toplamı ile karşılaştırılmamalıdır; bunun yerine, üretilen kontrol toplamı, programın düzgün çalışması için gereken bilgileri sağlamak amacıyla kullanılmalıdır. Örneğin, kontrol toplamındaki bir bayt, bir atlama tablosuna indeks olarak kullanılabilir veya kontrol toplamı kendisi, kritik kod veya veriyi şifresini çözmek için bir anahtar olarak kullanılabilir.

Sonraki program, bir kontrol toplamı değerini test etmek için fonksiyon göstericileri tablosunu nasıl kullanabileceğinizi göstermektedir. Kontrol toplamındaki her nibble veya yarım bayt, 16 girişli fonksiyon göstericileri tablosuna bir indeks olarak kullanılır; yalnızca doğru tablo girişi, sonraki nibble'ı kontrol etmek için fonksiyonu çağırır. Bu yöntem, kontrol toplamındaki her nibble için bir tablo olmak üzere 8 tablo ve 16 fonksiyon göstericisi gerektirir.


      #include <stdio.h>
      CRC_START_BLOCK(test)
      int test_routine(int a) {
          while (a < 12) a = (a - (a * 3)) + 1;
          return a;
      }
      CRC_END_BLOCK(test)
      typedef void (*crc_check_fn)(unsigned long *);
      static void crc_check(unsigned long *crc);
      static void crc_nib2 (unsigned long *crc);
      static void crc_nib3 (unsigned long *crc);
      static void crc_nib4 (unsigned long *crc);
      static void crc_nib5 (unsigned long *crc);
      static void crc_nib6 (unsigned long *crc);
      static void crc_nib7 (unsigned long *crc);
      static void crc_nib8 (unsigned long *crc);
      crc_check_fn b1[16] = {0,}, b2[16] = {0,}, b3[16] = {0,}, b4[16] = {0,},
      b5[16] = {0,}, b6[16] = {0,}, b7[16] = {0,}, b8[16] = {0,};
      #define CRC_TABLE_LOOKUP(table) \
      int index = *crc & 0x0F; \
      crc_check_fn next = table[index]; \
      *crc >>= 4; \
      (*next)(crc)
      static void crc_check(unsigned long *crc) { CRC_TABLE_LOOKUP(b1); }
      static void crc_nib2 (unsigned long *crc) { CRC_TABLE_LOOKUP(b2); }
      static void crc_nib3 (unsigned long *crc) { CRC_TABLE_LOOKUP(b3); }
      static void crc_nib4 (unsigned long *crc) { CRC_TABLE_LOOKUP(b4); }
      static void crc_nib5 (unsigned long *crc) { CRC_TABLE_LOOKUP(b5); }
      static void crc_nib6 (unsigned long *crc) { CRC_TABLE_LOOKUP(b6); }
      static void crc_nib7 (unsigned long *crc) { CRC_TABLE_LOOKUP(b7); }
      static void crc_nib8 (unsigned long *crc) { CRC_TABLE_LOOKUP(b8); }
      static void crc_good(unsigned long *crc) {
          printf("CRC is valid.\n");
      }
      int main(int argc, char *argv[]) {
          unsigned long crc;
          crc = crc32_calc(CRC_BLOCK_ADDR(test), CRC_BLOCK_LEN(test));
          #ifdef TEST_BUILD
          printf("CRC32 %#08X\n", crc);
          #else
          crc_check(&crc);
          #endif
          return 0;
      }
      

Bu program TEST_BUILD tanımlı olarak derlendiğinde, sonuçta elde edilen ikili, test_routine() fonksiyonu için hesaplanan CRC32'yi yazdıracaktır. Hesaplanan CRC32 0xFFF7FB7C ise, aşağıdaki tablo indisleri geçerli fonksiyon göstericilerini temsil edecektir: b1[12], b2[7], b3[11], b4[15], b5[7], b6[15], b7[15], b8[15]. Bunlardan her biri, kontrol toplamındaki sonraki nibble'ı işleyecek fonksiyona bir gösterici içerir, b8[15] hariç, kontrol toplamının geçerli olduğu kanıtlandığında çağrılan fonksiyona bir gösterici içerir. Kaynak kodundaki tablolar şimdi bu doğru değerleri yansıtacak şekilde yeniden yazılabilir:


crc_check_fn b1[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_nib2, 0, 0, 0 },
             b2[16] = { 0, 0, 0, 0, 0, 0, 0, crc_nib3, 0, 0, 0, 0, 0, 0, 0, 0 },
             b3[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_nib4, 0, 0, 0, 0 },
             b4[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_nib5 },
             b5[16] = { 0, 0, 0, 0, 0, 0, 0, crc_nib6, 0, 0, 0, 0, 0, 0, 0, 0 },
             b6[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_nib7 },
             b7[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_nib8 },
             b8[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, crc_good };
      

Tabii ki, NULL baytlar, geçersiz girişler oldukları gerçeğini gizlemek için başka değerlerle değiştirilmek zorunda kalacak. Yanlış kontrol toplamlarını ele alan fonksiyonlara işaretçilerle değiştirilebilirler ya da programı istikrarsız hale getirmek için çöp değerlerle doldurulabilirler. Örneğin:


crc_check_fn b8[16] = { crc_good - 64, crc_good - 60, crc_good - 56, crc_good - 52,
                        crc_good - 48, crc_good - 44, crc_good - 40, crc_good - 36,
                        crc_good - 32, crc_good - 28, crc_good - 24, crc_good - 20,
                        crc_good - 16, crc_good - 12, crc_good - 8, crc_good - 4,
                        crc_good };
      

Kontrol Toplamı ile Program Bütünlüğünün Korunması

Yazılım güvenliği, sürekli evrilen teknolojik ortamda önemini koruyan kritik bir konudur. Bu makalede, derlenmiş programların bütünlüğünü korumak ve yetkisiz değişiklikleri tespit etmek için kontrol toplamlarının nasıl kullanılabileceği ele alınmıştır. CRC32 algoritması üzerinden gidilerek, kontrol toplamı değerlerinin nasıl hesaplanacağı, saklanacağı ve doğrulanacağı üzerine detaylı bilgiler verilmiştir. Ayrıca, kontrol toplamı değerlerinin programın doğru şekilde çalışması için kritik birer unsur olarak nasıl kullanılabileceği de örneklerle gösterilmiştir.

Özellikle, kontrol toplamının basit bir doğrulama mekanizmasından çok daha fazlası olabileceği, programın bütünlüğünü korumanın yanı sıra güvenliğini artırmak için de bir araç olabileceği vurgulanmıştır. Kontrol toplamlarının, programın kritik kısımlarını şifresizleme anahtarı olarak kullanma veya programın beklenen akışını yönlendirmede kullanılması gibi yöntemler, yazılım güvenliğine katkıda bulunacak inovatif yaklaşımlar sunmaktadır.

Sonuç olarak, bu makalede sunulan teknikler, yazılım geliştiricilerine ve güvenlik uzmanlarına, uygulamalarının güvenliğini artırmak için pratik ve etkili yöntemler sunmaktadır. Kontrol toplamı kullanımı, geliştiricilere programlarının bütünlüğünü ve güvenliğini korumak için güçlü bir araç sağlar. Bu tekniklerin uygulanması, yetkisiz erişim ve değişikliklere karşı bir savunma hattı oluşturmanın yanı sıra, yazılımın sağlamlığını ve güvenilirliğini de önemli ölçüde artırabilir.

Bu makalede ele alınan konular, yazılım güvenliği alanında yeni ve etkili stratejiler geliştirmek isteyen herkes için değerli bir kaynaktır. Yazılımın bütünlüğünü korumak ve güvenlik önlemlerini artırmak, günümüzün teknolojik dünyasında her zamankinden daha önemli hale gelmiştir. Geliştiriciler ve güvenlik uzmanları, sunulan teknikleri kendi uygulamalarında değerlendirmeli ve bu yöntemlerin potansiyel faydalarını maksimize etmek için uygun adaptasyonları yapmalıdır.