Translated ['src/binary-exploitation/common-binary-protections-and-bypas

This commit is contained in:
Translator 2025-07-17 00:13:19 +00:00
parent 4cf51be28e
commit 1b9f714314

View File

@ -4,30 +4,96 @@
## Relro
**RELRO**, **Relocation Read-Only** anlamına gelir ve **GOT (Global Offset Table)** üzerine yazma ile ilişkili riskleri azaltmak için ikili dosyalarda kullanılan bir güvenlik özelliğidir. İki tür **RELRO** koruması vardır: (1) **Partial RELRO** ve (2) **Full RELRO**. Her ikisi de **GOT** ve **BSS**'yi ELF dosyalarından yeniden sıralar, ancak farklı sonuçlar ve etkileri vardır. Özellikle, **GOT** bölümünü **BSS**'den _önce_ yerleştirir. Yani, **GOT** daha düşük adreslerde bulunur, bu nedenle **BSS**'deki değişkenleri taşırarak **GOT** girişlerini yazmak imkansız hale gelir (belleğe yazmanın daha düşük adreslerden daha yüksek adreslere doğru gerçekleştiğini unutmayın).
**RELRO**, **Relocation Read-Only** anlamına gelir ve bir bağlayıcı (`ld`) tarafından uygulanan bir önlemdir; bu, ELF'nin veri segmentlerinin bir alt kümesini **tüm yeniden konumlandırmalar uygulandıktan sonra yalnızca okunur hale getirir**. Amaç, bir saldırganın program yürütülmesi sırasında dereferans edilen **GOT (Global Offset Table)** veya diğer yeniden konumlandırma ile ilgili tabloların girişlerini yazmasını engellemektir (örneğin, `__fini_array`).
Kavramı netlik için iki ayrı türüne ayıralım.
Modern bağlayıcılar, RELRO'yu **GOT'u** (ve birkaç diğer bölümü) **.bss**'den **önce** yerleştirerek ve en önemlisi dinamik yükleyici yeniden konumlandırmaları uyguladıktan hemen sonra `RX` olarak yeniden haritalanan özel bir `PT_GNU_RELRO` segmenti oluşturarak uygular. Sonuç olarak, **.bss**'deki tipik tampon taşmaları artık GOT'a ulaşamaz ve keyfi yazma ilkelere, RELRO korumalı bir sayfanın içinde bulunan işlev işaretçilerini yazmak için kullanılamaz.
### **Partial RELRO**
Bağlayıcı tarafından yayımlanabilecek **iki seviye** koruma vardır:
**Partial RELRO**, güvenliği artırmak için daha basit bir yaklaşım benimser ve ikilinin performansını önemli ölçüde etkilemez. Partial RELRO, **.got'u yalnızca okunur hale getirir (GOT bölümünün PLT dışı kısmı)**. Kalan bölümün (örneğin, .got.plt) hala yazılabilir olduğunu ve dolayısıyla saldırılara maruz kalabileceğini unutmayın. Bu, **GOT**'un **rastgele yazma** zafiyetleri tarafından kötüye kullanılmasını engellemez.
### Kısmi RELRO
Not: Varsayılan olarak, GCC ikilileri Partial RELRO ile derler.
* `-Wl,-z,relro` (veya `ld`'yi doğrudan çağırırken sadece `-z relro`) bayrağı ile üretilir.
* Sadece **GOT**'un **non-PLT** kısmı (veri yeniden konumlandırmaları için kullanılan kısım) yalnızca okunur segmente konur. Çalışma zamanında değiştirilmesi gereken bölümler en önemlisi **lazy binding**'i destekleyen **.got.plt** yazılabilir kalır.
* Bu nedenle, bir **keyfi yazma** ilkesinin hala bir PLT girişini yazması (veya **ret2dlresolve** gerçekleştirmesi) mümkündür.
* Performans etkisi önemsizdir ve bu nedenle **neredeyse her dağıtım yıllardır en az Kısmi RELRO ile paketler gönderiyor (2016 itibarıyla GCC/Binutils varsayılanıdır)**.
### **Full RELRO**
### Tam RELRO
**Full RELRO**, **tüm GOT'u (hem .got hem de .got.plt) ve .fini_array** bölümünü tamamen **salt okunur** hale getirerek korumayı artırır. İkili dosya başladığında tüm fonksiyon adresleri çözülür ve GOT'a yüklenir, ardından GOT salt okunur olarak işaretlenir, bu da çalışma zamanında herhangi bir değişikliğe karşı etkili bir şekilde koruma sağlar.
* **Her iki** bayrak ile üretilir: `-Wl,-z,relro,-z,now` (diğer adıyla `-z relro -z now`). `-z now`, dinamik yükleyicinin **tüm** sembolleri önceden çözmesini (istekli bağlama) zorlar, böylece **.got.plt** bir daha yazılmak zorunda kalmaz ve güvenli bir şekilde yalnızca okunur olarak haritalanabilir.
* Tüm **GOT**, **.got.plt**, **.fini_array**, **.init_array**, **.preinit_array** ve birkaç ek iç glibc tablosu yalnızca okunur bir `PT_GNU_RELRO` segmentinin içine yerleştirilir.
* Ölçülebilir bir başlangıç yükü ekler (tüm dinamik yeniden konumlandırmalar başlatıldığında işlenir) ancak **çalışma zamanı yükü yoktur**.
Ancak, Full RELRO ile ilgili bir takas, performans ve başlangıç süresidir. Çünkü GOT'u salt okunur olarak işaretlemeden önce tüm dinamik sembolleri başlangıçta çözmesi gerektiğinden, **Full RELRO etkin olan ikililer daha uzun yükleme süreleri yaşayabilir**. Bu ek başlangıç yükü, Full RELRO'nun tüm ikililerde varsayılan olarak etkin olmamasının nedenidir.
2023'ten itibaren birkaç ana akım dağıtım, **sistem araç zincirini** (ve çoğu paketi) varsayılan olarak **Tam RELRO ile derlemeye** geçti örneğin, **Debian 12 “bookworm” (dpkg-buildflags 13.0.0)** ve **Fedora 35+**. Bu nedenle, bir pentester olarak **her GOT girişinin yalnızca okunur** olduğu ikili dosyalarla karşılaşmayı beklemelisiniz.
Full RELRO'nun bir ikili dosyada **etkin** olup olmadığını görmek mümkündür:
---
## Bir ikilinin RELRO durumunu nasıl kontrol edersiniz
```bash
readelf -l /proc/ID_PROC/exe | grep BIND_NOW
$ checksec --file ./vuln
[*] '/tmp/vuln'
Arch: amd64-64-little
RELRO: Full
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
```
`checksec` (bir parçası [pwntools](https://github.com/pwncollege/pwntools) ve birçok dağıtım) `ELF` başlıklarını ayrıştırır ve koruma seviyesini yazdırır. Eğer `checksec` kullanamıyorsanız, `readelf`'e güvenin:
```bash
# Partial RELRO → PT_GNU_RELRO is present but BIND_NOW is *absent*
$ readelf -l ./vuln | grep -E "GNU_RELRO|BIND_NOW"
GNU_RELRO 0x0000000000600e20 0x0000000000600e20
```
## Bypass
Eğer Full RELRO etkinse, bunu aşmanın tek yolu, keyfi yürütme elde etmek için GOT tablosuna yazma gerektirmeyen başka bir yol bulmaktır.
```bash
# Full RELRO → PT_GNU_RELRO *and* the DF_BIND_NOW flag
$ readelf -d ./vuln | grep BIND_NOW
0x0000000000000010 (FLAGS) FLAGS: BIND_NOW
```
Eğer ikili çalışıyorsa (örneğin, bir set-uid root yardımcı programı), yürütülebilir dosyayı **`/proc/$PID/exe`** üzerinden inceleyebilirsiniz:
```bash
readelf -l /proc/$(pgrep helper)/exe | grep GNU_RELRO
```
---
Not edin ki **LIBC'nin GOT'u genellikle Partial RELRO'dur**, bu nedenle keyfi bir yazma ile değiştirilebilir. Daha fazla bilgi için [Targetting libc GOT entries](https://github.com/nobodyisnobody/docs/blob/main/code.execution.on.last.libc/README.md#1---targetting-libc-got-entries)**.**
## Kendi kodunuzu derlerken RELRO'yu etkinleştirme
```bash
# GCC example create a PIE with Full RELRO and other common hardenings
$ gcc -fPIE -pie -z relro -z now -Wl,--as-needed -D_FORTIFY_SOURCE=2 main.c -o secure
```
`-z relro -z now` hem **GCC/clang** ( `-Wl,` sonrasında geçildiğinde) hem de doğrudan **ld** için çalışır. **CMake 3.18+** kullanırken, yerleşik ön ayar ile Tam RELRO talep edebilirsiniz:
```cmake
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) # LTO
set(CMAKE_ENABLE_EXPORTS OFF)
set(CMAKE_BUILD_RPATH_USE_ORIGIN ON)
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-z,relro,-z,now")
```
---
## Bypass Teknikleri
| RELRO seviyesi | Tipik ilkel | Olası istismar teknikleri |
|----------------|-------------|---------------------------|
| Yok / Kısmi | Keyfi yazma | 1. **.got.plt** girişini üzerine yaz ve yürütmeyi yönlendir.<br>2. **ret2dlresolve** yazılabilir bir segmentte sahte `Elf64_Rela` & `Elf64_Sym` oluştur ve `_dl_runtime_resolve` çağrısı yap.<br>3. **.fini_array** / **atexit()** listesinde fonksiyon işaretçilerini üzerine yaz. |
| Tam | GOT salt okunur | 1. **diğer yazılabilir kod işaretçilerini** (C++ vtables, `__malloc_hook` < glibc 2.34, `__free_hook`, özel `.data` bölümlerindeki geri çağırmalar, JIT sayfaları) ara.<br>2. libc'yi sızdırmak ve **SROP/ROP into libc** gerçekleştirmek için *göreceli okuma* ilkelerini kötüye kullan.<br>3. **DT_RPATH**/`LD_PRELOAD` aracılığıyla bir sahte paylaşılan nesne enjekte et (eğer ortam saldırganın kontrolündeyse) veya **`ld_audit`**.<br>4. GOT'a dokunmadan kontrol akışını saptırmak için **format-string** veya kısmi işaretçi üzerine yazma istismar et. |
> 💡 Tam RELRO ile bile, **yüklenen paylaşılan kütüphanelerin GOT'u (örneğin libc'nin kendisi)** **yalnızca Kısmi RELRO**'dur çünkü bu nesneler yükleyici yerleştirmeleri uyguladığında zaten haritalanmıştır. Eğer başka bir paylaşılan nesnenin sayfalarını hedef alabilen bir **keyfi yazma** ilkesine sahip olursanız, libc'nin GOT girişlerini veya `__rtld_global` yığınını üzerine yazarak yürütmeyi yönlendirebilirsiniz; bu, modern CTF zorluklarında düzenli olarak istismar edilen bir tekniktir.
### Gerçek dünya bypass örneği (2024 CTF *pwn.college “aydınlanmış”*)
Zorluk Tam RELRO ile gönderildi. İstismar, bir yığın parçasının boyutunu bozmak için bir **off-by-one** kullandı, `tcache poisoning` ile libc'yi sızdırdı ve sonunda `__free_hook`'u (RELRO segmentinin dışında) bir gadget ile kod yürütmek için üzerine yazdı. Hiçbir GOT yazma gerekmiyordu.
---
## Son araştırmalar & güvenlik açıkları (2022-2025)
* **glibc 2.40 `__malloc_hook` / `__free_hook`'u kullanımdan kaldırıyor (2025)** Bu sembolleri kötüye kullanan modern yığın istismarlarının çoğu artık **`rtld_global._dl_load_jump`** veya C++ istisna tabloları gibi alternatif vektörlere geçmek zorundadır. Kancalar **RELRO'nun dışındadır**, bu nedenle kaldırılmaları Tam RELRO bypass'larını zorlaştırır.
* **Binutils 2.41 “max-page-size” düzeltmesi (2024)** Bir hata, RELRO segmentinin son birkaç baytının bazı ARM64 derlemelerinde yazılabilir verilerle aynı sayfayı paylaşmasına izin verdi ve `mprotect`'ten sonra yazılabilecek küçük bir **RELRO boşluğu** bıraktı. Üst akım artık `PT_GNU_RELRO`'yu sayfa sınırlarına hizalıyor ve bu kenar durumunu ortadan kaldırıyor.
---
## Referanslar
* Binutils belgeleri *`-z relro`, `-z now` ve `PT_GNU_RELRO`*
* *“RELRO Tam, Kısmi ve Bypass Teknikleri”* blog yazısı @ wolfslittlered 2023
{{#include ../../banners/hacktricks-training.md}}