Matrix’i Bükmek: GTA San Andreas’ın Dijital Anatomisi, Bellek Manipülasyonu ve Speedrun Felsefesinin Sınırları


Bölüm 1: Giriş – Oyunu Oynamaktan Oyunu “Yeniden Yazmaya” Geçiş

Bir bilgisayar oyununu çalıştırmak, özünde karmaşık bir illüzyonun sürdürülmesinden ibarettir. Ekranda beliren üç boyutlu nesneler, karakterlerin dinamik hareketleri, arka planda çalan müzikler ve oyuncunun girdilerine verilen anlık tepkiler, arka planda durmaksızın çalışan ve saniyede milyonlarca kez karar değiştiren bir durum makinesinin somutlaşmış yansımalarıdır. Standart bir kullanıcı için Los Santos sokaklarında turuncu gün batımı altında bisiklet süren Carl Johnson, yaşayan bir dünyanın parçasıdır. Oyuncu çetelerle savaşır, polislerden kaçar, mülk satın alır ve hikayenin kendisine sunduğu anlatıyı takip eder. Ancak bu dünyayı bir speedrunner’ın ya da tersine mühendislik uzmanının gözünden incelediğimizde, o turuncu gökyüzü ve binalar yerini adreslenebilir bellek bloklarına, değişken işaretçilerine ve işlemcinin adımları takip ettiği yürütme yığınlarına bırakır. Oyun, artık bir eğlence aracı olmaktan çıkıp sınırları ve açıkları olan deterministik bir hesaplama matrisine dönüşür.

Yazılım geliştirme süreçlerinin doğası gereği, her program belirli sınırlar ve kurallar çerçevesinde yazılır. Geliştiriciler, oyuncunun yapabileceği eylemleri tahmin ederek kodun içerisine çeşitli mantıksal sınırlar yerleştirirler. Örneğin, bir oyuncunun haritanın kilitli bir bölgesine geçmesini engellemek için nehirlerin üzerine görünmez duvarlar konulur ya da belirli bir görevi tamamlamadan bir sonrakinin tetiklenmeyeceğini garanti altına alan mantıksal bayraklar kullanılır. Ancak bu kurallar, yazılımcıların öngörüleriyle sınırlıdır. Bir yazılım ne kadar büyük ve karmaşıksa, içerisindeki mantıksal çelişkilerin ve beklenmedik durumların sayısı da o kadar doğrusal bir artış gösterir. GTA San Andreas gibi kendi dönemine göre devasa bir açık dünyaya, karmaşık yapay zeka rutinlerine ve birbirine entegre edilmiş düzinelerce alt sisteme sahip bir yapıda, bu çelişkilerin sıradan bir programdan katbekat fazla olması kaçınılmazdır.

Hız koşusu dünyası, bu karmaşık yapının sınırlarını test etme serüvenidir. İlk dönemlerde bu serüven, fizik motorunun sınırlarını zorlamaktan ibaretti. Oyuncular, araçların sürtünme katsayılarını istismar ederek daha hızlı viraj almanın yollarını arıyor, harita geometrisindeki boşlukları kullanarak fiziksel engelleri aşıyor ve her görevi milisaniyeler bazında optimize ederek en hızlı rotayı çizmeye çalışıyordu. Bu süreçte oyun hala “tasarlandığı şekliyle” oynanıyordu; yani oyuncu hala bir karakteri kontrol ediyor, araba sürüyor, ateş ediyor ve görevleri sırasıyla tamamlıyordu. Ancak speedrunning topluluğunun derinliklerinde, oyunun mantıksal yapısını manipüle etmeye yönelik daha radikal bir akım filizleniyordu. Bu akım, fiziksel dünyayı optimize etmek yerine, oyunun arkasında çalışan mantık motorunu yanıltmayı hedefliyordu.

Bu mantıksal manipülasyonun ilk büyük kırılma noktası, oyunun aktif bir görevde olup olmadığını kontrol eden mekanizmanın sabote edilmesiyle yaşandı. Geliştiriciler, oyuncunun aynı anda iki farklı görevi başlatmasını engellemek amacıyla, bir görev tetiklendiğinde küresel bir durumu kontrol eden bir değişken tasarlamışlardı. Ancak bu değişkenin güncellenme anı ile yeni bir görevin başlatılma anı arasında milisaniyelik bir boşluk bulunuyordu. Speedrunner’lar, bu boşluğu keşfettiklerinde oyunun durum yönetimini altüst ettiler. Aynı görevi aynı anda birden fazla kez başlatarak bellek üzerinde veri kopyalama işlemlerini tetiklediler. Bu durum, hız koşularında fiziksel hareketin ve rotaların önemini azaltırken, mantıksal durum yönetiminin önemini zirveye taşıdı. Koşucular artık sadece daha hızlı araba süren insanlar değil, oyunun belleğini oyun içi eylemlerle manipüle eden operatörler haline gelmeye başlıyordu.

Zaman içerisinde bu mantıksal durum manipülasyonları, yerini doğrudan bellek adreslemesini hedef alan siber güvenlik yaklaşımlarına bıraktı. Bilgisayar bilimlerinde bir programın çalışması, işletim sisteminin o programa tahsis ettiği geçici bellek alanının verimli bir şekilde yönetilmesine bağlıdır. Bu bellek alanında programın kendi kodları, dinamik olarak oluşturulan nesneler ve geçici değişkenler bulunur. Normal şartlar altında, bu bellek alanlarının birbirinin sınırını aşması engellenir. Fakat eski oyun motorlarında, bellek yönetimi modern işletim sistemlerinin sunduğu katı koruma kalkanlarından yoksundur. Geliştiriciler, bellek alan tasarrufu yapabilmek ve performansı artırmak adına birçok veriyi dinamik yığınlar yerine statik ve ardışık adreslerde tutmayı tercih etmişlerdir. Bu tercih, oyunun çalışmasını hızlandırırken, aynı zamanda bir bellek bölgesindeki taşmanın yanındaki diğer hayati verileri bozmasına zemin hazırlamıştır.

İşte tam bu noktada, hız koşularının en karmaşık ve en büyüleyici aşaması olan isteğe bağlı kod veya betik yürütme teknikleri devreye girer. Geleneksel siber güvenlik literatüründe, bir sistemdeki zafiyeti kullanarak hedef sistemde saldırganın istediği kodları çalıştırma işlemine Arbitrary Code Execution (İsteğe Bağlı Kod Yürütme) denir. Bu işlemde saldırgan, programın bellek sınırlarını aşarak işlemcinin program sayacını kendi enjekte ettiği makine koduna yönlendirir. Oyun dünyasında da bu tekniğin uygulandığı birçok meşhur örnek mevcuttur. Ancak GTA San Andreas gibi karmaşık işletim sistemi katmanlarının üzerinde çalışan modern portlarda, doğrudan işlemci seviyesinde makine kodu çalıştırmak, işletim sisteminin bellek koruma protokolleri nedeniyle son derece zordur ve genellikle oyunun çökmesiyle sonuçlanır. Speedrunner’lar bu engeli aşmak için daha rafine bir yöntem geliştirdiler: Arbitrary Script Execution (İsteğe Bağlı Betik Yürütme) ve Arbitrary Jump in Script (İsteğe Bağlı Betik Atlaması).

Bu iki teknik arasındaki farkı anlamak, oyunun arkasındaki mimariyi kavramak açısından hayati önem taşır. Arbitrary Jump in Script, oyunun kendi orijinal senaryo dosyası içinde mantıksal bir sıçrama yapmayı hedefler. Oyunun tüm görev senaryolarını barındıran derlenmiş betik dosyasında, her görevin başladığı ve bittiği yerler belirli adres ofsetleri ile tanımlanmıştır. Normal şartlarda bu ofsetler sırasıyla okunur ve yürütülür. Ancak AJS tekniğinde, bellek üzerindeki işaretçiler manipüle edilerek, betik yorumlayıcısının bir sonraki adımda okuyacağı adres doğrudan oyunun son görevinin tetiklendiği ofsete yönlendirilir. Bu durum, işlemci seviyesinde yeni bir kod çalıştırmaz; sadece oyunun kendi yorumlayıcısını kandırarak onu senaryonun en sonuna ışınlar.

Arbitrary Script Execution ise işi bir adım daha ileri götürür. Koşucular, sadece var olan senaryo dosyası içinde bir noktadan diğerine atlamakla kalmazlar; oyunun bellek üzerindeki değişken alanlarını, mini oyun girdilerini ve geçici veri tamponlarını kullanarak adeta kendi küçük betiklerini oyunun RAM’ine yazarlar. Ardından, betik yorumlayıcısını bu yeni yazılmış bellek alanını meşru bir senaryo koduymuş gibi okumaya zorlarlar. Bu süreç, oyun içinde belirli eylemleri milisaniyelik hassasiyetle gerçekleştirmeyi gerektirir. Örneğin, bir kumar makinesine girilen bahis miktarı, aslında bellek üzerinde belirli bir adrese yazılan hexadecimal bir veridir. Oyuncu, bu adrese sıradan bir sayı değil de oyun motorunun komut olarak algılayacağı özel bir sayı dizisi yazdığında ve ardından betik yorumlayıcısını bu adrese yönlendirdiğinde, oyun kendi orijinal kodunu çalıştırmak yerine oyuncunun hazırladığı bu gizli talimatları yürütmeye başlar.

Bu geçiş, hız koşusu kavramının felsefesini de kökten değiştirmiştir. Artık başarı, sadece el-göz koordinasyonu veya reflekslerle ölçülmez. Başarı, sistem mimarisini anlama, bellek haritalarını analiz etme ve deterministik bir sistemde kaotik durumlar yaratıp bu kaostan istenen sonucu çıkarma yeteneğine bağlıdır. Hız koşucusu, bir atlet olmaktan çıkıp bir sistem güvenlik araştırmacısına dönüşür. Oyunun dünyası ise fiziksel kurallarla yönetilen bir simülasyon olmaktan çıkıp, bellek adreslerinin manipüle edildiği soyut bir veri tabanına evrilir.

Bu durum, yazılım geliştirme dünyası için de paha biçilmez dersler barındırır. Geliştiricilerin yirmi yıl önce performans kazanmak adına yazdıkları küçük kod bloklarının, günümüzde nasıl birer güvenlik açığına dönüştüğünü görmek, modern yazılım mimarilerinin neden bu kadar katı koruma kurallarına sahip olduğunu anlamamızı sağlar. Hız koşucularının yirmi yıllık bir kod yığınını didik didik ederek buldukları bu açıklar, aslında insan zekasının karmaşık sistemlerdeki en zayıf halkayı bulma konusundaki sınırsız becerisinin birer kanıtıdır. Biz de bu analizde, bu karmaşık yapının derinliklerine inecek, bellek bloklarının nasıl manipüle edildiğini, oyun motorunun durum makinesinin nasıl çökertildiğini ve tüm bu sürecin arkasındaki teknik mantığı en ince ayrıntısına kadar inceleyeceğiz.


Bölüm 2: RenderWare Motorunun Anatomisi ve SCM (Script Main) Zafiyetleri

Bir önceki bölümde ele alınan, hız koşularının fiziksel mekaniklerden mantıksal durum manipülasyonlarına evrilmesi süreci, doğrudan oyunun üzerine inşa edildiği motorun mimari sınırlarıyla ilişkilidir. Bu mimariyi kavramak için öncelikle oyunun kalbinde yer alan RenderWare motorunun yapısını ve bu motorun üzerine giydirilen mantık katmanını mikroskobik düzeyde analiz etmek gerekir. Criterion Games tarafından geliştirilen RenderWare, iki binli yılların başındaki üç boyutlu oyun geliştirme dünyasında adeta bir standart haline gelmişti. RenderWare, temelde grafik işleme, sahne grafiği yönetimi, temel fizik hesaplamaları ve dosya sistemleri gibi alt seviye donanım etkileşimlerini üstlenen bir ara katman (middleware) yazılımıydı. Ancak bu motorun kendisi, bir oyunun görev yapısını, yapay zeka senaryolarını veya olay örgülerini yönetecek yüksek seviyeli bir mantık mekanizmasına sahip değildi. Rockstar North, RenderWare’in sunduğu bu grafiksel ve fiziksel iskeletin üzerine kendi özel oyun içi betik motorunu entegre etti. Bu entegrasyonun sonucunda ortaya çıkan yapı, oyunun tüm davranışlarını yöneten ve her saniye arka planda milyarlarca byte veriyi işleyen devasa bir durum makinesiydi.

Rockstar’ın geliştirdiği bu betik motoru, tüm oyun dünyasını ve görev akışlarını kontrol etmek için tasarlanmış özel bir sanal makinedir (virtual machine). Bu sanal makinenin okuduğu ve yorumladığı kod tabanı, derlenmiş formatta oyun klasörleri içinde yer alan ve main.scm olarak adlandırılan tek bir devasa dosyada saklanır. Bu dosya, oyunun tüm mantıksal mimarisini barındıran bir bytecode deposudur. Geliştiriciler, yüksek seviyeli bir programlama diliyle yazdıkları görev senaryolarını, nesne tanımlamalarını ve dünya kurallarını derleyerek bu ikili (binary) formattaki main.scm dosyasına dönüştürürler. Oyun çalışmaya başladığı anda, bellek üzerinde bu dosya için özel bir alan tahsis edilir ve betik yorumlayıcısı (script interpreter) adı verilen sanal makine iş parçacığı, dosyanın ilk byte’ından itibaren okuma yapmaya başlar. Bu okuma işlemi, işlemcinin doğrudan makine kodunu çalıştırmasına benzer şekilde, her bir talimatın (instruction) sırayla çözümlenmesi ve yürütülmesi esasına dayanır.

Bu sanal makinenin çalışma prensiplerini daha derinlemesine incelediğimizde, karşımıza kooperatif çoklu görev (cooperative multitasking) modeli çıkar. Dönemin donanım sınırları, özellikle de PlayStation 2 mimarisi, gerçek anlamda paralel iş parçacığı (multithreading) yönetimine izin vermiyordu. Bu nedenle, oyun motorunun aynı anda birden fazla nesneyi, görevi ve çevre olayını kontrol edebilmesi için pseudo-multithreading adı verilen sahte bir çoklu görev modeli uygulanmıştır. Bu modelde, main.scm içerisindeki kodlar tek bir ana yürütme hattında çalışmaz; bunun yerine birbirini bloke etmeyen birden fazla küçük betik iş parçacığı (script threads) oluşturulur. Her bir iş parçacığı, kendisine ayrılan zaman dilimi içerisinde çalışır ve bir WAIT (bekle) komutuyla karşılaştığında yürütme hakkını bir sonraki iş parçacığına devreder. Eğer bir iş parçacığı zamanında yürütme hakkını devretmezse veya sonsuz bir döngüye girerse, tüm oyun motorunun kilitlenmesi veya çökmesi kaçınılmaz hale gelir.

Bu kooperatif modelin en kritik bileşeni, her bir iş parçacığının nerede kaldığını takip eden Yürütme İşaretçisidir (Instruction Pointer – IP). Yürütme işaretçisi, o anda aktif olan betik iş parçacığının main.scm dosyası içerisindeki hangi byte adresinde (offset) olduğunu gösteren bir bellek adresidir. Yorumlayıcı, her bir işlem kodunu (opcode) okuduğunda yürütme işaretçisini o işlemin boyutu kadar ileri kaydırır. Örneğin, bir nesneyi spawn etmek veya bir koordinatı kontrol etmek gibi işlemler, kendilerine ait benzersiz birer operasyon koduna (opcode) sahiptir. Bu kodlar genellikle iki byte uzunluğundadır ve arkalarından gelen parametrelerle (değişkenler, koordinatlar, karakter kimlikleri) birlikte değerlendirilir. Eğer yorumlayıcı bir işlem kodunu başarıyla okursa, parametreleri çözer, ilgili eylemi gerçekleştirir ve bir sonraki işlem kodunun başlangıç adresine atlar.

Ancak bu karmaşık sanal makine mimarisi, modern yazılım mühendisliğinde standart kabul edilen “istisna yönetimi” (exception handling) mekanizmalarından tamamen yoksundur. İstisna yönetimi, bir program çalışırken beklenmeyen bir hata oluştuğunda (örneğin sıfıra bölünme hatası, geçersiz bellek adresine erişim veya uyumsuz veri türü eşleşmesi) programın çökmesini engelleyerek hatayı güvenli bir şekilde yakalayan ve akışı kontrol altında tutan koruyucu bir yapıdır. İki binli yılların başında, bellek alanının her bir kilobyte’ının ve işlemci döngülerinin her bir milisaniyesinin kritik öneme sahip olduğu donanım koşullarında, bu tür karmaşık hata kontrol mekanizmaları birer lüks olarak görülüyordu. Geliştiriciler, oyunun kodunu yazarken her şeyin her zaman kusursuz bir senkronizasyon ve mantıksal bütünlük içinde çalışacağını varsaymışlardı. Eğer oyun motoru beklenmedik bir durumla karşılaşırsa, bunu tolere edecek veya düzeltecek hiçbir mantıksal kalkan bulunmuyordu; sanal makine körü körüne yürütme işaretçisinin gösterdiği adresteki byte verilerini okumaya ve onları işlem kodu olarak yorumlamaya devam ediyordu.

Bu istisna yönetimi eksikliği, speedrunner’lar için oyunun tüm mantıksal yapısını çökertebilecek devasa bir zafiyet kapısı aralamaktadır. Bu zafiyetin en somut örneği, normal şartlarda aynı anda yaşanması imkansız olan iki farklı durumun (state) aynı anda tetiklenmesiyle ortaya çıkan durum çakışmalarıdır. Oyun motoru, her bir durum geçişini (örneğin serbest dolaşım modu, görev modu, ara sahne modu veya menü modu) yönetirken belirli mantıksal bayraklar (flags) kullanır. Bir ara sahne (cutscene) başladığında, oyun motoru oyuncunun kontrollerini devre dışı bırakır, kamerayı belirli bir spline rotasına kilitler ve dünyadaki diğer dinamik olayları askıya alır. Bu esnada, oyuncunun gardırop menüsü gibi etkileşimli bir arayüzü açması normal şartlarda kod seviyesinde engellenmiştir. Ancak, oyunun durum geçişlerindeki milisaniyelik gecikmeler veya eşzamanlı görev tetiklemeleri kullanılarak bu kilitler aşılabilir.

Bir ara sahne aktifken gardırop menüsünün veya benzer bir arayüzün açılması, iki farklı betik iş parçacığının aynı anda aynı bellek kaynaklarına erişmeye çalışmasına neden olur. Ara sahne iş parçacığı kamerayı ve oyuncu modelini manipüle etmeye çalışırken, gardırop menüsü iş parçacığı da oyuncunun kıyafetlerini ve görünümünü değiştirmek için bellekteki değişkenleri günceller. Bu durum, oyun motorunun istisna yönetimi barındırmayan yapısında tam bir kaosa yol açar. İki iş parçacığı birbirinin üzerine veri yazmaya başlar (race condition). Sonuç olarak, bellek üzerinde o anda aktif olan iş parçacığının Thread Control Block (Thread Kontrol Bloğu) içindeki yürütme işaretçisinin değeri bozulur. Yorumlayıcı, bir sonraki döngüde bu bozulmuş adrese gider ve orada bulunan tamamen alakasız verileri (örneğin oyuncunun koordinatlarını veya kıyafet kimliklerini) birer işlem kodu (opcode) olarak yorumlamaya başlar. Bu durum, sanal makinenin kontrol dışı kalmasına ve “desenkronizasyon” adı verilen duruma girmesine yol açar.

Desenkronizasyon anında, sanal makine artık orijinal senaryo dosyasının sınırları içinde hareket etmez; bellek üzerindeki herhangi bir rastgele veriyi kod olarak çalıştırmaya çalışır. Eğer bu esnada bellek adresi, oyunun çökmesine neden olmayacak ama akışı tamamen değiştirecek bir noktaya yönlendirilebilirse, Arbitrary Jump in Script (AJS) gerçekleştirilmiş olur. AJS, tam anlamıyla yürütme işaretçisinin (IP) zorla manipüle edilerek, main.scm dosyasındaki belirli bir görevin ofset adresinden tamamen başka bir görevin ofset adresine atlamasıdır. Bu atlama işlemi fiziksel dünyada bir ışınlanma gibi görünse de, kod seviyesinde sanal makinenin okuma kafasının dosya üzerinde devasa bir sıçrama yapmasından ibarettir.

Bu tekniğin en devrimsel uygulaması, oyunun henüz başlarında yer alan “Ryder” görevinin yürütme hattından, oyunun en son görevi olan “End of the Line Part 3” görevine yapılan sıçramadır. Normal akışta, oyuncunun bu iki nokta arasında yaklaşık otuz saatlik bir oynanış süresini tamamlaması, yüzlerce mantıksal bayrağı tetiklemesi ve haritadaki üç büyük şehri açması gerekir. Ancak AJS tekniğinde, “Ryder” görevinin tetiklendiği esnada oluşturulan yapay durum çakışması sayesinde, görev iş parçacığının yürütme işaretçisi manipüle edilir. Oyun motoru, o anda bir ara sahne izlediğini sanırken, arka planda bozulan bellek adresleri sayesinde yorumlayıcı doğrudan oyunun son jenerik akışını ve tamamlanma ekranını tetikleyen ofset adresine (End of the Line’ın final bloğuna) yönlendirilir.

Bu yönlendirme gerçekleştiğinde, sanal makine hiçbir şey olmamış gibi yeni ulaştığı adresteki byte verilerini okumaya başlar. Bu yeni adreste yer alan talimatlar, oyunun dünyasındaki tüm çete bölgelerinin temizlendiğini, ana düşmanların alt edildiğini ve hikayenin başarıyla sonlandığını belirten küresel değişkenleri (global variables) günceller. Oyun motoru, bu değişkenlerin güncellenmesini hikayenin doğal bir sonucu olarak kabul eder ve ekrana oyunun başarıyla tamamlandığını gösteren o meşhur final ara sahnesini yansıtır. Bu esnada işlemci seviyesinde yeni bir kod enjekte edilmemiştir; sadece RenderWare ve Rockstar’ın özel betik motorunun zafiyetleri kullanılarak, oyunun kendi orijinal senaryo dosyası kendi içerisinde bir kısa devreye uğratılmıştır.

Bu zafiyetlerin anlaşılması, sadece hız koşularının mantığını çözmekle kalmaz, aynı zamanda erken dönem üç boyutlu oyun geliştirme süreçlerindeki donanım ve yazılım kısıtlamalarının ne denli yaratıcı şekillerde manipüle edilebileceğini gösterir. Kod seviyesindeki bu katı sınırların eksikliği, bir yandan oyunların optimizasyonunu kolaylaştırırken, diğer yandan onları dışarıdan gelecek müdahalelere karşı tamamen savunmasız bırakmıştır. Bir sonraki bölümde, bu zafiyetlerin bellek üzerinde yarattığı en tuhaf anomalilerden biri olan, oyunun kendi kendini klonlaması ve aynı anda iki farklı gerçekliğin oluşması durumunu teknik detaylarıyla ele alacağız.


Bölüm 3: Bir Paradoksun Başlangıcı – “GOTO 0” ve İşaretçi (Pointer) Mantığı

İkinci bölümde detaylandırılan, RenderWare motorunun üzerine inşa edilmiş sanal makinenin ve main.scm betik yorumlayıcısının işleyişi, her adımın doğrusal bir sıra halinde takip edildiği deterministik bir yapı sunar. Ancak bu doğrusal yapının dışına çıkmak, yani yürütme işaretçisini (Instruction Pointer) beklenmedik bir anda tamamen alakasız bir adrese yönlendirmek, sistemin tüm mantıksal dengelerini sarsar. İşte bu sarsıntının en temel, en radikal ve hız koşusu topluluğunda en çok yankı uyandıran araçlarından biri, halk arasında ve izleyici yorumlarında sıklıkla yanlış telaffuz edilerek egzotik bir isimmiş gibi yansıtılan “GOTO 0” (Go To Zero) komutudur. Yorumlarda “go toe” şeklinde okunan ve adeta bir anime karakterinin ismiymiş gibi dalga geçilen bu kavram, aslında bilgisayar bilimlerinin en eski, en temel ve en çok tartışılan kontrol akış (control flow) mekanizmalarından biridir.

GOTO komutu, isminden de anlaşılacağı üzere, programın yürütme hattını koşulsuz olarak belirli bir etikete veya satır numarasına yönlendiren bir sıçrama (jump) talimatıdır. Assembly dilindeki doğrudan jmp komutunun veya düşük seviyeli programlama dillerindeki koşulsuz dallanmaların yüksek seviyeli betik dillerindeki karşılığıdır. Modern yazılım dünyasında, Edsger Dijkstra’nın ünlü makalesinde belirttiği gibi, programın okunabilirliğini öldüren ve kontrolü zorlaştıran “spaghetti kod” yapısına yol açtığı için GOTO kullanımı büyük oranda dışlanmış ve bir tabu haline gelmiştir. Ancak GTA San Andreas’ın yazıldığı dönemdeki oyun içi betik motoru, bu ilkel kontrol akış mekanizmasına derinden bağlıydı. Sanny Builder gibi topluluk yapımı araçlarla decompile edildiğinde görüleceği üzere, oyunun tüm mantıksal döngüleri, koşullu ve koşulsuz GOTO komutlarının (004D gibi opcodelar) ardışık olarak kullanılmasıyla sağlanır.

GOTO 0 işlemi tetiklendiğinde, betik yorumlayıcısı o anda hangi iş parçacığında veya görevde olursa olsun, yürütme işaretçisini (Instruction Pointer) main.scm dosyasının en başına, yani sıfırıncı byte adresine (offset 0) geri döndürür. Bu sıfırıncı adres, oyunun ilk kez başlatıldığı, tüm küresel sistemlerin kurulduğu, harita sınırlarının çizildiği ve başlangıç değişkenlerinin tanımlandığı kök dizindir. Ancak bu işlemin hız koşularında bir paradigma değişimine yol açmasının asıl nedeni, bu sıçramanın temiz bir “yeniden başlatma” (reboot) olmamasıdır. Bilgisayar mimarisinde bir sistemi yeniden başlatmak, belleğin (RAM) tamamen temizlenmesini, tüm değişkenlerin ilk varsayılan değerlerine döndürülmesini ve donanımın sıfır durumuna getirilmesini kapsar. GOTO 0 ise sadece ve sadece yürütme işaretçisinin konumunu sıfırlar; o ana kadar belleğe yazılmış olan dinamik verileri, değişkenleri ve durum bayraklarını asla sıfırlamaz.

Bu durum, bilgisayar bilimlerinde “durum asenkronizasyonu” (state desynchronization) olarak adlandırılan ve sistem güvenliğini tehdit eden en tehlikeli boşluklardan birini yaratır. Bellek üzerinde, oyunun o ana kadar oynanmış olan ilerlemesine dair tüm veriler (örneğin tamamlanan görevlerin sayısı, oyuncunun sahip olduğu para, açılan harita bölgeleri ve çete nüfuzları) aynen korunur. Ancak oyun motoru, yürütme işaretçisi sıfıra döndüğü için, mantıksal olarak oyunun henüz en başında, CJ’in havalimanından yeni çıktığı ilk saniyelerde olduğuna inanır. Karşımızda duran yapı artık mantıksal olarak bölünmüş, bir nevi “şizofrenik” bir oyun motorudur. Motorun bir yarısı oyunun başında olduğuna yemin ederken, belleğin tutulduğu diğer yarısı oyuncunun çoktan ilerlemiş bir oyun durumuna (state) ait niteliklere sahip olduğunu iddia eder.

Bu iki zıt gerçekliğin aynı anda bellekte var olması, oyunun güvenlik duvarlarında ve mantıksal kontrollerinde telafi edilemez gedikler açar. Normal şartlarda, oyun motorunun belirli görevleri veya dünya olaylarını tetiklemeden önce yaptığı mantıksal kontroller (sanity checks) bulunur. Örneğin, “eğer oyuncu haritanın ikinci şehrine geçmediyse, oradaki evleri satın alamasın” gibi bir kural, bellekteki bir değişkenin durumuna göre denetlenir. Ancak GOTO 0 sonrasında, bu kontrolleri yapan kod blokları yeniden çalıştırıldığında, bellekten okudukları veriler zaten “doğru” (true) olarak döner. Çünkü oyuncu daha önce o bölgeleri zaten açmıştır ve bellek sıfırlanmadığı için bu bilgi RAM adresi üzerinde kalıcıdır. Böylelikle oyun motoru, kendisinin henüz havalimanı sahnesinde olduğunu sanırken, oyuncunun ikinci veya üçüncü şehirdeki mülkleri satın almasına, oradaki etkileşimli alanları tetiklemesine hiçbir engel çıkaramaz.

Dahası, bu asenkron durum, bellek adreslerinin yanlış hizalanması (memory misalignment) ve değişkenlerin birbirinin üzerine yazılması süreçlerini doğrudan tetikler. İlk değer atama (initialization) kodları sıfırıncı adresten itibaren yeniden çalışmaya başladığında, bellekte halihazırda var olan değişken dizilerinin (global variable arrays) üzerine yeni veriler yazmaya çalışır. Ancak eski veriler bellekten temizlenmediği için, yeni atanan değerler eski değerlerle matematiksel olarak çakışır. Bu çakışma, oyunun ilerleyen aşamalarında göreceğimiz gibi, oyuncuya negatif bakiye verilmesi veya oyunun dünyasında aynı anda iki farklı fiziksel CJ nesnesinin dolaşması gibi fiziksel ve mantıksal anomalilere zemin hazırlar.

Yazılım mimarisi açısından bakıldığında, GOTO 0 komutuyla açılan bu delik, hız koşucularına oyunun tüm mantıksal akışını baştan aşağı manipüle etme şansı tanır. Sistem, kendi deterministik kuralları çerçevesinde bu kaosu çözmeye çalıştıkça daha büyük mantıksal hatalar yapar. Bir sonraki bölümde, bu mantıksal bölünmenin fiziksel dünyaya nasıl yansıdığını, bellekte hayatta kalan eski verilerin oyun dünyasında nasıl somut birer “ikiz karaktere” dönüştüğünü ve bu ikiz karakterin bellek üzerindeki yıkıcı etkilerini ele alacağız.


Bölüm 4: Schrödinger’in CJ’i – Bellekte Aynı Anda Var Olan İki Gerçeklik (Tulpa CJ)

Bir önceki bölümde ayrıntılarıyla incelenen “GOTO 0” komutunun yarattığı durum asenkronizasyonu ve bellekteki eski verilerin korunması durumu, kaçınılmaz olarak oyunun nesne tabanlı dünyasında fiziksel bir bölünmeye yol açar. Bilgisayar grafiklerinde ve oyun motorlarında gördüğümüz her karakter, araç veya etkileşimli nesne, arka planda Nesne Yönelimli Programlama (OOP) ilkelerine göre tasarlanmış birer sınıftır (class). GTA San Andreas’ın motor mimarisinde, oyuncunun kontrol ettiği Carl Johnson karakteri de basit bir görsel modelden çok daha fazlasıdır; o, bellekte CPlayerPed veya türevi bir sınıfın somutlaşmış bir örneğidir (instance). Bu nesne; koordinat vektörleri, can değeri, giyilen kıyafetlerin kimlikleri, sahip olunan silahların listesi ve animasyon durumları gibi yüzlerce farklı değişkeni kendi bellek bloğu içinde barındırır. Normal şartlar altında, bu sınıftan bellekte aynı anda sadece bir adet bulunabilir ve bu nesneye işaret eden küresel bir oyuncu işaretçisi (global player pointer) mevcuttur.

Ancak, GOTO 0 komutu çalışıp ana senaryo betiği sıfırdan itibaren yeniden yürütülmeye başladığında, oyunun ilk değer atama (initialization) kodları da kaçınılmaz olarak sıfırdan çalışır. Bu kodların en temel görevlerinden biri, oyun dünyasına oyuncuyu yerleştirmektir. Sanal makine, başlangıç kodlarını okurken yeniden bir oyuncu nesnesi oluşturma talimatını yürütür. C++ dilinin bellek yönetim kurallarına göre, yeni bir nesne oluşturulmadan önce bellekte halihazırda var olan eski nesnenin yıkıcı metodu (destructor) çağrılmalı ve o nesneye ayrılan bellek alanı işletim sistemine geri iade edilmelidir. Fakat oyun motoru, ortada doğal bir yeniden başlatma süreci olmadığını bilmediği için bu temizlik aşamasını tamamen atlar. Eski oyuncu nesnesini bellekten silmeden, heap (yığın) bellek alanında tamamen yeni bir CPlayerPed nesnesi daha oluşturur (instantiation).

Bu durum, yazılım literatüründe en sık karşılaşılan hatalardan biri olan ve sistem performansını sömüren “bellek sızıntısı” (memory leak) ve “sahipsiz nesne” (orphaned object) krizine yol açar. Bellekte artık yan yana duran iki farklı Carl Johnson nesnesi vardır. Eski nesnenin bellek adresi, sistemin oyuncuyu takip ettiği birincil kayıt adreslerinden (registries) silinmiş olsa da, nesnenin kendisi fiziksel olarak RAM üzerinde varlığını sürdürmeye devam eder. Oyun motorunun her karede dünyadaki tüm varlıkları güncellediği ana döngü (entity update loop), bellekteki tüm ped (pedestrian) nesnelerini sırayla tarar. Bu tarama esnasında, sahipsiz kalmış olan eski CJ nesnesi de aktif bir varlık olarak algılanır. Görsel olarak harita üzerinde donmuş, hareket edemeyen, ancak fiziksel olarak dünya geometrisiyle etkileşime girebilen bu hayalet karakter, topluluk tarafından “Tulpa CJ” olarak adlandırılmıştır.

Tulpa CJ’in varlığı, nesne yönelimli programlama mimarisinin en temel prensiplerinden biri olan “tek bir doğruluk kaynağı” (single source of truth) ilkesinin tamamen çökmesine neden olur. Oyuncunun klavye veya kontrolcü girdileri, işletim sistemi tarafından doğrudan en son oluşturulan, yani aktif olan yeni CJ nesnesine yönlendirilir. Oyuncu bu yeni karakteri hareket ettirebilir, araba sürebilir ve dünyayla etkileşime geçebilir. Ancak, oyunun bazı alt sistemleri (örneğin istatistik yöneticisi, can barı göstergesi veya ödül hesaplama algoritmaları) bellek adreslerini tararken hala eski CJ nesnesinin işaretçisini (pointer) referans almaya devam eder. Çünkü eski nesne, bellekteki varlık listesinde fiziksel olarak daha erken bir adreste yer almaktadır ve bazı seçici algoritmalar karşılarına çıkan ilk geçerli oyuncu nesnesine kilitlenmektedir.

Bu çift yönlü bellek erişiminin yarattığı en büyük ve en kritik hata, hız koşusunun ilerleyen aşamalarında hayati bir rol oynayacak olan “Stunt Jump” (Dublör Atlayışı) mekanizmasında kendisini gösterir. Oyun motoru, oyuncunun bir motosiklet veya araba ile havada süzüldüğü dublör atlayışlarını ödüllendirmek için özel bir matematiksel formül kullanır. Bu formül, atlayışın başladığı andaki kalkış koordinat vektörü ($V_{kalkis}$) ile atlayışın bittiği andaki iniş koordinat vektörü ($V_{inis}$) arasındaki mesafeyi ve yükseklik farkını hesaplar. Normal şartlarda bu iki vektör arasındaki fark pozitiftir ve oyuncuya havada kaldığı süreyle doğru orantılı olarak bir para ödülü verilir.

Ancak bellekte iki farklı CJ nesnesi varken bir dublör atlayışı tetiklendiğinde, matematiksel hesaplama algoritması tam bir çıkmaza girer. Aktif olan yeni CJ nesnesi motosikletle havada uçarken, kalkış koordinatı bu aktif karakter üzerinden okunur. Fakat atlayışın tamamlandığı iniş anında, ödül hesaplama motoru oyuncu koordinatlarını okumak için bellekteki ilk nesneye, yani haritanın tamamen başka bir yerinde donup kalmış olan eski Tulpa CJ’e gider. Algoritma, iniş koordinatı olarak Tulpa CJ’in durağan pozisyonunu referans aldığında, kalkış koordinatı ile iniş koordinatı arasındaki matematiksel delta (fark) beklenmeyen bir şekilde negatif bir değer üretir. Karakterin havada ileriye doğru gitmesi gerekirken, formül onun kilometrelerce geriye, oyunun başlangıç noktasına doğru “ışınlandığını” hesaplar.

Yazılımcıların bu formülün sonucunu kontrol etmek için herhangi bir sınır veya mutlak değer (absolute value) filtresi koymamış olması, bu matematiksel deltanın doğrudan negatif bir sayıya dönüşmesine yol açar. Bu negatif kayan noktalı sayı (float), oyuncunun cüzdanındaki para miktarını tutan tamsayı (integer) değişkenine aktarıldığında, oyuncunun bakiyesi bir anda sıfırın altına iner. Oyuncu, yaptığı başarılı bir atlayış sonrasında ödül almak yerine, oyun motorunun bu iki farklı gerçekliği birbiriyle karıştırması yüzünden devasa bir borç batağına sürüklenir.

Yazılım dünyasındaki bu OOP çöküşü, hız koşucuları için bir felaket değil, tam aksine planın en önemli parçasıdır. Çünkü elde edilen bu devasa negatif bakiye, sonraki aşamalarda kumarhanedeki bellek adreslerini manipüle etmek için kullanılacak olan en güçlü yazma ilkelini (write primitive) oluşturacaktır. Bir sonraki bölümde, bu negatif bakiyenin kumarhanedeki sayısal taşma (integer overflow) zafiyetiyle nasıl birleştiğini ve bu sayede oyunun RAM adreslerine doğrudan nasıl kod yazıldığını detaylarıyla inceleyeceğiz.


Bölüm 5: Sayısal Taşma (Integer Overflow) ve Casino Ruleti

Motosikletle havada süzülürken iki farklı Carl Johnson nesnesinin koordinatlarının çakışması ve bu çakışmanın matematiksel deltasının negatif bir float değere dönüşmesi, oyuncuyu geleneksel oyun sınırlarının tamamen dışına çıkarır. Stunt Jump hatası sonrasında bakiye, oyunun normal kod akışında asla göremeyeceği eksi değerli bir borç durumuna evrilir. Bu negatif bakiye, Las Venturas kumarhanelerindeki oyun masalarını sıradan birer eğlence mini oyunu olmaktan çıkarıp, doğrudan oyun belleğine sızmak için kullanılan gelişmiş birer siber güvenlik aracına dönüştürür. Geleneksel bir oyuncu için kumarhanedeki Wheel of Fortune (Çarkıfelek) masası bir şans oyunuyken, sistemin zafiyetlerini avuçlarının içi gibi bilen bir speedrunner için bu masa, bellek adresi manipülasyonu sağlayan soğuk ve deterministik bir yazma ilkelidir (write primitive). Bu süreç, bilgisayar mimarilerindeki en temel bellek güvenliği açıklarından biri olan sayısal taşmanın (integer overflow) ve tampon bellek yönetim hatalarının kusursuz bir zincirleme reaksiyonudur.

Sürecin ilk halkası, oyun motorunun kumarhanede oyuncunun ne kadar borçlanabileceğini denetleyen koruma kalkanının sabote edilmesiyle başlar. Geliştiriciler, oyuncunun sıfır kumar yeteneğiyle (gambling skill) masalara oturduğunda en fazla yüz dolar borçlanabilmesini hedeflemişlerdir. Yani oyuncunun cüzdanındaki para miktarı en fazla eksi yüz dolara (-$100) inebilir. Kod seviyesinde bu korumayı sağlamak için yazılan denetim mekanizması, mantıksal bir karşılaştırma hatası (CWE-482: Incorrect Comparison) barındırmaktadır. Yazılımcılar, oyuncunun bakiyesini kontrol ederken “bakiye eksi yüzden küçük veya eşitse borçlanmayı durdur” (Balance <= -100) kuralını yazmak yerine, “bakiye tam olarak eksi yüze eşitse borçlanmayı durdur” (Balance == -100) mantıksal eşitlik sorgusunu yerleştirmişlerdir. Bu tarz tek bir byte’lık karşılaştırma hataları, siber güvenlik literatüründe “off-by-one” olarak adlandırılan ve sistemleri tamamen savunmasız bırakan klasik birer mantık hatasıdır.

Motosikletle gerçekleştirilen dublör atlayışındaki koordinat çakışması, bakiyeyi milisaniyeler içinde eksi yüzden çok daha derin bir noktaya (örneğin eksi dört yüz kırk iki dolara) fırlatmıştır. Oyuncu kumarhane kapısından içeri adımını attığında, oyun motorunun koruyucu denetim kodu çalışır ve oyuncunun bakiyesinin tam olarak eksi yüz dolara eşit olup olmadığını kontrol eder. Bakiye eksi dört yüz kırk iki dolar olduğu için, bu mantıksal eşitlik asla tetiklenmez. Eşitlik sorgusu yanından sessizce geçilen bir tuzak gibi arkada kalır ve oyun motoru borçlanma sınırının aşılmadığına hükmederek tüm masaları oyuncunun erişimine açar. Koruyucu kalkanın bu şekilde devre dışı kalması, sistemin bellek sınırlarını zorlayacak olan asıl sayısal manipülasyonun önünü açar.

Bu aşamada devreye bilgisayar bilimlerinin en temel sayı temsil kuralları girer. Modern bilgisayar mimarilerinde tam sayılar (integers), sınırlı bir bellek alanında (genellikle 32-bit) ve ikiye tümleyen (two’s complement) adı verilen ikili (binary) sistemde saklanır. İşaretli bir 32-bit tam sayının (signed 32-bit integer) alabileceği en büyük pozitif değer iki milyar yüz kırk yedi milyon sekiz yüz kırk üç bin altı yüz kırk yedidir (0x7FFFFFFF). Bu sınıra ulaşıldığında, sayının üzerine sadece tek bir sayı eklenmesi bile bilgisayarın en anlamlı bitini (most significant bit) değiştirir ve sayı bir anda en büyük negatif değere, yani eksi iki milyar yüz kırk yedi milyon sekiz yüz kırk üç bin sekiz yüz kırk sekize (-2,147,483,648) yuvarlanır. Bu fenomene “işaretli tam sayı taşması” (signed integer overflow) adı verilir.

Borçlanma sınırı tamamen ortadan kalkan oyuncu, kumarhanedeki limitsiz bahis yapma imkanını kullanarak bu sayısal taşmayı bir para ve kod üretme fırınına (money forge) dönüştürür. Cüzdandaki bakiye eksi yönde katlanarak artmaya devam ederken, 32-bitlik sınırın en uç noktasına kadar zorlanır. Bakiye negatif sınırın en sonuna ulaştığında ve borçlanma eylemi devam ettiğinde, sayısal taşma mekanizması tersine çalışır. Değer bir anda eksi sınırından sıçrayarak en büyük pozitif değere geçer. Oyuncu, bu matematiksel döngüyü (wrap-around) milimetrik olarak kontrol ederek cüzdanındaki para miktarını istediği herhangi bir 32-bit hexadecimal değere sabitleyebilir. Bu işlem, sadece oyunda sınırsız para elde etmek anlamına gelmez; cüzdandaki para değişkeninin tutulduğu bellek adresi, aslında oyun motorunun okuyacağı bir “veri enjeksiyon” alanıdır. Cüzdana yazılan her bir kuruş, aslında işlemcinin veya sanal makinenin yorumlayacağı birer operasyon kodunun (opcode) hexadecimal byte karşılığıdır.

İstediği hexadecimal değeri cüzdanında oluşturmayı başaran speedrunner, bu veriyi kalıcı olarak oyunun betik belleğine (SCM memory) yazmak için Wheel of Fortune masasına yönelir. Çarkıfelek oyununda, oyuncunun üzerine bahis oynayabileceği altı farklı bahis pozisyonu bulunur. Oyun motorunun bu mini oyunu yönetebilmesi için, her bir pozisyona ne kadar bahis yatırıldığını hafızasında tutması gerekir. Yazılımcılar, bu altı farklı bahis miktarını saklamak için main.scm betik belleği bloğu içinde ardışık altı adet 4-byte’lık (32-bit) küresel değişken tanımlamışlardır. Bu değişkenler, oyun dünyasındaki sıradan nesnelerin koordinatları veya can barları gibi korumasız bellek alanlarıyla yan yana durur.

Oyuncu, cüzdanındaki o devasa, özenle hesaplanmış taşma değerlerini bu altı bahis pozisyonuna sırayla yerleştirir. Bu eylemle birlikte, bellek üzerinde bulunan altı adet global değişken, doğrudan oyuncunun belirlediği hexadecimal kodlarla doldurulur. Ancak, bu işlemin kalıcı bir “yazma ilkeli” (write primitive) haline gelebilmesi için bir zafiyetin daha tetiklenmesi gerekir. Normal şartlarda, oyuncu bahis yaptıktan sonra çarkı çevirmeden masadan kalkarsa, yatırılan bahis miktarlarının temizlenmesi ve bu değişkenlerin sıfıra döndürülmesi gerekir. Fakat oyun motorunda “hassas verilerin bellekten temizlenmemesi” (CWE-226: Sensitive Data Not Cleared) zafiyeti mevcuttur. Oyuncu masadan kalktığında, oyun motoru bu değişkenleri sıfırlamaz; eski değerleri bellek hücrelerinde aynen bırakır. Çarkıfelek mini oyunu sona erdiği için de artık hiçbir sistem gelip bu bellek hücrelerinin üzerine yeni veri yazmaz.

Böylelikle, oyuncunun tamamen kendi kontrolünde olan ve dilediği makine kodunu/betik kodunu temsil eden altı adet 32-bitlik kelime (word), oyunun aktif RAM adreslerinde, yeri kesin olarak bilinen statik bir bellek bloğuna sarsılmaz bir şekilde kazınmış olur. Bu veri bloğu, sıradan bir oyun değişkeni olmaktan çıkıp, sanal makinenin birazdan yürüteceği yeni talimatlar silsilesinin (payload) ta kendisi haline gelir. Bu zincirleme reaksiyon, bir oyun açığının çok ötesinde, modern siber güvenlik dünyasında hala her gün milyonlarca yazılımda karşılaşılan mantıksal karşılaştırma hataları, sayısal taşmalar ve temizlenmeyen bellek tamponları gibi klasik açıkların ne denli yıkıcı sonuçlar doğurabileceğinin kusursuz bir kanıtıdır. Bir sonraki bölümde, bellek adresine kazınan bu gizli talimatların, oyunun yürütme işaretçisi (Instruction Pointer) tarafından nasıl keşfedileceğini ve program akışının bu adrese nasıl zorla yönlendirileceğini detaylarıyla ele alacağız.


Bölüm 6: İtfaiye Görevinin 60 Kez İptal Edilmesi – Yığın (Stack) Düzenlemesi

Beşinci bölümde detaylandırılan, Çarkıfelek masasında cüzdanın sayısal taşma sınırları zorlanarak oluşturulan 32-bitlik hexadecimal verilerin betik belleğine kazınması süreci, hız koşusunun temel mantıksal sütunlarından birini dikmiştir. Ancak, bu verilerin sadece bellek adreslerinde sessizce beklemesi tek başına yeterli değildir. Bilgisayar mimarisinde, belleğe yazılan bir talimatın (payload) anlam kazanabilmesi için, o talimatı okuyacak olan yürütme işaretçisinin (Instruction Pointer) veya bir nesne işaretçisinin, verinin yazıldığı adresle milimetrik bir şekilde hizalanması gerekir. Eğer bu hizalama işleminde tek bir byte’lık bile kayma olursa, sanal makine veya işlemci veriyi yanlış bir ofsetten okumaya başlar; bu da komutların bozulmasına ve sistemin anında çökmesine (Access Violation) yol açar. İşte dışarıdan bakan sıradan bir izleyiciye oyun dünyasının en saçma, en anlamsız ve komik eylemi gibi görünen “İtfaiye görevini tam altmış kez başlatıp anında iptal etme” süreci, aslında siber güvenlik literatüründeki “Stack Alignment” (Yığın Hizalama) ve “Heap Spraying” (Yığın Serpiştirme) tekniklerinin oyun içi mekaniklerle gerçekleştirilen kusursuz bir uygulamasıdır.

Bu hizalama operasyonunun teknik arka planını kavramak için, oyun içi alt görevlerin (sub-missions) ve özellikle PC sürümündeki “tekrar oynatma” (replay) sisteminin bellek üzerindeki yönetim biçimini incelemek gerekir. İtfaiye, ambulans veya taksi gibi alt görevler başlatıldığında, main.scm betik motoru bu görevleri yönetebilmek için dinamik olarak yeni bir betik iş parçacığı (script thread) oluşturur. Her bir iş parçacığı, kendi yerel değişkenlerini (local variables), yığın işaretçilerini ve durum verilerini barındıran birer Thread Control Block (Thread Kontrol Bloğu) alanı işgal eder. Normal şartlarda, bir görev sonlandırıldığında veya iptal edildiğinde bu kontrol bloğunun bellekten tamamen silinmesi ve belleğin temizlenmesi beklenir. Ancak, daha önceki bölümlerde de değinilen temizlenmeyen bellek zafiyetleri ve kooperatif çoklu görev modelinin getirdiği sınırlar nedeniyle, alt görevin her başlatılıp iptal edilmesi bellek yığınında (stack) veya dinamik yığın alanında (heap) temizlenmeyen küçük veri izleri (memory footprints) bırakır.

Bu veri izleri, bellekteki serbest alanların yerleşimini ve bir sonraki dinamik nesnenin hangi adrese yazılacağını belirleyen işaretçi konumlarını doğrusal bir şekilde kaydırır. PC sürümünde yer alan ve son otuz saniyelik oynanışı hafızada tutan tekrar oynatma (replay) tamponu devreye girdiğinde, bellek manipülasyonu çok daha karmaşık bir boyuta ulaşır. Tekrar oynatma sistemi, araçların ve yayaların (actors) konumlarını, hız vektörlerini ve kimliklerini dairesel bir bellek tamponunda (circular buffer) sürekli olarak günceller. Oyuncu bir tekrar oynatmayı başlattığında veya bu tamponu tetiklediğinde, oyun motoru aktif dünyadaki nesne listesini askıya alır ve tekrar oynatma tamponundaki verileri geçici olarak heap bellek alanına yükler.

İşte bu esnada, Big Smoke’un başlangıç görevini yöneten ana senaryo betiğinin, bellekte oluşturacağımız o özel “çakışma” anında Big Smoke’un NPC işaretçisini (pointer) doğru adreste bulması gerekir. Big Smoke karakteri, oyun dünyasında bir CPed sınıfı örneği olarak oluşturulur ve bellekte kendisine tahsis edilen dinamik bir adreste yaşar. Speedrunner’lar, Çarkıfelek masasında kazıdıkları o özel hexadecimal verilerin, Big Smoke’un karakter parametreleri ve görev başlatma değişkenleri üzerine tam olarak oturmasını (overlay) hedeflemektedirler. Eğer bu hizalama yapılmazsa, oyun motoru Big Smoke’un görevini başlattığında bizim hazırladığımız sahte talimatları değil, bellekteki rastgele verileri okuyacak ve oyun çökecektir.

Tersine mühendislik (reverse engineering) uzmanları, IDA Pro ve Ghidra gibi hata ayıklayıcılar (debuggers) vasıtasıyla oyunun RAM haritasını kare kare inceleyerek bu iki dinamik adres bloğu arasındaki mesafeyi milimetrik olarak ölçmüşlerdir. Bu ölçümler sonucunda, İtfaiye alt görevinin her başlatılıp iptal edilmesinin, bellek yığınındaki hedef işaretçi konumunu tam olarak belirli bir byte uzunluğu kadar (örneğin 32 byte) ileriye kaydırdığı keşfedilmiştir. İki dinamik adres arasındaki boşluğu kapatmak ve Çarkıfelek’te yazılan veriyi Big Smoke’un tetikleyici parametrelerinin tam üzerine denk getirmek için gereken kayma miktarı hesaplandığında, ortaya çıkan matematiksel sonuç tam olarak altmış (60) tekrardır.

Oyuncu, itfaiye aracına binip alt görevi her başlatıp iptal ettiğinde, bellek yığınındaki işaretçiyi adeta bir dişliyi tek tek çevirir gibi ileriye doğru iter. Bu işlem tam altmış kez hatasız bir şekilde gerçekleştirildiğinde, birikimli byte kayması (cumulative byte offset) hedeflenen mesafeye ulaşır. Bu adımın sonunda, oyun içi nesne işaretçileri bellek haritasında öyle bir konuma yerleşir ki, bir sonraki aşamada Big Smoke görevi tetiklendiğinde, sanal makine Big Smoke’un orijinal kod parametrelerini okumak yerine, tam olarak bizim Çarkıfelek masasında kazıdığımız ve yığın hizalamasıyla tam hedef noktaya kaydırdığımız o sahte komut zincirini okumak zorunda kalır.

Bu süreç, dışarıdan bakıldığında anlamsız bir oyun içi ritüel gibi görünse de, yazılım güvenliği açısından bakıldığında, modern işletim sistemlerindeki ASLR (Bellek Adresi Rastgeleleştirme) gibi koruma mekanizmaları geliştirilmeden önce, bir sistemdeki statik bellek ofsetlerinin ne denli kararlı ve öngörülebilir olduğunu gösteren muazzam bir mühendislik çalışmasıdır. Deneme yanılmanın tamamen ötesinde, tamamen deterministik matematiksel hesaplamalara dayanan bu cerrahi müdahale, hız koşularında bellek manipülasyonunun ne denli derinleşebileceğinin en somut göstergelerinden biridir. Bir sonraki bölümde, yığın hizalaması tamamlanan ve adeta kurulmuş bir saat gibi bekleyen bu bellek yapısının, oyun dünyasında tersine gitmek ve kıyafet değiştirmek gibi absürt eylemlerle nasıl tetikleneceğini inceleyeceğiz.


Bölüm 7: Mimari Uçurum – Win32, UWP ve Bellek Adresi Rastgeleleştirme (ASLR)

Beşinci bölümde ele alınan kumarhanedeki sayısal taşma (integer overflow) zafiyeti ile altıncı bölümdeki altmış turluk itfaiye görevi yığın hizalaması (stack alignment) süreci, hız koşusu topluluğunun orijinal PC sürümünde (Win32) gerçekleştirdiği o muazzam zafiyet zincirinin temel taşlarıdır. Ancak bu noktada, hız koşularını ve yazılım mimarilerini yakından takip eden teknik izleyicilerin zihninde haklı bir soru işareti belirmektedir. Birçok oyun severin yorumlarda da hararetle tartıştığı üzere, bu tarz tüm oyunu kısa devre yaptıran sıçrama hileleri (AJS ve ASE) neden ilk olarak iki bin on dokuz yılında Windows Store sürümünde bulunmuş ve orijinal iki bin dört çıkışlı Win32 PC sürümünde (versiyon bir sıfır) hayata geçirilmesi yıllar sürmüştür? Bu sorunun cevabı, sıradan bir oyun mekaniği farkından çok uzaktadır; doğrudan işletim sistemlerinin bellek sanallaştırma teknolojilerinde, modern derleyici optimizasyonlarında ve Microsoft’un Universal Windows Platform (UWP) ile klasik Win32 mimarileri arasındaki derin uçurumda yatmaktadır.

Bilgisayar güvenliği dünyasında, bir yazılımdaki bellek açıklarının sömürülmesini engellemek için geliştirilen en temel savunma mekanizmalarından biri ASLR, yani Address Space Layout Randomization (Bellek Adresi Yerleşim Rastgeleleştirmesi) protokolüdür. ASLR, bir program her başlatıldığında, onun yürütülebilir kodlarının, yığın (stack) ve dinamik yığın (heap) alanlarının RAM üzerindeki adreslerini tamamen rastgele koordinatlara dağıtır. Bu sayede, bir saldırgan veya bir hız koşucusu sistemdeki bir zafiyeti tetiklese bile, hedef kodun veya değişkenin o an RAM’de hangi adreste olduğunu tahmin edemez. Modern işletim sistemlerinde ve özellikle Windows Store üzerinden dağıtılan modern UWP (Universal Windows Platform) uygulamalarında ASLR katı bir zorunluluktur.

Ancak iki bin dört yılında piyasaya sürülen orijinal Win32 (Windows 32-bit API) tabanlı GTA San Andreas sürümü piyasaya çıktığında, Windows XP dünyasında ASLR henüz standart bir güvenlik protokolü değildi. Microsoft, bu korumayı ancak Windows Vista ve Windows 7 ile birlikte işletim sistemi seviyesinde varsayılan hale getirebilmişti. Dolayısıyla, orijinal Win32 San Andreas sürümü derlenirken ASLR koruması olmadan paketlenmiştir. Bu durum, orijinal oyunun RAM üzerindeki bellek haritasının tamamen statik ve deterministik olduğu anlamına gelir. Oyun bilgisayarınızda her açıldığında, oyuncunun para miktarını tutan değişken de, görev durum bayrakları da, main.scm betik yorumlayıcısının iş parçacıkları da her zaman milimi milimine aynı RAM adreslerinde (ofsetlerde) oluşturulur. Bu öngörülebilirlik, teorik olarak bir bellek sömürüsünün (exploit) Win32 üzerinde çok daha kolay yazılması gerektiği izlenimini uyandırır.

İronik olan şudur ki, pratik gerçeklik bu teorik beklentinin tamamen tersi yönünde gelişmiştir. Bunun nedeni, Windows Store sürümünün (ve daha sonra çıkan bazı remaster versiyonların) mimari açıdan tam bir “spaghetti kod” çorbasına dönüşmüş olmasıdır. Windows Store sürümü, doğrudan orijinal PC kod tabanından değil, mobil cihazlar (iOS/Android) için War Drum Studios tarafından taşınan ve üzerine emülasyon katmanları giydirilmiş olan mobil porttan devşirilmiştir. Geliştiriciler, orijinal C++ kod tabanını modern platformlara uyarlarken, bellek yönetimini optimize etmek yerine, sistemin üzerine loose (gevşek) ve hantal soyutlama katmanları (wrappers) eklemişlerdir. Bu hantal portlama süreci, oyunun orijinal bellek yapılarındaki sıkı paketlemeyi bozmuş, tampon bellek sınırlarını esnetmiş ve sanal makine ile fiziksel bellek arasındaki koordinasyonu zayıflatmıştır.

Windows Store (UWP) sürümündeki bu gevşek bellek mimarisi, normalde ASLR koruması altında olmasına rağmen, oyun içi betik motorunun kendi iç bellek yönetimini (internal memory manager) tamamen savunmasız bırakmıştır. UWP konteyneri içinde çalışan sanal makinenin bellek adresleri, dışarıdan işletim sistemi tarafından rastgeleleştirilse de, konteynerin kendi içindeki main.scm ofsetleri son derece kararsız ve kırılgan hale gelmiştir. Bu durum, Powdinet’in iki bin on dokuz yılında keşfettiği, sadece bir polis motosikletine binip vigilante (asayiş) görevini milisaniyelik bir zaman aralığında başlatıp iptal ederek tüm oyunu saniyeler içinde bitiren o ilk ilkel AJS açığının kapısını aralamıştır. UWP sürümündeki bu gevşek paketleme, tek bir tampon bellek taşması (buffer overflow) ile yürütme işaretçisinin doğrudan oyunun sonuna fırlatılmasına izin verecek kadar esnektir.

Buna karşın, orijinal iki bin dört çıkışlı Win32 v1.0 sürümü, kendi döneminin donanım kısıtlamaları nedeniyle son derece agresif bir şekilde optimize edilmiş, derlenmiş ve sıkı sıkıya paketlenmiş (tightly packed) saf bir C++ ikili (binary) dosyasıdır. Bu sürümde hiçbir modern katman, emülatör veya esnek wrapper bulunmaz. Her bir bellek yapısı, her bir yapı taşı (struct) ve betik değişkeni bellek üzerinde milimetrik bir düzen içinde, adeta birbirine geçmiş dişliler gibi son derece dar bir alanda yaşar. Bu sıkı paketleme ve optimize edilmiş bellek yönetimi, Win32 sürümünü basit sızma girişimlerine karşı son derece dirençli kılmaktadır. Win32 üzerinde, UWP sürümündeki gibi basit bir tek adımlık motosiklet görevi iptaliyle tampon bellek sınırlarını aşmak ve yürütme işaretçisini saptırmak imkansızdır; çünkü taşan veri anında yanındaki hayati bir sisteme çarparak oyunun çökmesine (crash) sebep olur.

İşte bu yüzden, hız koşucularının ve programcıların orijinal Win32 v1.0 sürümünü dize getirebilmek için, o otuz bir adımdan oluşan devasa mantıksal tiyatroyu inşa etmeleri gerekmiştir. Orijinal sürümün o aşılmaz, sıkı bellek duvarlarını aşabilmek için, basit bir tampon taşması yerine, oyun motorunun kooperatif çoklu görev (cooperative multitasking) mantığını kendi silahıyla vurmak zorunda kalmışlardır. Bu süreç; ikinci bölümde bahsedilen o iki eşzamanlı görevin (Just Business ve Big Smoke) aynı anda başlatılmasıyla oluşturulan mantıksal senkronizasyon çökertmesini tetikler.

Bu çift yönlü görev çökertmesi, oyunun tek iş parçacıklı sanal makine motorunu öyle bir çıkmaza sokar ki, motor iki görevin değişkenlerini ve yürütme işaretçilerini birbiriyle karıştırır. Kumarhanede yazdığımız o gizli hexadecimal talimatlar ve itfaiye göreviyle yaptığımız milimetrik yığın hizalaması, işte bu sıkı paketlenmiş Win32 bellek duvarında tam da bu çift görev çakışması anında bir gedik açılmasını sağlar. Orijinal sürümün aşılmaz kalesi, esnek bir wrapper hatasıyla değil, kendi iç mantık yapısının iki farklı görev hattı arasında sıkıştırılıp ezilmesiyle fethedilmiştir. Bu mimari mücadele, yazılım dünyasında optimizasyonun ve sıkı paketlemenin bir sistemi nasıl doğal yollardan koruyabileceğini, ancak mantıksal tasarım hatalarının en sıkı kaleleri bile nasıl yıkabileceğini gösteren paha biçilemez bir ders niteliğindedir. Bir sonraki bölümde, bu karmaşık zafiyet zincirinin tetiklenmesi için oyuncunun fiziksel dünyada yapması gereken o en sıra dışı eylemleri (geriye doğru araba sürmek ve kıyafet değiştirmek) ve bunların arkasındaki teknik tetikleyicileri ele alacağız.


Bölüm 8: Theseus’un Gemisi ve Speedrun Felsefesi: Oyunu Kim Oynuyor?

Yedinci bölümde ele alınan, orijinal Win32 v1.0 sürümünün o sıkı paketlenmiş bellek duvarlarını aşabilmek için kurgulanan otuz bir adımlık mantıksal tiyatro, bizi kaçınılmaz olarak yazılımın ve oyunculuğun doğasına dair derin bir felsefi sorgulamaya yönlendirir. Oyun içindeki bu karmaşık ritüelleri gerçekleştiren, yani saniyede onlarca kez duraklatma tuşuna basan, bir cutscene esnasında gardırop menüsünü açan, itfaiye görevini tam altmış kez başlatıp iptal eden ve arabayı geriye doğru sürerek yüklenmemiş bir dünyaya dalan bir speedrunner’ın eylemleri, geleneksel oyuncu topluluğunda büyük bir tartışmanın fitilini ateşler. Video yorumlarında sıkça karşılaşılan “Bu yapılan şey hile yapmaktır, oyunu gerçekten bitirmek değildir” ya da “Bu şekilde oynamak oyunu oynamak değil, sadece bir programı bozmaktır” şeklindeki tepkiler, dijital çağın en eski ontolojik ve fenomenolojik sorularından birini gün yüzüne çıkarır: Bir bilgisayar oyununu oynamak ve onu “bitirmek” tam olarak ne anlama gelir?

Bu soruyu yanıtlayabilmek için lüdoloji (oyun bilimi) ve anlatıbilim (narratology) arasındaki o ezeli mücadeleye bakmak gerekir. Geleneksel ve anlatı odaklı bir oyuncu için oyun, geliştiricilerin kurguladığı hikaye evrenidir; yani Carl Johnson’ın ihanete uğraması, Los Santos’tan sürülmesi, San Fierro ve Las Venturas’ta güçlenip geri dönerek Big Smoke’tan intikam alması sürecidir. Bu perspektiften bakıldığında, oyunu “bitirmek”, bu anlatısal patikayı sadakatle takip etmek ve hikayenin sonuna ulaşmaktır. Ancak lüdolojik ve sistem teorisi odaklı bir bakış açısı için oyun, bu hikayenin çok daha ötesinde, belirli matematiksel kurallar, kısıtlamalar ve durum geçişleriyle tanımlanmış deterministik bir “durum makinesidir” (state machine). Bu makine için Carl Johnson’ın acıları, çete savaşları veya intikam duygusu tamamen anlamsızdır. Durum makinesi için var olan tek gerçeklik, bellekteki $TOTAL_MISSIONS_PASSED gibi bir değişkenin değerinin sıfırdan yüze ulaşması veya oyunun bittiğini müjdeleyen o tek bir boolean bayrağın (flag) sıfırdan bire dönmesidir.

Hız koşucuları, oyunu tam da bu lüdolojik ve sistem odaklı ekstrem uçta oynarlar. Onlar için Carl Johnson yaşayan bir karakter değil, bellekteki bir CPed sınıfı işaretçisidir; oyun dünyası ise fiziksel bir şehir değil, adreslenebilir bir bellek haritasıdır. Bir speedrunner, haritada araba sürerken aslında fiziksel bir yolu katetmez; sadece bellek üzerindeki koordinat vektörlerini günceller. Bu bağlamda, dışarıdan üçüncü parti bir yazılım (Cheat Engine, RAM enjektörleri, trainerlar veya modifikasyonlar) kullanmadan, sadece oyunun geliştiricileri tarafından disk üzerine basılmış ve bilgisayara kurulmuş olan o orijinal ikili (binary) kod bloklarını kullanarak oyun motorunu “bu oyun bitti” durumuna ikna etmek, bilgisayar bilimleri açısından tamamen meşru ve kusursuz bir sistem yönetimidir.

Bu durum, felsefe tarihinin en meşhur paradokslarından biri olan Theseus’un Gemisi paradoksuyla muazzam bir paralellik gösterir. Paradoksa göre, antik Atina kahramanı Theseus’un gemisi limanda sergilenirken, zamanla eskiyen ve çürüyen tahtaları tek tek yenileriyle değiştirilir. Yıllar sonra geminin orijinal tek bir tahtası bile kalmadığında, karşımızda duran gemi hala “Theseus’un Gemisi” midir? Eğer değiştirilen tüm eski tahtalar bir depoda saklanıp onlardan yeni bir gemi inşa edilirse, gerçek Theseus’un Gemisi hangisidir?

GTA San Andreas’ın Win32 v1.0 sürümünde gerçekleştirilen ASE (Arbitrary Script Execution) süreci, bu felsefi paradoksun dijital bellek üzerindeki canlı bir simülasyonudur. Koşucu, oyunu başlatırken diskte yer alan orijinal main.scm ve yürütülebilir kod bloklarını (geminin orijinal tahtalarını) kullanır. Ancak oyunun normal akışı sırasında gerçekleştirilen o otuz bir adımlık mantıksal tiyatroyla (arabayı geri sürmek, kıyafet değiştirmek, telefonla konuşmak, kumar oynamak), oyunun mantıksal yürütme hattı tamamen yerinden sökülür ve yeniden monte edilir. Oyuncu, dışarıdan hiçbir yabancı “odun” (yani harici kod) eklemez. Sadece oyunun kendi orijinal tahtalarını, yani geliştiricilerin yazdığı ama asla bu sıra dışı kombinasyonla bir araya geleceğini öngöremediği alt programları (itfaiye görevi, kumar masası, dans mini oyunu) kullanarak, durum makinesinin akışını tamamen yeni ve bambaşka bir rotaya yönlendirir. Elde edilen o final jeneriği ve bitiş ekranı, her ne kadar geliştiricilerin kurguladığı hikaye patikasından geçmemiş olsa da, oyunun kendi orijinal kodlarının, kendi orijinal işlemci döngüleriyle, tamamen meşru girdiler (girdi aygıtı polling verileri) vasıtasıyla ürettiği matematiksel ve fiziksel bir gerçekliktir.

Bu bağlamda, “Oyunu kim oynuyor?” sorusu da yeni bir boyut kazanır. Sıradan bir oyuncu, geliştiricilerin tasarladığı o tatlı illüzyonun içinde, onların kurallarına göre oynar; yani oyunun hikayesine teslim olur. Hız koşucusu ise illüzyonun arkasındaki o soğuk, deterministik makineyle, yani bilgisayarın kendisiyle oynar. Karakteri kontrol eden oyuncu, aslında o karakteri bir “donanım probu” (hardware probe) gibi kullanarak RAM adreslerini dürtmektedir. Bu, oyunun hikayesine karşı değil, oyunun kaynak koduna, derleyici mantığına ve matematiğine karşı kazanılmış entelektüel ve sistemik bir zaferdir.

Yorumlarda bazı izleyicilerin bu tarz zafiyet zincirlerini “sıkıcı” veya “oyunun büyüsünü bozan” eylemler olarak nitelendirmesi, aslında büyünün sadece yer değiştirdiğini görmemelerinden kaynaklanır. Büyü, fiziksel reflekslerden ve el-göz koordinasyonundan, sistem mimarisini bükebilen saf akıl gücüne ve tersine mühendislik dehasına kaymıştır. Bir sonraki bölümde, bu sistemik dehanın ve bellek bükme yeteneğinin sınırlarının nerede bittiğini, AJS’den gerçek anlamda bir kod çalıştırma (ACE) boyutuna geçildiğinde bilgisayarların ve yazılımların karşı karşıya kaldığı o gerçek güvenlik risklerini teknik detaylarıyla ele alacağız.


Bölüm 9: AJS’den Tam Kapsamlı ACE’ye (Arbitrary Code Execution) Geçişin Tehkelileri

Sekizinci bölümde ele alınan, hız koşucularının oyunun kendi durum makinesini bükerek Theseus’un Gemisi gibi kendi kuralları içinde yeni bir gerçeklik inşa etme felsefesi, teorik düzeyde büyüleyici bir entelektüel zaferdir. Ancak bu sistemik bükülme süreci, sanal makinenin (SCM yorumlayıcısının) sınırlarından taşıp doğrudan ana bilgisayarın donanım katmanıyla etkileşime girmeye başladığında, eğlenceli bir hız koşusu hilesi olmaktan çıkarak ciddi bir siber güvenlik tehdidine dönüşür. Topluluk içinde büyük bir hayranlıkla karşılanan ve yorumlarda “oyunun içinde Bad Apple klibini oynatmayı başardılar” şeklinde gururla bahsedilen o efsanevi olay, aslında bir oyunun ne denli tehlikeli bir siber zafiyet (vulnerability) barındırabileceğinin en somut, en teknik kanıtıdır. Bu noktada, AJS (Arbitrary Jump in Script) ile tam kapsamlı ACE (Arbitrary Code Execution) arasındaki o ince ama keskin çizgiyi ve bu çizginin aşılmasının yarattığı güvenlik risklerini bilgisayar mimarisi düzeyinde incelemek gerekir.

İkinci bölümde detaylandırılan main.scm sanal makine yapısı, kendi içinde kapalı bir ekosistemdir. AJS tekniğinde, her ne kadar bellek üzerinde büyük manipülasyonlar yapılsa da, yorumlayıcı hala oyunun kendi orijinal işlem kodlarını (opcodes) okumaya devam eder. Yani sanal makine, kendi sınırları içinde kalır ve sadece oyunun kendi betik dünyasında izin verilen eylemleri (koordinat değiştirme, nesne spawn etme, görev tetikleme) yapabilir. Ancak ACE (İsteğe Bağlı Kod Yürütme) gerçekleştiğinde, bu sanal makine kafesi tamamen parçalanır. ACE süreci, sanal makinenin yorumlayıcısını bypass ederek doğrudan bilgisayarın fiziksel işlemcisinin (CPU) program sayacını (EIP veya RIP kayıtçısı) ele geçirmeyi hedefler. Bu durum, oyunun kendi kodlarından çıkıp, fiziksel işlemcinin doğrudan x86 Assembly (makine dili) seviyesindeki harici talimatları yürütmeye başlaması anlamına gelir.

Topluluğun oyunun içinde “Bad Apple” videosunu oynatmayı başarması, bu zafiyetin sistem üzerinde ne kadar mutlak bir kontrol sağladığının en net göstergesidir. Bu işlemde, oyunun kendi grafik çizim kütüphaneleri ve RenderWare motorunun video oynatma yetenekleri kullanılmamıştır. Bunun yerine, doğrudan işlemciye enjekte edilen Assembly kodları vasıtasıyla, ekran kartının çerçeve arabelleği (framebuffer) doğrudan manipüle edilmiş ve video kareleri fiziksel işlemcinin doğrudan çizim komutlarıyla ekrana yansıtılmıştır. Bu başarı, oyunun artık bir eğlence yazılımı olmaktan çıkıp, saldırganın dilediği her türlü kodu en yüksek yetkiyle çalıştırabileceği bir komut satırı (command prompt) arayüzüne dönüştüğünü kanıtlamıştır.

Bu dönüşümün en karanlık ve tehlikeli boyutu, modifiye edilmiş kayıt dosyaları (save files) üzerinden gerçekleştirilen saldırılarda kendisini gösterir. GTA San Andreas’ta bir kayıt dosyası (örneğin GTASAsf1.b), temel olarak serileştirilmiş (serialized) bir veri deposudur. Bu dosyanın içinde oyuncunun konumu, sahip olduğu silahlar, tamamlanan görevlerin durumları ve en önemlisi aktif olan betik iş parçacıklarının (script threads) yerel değişkenleri ve yığın durumları saklanır. Oyun motoru bir kayıt dosyasını yüklemeye çalışırken, bu dosyadan okuduğu byte bloklarını doğrudan RAM üzerindeki önceden ayrılmış tampon belleklere (buffers) kopyalar.

Eğer bu kopyalama işlemi sırasında, verilerin boyutu tampon belleğin sınırlarını aşacak şekilde tasarlanmışsa (Buffer Overflow), siber güvenlik dünyasındaki en klasik saldırı yöntemi tetiklenmiş olur. Yedinci bölümde bahsedildiği üzere, orijinal Win32 v1.0 sürümü derlenirken iki bin dört yılında DEP (Data Execution Prevention – Veri Yürütme Engellemesi) gibi modern bellek koruma protokolleri etkinleştirilmemiştir. DEP, işletim sistemi seviyesinde çalışan ve bellek üzerindeki veri saklama alanlarının (örneğin kayıt dosyasının yüklendiği tamponların) kod olarak çalıştırılmasını (execute) engelleyen bir kalkan sistemidir. San Andreas’ta bu kalkanın olmaması nedeniyle, oyun motorunun kayıt dosyasını yüklerken RAM’e kopyaladığı o hatalı ve taşan veriler, işlemci tarafından doğrudan yürütülebilir kod (shellcode) olarak algılanır.

Saldırgan, kayıt dosyasının yığın alanındaki (stack) dönüş adresini (return address) manipüle ederek, işlemcinin program sayacını kendi hazırladığı bu shellcode’un başlangıç adresine yönlendirir. Bu andan itibaren, oyun motoru tamamen felç olur ve fiziksel işlemci doğrudan kayıt dosyasından sızan Assembly komutlarını çalıştırmaya başlar. Bu durum, internet üzerindeki kontrolsüz forumlardan veya üçüncü parti sitelerden indirilen “yüzde yüz tamamlanmış kayıt dosyalarının” nasıl sessiz birer siber silaha dönüşebileceğini gösterir.

Teorik düzeyde bakıldığında, bu sızma yöntemi kullanılarak hazırlanmış zararlı bir kayıt dosyası, oyunu başlattığınız anda bilgisayarınıza arka planda sızacak bir trojan enjekte edebilir, kişisel verilerinizi çalabilir veya tüm sabit diskinizi şifreleyen bir fidye yazılımını (ransomware) tetikleyebilir. Çünkü işletim sistemi, bu zararlı kodları çalıştıran yapıyı dışarıdan gelen bir virüs olarak görmez; kodlar doğrudan oyunun kendi meşru süreci (gta_sa.exe) içinden ve kullanıcının oyuna verdiği tüm sistem yetkileriyle çalıştırılır. Bu durum, oyunların sadece sanal dünyalar sunan eğlenceli yapılar olmadığını, aynı zamanda işletim sistemi üzerinde tam yetkiyle çalışan karmaşık birer yazılım olduğunu ve her karmaşık yazılım gibi siber güvenlik kurallarına sıkı sıkıya bağlı kalması gerektiğini hatırlatır. Bir sonraki ve son bölümde, yirmi yılı aşkın süredir devam eden bu muazzam kod savaşının ve bellek bükme serüveninin topluluk üzerindeki etkilerini ve bu serüvenin yazılım dünyasına bıraktığı mirası ele alarak analizimizi sonlandıracağız.


Bölüm 10: Sonuç – 20 Yıllık Bir Kod Dağını Fethetmek ve İnsan Zekasının Sınırları

Dokuzuncu bölümde derinlemesine incelediğimiz, sanal makinenin sınırlarından taşarak doğrudan bilgisayar donanımını ve işletim sistemini hedef alan ACE (İsteğe Bağlı Kod Yürütme) zafiyetleri, yazılım dünyasındaki kontrol mekanizmalarının ne denli kırılgan olabileceğini gözler önüne sermiştir. Ancak bu teknik analizlerin, siber güvenlik protokollerinin ve bellek haritalarının ötesinde, bu karmaşık zafiyet zincirini ilmek ilmek dokuyan insan faktörünü incelemek, belki de tüm bu sürecin en büyüleyici kısmıdır. Hız koşusu topluluğunun bu inanılmaz keşifleri sonrasında, forumlarda ve video yorumlarında sıkça dile getirilen o meşhur “Bu insanlar bu dehalarıyla neden kansere çare bulmuyorlar?” serzenişi, aslında insan psikolojisinin, yaratıcılığının ve motivasyonunun doğasını tamamen yanlış anlayan, yüzeysel ama son derece yaygın bir tespittir.

İnsan zekası ve merakı, doğrusal bir toplumsal fayda çizgisi üzerinde siparişle çalıştırılabilecek mekanik bir araç değildir. Kanser araştırmaları; tıp, moleküler biyoloji, onkoloji ve biyokimya disiplinlerinin tamamen kendine has laboratuvar koşullarında, kendine has metodolojilerle yürüttüğü süreçlerdir. Diğer tarafta, yirmi yıllık kapalı devre bir oyun motorunun bellek bloklarını analiz etmek ise tamamen bilgisayar bilimleri, sistem mühendisliği, tersine mühendislik ve derleyici mimarisi uzmanlığı gerektiren apayrı bir uzmanlık alanıdır. Bu iki disiplinin pratik araçları arasında hiçbir kesişim kümesi bulunmamaktadır. Ancak bu haksız serzenişin ıskaladığı asıl gerçek, bu keşifleri yapan topluluğun bilişsel yapısıdır. Hız koşucuları ve kod kırıcılar (Powdinet, Joshimuz, Piturrete, Creezyful ve batzera gibi figürler), sadece monitör karşısında vakit öldüren sıradan oyuncular değildir. Bu insanlar; karmaşık sistem teorilerini pratik dünyada uygulayabilen, bellek dökümlerini (memory dumps) ham hexadecimal kodlar halinde okuyup analiz edebilen, C++ nesne mimarilerini ve işlemci kayıtçılarının (registers) durumlarını avuçlarının içi gibi bilen gayriresmi sistem mühendisleri ve veri analistleridir.

Orijinal Win32 v1.0 PC sürümünün otuz dakikanın hemen üzerinde bir sürede tamamlanmasını sağlayan o otuz bir adımlık devasa manipülasyon zinciri, sadece dijital bir dünya rekorundan ibaret değildir. Bu başarı, insan merakının, bitmek tükenmek bilmeyen inatçılığının ve soyut sınırları zorlama arzusunun yazılım tarihindeki en büyük anıtlarından biridir. Yirmi yılı aşkın bir süre boyunca milyonlarca insan tarafından oynanmış, kodları milyarlarca kez çalıştırılmış kapalı bir sistemi, dışarıdan tek bir harici kod veya hile aracı eklemeden, sadece kendi içindeki kırık dökük parçaları (itfaiye görevi, kumar masası, dans mini oyunu, dublör atlayışları) bir araya getirerek esir almak, kelimenin tam anlamıyla dijital bir sanat eseridir.

Bu süreç, yazılım mühendisliği dünyası için de paha biçilemez bir kod arkeolojisi mirası bırakmaktadır. Bir oyunun veya programın geliştirilmesinin üzerinden ne kadar zaman geçerse geçsin, o yazılımın asla tamamen “keşfedilmiş” veya “bitmiş” sayılamayacağını kanıtlar. Her derlenmiş ikili (binary) dosya, aslında kendi içinde çözülmeyi bekleyen dinamik ve yaşayan bir bulmaca barındırır. Hız koşucuları, bu bulmacaları çözerek sadece oyunları daha hızlı bitirmekle kalmazlar; eski yazılım mimarilerinin zayıf noktalarını gün yüzüne çıkararak, modern sistemlerin nasıl daha güvenli, daha dayanıklı ve daha kararlı inşa edilmesi gerektiğine dair siber güvenlik uzmanlarına canlı laboratuvar örnekleri sunarlar.

Sonuç olarak, GTA San Andreas’ın otuz dakikada bitirilmesini sağlayan bu muazzam ASE zafiyet zinciri, insanın dijital sistemler üzerindeki mutlak hakimiyet kurma mücadelesinin bir zaferidir. Karşımızdaki bu devasa kod dağı, sadece el-göz koordinasyonuna dayalı reflekslerle değil; bellek adreslerinin, işaretçilerin ve işlemci döngülerinin saf akıl gücüyle bükülmesi sayesinde tamamen fethedilmiştir. Bu serüven, yazılım tarihinin tozlu sayfalarında, insan zekasının deterministik makinelere karşı kazandığı en sıra dışı, en yaratıcı ve en saygıdeğer entelektüel zaferlerden biri olarak kalmaya devam edecektir.

Scroll to Top