Geçen gün YouTube'da gezerken bir şeye denk geldim. Bir adam, Google'ın CTF'ini (Capture The Flag, Bayrak Kapmaca) oynuyordu. Bir JavaScript görevini çözmeye çalıştığı için hemen kulaklarım havaya dikildi ve dikkatle izlemeye başladım.
Ancak daha önce hiç Kriptografi veya Reverse Engineering (tersine mühendislik) ile ilgilenmediğim için, bu Youtuber arkadaşın çözdüğü bölüm, beynimin kıvrımlarını eritmeye yetmişti.
Daha fazla spoiler (sürpriz kaçıran) almamak için videoyu kapattım ve Google CTF'i oynamaya karar verdim. Gerek retro tasarımı, gerekse eğlenceli ilerleyişiyle beni içine çekmeyi başardı.
Haydi başlayalım.
Bölüm 1 - Letter
Bize bir ZIP dosyası içerisinde bir PDF verdi. PDF'in içeriği şu şekilde:
Öncelikle bu dosyayı bir HEX Editör olan Hexed.it ile açmayı denedim. İstenen şifrenin CTF{...} şeklinde olduğunu bildiğimden CTF ile alakalı bir string aradım.
Ancak dosyanın büyüklüğü (59KB) beni benden aldı ve farklı bir yöntem aramaya başladım.
Aklıma bu şifreyi PDF dosyasından kopyalamak geldi. Sonuçta dosyanın içerisinde, üzeri siyah bir biçimde çizilmiş bir yazı vardı.
Kopyalayıp bir text editörüne yapıştırdığımda, bu siyah çizginin, şifrenin üzerindeki bir filtre olduğunu, istediğimiz cevabın bu katmanın altında olduğunu gördüm.
Bu şifreyi, yani CTF{ICanReadDis} dizgesini boşluğa girdim ve ikinci katmana geçtim.
Bölüm 2 (Ortadaki, Pembe) - Floppy
ZIP dosyasının içerisinden bir adet foo.ico dosyası çıktı. Dosyayı yine başlangıç olarak Hexed.it ile açtım. Biraz incelediğimde içinde iki adet dosyanın, driver.txt
ve www.com
'un, gömülü olduğunu gördüm.
7ZIP programı ile foo.ico içerisinde girdim ve dosyaların gerçekten burada olduğunu doğruladım.
driver.txt
dosyasının içeriği:
www.com
dosyasının içeriği:
Çözüm, driver.txt
dosyasının içindeydi. CTF{qeY80sU6Ktko8BJW} dizgesini boşluğa girdim ve üçüncü katmana geçtim.
Bölüm 3 (Ortadaki, Mavi) - JS SAFE
Geçmeden önce şunu söylememde fayda var. Bu bölüm bundan önceki diğer iki bölümden ÇOK daha zor. Bu yüzden size tavsiyem, sindire sindire okumanız.
Bu bölümü geçebilmek için Debugging yapabilirdim, ancak tipik bir JavaScript hayranı olduğumu belirtmek için Tracing kullanmaya karar verdim. Bazılarına Debug işlemi daha kolay gelebilir.
Bu bölümde de bizden istenen şey, her zamanki gibi CTF anahtarını bulmamız. Ancak bu sefer elimizde bir bulmaca değil de, bir şeytan düğümü var.
Parçalama:
open_safe()
Asenkron bir fonksiyon, kasanın açılması için gerekli komutları bulunduruyor.
CTF{([0-9a-zA-Z_@!?-]+)}
password değişkenine CTF'in regex'ini atıyor.
!password || !(await x(password[1]))
true dönerse denied yemişiz demektir. Yani password && await x(password[1])
'un true döndürmesi, şifreyi bulduğumuz anlamına gelir.
Stringimiz CTF{12345} şeklinde olduğunda password
değişkeninin 1. indexteki değeri 12345 olacak. Bu değeri x
'e gönderip true almamız bekleniyor.
x
fonksiyonu korkutucu. code
adında bir değişkenimiz var ve şu değeri tutuyor:
Bir de env
adında bir değişkenimiz var, bu da bir obje ve içerisinde fonksiyon'lar, integer'lar, array'ler tutuyor. Kısacası ortalık bayağı bir karışık.
Burada görüyoruz ki a
,b
,c
ve d
birer Function, e
,f
ve h
birer Integer ve g
bizim şifremizi Byte Array şeklinde tutuyor.
Bizden, şu kodun çalıştırılması isteniyor:
Kodu şu şekilde açıklayabiliriz:
- 0'dan başlayıp sona kadar her 4. karakteri al. yani 0,4,8...1712 indeksler
- Bu dört karakteri lhs, fn, arg1 ve arg2 değişkenlerine ata.
- Eğer zaten böyle bir env değeri varsa çağır
- Eğer böyle bir env değeri yok ise (catch) oluştur
- Üstteki fonksiyonun çalıştığından (promise) emin ol
- Eğer h 0 ise true dön
Bu koddan görüyoruz ki bu işlem sadece env
'de sayıları işlemiyor, gerektiği zamanda fonksiyonu yaratıyor.
İlk iterasyonu inceleyelim.
İlk 4 karakter "icff". env[lhs] = env[fn](env[arg1], env[arg2]);
i = c(f,f)
i = f+f
Yani f ve f'i c fonksiyonuna sokup çıkanı i'ye atıyor.
Bu işlem bana nedense Opcode ve Operand'ları hatırlattı.
Üstteki for döngüsünün içerisine şunu yazıp her bir iterasyonda değerlerimizi kontrol edelim:
Burada env'in kopyasını aldığıma dikkat edin. Bunu yapmamın sebebi, env'in sadece bir referans olarak değil, yeni bir obje olarak oluşturulmasını istemem. Eğer referans olarak alsaydık, tüm yazdırılan env'leri aynı görürdük çünkü Chrome konsolu, bu objeyi açmaya çalıştığımızda son düzenlenen veriyi gösteriyor.
İlk iterasyondaki değişim:
Sondaki env["h"]
'ı koymamın nedeni, bu değerin nerede değiştiğini ve bizi 0'dan büyük bir değere götürdüğünü anlamak. Eğer h
'nin neden değiştiğini bulabilirsem, değiştiği yerdeki fonksiyonun ne işe yaradığını çözmeyi deneyebilirim.
h
ilk defa değişiyor ve elimizde bir byte array var.
Bu Byte Array'i Hash String'e dönüştürüyorum...
Bize dönen sonuç:
Bunu Google'da arattığımızda şöyle bir şeyle karşılaşıyoruz:
Eğer hala göremediyseniz, açıklama kısmına bakın. Rastgele denediğimiz şifreyi, 1234'ü gösteriyor.
Bu da demek oluyor ki, bizim password stringimiz hash'e dönüştürülmüş.
Peki bu hash ne ile kıyaslanıyor da bizim h
'ımız artıyor?
Bunun için az önceki console.log
'a, bu değerleri alan fonksiyonu bulmak adına bir parametre daha ekliyorum.
Bütün bunlardan sorumlu olan fonksiyonu arıyorum.
Burada iki fonksiyon var. Biri XOR, biri OR işlemi yapıyor. Bizim password hashimiz ile işleme giren fonksiyon, ѡ
fonksiyonu.
Bunun için tüm ѡ
fonksiyonunun ilk parametresinin 1. indexindeki değerleri bir array'e kaydediyorum.
Talk is cheap, show me the code.
Linus Torvalds
Söylediklerimden hiçbir şey anlamamış, ekrana boş boş bakanlar için, demeye çalıştığım şey şu:
Önüme şöyle bir byte array çıkıyor:
Bunu az önceki gibi dönüştürüp Google'da aratıyorum.
Ve görüldüğü üzere şifremiz CTF{Passw0rd!}
CTF bitmedi ama ben bittim.
Umarım şu ana kadar olan maceramızı beğenmişsinizdir. Bir sonraki bölümde görüşmek üzere!