KategorilerGenelPosts

a bit of mobile: Android Shared Library Injection

Shared Library Injection

Bir processin içine özel bir dinamik kütüphaneyi yükleyerek o processin davranışını değiştirmek veya manipüle etmek için kullanılan güçlü bir tekniktir. Bu işlemin en yaygın yollarından biri ptrace sistem çağrısını kullanmaktır.

PTRACE

Bir processi (Tracer) hedef processe (Tracee) bağlayarak kontrol etme ve hedef processin(Tracee) sanal bellek alanında dlopen fonksiyonunu çağırma işlemini gerçekleştirir.

Bunu aşağıdaki şema ile daha ayrıntılı açıklayalım:

Bu şemada Tracer’ın process id değeri(pid)1234 ve Tracee’nin process id değeri(pid) 1235 olarak atanmıştır.

Şemada belirtilen mor ok kısmında(take a control), Tracer PTRACE_ATTACH sistem çağrısını kullanarak hedef processe(Tracee) bağlanır.Linux Kernel,hedef processin(Tracee) çalışmasını durdurur ve kontrolü Tracer’a bırakır.

Sonuç olarak mor ok kısmı(take a control),Tracer’ın Tracee’ye bağlanma sürecini temsil eder.Bu kısımda hedef processin(Tracee) kontrolü Tracer tarafından ele geçirilmiş olur.

Şemada belirtilen yeşil ok kısmında,Tracer PTRACE_GETREGS sistem çağrısıyla Tracee’nin CPU kayıtlarını okur ve analiz eder.Tracee’nin bellek haritası okunarak bellek düzeni hakkında bilgi edinilir(Bellek haritası örnek olarak /proc/pid/maps dosyasından okunabilir). Bu bilgiler,bellek manipülasyonu ve işlem adreslerinin belirlenmesi için kullanılır.

Sonuç olarak yeşil ok kısmı,Linux Kernel aracığıyla Tracer’ın Tracee’nin belleğine ve CPU kayıtlarına erişimini temsil eder.

Şemada gösterilen ptrace kısmı,Tracer ile Linux Kernel arasındaki iletişim mekanizmasını temsil ediyor.Tracer,Tracee üzerinde işlem yapabilmek için Linux Kernel’a çeşitli ptrace istekleri gönderir.Bu isteklerin her biri hedef processin(Tracee) bir parçasını manipüle etmek için kullanılır.

Bu mekanizma sayesinde, Tracer hedef processin belleğine erişebilir ve manipüle edebilir. İşte bu noktada dlopen devreye girer. dlopen, hedef processte dinamik olarak bir kütüphane yüklemek ve o kütüphanedeki işlevlere erişim sağlamak için kullanılır. Ptrace sayesinde, dlopen işlevi hedef processin bağlamında çalıştırılabilir.

Şemada belirtilen siyah ok kısımlarında,Tracerdan Linux Kernel’a giden okta Tracer, ptrace sistemi aracılığıyla Linux Kernel’a talepte bulunuyor.Bu talep,izleme ve kontrol işlemleri için yapılan bir sistem çağrısıdır.Linux Kernel’dan Traceer’e giden okta ise Linux Kernel,Tracer’dan aldığı talimatları Tracee’ye uygular.

Sistemdeki bir processe bağlanmak ve güvenlik kontrollerini aşmak için root yetkisi gereklidir.Root olmadan ptrace sistem çağrısı kullanılabilir ancak iki processin aynı process id(pid) değerinde olması gerekir.

Güvenlik kontrollerini tanıyalım ve bunları nasıl aşabiliriz onlara bakalım:

1. SELinux (Security-Enhanced Linux)

SELinux, Android’de zorunlu erişim kontrolü (MAC) sağlayan bir güvenlik modülüdür. SELinux, sistemdeki her işlem ve kaynak için güvenlik politikaları belirler ve bu politikaların uygulanmasını sağlar.

SELinux, sadece yetkili süreçlerin belirli kaynaklara erişmesine izin verir. Bu, kötü amaçlı yazılımların sistemde yayılmasını zorlaştırır ve potansiyel güvenlik ihlallerini önler.

SELinux, iki modda çalışabilir: Permissive ve Enforcing. Bu modlar, SELinux’un nasıl davrandığını belirler:

Enforcing Mode

  • Enforcing mode, SELinux’un tam anlamıyla etkin olduğu moddur. Bu modda, SELinux güvenlik politikalarını zorunlu kılar ve politikaları ihlal eden tüm işlemler engellenir.
  • SELinux, belirlenen güvenlik politikalarına uymayan tüm işlemleri engeller.
  • Politika ihlali girişimleri kayıt altına alınır.
  • Enforcing mode, sistemin güvenliğini maksimize eder, çünkü tüm potansiyel güvenlik ihlalleri aktif olarak engellenir.
  • Enforcing mode, genellikle üretim ortamlarında kullanılır. Bu mod, sistemin güvenliğini sağlamak ve kötü niyetli aktiviteleri önlemek için idealdir.

Permissive Mode

  • Permissive mode, SELinux’un aktif olduğu ancak güvenlik politikalarını zorunlu kılmadığı moddur. Bu modda, güvenlik politikalarına uymayan işlemler engellenmez, ancak loglanır.
  • SELinux, güvenlik politikalarını izler ve politikaları ihlal eden işlemleri loglar, ancak bu işlemleri engellemez.
  • Politika ihlali girişimleri kayıt altına alınır.
  • Permissive mode, güvenlik seviyesini artırmaz, ancak güvenlik politikalarının nasıl çalıştığını ve hangi işlemlerin ihlal oluşturduğunu görmek için faydalıdır.
  • Permissive mode, genellikle geliştirme ve test ortamlarında kullanılır. Bu mod, SELinux politikalarının sistem üzerindeki etkilerini değerlendirmek ve politikaların doğru çalıştığından emin olmak için idealdir.

Bu şemada SELinux ve ptrace arasındaki ilişki anlatılmıştır. Şemayı daha ayrıntılı inceleyelim:

Root, tüm processlere erişim yetkisine sahiptir. Şemada rootun u0_a123 processine ptrace sistem çağrısı yapabildiği gösterilmiştir.

u0_a123, kendi bağlamındaki başka bir u0_a123 processine ptrace sistem çağrısı yapabileceği gösterilmiştir.(SELinux güvenlik politikası buna izin verir.)

U0_a124, farklı bir bağlamda olduğu için u0_a123 processine ptrace sistem çağrısı yapamayacağı gösterilmiştir.(SELinux güvenlik politikası buna izin vermez.)

1.1.SELinux Devre Dışı Bırakma

SELinuxu “setenforce 0” komutu ile devre dışı bırakabiliriz.Ya da

Cihazda root olarak “echo 0 > /sys/fs/selinux/enforce” bu komutu çalıştırabiliriz.

İkisi de aynı işlevi görmektedir.

1.2.SELinux Devre Dışı Bırakma

Cihazınızın üreticisine bağlı olarak selinuxfs bağlanmamış olabilir (/sys/fs/selinux böyle bir durumda mevcut olmayabilir). SELinux’un devre dışı bırakılmasına cihazda izin verilmeyebilir.

Böyle durumlarda sistemin güvenlik bağlamlarını düzenleyerek ptrace işlemi kullanılabilir hale getirilebilir:

1.Uygun SELinux Bağlamını Bulma

İlk olarak ptrace işlemine izin veren bir SELinux bağlamı bulunmalıdır.

Shellde “ls -Z /data/data/” komutunu çalıştırarak /data/data klasöründeki paketlerin SELinux bağlamlarını görebiliriz.Burada ptrace izin verebilecek potansiyeldeki SELinux bağlamlarını bulmalıyız.

Bu SELinux bağlamını adım adım açıklayalım:

  • u(Kullanıcı): SELinux kullanıcı sınıflandırmasını temsil eder.

SELinux kullanıcıları, işlemleri veya dosyaları belirli roller ve türlerle sınırlandırır:

  • u: Genel kullanıcı işlemleri için kullanılır (örneğin uygulama ve sistem verileri)
  • system_u: Sistem kullanıcıları için kullanılır
  • root: Root kullanıcı işlemleri için kullanılır.
  • object_r(Rol): Bağlamın rolünü temsil eder.

SELinux rolleri, bir işlemin veya nesnenin (örneğin dosya veya işlem) ne tür bir role sahip olduğunu tanımlar:

  • object_r: Genelde nesneler (dosyalar, dizinler) için kullanılır.
  • system_r: Sistem hizmetlerini çalıştıran işlemler için kullanılır.
  • kernel_r: Kernel seviyesindeki işlemler için kullanılır.
  • shell_data_file(Tür): Dosyanın veya işlemin türünü belirtir.

SELinux türü, dosyaların veya işlemlerin belirli bir politika altında nasıl davranacağını tanımlar.

Türler, SELinux politikalarındaki izinleri belirlemede en önemli faktördür:

  • apk_data_file: APK dosyaları için kullanılır.
  • app_data_file: Uygulama verileri için kullanılır.
  • shell_data_file: Android shell (örneğin adb shell) bağlamında kullanılan dosyalar için kullanılır.
  • s0 (Hassasiyet): Hassasiyet seviyesini (sensitivity level) belirtir.

SELinux hassasiyetleri, Multi-Level Security (MLS) veya Multi-Category Security (MCS) bağlamında kullanılır.

Hassasiyetler genellikle şu seviyelerden oluşur:

  • s0 (Standart Güvenlik Seviyesi): SELinux bağlamlarındaki en temel ve genelde en geniş erişim yetkisine sahip güvenlik seviyesidir.
  • s1, s2, … (Daha Yüksek Güvenlik Seviyeleri): Daha özel, daha sınırlı veya daha kritik sistem işlemler için kullanılan güvenlik seviyeleridir.
  • com.android.shell: Android cihazlarda test ve hata ayıklama işlemleri için kullanılan bir contexttir. Bu context altında çalışan dosyalar ve işlemler genellikle SELinux tarafından daha geniş izinlere sahiptir.
  • u:object_r:shell_data_file:s0, Android shell contexti altında çalışan dosyalar için tipik bir contexttir ve ptrace gibi işlemlerde kullanılması muhtemeldir.

Eğer ptrace işlemi için uygun bir context tespit edildiyse injector gibi bir aracı bu context altında çalıştırabiliriz:

Dosyayı hedef contexte taşıyoruz:

SELinux contextini doğrulamamız gerekiyor:

Doğru çıktı şu şekilde olmalıdır:

Eğer uygun bir SELinux contexti bulamadıysak “chcon” ve “restorecon” kullanarak SELinux contextini değiştirip ptrace işlemini kullanılabilir hale getirebiliriz:

Eğer hedef contextin izinleriyle uyuşmuyorsa, restorecon ile contexti varsayılan hale getirin:


2. dm-verity (Device Mapper Verity)

Linux çekirdeği tarafından sağlanan bir modül olup,read-only (yalnızca-okunabilir) bölümlerin (örneğin /system ve /vendor) bütünlüğünü korumak için kullanılır. Android gibi sistemlerde dm-verity cihazın çalıştırdığı sistem bölümlerinin yalnızca doğrulanmış, güvenilir bir kaynaktan geldiğini garanti altına alır. Bu, sistem dosyalarının kötü amaçlı yazılımlar veya beklenmeyen değişiklikler tarafından bozulmasını önler.

dm-verity, hash tabanlı bir doğrulama mekanizması kullanır. Sistem bölümlerinin her bir bloğu için bir hash değeri hesaplanır ve bu hash değerleri bir hash ağacında saklanır. Sistem bir dosyaya eriştiğinde, dm-verity o dosyanın hash değerini kontrol eder ve bu değeri hash ağacıyla karşılaştırır.

Eğer bir uyuşmazlık varsa, dosyanın değiştirilmiş olduğu anlaşılır ve bu dosyaya erişim engellenir.

Eğer hash değerleri doğruysa, dosyaya erişime izin verilir.

2.1.dm-verity Devre Dışı Bırakma

“setenforce 0 ” komutu ile SELinux devre dışı bırakılır.

adb push komutuyla injector ve inject.so dosyası /data/local/tmp klasörüne aktarılır:

Ardından injection çalıştırılır:


3. ASLR (Address Space Layout Randomization)

Bellek tabanlı saldırılara karşı bir koruma mekanizmasıdır. Processlerin bellek alanındaki kritik bileşenlerin (örneğin stack, heap, dinamik kütüphaneler) adreslerini rastgeleleştirerek, saldırganların bellek adreslerini tahmin etmesini zorlaştırır.

Bir uygulama çalıştırıldığında, bellekteki tüm bölümler (örneğin stack, heap, kütüphaneler) rastgele bir konuma yerleştirilir. Bu rastgeleleştirme, saldırganların bellek adreslerini doğru bir şekilde tahmin etmesini zorlaştırır.

3.1.ASLR Devre Dışı Bırakma

ASLR güvenlik mekanizmasındaki problem şu;

Kendi processimizde bir kütüphanenin adresini (örneğin dlopen veya mmap) biliyoruz ancak hedef processte ASLR nedeniyle bu adres farklı bir konuma yüklenmiştir. Yani, aynı sembol (örneğin mmap fonksiyonu), hedef processte farklı bir adreste bulunabilir.

Bir sembolün(örneğin mmap,dlopen gibi bir fonksiyon) kütüphanedeki konumu, kütüphanenin base adresine göre sabit bir ofsete sahiptir.

Bir processin belleğinde hangi kütüphanelerin yüklü olduğunu ve bu kütüphanelerin hangi adreslerde bulunduğunu /proc/pid/maps dosyasında görebiliriz.

Bu kodu kısaca açıklayacak olursak:

  • Hedef processin bellek haritasını içeren /proc/pid/maps dosyasının tam yolu oluşturulur. Örneğin: /proc/1337/maps.
  • Oluşturulan dosya yolu okuma modunda açılır. Eğer dosya açılamazsa, hata mesajı döndürülerek işlem sonlandırılır.
  • Dosya satır satır okunur ve hedef kütüphanenin adı (ör. “libc.so”) aranır.
  • Kütüphane adı bir satırda bulunduğunda, o satırdaki base address alınır ve döngü sonlandırılır.
  • Dosya düzgün bir şekilde kapatılır.
  • Kütüphane bulunmuşsa base address, bulunamamışsa 0 döndürülür.

Eğer hem kendi sürecimizde hem de hedef süreçte bir kütüphanenin temel adresini bulabilirsek;

Bu formülle, hedef processin içindeki sembol adresini(örneğin mmap,dlopen adresi) hesaplayabiliriz.

Temelde, bir fonksiyonun local addressini alıyoruz ve buna local  kütüphanenin local base addressi ile hedef processteki kütüphanenin remote base adresi arasındaki farkı uyguluyoruz. Kod, aşağıda anlatıldığı gibi çalışmaktadır;

  • findLibrary(library, getpid()) fonksiyonu: Belirtilen kütüphanenin kendi processimizdeki  local base addressini  bulur ve local_handle değişkenine atar.
  • findLibrary(library) fonksiyonu: Hedef processin /proc/pid/maps dosyasını analiz ederek aynı kütüphanenin hedef processteki remote base addressini bulur ve remote_handle değişkenine atar.

Kod, formülü uygular:

local_addr, kendi processimizdeki sembolün (örneğin mmap) adresini temsil eder.

remote_handle – local_handle, hedef process ile kendi processimizdeki kütüphane base adresleri arasındaki farkı hesaplar.

Bu fark, yerel sembol adresine eklenerek hedef processin sembolün kesin adresi hesaplanır.

4. Linker Namespace

Dinamik olarak yüklenen kütüphanelerin güvenliğini artırmak için kullanılan bir mekanizmadır. Modern işletim sistemlerinde uygulamalar, çalışırken dış kütüphaneleri yükleyebilir (.so dosyaları gibi) ancak kötü niyetli bir kütüphane yüklendiğinde sistemin güvenliği riske girebilir. Linker Namespace, bu processi izole ederek her uygulamanın yalnızca kendi yetkilendirilmiş kütüphanelerini yüklemesine izin verir.

Her uygulamaya özel bir kütüphane “alanı” oluşturulur. Bu alan, uygulamanın yalnızca kendi izin verilen kütüphanelerine erişebilmesini sağlar. Dinamik bağlantı sırasında, kütüphanelerin doğru bir şekilde yüklendiğinden emin olunur. Kötü niyetli veya yetkilendirilmemiş kütüphanelerin yüklenmesi engellenir.

/system/lib/ gibi güvenli alanlardan kütüphane yüklenmesine izin verir.

/data/local/tmp/ veya /sdcard/ gibi güvenilmeyen alanlardan kütüphane yüklenmesi engellenir.

4.1.Linker Namespace Devre Dışı Bırakma

1. libRS.so’nun Kullanımı

libRS.so, Android’in linker namespace alanında özel bir yetkiye sahiptir ve /data/ içindeki herhangi bir yerden kütüphane yüklenmesine izin verir.

Bu kütüphanenin kod segmentini hedef processte kullanarak dlopen çağrısını baypass edilebilir.

Bu kod, cihazın mimarisine bağlı olarak libRS.so kütüphanesinin doğru yolunu tanımlar.

  • Eğer cihaz 64-bit ARM mimarisi (aarch64) kullanıyorsa, libRS.so dosyası /system/lib64/ içinde bulunur.
  • Eğer cihaz 32-bit ARM mimarisi kullanıyorsa, libRS.so dosyası /system/lib/ içinde bulunur.

 GetModuleBaseAddr(pid, VNDK_LIB_PATH), pid ile belirtilen hedef processin  sanal belleğinde libRS.so kütüphanesinin başlangıç adresini bulur.

Bu başlangıç adresi, dlopen çağrısı sırasında dönüş adresini bu kütüphaneye yönlendirmek için kullanılacaktır.

2. Dönüş Adresini libRS.so’ya Yönlendirme

  • CallRemoteFunctionFromNamespace, hedef processin linker namespace alanını değiştirerek dlopen çağrısını gerçekleştirir.
  • function_addr: dlopen fonksiyonunun adresi.
  • vndk_return_addr: libRS.so’nun başlangıç adresi (dönüş adresi olarak ayarlanır).
  • params: dlopen çağrısı için gerekli parametreler.

Bypass sırasında, dlopen çağrısının dönüş adresi libRS.so’nun başlangıç adresine yönlendirilir.

Bu, Android’in linker namespace mekanizmasını aldatır ve dlopen çağrısı, libRS.so’nun yetkilerine göre yapılır.

Sonuç olaraklibRS.so, linker namespace kısıtlamalarını kaldırdığı için, güvenilmeyen yerlerden kütüphaneler başarıyla yüklenebilir.

3. ARM Mimarisinde Link Register’i (LR) Ayarlama

regs.ARM_lr, ARM işlemcisindeki Link Register’dir. Bu, bir fonksiyon çağrısından sonra nereye dönüleceğini belirler.

return_addr, libRS.so’nun başlangıç adresidir. Böylece, dlopen çağrısı tamamlandığında kontrol libRS.so’ya döner.

PtraceSetRegs, hedef processin CPU registerlarını uzaktan değiştirmek için kullanılır.

Buradaki amaç dönüş adresini libRS.so olarak ayarlamaktır.

4. Uzaktan Fonksiyon Çağrısı ve Context Yönetimi

dlopen çağrısı tamamlandıktan sonra, CPU registerları ve process contexti eski haline döndürülür.

RestoreRegisters, önceden kaydedilen registerları geri yükleyerek hedef processin normal çalışmasını sağlar.

ptrace, güçlü ve faydalı bir araç olsa da varsayılan olarak herkesin kullanımına açık bırakılması güvenlik açısından ciddi bir risk oluşturur. Çoğu kullanıcı hata ayıklama yapmayacağı için, ptrace’in devre dışı bırakılması veya yalnızca belirli durumlarda açılması daha güvenli bir sistem sağlar. Böylece kötü niyetli yazılımlar ve izinsiz süreç erişimleri büyük ölçüde engellenir.

Ptrace sistem çağrısının devre dışı bırakılması bazı olumsuz sonuçlara neden olabilir. Örneğin, strace ve gdb gibi hata ayıklama araçları ptrace olmadan çalışamayacağı için debugging işlemleri mümkün olmayacaktır.

Sistem güvenliğini artırmak amacıyla ptrace‘i ve root kullanıcıyı devre dışı bırakmayı düşünebilirsiniz ancak bunları tamamen devre dışı bırakmak mümkün değildir.

Bunun nedeni, bu durumun CIA prensibi (Gizlilik, Bütünlük, Erişilebilirlik) ile çelişmesidir. Gizlilik ve bütünlük artırıldığında, genellikle erişilebilirlik azalır. Bu bağlamda, ptrace‘i devre dışı bırakmak, sistemi neredeyse hata ayıklanamaz ve bakılamaz hale getirebilir.

Ptrace Sistem Çağrısıyla Injection Nasıl Gerçekleşiyor?

1.Hedef processi izlemek ve kontrol etmek için ptrace kullanılarak başlatılır. Bu sistem çağrısı, injector’a hedef processin belleği üzerinde işlem yapma yetkisi verir. Buradaki amaç hedef processin akışını geçici olarak kontrol altına almaktır.

2.Injector, ptrace ile hedef processi askıya alır. Bu, hedef processin çalışmasını durdurur ve injector’ın güvenli bir şekilde değişiklik yapmasını sağlar. Buradaki amaç injection sırasında processin çalışması nedeniyle oluşabilecek tutarsızlıkları önlemektir.

3.  Hedef processin CPU registerları okunur. Bu registerlar, processin o anda hangi komutları çalıştırdığını ve bellekte hangi verileri kullandığını içerir.

Injector, kayıtların içeriğini kaydederek processin çalıştığı contexti korur. Buradaki amaç injection tamamlandıktan sonra hedef processin eski durumuna dönebilmesini sağlamaktır.

4. Injector, hedef processin sanal belleğinde mmap,dlopen ve dlsym fonksiyonlarının yerini tespit eder.

Bu fonksiyonlar, hedef processin belleğinde kullanılan dinamik bağlantı kütüphanesi tarafından sağlanır.

5. Injector, hedef processin belleğinde injection için gerekli olan veriler (stack ve heap gibi) için mmap fonksiyonu ile yeni bir bellek alanı ayırır.

6. Injector, hedef processe inject edilecek kütüphaneyi (.so dosyası) yüklemek için dlopen fonksiyonunu kullanır.

7. Injector, kütüphaneye inject edilen bir fonksiyonun adresini almak için dlsym fonksiyonunu kullanır. Buradaki amaç hedef processin, kütüphanedeki özel bir fonksiyonu çağırabilmesini sağlamaktır.

8. Injector, dlsym fonksiyonu ile elde ettiği fonksiyon adresini kullanarak inject edilen kütüphanedeki özel bir fonksiyonu çalıştırır. Bu, hedef processin davranışını değiştirebilir veya yeni özellikler ekleyebilir.

9. Injector, önceki adımlarda kaydettiği CPU registerlarını geri yükler. Bu, hedef processin eski durumuna dönmesini sağlar.

Buradaki amaç hedef processin sanki hiçbir şey olmamış gibi çalışmaya devam etmesini sağlamaktır.

10.Hedef process normal akışıyla yeni inject edilen kütüphaneyle beraber çalışmaya devam eder.

PTRACE OLMADAN CODE INJECTION

Bu şema bir programın sanal bellek yapısını ve bir code injection saldırısının nasıl gerçekleştirildiğini iki farklı durumu yan yana koyarak görselleştiriyor.

Sol taraftaki bellek injection öncesi durumu göstermektedir.Bu,programın çalıştığı sıradaki normal bellek düzenini gösteriyor.

libc.so: Programın çalışması için kullandığı standart C kütüphanesinin(libc) yüklendiği bölgeyi temsil ediyor.

Bu kütüphane malloc gibi birçok temel fonksiyonu içerir.

malloc: Bir programın çalışırken ihtiyaç duyduğu dinamik bellek alanını ayırmasını sağlayan kritik bir fonksiyondur. Bu fonksiyonun çalışması için gerekli olan kod ve veri libc içinde bir bellek bölgesine yerleştirilmiştir.

Daha basit bir şekilde açıklamak gerekirse malloc, libc’nin bir parçası olarak, bellek üzerinde kendine ayrılmış özel bir alanı kullanır. Bu alan, programın dinamik bellek taleplerini karşılamak üzere hazırlanmıştır.

Saldırganın hedefi burayı manipüle etmektir.

timezone: Normalde sistemin veya programın zaman bilgisiyle ilgili veri tutan bir değişkendir ancak saldırı sırasında bu değişken manipüle edilerek shellcode’un çalıştırılmasını kontrol eden bir mekanizma haline getirilir.

Sağ taraftaki bellek injection sonrası durumu gösteriyor.

Bu şema, saldırının başarılı bir şekilde gerçekleştirildiği ve belleğin artık saldırganın kodunu çalıştıracak şekilde değiştirildiği durumu gösteriyor.

Saldırgan, hedeflenen bellek bölgelerini manipüle ederek kendi özel kodunu (shellcode) belleğe yerleştirmiş ve çalıştırmaya hazır hale getirmiştir. Şimdi, belleğin bu durumunu detaylıca açıklayalım:

Saldırgan, Linux’taki /proc/mem dosyasını kullanarak hedef processin belleğine doğrudan erişmiş ve istediği değişiklikleri yapmıştır.

Bu dosya, bir işlemin bellek alanını okuma/yazma yeteneği sağlar ve saldırgan bu özellikten faydalanarak bellek üzerinde shellcode yazmıştır.

libc.so(Saldırıdan Etkilenmeyen Bölge):

Bu bölge saldırıdan etkilenmez çünkü saldırgan yalnızca spesifik bir hedefi (malloc adresini) manipüle etmeyi amaçlamıştır. Başka bir deyişle libc’nin geri kalan kodu ve veri alanı olduğu gibi korunur sadece saldırganın seçtiği belirli bir fonksiyonun davranışı değiştirilir.

malloc → shellcode (Hedeflenen Bölge):

Saldırgan, malloc’un adresinin bulunduğu bellek bölgesine shellcode adı verilen, kendi hazırladığı özel bir kod parçası yazmıştır.

Shellcode, saldırganın istediği işlemleri gerçekleştiren makine kodlarından oluşur.

Shellcode ile saldırgan reverse shell oluşturabilir, sistemin kontrolünü ele geçirebilir, verileri çalabilir veya değiştirebilir.

Artık malloc çağrıldığında, beklenen davranış yerine(malloc fonksiyonu ile beklenen davranış bellek tahsisiydi), saldırganın shellcode’u çalışır. Bu, saldırının ana hedefidir. Saldırgan, programın normal işleyişini kendi koduyla(shellcode) ile değiştirmiş oldu.

timezone (Kontrol Değişkeni):

Saldırgan, bu değişkeni manipüle ederek shellcode’un ne zaman ve nasıl çalışacağını kontrol edebilir.

Örneğin:

Eğer timezone belirli bir değere ayarlanırsa, shellcode tetiklenir. Başka bir değere ayarlanırsa shellcode çalışmayabilir veya farklı bir işlem gerçekleştirebilir.

Kontrol değişkeni, saldırganın shellcode’un çalışmasını dinamik olarak kontrol etmesini sağlar. Böylece shellcode, belirli koşullar yerine getirildiğinde devreye girer bu da saldırının daha gizli ve esnek olmasını sağlar.

Bu süreç, saldırganın hedef programın belleğini manipüle ederek kendi kodunu (shellcode) çalıştırmasını sağlayan adımlardan oluşur. Başlangıçta hedef fonksiyonun adresini bulmakla başlayan bu işlem, bellekte dinamik değişiklikler yaparak saldırıyı tamamlar.Şemayı detaylıca açıklayalım:

1.parse /proc/maps

İlk adım, hedef programın bellek yapısını analiz etmektir. Bu, /proc/maps dosyasını okuyarak yapılır.

/proc/maps, bir sürecin bellek bölgelerini ve bu bölgelerin adreslerini detaylı bir şekilde listeler.

Saldırgan, hedef fonksiyonun (örneğin malloc) ve kontrol değişkeninin (örneğin timezone) bellek üzerindeki tam adreslerini bu dosyadan bulur.

Buradaki amaç, hedeflenen bölgelere hassas bir müdahale yapabilmek için, adres bilgilerine ihtiyaç duyulur. Bu işlem saldırının temelidir.

2. backup target function and variable

Hedef fonksiyonun (malloc) ve kontrol değişkeninin (timezone) bellekteki mevcut durumu kopyalanır.

Yedeklenen bu veriler, injection işlemi tamamlandıktan sonra belleğin eski haline döndürülmesi için kullanılır.

Buradaki amaç, programın doğal akışını bozmadan, saldırıyı fark edilmesi zor bir şekilde gerçekleştirmektir.

3. zero out variable and overwrite function

Kontrol değişkeni (örneğin timezone) sıfırlanır veya saldırganın belirlediği özel bir değerle güncellenir. Bu, shellcode’un tetiklenmesi için gereken koşulları oluşturur.

Hedef fonksiyonun bellek alanı, saldırgan tarafından yazılmış olan ilk aşama shellcode ile üzerine yazılır.

Buradaki amaç,ilk aşama shellcode bellekte saldırganın kontrolünde yeni bir bellek alanı oluşturur. Bu yeni alan, iş parçacıklarının (threads) senkronize edilmesi ve ikinci aşama shellcode’un çalıştırılması için hazırlanır. Böylece saldırgan, bellek üzerinde tam kontrol sağlayarak sonraki adımlara zemin hazırlar.

Bu aşama, saldırının temel yapı taşıdır ve shellcode’un başarılı bir şekilde çalışması için gerekli altyapıyı oluşturur.

4. check control variable

Eğer kontrol değişkeni belirlenen bir değere ayarlanmışsa, shellcode sonraki adımları başlatır.

Değişken uygun değere sahip değilse, shellcode bekler ve değişkeni tekrar kontrol eder.

Buradaki amaç, saldırının dinamik olarak tetiklenmesini sağlamak ve shellcode’un çalışmasını saldırganın kontrolüne bırakmaktır.

5. update target function with self-jmp

Bu adımda, ilk aşama shellcode kendi işleyişini düzenleyecek şekilde güncellenir ve bellekte bir sonraki aşamaya geçiş için güvenli bir yapı oluşturulur.

Shellcode, içindeki işlem sırasını takip edebilmesi için bir kendine döngü (self-jump) mekanizmasıyla yeniden yapılandırılır.

Bu mekanizma, shellcode’un kesintisiz ve hatasız bir şekilde çalışmasını sağlar ve bir sonraki adımın başlaması için uygun koşulları oluşturur.

Buradaki amaç, iş parçacıkları (threads) arasındaki senkronizasyonu sağlamaktır. Böylece shellcode tüm süreçle uyumlu hale gelir. Bellekte ikinci aşama shellcode’un çalıştırılmasını başlatacak ortamı hazır hale getirir.

Hedef fonksiyonun güncellenmesi, saldırının kritik bir parçasıdır; shellcode’un kararlı bir şekilde çalışmasını ve saldırının bir sonraki adımını başlatmasını güvence altına alır.

6. restore the function and the variable

Shellcode’un ilk aşaması tamamlandığında, bellekte yapılan değişiklikler geri alınır.

Yedeklenen hedef fonksiyon (malloc) ve kontrol değişkeni (timezone), orijinal haliyle belleğe geri yüklenir.

Buradaki amaç, programın doğal akışını koruyarak saldırının gizlenmesini sağlamaktır. Bu adım, saldırının neredeyse algılanamaz hale gelmesini mümkün kılar.

7. update new map with second-stage shellcode

İlk aşama shellcode, yeni bir bellek alanı oluşturarak ikinci aşama shellcode’u buraya yükler.

Yeni bellek bölgesi, saldırganın asıl hedeflerini gerçekleştirecek olan ikinci aşama shellcode ile doldurulur.

İkinci aşama shellcode ile genellikle sistemin kontrolünü ele geçirme,hassas verilere erişim sağlama,saldırıyı daha ileri taşıma ve başka saldırıları başlatma işlemlerini gerçekleştirir.

Buradaki amaç, saldırının tam etkisini ortaya çıkarmak ve saldırganın asıl hedeflerine ulaşmasını sağlamaktır.

Bu süreç, hedef fonksiyonun birden fazla iş parçacığıyla(threads) çalıştığı durumlarda, shellcode’un güvenilir bir şekilde çalışmasını sağlamak ve ikinci aşama shellcode’un enjekte edilip çalıştırılmasını mümkün kılmak için tasarlanmıştır. Bu süreci detaylı bir şekilde açıklayalım:

1. Check the Control Bit

Shellcode, ilk olarak bir kontrol biti kontrol eder. Kontrol biti, bir iş parçacığının shellcode’un sonraki adımlarını (örneğin yeni bellek haritasının oluşturulması, kontrol değişkeninin güncellenmesi ve yeni haritaya atlama işlemleri) çalıştırmaya yetkili olup olmadığını belirler.

Eğer kontrol biti set edilmiş (bit is set) durumdaysa bu iş parçacığı(thread), shellcode’un sonraki adımlarını çalıştırır.

Eğer kontrol biti set edilmemiş (bit is not set) durumdaysabu iş parçacığı(thread) beklemeye alınır ve kontrol biti tekrar incelenir.

Buradaki amaç, birden fazla iş parçacığının(threads) aynı anda shellcode’u çalıştırmasını engellemektir. Bu kontrol, işlemlerin çakışmasını veya bellek manipülasyonu sırasında oluşabilecek hataları önleyerek güvenilir bir çalışma ortamı sağlar.

2.Set Control Bit

Shellcode, kontrol bitini set ederek hangi iş parçacığının geri kalan işlemleri (örneğin yeni bellek haritasının oluşturulması ve shellcode’un çalıştırılması) yürüteceğini belirler.

Bu işlem, atomik bir şekilde gerçekleştirilir yani kontrol biti yalnızca bir iş parçacığı tarafından değiştirilebilir. Diğer iş parçacıkları bu değişikliğe müdahale edemez veya aynı anda bu işlemi gerçekleştiremez.

Buradaki amaç, shellcode’un kritik adımlarını yalnızca bir iş parçacığının çalıştırmasını sağlayarak, çoklu iş parçacıkları arasındaki çakışmaları önlemektir. İşlemleri düzgün bir şekilde senkronize ederek saldırının güvenilirliğini ve etkinliğini artırmaktır.

Bu adım, shellcode’un koordinasyonunu sağlamak için kritik bir rol oynar ve sonraki adımlara geçişi güvence altına alır.

3.Save Registers

Shellcode, işlemcinin o anki durumunu temsil eden mevcut register değerlerini kaydeder.

Bu işlem, ilerleyen adımlarda hedef fonksiyonun orijinal halinin tam ve doğru bir şekilde geri yüklenmesini sağlar.

Buradaki amaç, hedef fonksiyonun doğal akışına kesintisiz bir şekilde geri dönebilmesi için gerekli bilgileri güvence altına almaktır.

Bu kritik adım, programın işleyişinde herhangi bir bozulma yaratmadan saldırıyı başarıyla gerçekleştirebilmek için bir temel oluşturur.

4.Call mmap

Shellcode, mmap sistem çağrısını kullanarak bellek üzerinde yeni bir bölge tahsis eder.

Tahsis edilen bu bellek alanı, ikinci aşama shellcode’un enjekte edileceği ve çalıştırılacağı yer olarak hazırlanır.

Buradaki amaç, ikinci aşama shellcode için izole ve güvenli bir çalışma alanı oluşturmaktır.

Bu adım, saldırının sonraki aşamalarını gerçekleştirmek için gerekli altyapıyı sağlamada kritik bir rol oynar.

5.Write Address to Control Variable

Shellcode, yeni tahsis edilen bellek alanının adresini bir kontrol değişkenine (timezone) kaydeder.

Bu işlem, injection yapan processin yeni bellek adresine erişebilmesini ve bu adres üzerinden sonraki işlemleri gerçekleştirmesini sağlar.

Buradaki amaç, shellcode ve injection processi arasında bir iletişim köprüsü oluşturmaktır. Yeni bellek alanının adresini paylaşarak, ikinci aşama shellcode’un bu alana güvenli bir şekilde inject edilmesi için uygun ortam hazırlamaktır.

Bu adım, saldırının koordinasyonu için kritik bir rol oynar ve shellcode’un sonraki aşamalarının başarılı bir şekilde yürütülmesini sağlar.

6. Write Self-Jump to the New Map

Shellcode, yeni tahsis edilen bellek alanına bir kendine atlama (self-jump) talimatı yazar.

Bu talimat, shellcode’un kontrolünü tamamen yeni bellek bölgesine devreder, böylece işlemler artık bu izole alanda yürütülmeye devam eder.

Buradaki amaç, shellcode’un, tahsis edilen yeni bellek bölgesinde güvenilir ve izole bir şekilde çalışmasını sağlamak ve saldırının sonraki aşamalarını yeni bellekte gerçekleştirmek için uygun bir ortam oluşturmaktır.

Bu adım, shellcode’un çalışma alanını güvenli bir şekilde yeni bir belleğe taşıyarak saldırının etkinliğini ve başarı şansını artırır.

7. Jump to the New Map

Shellcode, tahsis edilen yeni bellek alanına atlama yaparak çalışmasını burada sürdürür.

Yeni belleğe geçtikten sonra, shellcode ikinci aşama için tamamen hazır hale gelir ve inject edilecek kodu beklemeye başlar.

Buradaki amaç, shellcode’un kontrolünü tamamen yeni bellek haritasına devrederek saldırının sonraki aşamalarını izole bir alanda yürütmek ve ikinci aşama shellcode’un çalıştırılabilmesi için güvenli bir altyapı ve uygun bir çalışma ortamı oluşturmaktır.

Burada ikinci aşama shellcode’un çalışma süreci gösterilmiştir. Bu aşama, saldırının karmaşıklığını artırarak hedef sistem üzerinde daha fazla kontrol sağlamak için kritik bir rol oynar. Bu süreci detaylı bir şekilde açıklayalım:

1.Self-Jump

İlk aşama shellcode, bir iş parçacığı (Thread-1) üzerinde sürekli bir kendine atlama (self-jump) döngüsünde bekler.

Bu sırada injection yapan süreç, ikinci aşama shellcode’u bellek alanına yazar ve kodu günceller.

Buradaki amaç, ikinci aşama shellcode’un yüklenmesini ve çalıştırılmasını beklerken shellcode’un kontrolü elinde tutmasını sağlamaktır.

Bu adım, saldırının kararlı ve hatasız bir şekilde ilerlemesi için temel oluşturur.

2. Call dlopen

İkinci aşama shellcode, dosya sisteminde bulunan bir shared libraryi belleğe yüklemek için dlopen fonksiyonunu çağırır.

Yüklenen bu kütüphane, saldırganın hedef sistem üzerinde gerçekleştirmek istediği özelleştirilmiş ve karmaşık işlemleri içeren kodları barındırır.

Buradaki amaç, sisteme özel kodlar yükleyerek saldırının kapsamını genişletmek ve shared library aracılığıyla, saldırganın sistem üzerinde tam kontrol sağlamasına olanak tanımaktır.

3.Load Registers

Shellcode, saldırıdan önce kaydedilmiş olan register değerlerini tekrar belleğe yükler.

Bu işlem, programın saldırıdan etkilenmeden normal işleyişine devam edebilmesini sağlar.

Buradaki amaç, hedef sistemin normal çalışma durumunu koruyarak saldırının tespit edilmesini önlemek ve register değerlerini doğru bir şekilde geri yükleyerek programın akışını kesintiye uğratmadan saldırıyı sürdürmektir.

4.Jmp Back to Hijacked Function

İkinci aşama shellcode çalıştırıldıktan sonra, kontrol ele geçirilen hedef fonksiyona devredilir.

Bu adım, hedef programın normal akışına geri dönmesini ve saldırının izlerini gizlemesini sağlar.

Buradaki amaç, programın normal çalışmasını sürdürerek saldırının fark edilme riskini minimize etmek ve saldırganın işlemlerini tamamladıktan sonra hedef sistemin normal işleyişine devam etmesini sağlamaktır.

İkinci Aşama Shellcode Türleri

İkinci aşama shellcode, saldırının ana operasyonlarının gerçekleştirildiği bölümdür ve kullanılan yöntem saldırının türüne ve hedef sistemin koşullarına göre değişiklik gösterebilir.Üç tane ikinci aşama shellcode türü vardır.Her türün kendine özgü özellikleri ve kullanım alanları vardır.Bunları detaylıca inceleyelim:

1Shellcode Loader

Belleğe yeni bir shellcode yükleyip çalıştırmayı hedefleyen en temel yöntemdir. Basit yapısı ile doğrudan ve hızlı bir çözüm sunar.

Çalışma Mantığı

Bellekte bir alan tahsis eder.

Yeni shellcode’u bu alana yazar ve çalıştırır.

2. Raw-dlopen Shellcode

Dosya sisteminde mevcut bir shared libraryi yüklemek için dlopen fonksiyonunu kullanır. Bu yöntem, saldırganın daha karmaşık ve özelleştirilmiş kodları sisteme yüklemesini sağlar.

Buradaki raw’ın işlevi ise dosya sisteminden mevcut bir shared libraryi doğrudan yüklemektir. Saldırganın hazırladığı shared library, dosya sistemine yerleştirilir ve dlopen fonksiyonu ile belleğe yüklenir.

3.Memfd-dlopen Shellcode

Saldırganların zararlı kütüphaneleri bellek üzerinde yükleyip çalıştırmasına olanak tanıyan ileri düzey bir tekniktir. Bu yöntemde, zararlı kütüphane içeriği, dosya sistemini kullanmadan bellekte bir anonim dosya oluşturulmasını sağlayan memfd ile bellekte saklanır ve dlopen ile çalıştırılır.

dlopen yerine neden memfd kullanıldığını detaylıca açıklayalım:

dlopen, klasik kullanımıyla dinamik kütüphaneleri yüklemek için dosya sisteminde fiziksel olarak bulunan bir dosyayı kullanır ancak bu, saldırgan açısından bazı dezavantajlar taşır:

dlopen, .so uzantılı bir dosyanın diskte var olmasını gerektirir.

Diske yazılmış bir dosya,antivirüs yazılımları tarafından kolayca tespit edilebilir.

Fiziksel bir dosya, saldırının tamamlanmasından sonra bile sistemde iz bırakabilir.

memfd kullanılarak bellekte saklanan dosyalar, fiziksel bir dosya gibi taranamaz ve dosya sisteminde görünmediği için davranış tabanlı analizlerden kaçabilir. Bu durum, saldırganın güvenlik çözümlerini atlatmasına yardımcı olur.

memfd, dosya sistemini tamamen devre dışı bırakarak kütüphane içeriğinin yalnızca bellekte saklanmasını sağlar. Böylece, diske herhangi bir zararlı dosya yazılmaz ve saldırı tespit edilmesi çok daha zor hale gelir.

dlopen ile bir dosya yüklendiğinde, bu dosya bir sistem yolu ile ilişkilidir (örneğin /data/local/tmp/injector.so).

Dosya sistemindeki bir kütüphane, güvenlik çözümleri tarafından şüpheli etkinlik olarak algılanabilir.

Dosya yolu, sistem günlüklerinde açıkça kaydedilir ve saldırının izlenmesine olanak tanır.

memfd ile oluşturulan anonim dosya hiçbir sistem yolu ile ilişkilendirilmez ve anonim dosya geçicidir süreç sona erdiğinde otomatik olarak bellekten temizlenir.

Bu, saldırganın kullandığı zararlı kütüphanenin tespit edilmesini zorlaştırır.

dlopen çağrısı sırasında kullanılan dosya yolları ve sistem çağrıları (örneğin, open, read, write) güvenlik loglarında açıkça görülebilir. Bu da saldırganın tespit edilmesini kolaylaştırır.

memfd, yalnızca bellek üzerinde çalışan bir sistem çağrısıdır. Sistem loglarında sıradan bir bellek işlemi gibi görünür. Bu, saldırganın etkinliklerini sıradan bir işlem gibi göstermesine olanak tanır.

DEMO

Uygulamaya libinject_shellcode.so dosyasını inject edelim:

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir