PHP Bytecode Koruma Mekanizmasının Güvenlik Analizi

Not: Makaleyi okumaya başlamadan önce aşağıdaki terim sözlüğüne göz atınız.

Programlama Dili: yazılımcının bir algoritmayı ifade etmek amacıyla, bir bilgisayara ne yapmasını istediğini anlatmasının tektipleştirilmiş yoludur.

Native: Bağımsız, Yerel gibi çeşitli anlamları vardır. Ancak yazı içerisinde biz bağımsız anlamında kullanacağız.

Kaynak Kod: Herhangi bir yazılımın işlenip makina diline çevrilmeden önce insanların okuyup üzerinde çalışabildiği programlama diliyle ya da Assemblyle yazılmış halinin bir IDEde açılabilen ya da derlenebilen çalışabilir kaynak kod dosyalarının tümü.

Tersine Mühendislik: Bir aygıtın, objenin veya sistemin; yapısının, işlevinin veya çalışmasının, çıkarımcı bir akıl yürütme analiziyle keşfedilmesi işlemidir. Makine veya mekanik alet, elektronik komponent, yazılım programı gibi) parçalarına ayrılması ve çalışma prensiplerinin detaylı şekilde analizini içerir.

Obfuscation: Türkçe karşılığı gizlemek olan bir işlemdir. Bu işlem, yazılımda, kodların anlaşılabilirliğinin daha az olması için yapılmaktadır. Yapılma amacı ise güvenlik ve gizliliktir. Biz bu kelimeyi makale içerisinde karmaşıklaştırma olarak kullandık.

Bytecode (Baytkod): Aynı zamanda taşınabilir kod olarak da bilinir- bir yorumlayıcı tarafından çalıştırılabilir ve aynı zamanda makine diline derlenebilir durumdaki komut setinin isimlendirilmesinde kullanılmaktadır.

Fikri Mülkiyet: Bir kişiye veya kuruluşa ait olan bir fikir ürünüdür; söz konusu kişi ya da kuruluş, sonradan, bunu serbestçe paylaşmayı veya kullanımını belirli biçimlerde kontrol etmeyi tercih edebilir. Yazının bazı kısımlarında kısaltma olarak IP (intellectual property) kullanılmıştır.

Yorumlayıcı (Interpreter): Yazılımı kısım kısım ele alarak doğrudan çalıştırır. Yorumlayıcılar standart bir çalıştırılabilir kod üretmezler. Yorumlama işlemi aşama aşama yapılmadığı için genellikle ilk hatanın bulunduğu yerde programın çalışması kesilir.

ionCube, Zend Guard ve Source Guardian: Gibi şirketlerin geliştirdikleri yazılımlar ile php sayfalarınızı, projelerinizi şifreleyebilirsiniz. Bu şifrelemeyi yaptıktan sonra, bu dosyaların serverda çalışabilmesinin tek yolu ise, serverda ioncube (veya diğer şirketlerin loaderları) loader programın kütüphanesinin kurulu olmalıdır. Ioncube loader, ioncube ile şifrelenmiş php dosyalarını çözer ve sanki şifrelenmemiş gibi çalıştırır.

Loader (Yükleyici): Bir program yazıldıktan ve derlendikten (compile) sonra programın makine dilindeki karşılığı elde edilir. Bu karşılık tam bir kod olmayıp harici kütüphanelerden faydalanıyor olabilir. Bu kütüphaneler de programa dahil edilip tam bir program elde edildikten sonra (yani bağlandıktan sonra (linker) ) program artık çalıştırılmaya hazırdır. Programın çalışması ise programın CPU üzerinde yürütülmesi ile olabilir ve bunun için programın öncelikle hafızaya (RAM) yüklenmesi gerekir. Burada yükleyici (loader) devreye girer. Yükleyici makine dilindeki bu kodu alarak işletim sisteminin işaret ettiği adrese programı yükler. Buradan sonrası işletim sistemi tarafından yürütülür.

Server (Sunucu): Bilgisayar ağlarında, diğer ağ bileşenlerinin (kullanıcıların) erişebileceği, kullanımına ve/veya paylaşımına açık kaynakları barındıran bilgisayar birimi.

Compiler (Derleyici): Basitçe bir dilde yazılmış olan kodu (kaynak kodu yada source code) istenilen başka bir kod haline dönüştüren programdır. Genelde üretilen bu kod ortama göre çalıştırılabilir kod (executable code) olarak üretilmektedir. Ancak bir derleyicinin daha doğru tanımı bir dildeki kodu başka dile çeviren program olarak yapılabilir. Örneğin C dilinde yazılan bir programı PASCAL diline çeviren programlara derleyici adı verilebilir. Derleyicinin diğer bir tanımı ise daha üst seviye bir dilden daha alt seviyeli bir dile tercüme olarak kabul edilebilir. Buna göre örneğin C dilinden Assembly veya makine dili gibi daha alt dillere tercüme ile derleyici kavramı daha da sınırlandırılmış olarak kabul edilebilir. Biz makalemizde bu terimi derleyici olarak kullanacağız.

Decompiler (Kaynak Koda Dönüştürmek): Derlenmiş kodu tekrar kaynak kodlarına dönüştürülmesine decompile bu işlemi yapana ise decompiler nedir. Biz makalede bu terimi geri derleme olarak kullanacağız.

Interpreted Diller (Yorumlanmış Diller): Yorumlanan programlama dili bilgisayar programlamada yazılan programların çalışabilmeleri için kaynak kodlarının bir yorumlayıcı tarafından yorumlanması gerektiğini ifade etmektedir. “Interpreted programming language” olarak da geçmektedir.

Cross-platform: Birden fazla işletim sisteminde çalışabilen demektir.

Zend Engine (Zend Motoru): PHP programlama dilini yorumlayan açık kaynak kodlu betik motorudur. Andi Gutmans ve Zeev Suraski tarafından, İsrail Teknoloji Enstitüsü’nde öğrenim gördükleri sırada geliştirilmiştir.

Token (Simge): Bir programlama dilinde anlam taşıyan en küçük birimdir. Türkçe karşılığı simge, belirteç vs. biz makalemizde token olarak kullanacağız.

Core (Çekirdek, Esas): Core kelime anlamı olarak esas, ana, çekirdek manasına gelir biz makalemizde core olarak kullanacağız.

Veri Tipleri Genel Tanımlar:

Integer: Integer değerler pozitif veya negatif tamsayılardır.

String: Bir dizi karakterin oluşturduğu veri türüne denir. String ifadeler veya diğer adıyla karakter dizileri, tek tırnak veya çift tırnak içine yazılırlar.

Boolean: Boolean veriler true (doğru) ve false (yanlış) olarak değer alırlar. Bunlar genellikle mantıksal bir sınama işlemi sonucunda ortaya çıkar ve bu bağlamda kullanılırlar.

Handler (İşleyici): Genel anlamı ile işleyici veya tanıtıcı olarak türkçeye çevrilebilir. Makalemizde handler olarak kullacağız.

Opcode (Operation Code): Bilgisayar teknolojisinde makine dili komutunun, gerçekleştirilecek işlemi belirten kısmıdır.

Operand: Bilgisayar programlama dillerinde, operatör ve işlenen tanımları matematikte olduğu gibi hemen hemen aynıdır. Hesaplamada, bir işlenen, bir verinin kendisini temsil ederken aynı zamanda hangi verinin üzerinde manipüle edileceğini veya çalıştırılacağını belirten bir bilgisayar komutunun parçasıdır.

Array (Dizi): Hafızada dil tarafından ardışık olarak tutulan özdeş bölümlere verilen isimdir. Yani basitçe bir değişken tek bir bilgi birimi tutabilirken bu değişkenlerden birden fazla adışık uniteye ihtiyaç duyulursa dizi tanımlanabilir. Makale içerisinde array olarak kullanıldı.

PHP Class (Klass): Sınıflara bir çok fonksiyonu bir düzene göre bir arada barındıran yapılardır. Belli bir konu ya da olayın fonksiyonlarını içerir ve belli bir amacı olduğu için sınıf kurulma ihtiyacı duyulur. Mesela bir toplama işlemi için sınıf tanımlamamıza gerek yok, ama bir üyelik ya da alışveriş sepeti için sınıf hazırlayabilirsin.

Metot: Sadece belirli bir işi yapan küçük programcıklardır.

Parse Etmek (Ayrıştırmak): Bir sembol dizisi, bu işlem sonunda yapıtaşları ve bunların birbiriyle ilişkini gösteren bir yapıya çevrilir. Eğer bunu bir compiler (derleyici) okuyacaksa, bunlar bir kod üretmek için kullanılır. Eğer bir interpreter (yorumlayıcı) bunu okuyacaksa, bunlar bir sonuç üretmek için kullanılır. Ya da, bunlar opcode’da çevirilip bir sanal makinede de çalıştırılabilir. Bunlar dışında başka şekillerde de kullanılması mümkün.

Encoder: Kodlayıcı olarak düşünülebilir.

XOR: Veri güvenliğinde kullanılan en basit şifreleme algoritmalarından birisidir. Şifreleme ailesi olarak blok şifreleme (Block Cipher) ailesinden simetrik şifreleme olarak kabul edilebilecek olan bu şifreleme algoritmasında mesaj önce verilen blok boyutunda parçalara bölünür. Ardından her parça anahtar ile Yahut (XOR) işlemine tabi tutulur. Çıkan sonuçlar birleştirilerek şifreli mesaj elde edilir.

Hook: Programlamada, bir hook bir yerdir ve genellikle, paketlenmiş kodda sağlanan ve bir programcının özelleştirilmiş programlamayı yerleştirmesini sağlayan bir arabirimdir. Örneğin, bir programcı, bir programda belirli bir mantık yolunun ne sıklıkta alındığını analiz eden bir kod sunmak isteyebilir.

PRNG: Sözderastsal (rastgele) sayı üreteci (pseudorandom number generator, PRNG), öğeleri arasında kolay kolay ilişki kurulamayacak bir sayı dizisi üreten algoritma türlerine verilen genel isimdir. Sözderastsal sayı üreteçlerinin çıktıları gerçek anlamda rastsal değildir, bu tür algoritmalar gerçek rastsal sayı dizilerinin bazı özelliklerini yaklaşık olarak taşır.

MD4: MD5’in önceki versiyonu olarak tanımlanan 128-bitlik mesajları şifreleyerek gizlemek amacıyla kullanılan şifreleme algoritmasıdır.

GZIP: web sitenize giriş yapan ziyaretçilerinize, web sitenizin dosyalarını sıkıştırarak iletmesidir.

CBC: Kriptografide çalışma kipleri, bir blok şifrenin tek bir anahtar altında güvenli bir şekilde tekrarlı kullanımına olanak veren yöntemlerdir. Değişken uzunluktaki mesajları işlemek için veriler ayrı parçalara bölünmelidir. Son parça şifrenin blok uzunluğuna uyacak şekilde uygun bir tamamlama şeması ile uzatılmalıdır. Bir çalışma kipi bu bloklardan her birini şifreleme şeklini tanımlar ve genellikle bunu yapmak için ilklendirme vektörü olarak adlandırılan rastgele oluşturulmuş fazladan bir değer kullanır.

Lempel-Ziv: Lempel-Ziv-Welch (LZW), Abraham Lempel, Jacob Ziv ve Terry Welch tarafından oluşturulan evrensel bir kayıpsız veri sıkıştırma algoritmasıdır.

PHP Bytecode Koruma Mekanizmasının Güvenlik Analizi

Özet. Php web uygulamarının en popüler programlama dilidir. PHP scriptini derlemek veya korumak için native bir çözüm bulunmadığından, PHP uygulamaları genellikle bir düşman tarafından kolayca anlaşılan veya kopyalanan basit bir kaynak kodu olarak gönderilir. Saldırıları engellemek için bazı ticari ürünler ionCube, Zend Guard ve Source Guardian kaynak kodlarının korumasını sağlar.

Bu makalede, bu araçların kullanımlarını, güvenliğini ve statik, dinamik analiz tekniklerinin güç aktarımı ile kaynak kodlarının iyileştirilmesi için bir method öneriyoruz. Sonuç olarak 10 adet uygulamanın yaklaşık 1 milyon kaynak kodu satırlarından, daha önceden bilinmeyen zayıf noktalar ve izinsiz erişim geçişi olduğunu keşfettik.

Anahtar kelimeler: Güvenlik, Tersine Mühendislik, Obfuscation (Karmaşıklaştırma) , PHP, Bytecode (Baytkod)

Giriş

Fikri mülkiyeti (IP) yazılım sistemlerinin içinde korumak, algoritma, şifrelenmiş anahtarlar, seri numaraları, veya telif hakkı filamaları zorlu bir problemdir.  Bir düşman statik veya dinamik analiz methodları ile programı inceleyebilir ve kritik bilgileri elde etmeye çalışabilir. Bu tür incelemeleri engelemek için bir çok farklı türde, karmaşıklaştırma tekniği geliştirildi. Programın semantik yapısı farklı (otomotikleştirilmiş) ters mühendislik metodları ile yeniden oluşturalabilmesine rağmen, karmaşıklaştırma, kaynak kodunun en azından bir kısmını korur ve düşmanın erişmesini bir nebze engeller.

IP koruması web bağlamında daha zorlayıcı: En popüler server-side (sunucu taraflı) programlama dili olan PHP, yorumlanmış bir dildir. Bu bir yorumlayıcının (interpreter) gelen talep üzerine PHP kaynak kodunu baytkoda çevrildiğini belirtir. Bu nedenle, kaynak koduna erişebilen bir düşman (Örneğin: yazılım hataları, yasal deneme sürümü) kodu doğrudan inceleyebilir ve değiştirebilir veya kritik bilgileri açığa çıkarabilir. Bu tür saldırılara çözüm bulması için kod koruması sunan farklı araçlar bulunmakta: ionCube, Zend Guard ve Source Guardian gibi ticari ürünler, lisanssız kullanımı ve ters mühendisliği önlemek ve fikri mülkiyet haklarını korumayı vadediyor.

Tüm bu araçlar aynı yöntemi izlemektedir: önce PHP kaynak kodunu önderleme yaparak gizlenmiş baytkoda çevirirler, daha sonra derlenen kodları orijinal kaynak kodları olmaksızın gönderirler. Bu araçlar sunucu (server) tarafında baytkodu çalıştırması için bir eklentiye ihtiyaç duyarlar. Popüler PHP yazılımları NagiosFusion, WHMCS ve xt-Commerce gibi: IP korumak için bu tür araçlarla korunan bazı dosyalar gönderir. Sonuç olarak düşman sadece önceden derlenmiş baytkoda erişebilir ve orijinal kaynak koduna erişemez. Ne yazık ki, bu ürünlerin dahili olarak nasıl çalıştıkları ve sağladıkları güvenlik garantileri belgelenmemiştir.

Bu yazıda bu açıklığı ele alacağız. En popüler üç ticari PHP kod koruma ürününü inceledik ve güvenlik özelliklerini analiz ettik ve üç yazılımında düşmanın orijinal kodun semantiğini yeniden inşa etmesine olanak veren sınırlamayı paylaştığını bulduk. Daha spesifik olarak, baytkodunun yorumlanmasını statik ve dinamik olarak analiz ederek kodu kurtarmak için yöntemler sunacağız. Yorumlayıcının (interpreter) şifreli / karmaşık bayt kodunu tekrar makine koduna çevirmesi gerektiğinden, bu aşamada semantik bilgiyi iyileştirebiliriz. Tüm araçların (üstteki yazılımların) düşman tarafından alt edilebileceğini ve PHP kaynak kodunun başarılı bir şekilde yeniden oluşturabileceğimizi bulduk. Bu yazıda, öncelikle farklı PHP kod koruma araçlarının manuel tersine mühendislik çalışmalarımızdan elde ettiğimiz bulguları sunuyoruz. Bu bulgulara dayanarak, dinamik analiz teknikleri kullanarak şifreleme ve gizlenmiş katmanları kırma yöntemimizi tanıtmak ve genel bir geri derleyicinin (decompiler) nasıl oluştuğunu gösteriyoruz. Tekniklerimizin baytkodu yorumuna dayanan tüm PHP baytkodu koruyucularına karşı kullanılabileceğini ve yöntemimizin analiz edilen üç ürünle sınırlı olmadığını unutmayın.

Derleyicimizi değerlendirmek için birkaç koruma yazılımını inceledik. Bu yazılımların bazılarında kritik güvenlik açıkları ve gizli girişlerin olduğunu ortaya çıkardık. Ayrıca şifrelenmiş uygulamanın sunucu güvenliğini zayıflatan kritik güvenlik açıklarına da rastladık. Sonuç olarak, araştırmalarımızın sonuçları PHP kaynak kodu koruma araçlarının satıcıları tarafından iddia edildiği kadar güçlü olmadığını ve bu tür araçların saldırı alanını arttırabileceğini gösteriyor.

Özetle, bu yazıda şu katkıları yapıyoruz:

En popüler 3 PHP baytkodu koruyucusunun iç çalışmasını detaylı bir şekilde analiz ediyor ve belgeliyoruz.

Yorumlama aşamasında orijinal kodun semantiğini iyileştirebileceğimiz metotlar öneriyoruz. Otomatikleştirilmiş bir yol ile korunan PHP kodunu yeniden yapılandırmayı ve bir de prototip decompiler (geri derleyici) uygulaması sunuyoruz.

Bizim prototip decompiler ı 10 korumalı, aktif çalışan uygulamala ile denedik ve korumalı baytkoddan orijinal kaynak kodunu yeniden oluşturmanın mümkün olduğunu size göstereceğiz.

Son olarak, PHP bayt kodu koruyucuların kullanımı ve bunların kritik verilerin üzerindeki etkinliği konusunda sizleri bilgilendirmek istiyoruz. Araştırmalarımızın, yorumlanmış dilleri (interpreted diller) koruma konusunda gelecekte yapılacak olan çalışmalara faydalı olmasını umarız.

Arka Plan

PHP kaynak kod koruyucularını analiz etmek için, öncelikle birkaç PHP içeriğe göz atalım. PHP yorumlayıcısı (interpreter), sanal makine ve talimatlar hakkında kısa bir giriş yapalım. Ardından, PHP kaynak kod koruyucularının genel konseptini ana hatlarıyla açıklar ve piyasadaki en popüler 3 aracı sizlere tanıtırız.

PHP Yorumlayıcı (PHP Interpreter)

PHP, PHP yorumlayıcısı (interpreter) tarafından ayrıştırılan, çözümlenen platformdan bağımsız bir scripting (betik) dilidir. PHP yorumlayıcısı C’de yazılmıştır ve cross-platform da derlenebilir. C gibi alt düzey dillerin aksine, PHP kodu için yürütülebilir bir dosyaya manuel olarak derleme yapılmaz. Bunun yerine, bir uygulamanın kodu her çalıştığında Zend engine (motoru) tarafından PHP baytkodu olarak derlenir. Zend engine PHP’nin esas bir parçasıdır ve kod yorumundan sorumludur. Derleme işlemi sırasında PHP dosyasının kodu sınıflara ayrılır. İşlem PHP’nin esas fonksiyonu olan Zend derleme dosyası ile başlatılır. Kodları sınıflara ayırdıktan sonra, derleyici tokenları (simgleri) kullanarak onları baytkoda derler. Benzer şekilde, core (esas) fonksiyon Zend string() bir dize derler ve örneğin eval() içinde kod çalıştırmak için kullanır. 4. Bölümde göreceğimiz gibi PHP core fonksiyonları (esas) baytkod koruyucularının dinamik analizi için önemli rol oynamaktadır. PHP yorumlayıcısının yapısına genel bir bakış Şekil 1’de verilmiştir. Engine (motor) ayrıştırıldıktan ve PHP kodunu baytkoduna derledikten sonra, talimatları Zend engine (motor) ile gelen PHP’nin sanal makinası (VM) tarafından yürütür. Sanal bir CPU ve kendi talimatları vardır. Bu talimatlar normal makina kodundan daha üst seviyededir ve doğrudan CPU tarafından yürütülmez. Bunun yerine sanal makine, VM komutunu ayrıştıran ve native CPU kodunu çalıştıran her komut için bir handler sağlar.

Uygulama işlemi opcode dizisini zend_execute () fonksiyonuna geçirerek başlatılır. Opcodeları tekrarlar ve birbiri ardına yürütür. Kullanıcı tanımlı fonksiyonlara yapılan çağrılar yinelemeli olarak işlenir ve çağrı sitesinin işlem koduna geri dönülür. Fonksiyon, ana işlem kodu dizisinde bir işlem kodu bulunca sona erer. Bir sonraki bölümde işlem kodlarına ayrıntılı olarak bakıyoruz.

php decode

 

Şekil 1 – PHP yorumlayıcı ve onun çekideği (core), eklentileri ve Zend engine.

 

 

 

PHP Baytkod

PHP’nin kayıt tabanlı baytkodu işlem kodları, sabitler, değişkenler ve meta bilgilerinden oluşur. PHP mevcut dil yapılarını kapsayan yaklaşık 150 farklı işlem kodu içermektedir. Temel olarak her opcode bir arabul tablosunda bir opcode’a karşılık gelen, iki parametre işleneni ve dönüş değerlerini depolamak için bir sonuç işlenen içerir. Bir işlem kodunun parametre operandları (işlenenler), işlemde işlenmiş değerleri depolar. Bu operandların (işlenenler) türleri farklı olabilir ve çeşitli kullanım durumları vardır. Örneğin, bir operand sabit veya değişken olabilir. Geçici değişkenler bir değişkene atanmamış yardımcı hesaplamalar ve sonuçlar için kullanılır.

Farklı operand türleri olduğu için, her bir komut için 25 olası operand kombinasyonu olduğundan, birden çok işleyici örneği vardır. Örneğin, iki değişkeni eklemek için kullanılan handler fonksiyonu, iki sabiti eklemek için kullanılan fonksiyondan farklıdır. Handler fonksiyonlarının toplam mevcutu 150*25 den azdır, çünkü bazı kombinasyonlar gereksiz veya geçersizdir. Handler adresini handler tablosundan almak için kullanılan index, aşağıdaki formül kullanılarak hesaplanır.

index = opcode_sayısı *25 + op1_type *5 + op2_type

Her operand kombinasyonu her bir opcode (işlem kodu) için bu tabloda saklanır ve işlemi gerçekleştiren uygun handler ile bağlantı kurulur. Geçersiz kombinasyonlar, PHP işlemini ilgili bir hata mesajıyla sonlandırır.

Baytkod opcode ların yanında, yapı kodlarıda içerir. Bunlar sayılar (number) ve string (dizi) gibi sabit değerleri ve operandların bir şifre ile referans alınan değişken adlarını tutar. Ayrıca, satır numaraları ve dökuman yorumları gibi meta bilgilerin yanı sıra, ek bilgilerin saklanmasına izin veren reserved (ayrılmış) bir değişken mevcuttur. Kullanıcı tanımlı fonksiyonların  ve metotların baytkodu, opcode array (dizi) lerinde benzer şekilde saklanır. Burada isim ve argüman bilgileri ayrıca kaydedilir. Genel fonksiyon tablosu, fonksiyon adı ile uygun opcode array (dizi) ine bağlar. Klasslar (Class) metotlara bağlantı veren kendi metot tabloları vardır. Bir fonksiyon veya metot çağrıldığında, PHP bu tabloları uygun opcode array (dizi) ini bulmak için kullanır.

Aşağıda, bir kod öreneğine ve derleme sonrası bay koduna bakacağız. Aşağıdaki PHP kodu bir matematiksel işlem gerçekleştirir, sonucu statik bir string (dize) ile birleştirir ve sonucu RAID2015 olarak yazdırır.

$year = 2000 + 15;

echo “RAID” . $year;

Derlenen kodun dağılımı Tablo 1’de gösterilmektedir. Zaten opcode sayılarını, işlenen adlarına karşılık gelen handler adlarına ve değişken adlarına eşledik. Derleme işlemi scripti dört işlemde dönüştürdü. Birinci adım, ADD opcode handler 2000 sabitini 15 ile topladı ve geçici değişkende depoladı TMP: 1. İkinci adımda, ASSIGN opcode handler geçici değişkeni TMP:1 year değişkenine atadı. Üçüncü olarak CONCAT (.) opcode handler ‘RAID’ stringini year değişkeni ile birleştirdi ve TMP:2 geçici değişkeninde depoladı. Son olarak ECHO opcode handler TMP:2 geçici değişkenini yazdırdı.

Tablo 1. Örnek gösterilen Bayt kod

opcode

PHP Baytkod Encoder (Kodlayıcı)

 Kapalı kaynaklı bir PHP uygulaması oluşturmak için genel fikir, bir kez PHP scripti derlemek ve tüm opcode arrayleri dökmektir. Bu veriler daha sonra, kaynak kodu başka bir derleme yapılmadan doğrudan PHP yürütücüsüne konuşlandırılır. PHP’nin bunun için native (bağımsız) bir çözümü bulunmadığı için baytkodu çalıştırılmadan önce bir dosyaya atan özel bir PHP uzantısı uygulanabilir. İkinci bir uzantı (yükleyici) daha sonra döküm dosyasını ayrıştırır (parse) ve baytkodu PHP engine’e konuşlandırır. İşlem Şekil 1 de kesikli ok ile gösterilmektedir. Bir dezavantaj olarak, baytkodu formatı farklı PHP sürümleri ile değişiyorsa, o PHP sürümüne özgü uzantılar sağlanmalıdır.

Bununla birlikte, Bölüm 2.2’de gördüğümüz gibi PHP bayt kodları hala okunabilir, dolayısıyla bu yüzden ek koruma mekanizmaları maküldür. Örneğin, baytkodunun etrafına birkaç şifreleme katmanı eklemek mümkündür. Ayrıca şifrelenmiş baytkodunun yürütülmesi, yükleyici (loader) uzantısı ile belirli bir kullanıcı lisansı veya donanım ortamıyla  sınırlandırılabilir. Bu tür mekanizmalar güveliği arttırırken, bir uygulamanın performansını düşürebilir. Aşağıda en popüler 3 PHP baytkod koruma aracını sunduk. Bu üçlü için yükleyici uzantısı ücretsiz, enkoder (encoder) uzantısı ise ticari olarak mevcuttur. Her üç üründe şifreleme, çevre kısıtlaması, dosya müdahalesinin önlenmesi ve simge adı karmaşıklaştırma gibi hizmetler sunarak baytkod korumasını vadediyor (SourceGuardian hariç).

ionCube, muhtemelen 2003 yılından beri PHP scriptleri şifreleme, karmaşıklaştırmada en popüler ve en çok kullanılan yazılımdır. Firma ürünün tanıtımını yaparken “PHP’yi korumak için en ideal ve taviz vermeyen çözüm” olarak tanımlıyor. En son sürüm 8.3’ün tek kullanıcı için bir lisans fiyatı 199$.

Zend Guard, scriptleri yazılım korsanlarından korumak için Zend Technologies tarafından geliştirilmiştir. Mevcut sürümü 7.0’ın yıllık fiyatı 600$’dır ve en pahalı çözümdür. Satıcının online mağazında vadettiklerini şu şekilde bir araya getiriyor “Lisansız kullanımı ve ters mühendisliği önler ve fikri mülkiyetinizi şifreleme yoluyla korur”. Bununla birlikte, analizimiz sırasında herhangi bir şifreleme süreci tespit edilmedi.

SourceGuardian, 2002’den beri var ve 2006 yılında phpShield ile birleşti. Her iki üründe benzer özellikleri içeriyor, SourceGuardian farklı olarak çevre kısıtlamaları özelliğini içermektedir. Şifrelenmiş dosyalar aynı zamanda PHP’nin farklı sürümleri ile uyumlu olabilir. Ürün “piyasadaki en gelişmiş PHP enkoder” olarak kendini tanımlamaktadır. Son sürüm olan 10.1.3 159$’dan satılmaktadır.

Yükeleyici Uzantılarının Statik Analizi

Sunulan araçların iç işleyişlerini ortaya çıkarmak için karşılık gelen loader (yükleyici) uzantılarına tersine mühendisli uygulamaları yaptık. Yaklaşık dört hafta içinde, analizlerimiz sırasında enkoderlar için yaygın olarak desteklenen tek PHP sürümü olan PHP 5.4 sürümüne ait enkoderi analiz ettik. Sonuç olarak koruma mekanizmalarını, algoritmalarını ve güvenlik açıklarını tespit edebildik. Enkoderların yeni sürümleri bu arada piyasaya sürülmesine rağmen, iç çalışmalarında önemli bir değişiklik olmadı. Bu bölümde önce enkoder benzerliklerine kısa bir göz atacağız ve daha sonra ürüne özgü ayrıntılara gireceğiz. Alan kısıtlaması ve etik hususlardan dolayı (bkz. Bölüm 8), temel bulgularımıza odaklanıyoruz.

loader

Şekil 2. Bir loader (yükleyici) uzantısının iş akışı: Korunan bir dosyanın ikili verilerini ayrıştırır ve sonra yürütülen PHP baytkoduna ayıklar.

Genel Bakış

Analiz edilen enkoderların tümü veri kodlaması ve şifreleme için farklı yöntemler kullanmasına rağmen, korunan bir dosyanın genel yapısı ve ikili verileri benzerdir. Şekil 2’de genel çalışmasını tasvir ettik. İlk olarak, korunan her dosya şifrelenmiş olarak kendini tanımlar. Ardından, Native PHP kodu yükleyici bulunamaması durumda fallback routine (yedek yordam) sağlar. Bu kullanıcıyı eksik uzantı hakkında bilgilendirir ve yürütmeyi sonlandırır. Eğer yükleyici mecvut ise, ikili verileri (binary data) kişiye özel ikili verilerin içine PHP baytkodu olarak saklanır. İlk olarak PHP ve enkoder sürümünü gösteren şifrelenmemiş header çıkarılır. Birinci header’ı takip eden ikinci header şifrelenmiş veya lisanslanmıştır ve lisans bilgileri, kullanım süreleri ve çevre kısıtlamaları ile ilgili daha ayrıntılı bilgileri saklar. Bu bilgilerden bazıları harici bir lisans dış kaynak olabilir. Bu durumda, ikinci header bu dış kaynak dosyalarının korunması ile ilgili bilgileri depolar. Son olarak, PHP bayt kodu kişiye özel formatıyla headerları takip eder. Lisansın süresi dolmamış ise ve çalışma zamanı çevre kısıtlama kuralları ile eşleşiyorsa, PHP baytkodu PHP VM’ye ileterek çalıştırır. Aşağıda ürüne özgü özellikleri paylaştık. Her bir loader (yükleyici) uzantısının baytkodunu adım adım ikili verilerden çıkarma işlemine tersine mühendislik yaptık. Şaşırtıcı bir şekilde, uzantıların kendisinde çok az karmaşıklaştırma (obfuscation) kullanılır. Bu tersine mühendislik işlemini zorlaştırır ve daha fazla uğraş gerektirir ancak engelleyemez. Tanımlanan çekirdek özelliklerine (core feature) Tablo 2’de genel bir bakış verilmiştir.

core feature

Tablo 2. Yükleyici uzantılarının içlerine genel bakış

 

IonCube

ionCube loader (yükleyici) çoklu optimizasyonlarla ve hata ayıklama sembolleri olmadan derlenir. Bütün iç fonksiyon adları karmaşıklaştırılmıştır. İç dizi hataları statik 16 bayt anahtarı ile XOR edilirken, dizideki önek bir karekter başlatılacak XOR işleminin offsetini (konumunu) gösterir. Tersine mühendisliğe karşı diğer koruma mekanizmaları mevcut değildir. Loader (yükleyici) zend derleme dosyasını hooklar (kancalar) ve yürütülen bir PHP dosyasının başına <?php // stringi (dizisi) için test eder. Ardından gelen onaltılık sayı native PHP fallback kodunun boyutunu belirtir. IonCube uzantısı yüklendiyse, fallback (geri yükleme) kodu atlanır ve loader (yükleyici) dosyanın sonundaki binary data (ikili verileri) ayrıştırmaya başlar (parse eder). IonCube ikili verileri (binary data) işlenmemiş veya özel bir base64 kodlamasıyla kodlanmış olarak (encoded) gönderir. Base64 karakter takımı, takımlarda önce sayıların olması için biraz modifiye edilir. Veriler çözüldükten sonra, onu ayrıştırmak için bir ikili akış kullanılır. İlk dört bayt kodlama (encoding) için kullanılan ionCube sürümünü belirtir. IonCube formatı zamanla değiştiğinden, farklı ayrıştırma yordamları (parsing routines) kullanılır. Örneğin  0x4FF571B7 versiyonun 8.3 olduğunu belirtir. Sürüm tanımlayıcısını üç integer (tam sayı) takip eder: dosya boyutu, header boyutu ve header key (şifre). Bu key (şifre) aşağıdaki hesaplamayı kullanarak header boyutunun ve dosya boyutunun şifresini çözmek için kullanılır.

header_size = header_key ^ ((header_size ^ 0x184FF593) – 0xC21672E) file_size = (file_size ^ header_key ^ 0x23958cde) – 12321

Dosya boyutu hata ayıklama için kullanılırken, header boyutu ikinci header’in length (uzunluğunu) belirtir. İkinci header sahte bir rastgele bayt akışı ile XOR edilmiştir. Bu amaçla, bir superKISS benzeri PRNG, birinci başlığın header key (şifre) ile birleştiririlir. Şifrelenmiş ikinci header’ın son 16 baytı şifreli ikinci header verilerinin MD4 sağlama toplamını temsil eder. Birinci ve ikinci header’ın yanı sıra MD4 sağlama toplamını doğrulamak için bir Adler32 sağlama toplamı vardır. Modifiye edilen Adler32 algoritmasında, tüm baytların toplamı 1 yerine 17 ile başlar. MD4 sağlama toplamı aşağıdaki sahte (pseudo) algoritmayı kullanarak önce şifresiz hale getirilir.

md4_sum = substr(header, -16);

for(i=0; i<16; i++) {

md4_sum[i] = ((md4_sum[i] >> 5) | (md4_sum[i] << 3));

}

Burada sağlama toplamının her baytı 5 bit olarak dönderilir. Ardından, PRNG başlatılır ve pseudo (sahte) rastgele baytlar ve MD4 sağlama toplamının parçaları şifre çözme key (anahtarı) olarak kullanılır. Aşağıdaki pseudo algoritması ikinci headerın bayt şifresini çözmek için kullanılır.

prng = new ion_prng(header_key);

for(i=0; i < header_size – 16; i++) {

raw_header[i] ^= md4_sum[i & 15] ^ (prng->next() & 0xFF);

}

Sonunda, header verilerinin bütünlüğü MD4 sağlama toplamını hesaplayarak ve karşılaştırarak doğrulanır. İkinci header ionCube korumasının yapılandırma değerlerini içerir. Örneğin, bir sürüm numarası yükleyicinin dosyasının PHP sürümünün sitemin PHP sürümü ile uyuşup uyuşmadığını belirlemesine ve ilgili decoding routine (kod çözme yordamı) nı bulmasına izin verir. Native fallback (bağımsız geri dönüş) kodunun bir sağlama toplamı bütünlüğünü doğrulamaya izin verir. Ayrıca lisans bilgisi ve çevre kısıtlama kuralları bulunmaktadır. İsteğe bağlı isimlerin gizlenmesi için, MD4 ile değişkenlerin, fonksiyonların ve klasların (class) adlarını karma yapmak için kullanılan bir teknik vardır. Sınırlama kuralları bir lisans dosyasına dış kaynak olarak verilmiş ise, dosya yolu ve şifre çözme anahtarı (key) sağlanır.

İkinci header ve sağlama toplamından sonra, iki tamsayı ve şifreli PHP verileri gelir. Şifrelenmiş PHP verilerinin XOR anahtarı (key) olan bir bayt dizisi üretmek için ilk tam sayı (integer) bir PRNG’yi tohumlanır. Başarılı bir şifre çözme işleminden sonra veriler GZIP kullanılarak açılır. Bu noktada baytkodu içerisindeki opcode (işlem kodu) numaraları yine de şifrelidir. Yeni bir PRNG ikinci bir tam sayı ile tohumlanır. İkinci header daki statik bir değer ile kombinasyon halinde, opcode numaralarının şifresini çözmek ve işleyiş süresi karmaşıklaştırlmasını gerçekleştirmek için yeni bir bayt dizisi oluşturulur. Bu işlemi Bölüm 4.2’de detaylı olarak açıklıyoruz.

Zend Guard ve SourceGuardian’in aksine ionCube, kodu çalıştırmak için baytkodunu PHP’nin native (bağımsız) yürütme fonksiyonuna göndermez. Bunun yerine, native PHP yorumlayıcısının nispeten değiştirilmiş parçaları loader (yükleyici) uzantısında derlenir ve bunlar daha da karmaşıklaştırılmış özelliklere sahip özel VM olarak kullanılırlar.

Zend Guard

Zend Guard loader (yükleyici) uzantısı, ayrıntılı hata mesajlarının okunmaması haricinde tersine mühendisliğe karşı koyamaz. Bunlar dört bayt \xF8\x43\x69\x2E kullanılarak XOR edilmiştir. Dahası Zend Guard en son loader (yükleyici) sürümünde hata ayıklama sembolleri içeriyor olsa da, kütüphane kodunun tersine mühendislikten çıkarılmasına yardımcı olan derleme bilgileri sızdırıyor.

Zend Guard kodlanmış dosyaları algılamak için PHP VM’nin fonksiyonlarını zend_compile_file() ile değiştirir. Eğer string <?php @Zend; dosyanın başında bulunmazsa, orijinal derleme fonksiyonuna geri aktarılır. Aksi takdirde dosya Zend Guard tarafından işlenir. İkinci satırdaki sekizlik rakamı okur ve dosyanın sonundaki işlenmemiş ikili veriye erişmek için fallback routine (geri dönüş yordamından) bayt miktarını atlar.

Verileri almak için bir ayrıştırıcı (parser) bayt akışında yinelemeye başlar. Veri blokları temel olarak dört farklı veri tipinden oluşan bir basit ikili format kullanılarak saklanır; bayt, bool, sayı ve string (dizi). Akıştan tek bir bayt veya bool istenildiğinde ayrıştırıcı (parser) bir karakter okur. Sayısal değerler sayının uzunluğunu tanımlayan tek bir bayttan ve gerçek integer (tam sayı) dan oluşur (Örneğin ; [\x05][2015\x00]). Stringler (dizeler) bir bayt dizge ve uzunluğu ile integer (tam sayı) tipini genişletir (Örneğin; [\x02][5\x00][RAID\x00]). Hem stringler hem de sayılar boş bir bayt ile sonlandırılır.

İkili veri iki kısımdan oluşur: minimalist bir header ve ardından sıkıştırılmış veriler. İlk header’da dört değer saklanır. İlk değer Zend Guard’un sürümünü, ikinci değer ise komut dosyasının derlendiği PHP sürümünü belirtir. Ardından, iki sayısal değer sıkıştırılmış ve sıkıştırılmamış verilerin boyutunu belirtir. Ardından gelen veriler GZIP ve özel bir sıkıştırma sözlüğü kullanılarak sıkıştırılır. Bu sözlük, sıkıştırma oranını artırmak için PHP kodunda sıklıkla bulunan kelimeleri listeler. Verilerin sıkıştırılmasının ve Zend Guard yükleyicisinde saklanması gereklidir. Sıkıştırılmış verilerde ikinci bir header ve PHP baytkodu bulunur.

İkinci header’da, lisans sahibinin adı gibi lisans bilgileri ve lisans dosya olarak eklenmişse yapılandırma işaretleri saklanır. Ayrıca bir son kullanım tarih bilgisini içerir. Geçerli zaman bilgisi o anki zaman bilgisinden önce ise veya lisans geçersiz ise Zend Guard dosya yürütmeyi reddeder. Lisans, sahibinin adının bir Adler32 sağlama toplamı hesaplanarak ve header’daki sağlama toplamıyla karşılaştırılarak doğrulanır. Bu amaçla, modulo işleminden yoksun biraz modifiye edilmiş bir Adler32 algoritması kullanılır. İkinci header tamamen çözülür ve doğrulanırsa, derlenmiş PHP verileri ayrıştırılır (parse edilir). Bu amaçla Zend Guard, veriyi ayrıştırmak için daha önce tanıtılan akış biçimini kullanır. Opcodes, literaller ve değişkenler bu yapının ana parçasıdır. Ancak, kodlama sırasında açıkça devre dışı bırakılmadıysa, satır numaraları veya yorumları gibi meta bilgileri de kullanılabilir. Dahası, Zend Guard opcode numaralarını karıştırır. Kurtarma için, iki arrayden sabit değerler ve ikinci header’dan lisans sahibi sağlama toplamı kullanarak bir değişim tablosu oluşturulur. Değişim tablosu ve mevcut opcode indeksinin yardımı ile orijinal opcode numarası hesaplanabilir.

Dahası, Zend Guard fonksiyon ve değişken isimlerini karmaşık hale getirebilir. Burada küçük harfli isim, orijinal adı kaybolacak şekilde MD5 adının sağlamasıyla XOR edilir. Bununla birlikte, isim bir sözlükle kaba kuvvet uygulayabilir. Ayrıca, anahtar alan azaltılır çünkü orijinal isim ilk önce küçük harfle yazılır ve karmaşıklaştırılan isim de orjinali ile aynı uzunluğa sahiptir.

SourceGuardian

SourceGuardian’ı ters mühendislik işlemini, yükleyici uzantısı içinde mevcut hata ayıklama simgeleri rahatlatır. Hemen hemen tüm fonksiyon ve sembol isimleri bozulmamış ve iç süreçlerin anlaşılmasını kolaylaştırır. Bir PHP dosyası koruması tanımlayıcıyla birlikte ilk satırda @”SourceGuardian” olarak gösterilir (@”phpSHIELD” eski sürümler için). Zend_compile_file() fonksiyonuna çengel atmak (hook) yerine, SourceGuardian kişiye özel parse edilen PHP core kısmına yeni bir sg_load() native fonksiyonu ekliyor.

<?php @”SourceGuardian”;

if(!function_exists(‘sg_load’)){ // fallback code

} return sg_load(‘12345678CHECKSUM/BASE64/BASE64/BASE64/BASE64=’);

Sg_load () argümanı, 16 onaltılı karakterin ve base64 ile kodlanan ikili verilerin birleştirilmesidir. Önce, açılış PHP etiketi <?php den sg_load () argümanının 8. karakterine kadar değişen karakterler üzerinde bir sağlama toplamı hesaplanır. Bu sağlama toplamı daha sonra, geri dönüş kodunun bütünlüğünü doğrulamak için argümanın sonraki sekiz karakteriyle karşılaştırılır. Sağlama toplamı algoritması, BSD sağlama toplamının 32 bitlik bir sürümü gibi görünür. Base64 kodlu ikili veri çözülür ve dört veri türüne sahip bir ikili format ortaya çıkar. Karakter türü, tekil karakterleri, küçük sayıları ve boolean değerlerini temsil etmek için kullanılır. Tamsayılar, yüksek son haneli formatında (int tipi) dört bayt kullanılarak saklanır ve stringler sıfır olarak sonlandırılabilir (tür zstr) veya ön ek uzunluğuna (tür lstr) sahip olabilir.

Başta, dosyanın belirli bir IP adresine veya ana makine adına kilitlenmesi durumunda, bir sürüm numarası ve koruma ayarları içeren bir ilk header ayrıştırılır (parse edilir). Birinci header daki veri blogunun birinci baytı gelecek baytların amacını bir 0xFF gelene kadar belirler. Örneğin, 0x2 değeri, yürütmenin bir ana makine adıyla sınırlandırıldığını ve 0x4 değeri, ikinci header uzunluğunun izlediğini gösterir.

Şifreli ikinci header’ın ofseti ilk header’dan elde edildiğinde, blok şifre CBC modunda Blowfish kullanılarak şifresi çözülür. Bu amaçla SourceGuardian’ın yükleyicisi üç farklı gruba ait birden fazla statik anahtarla gelir. Dosya sürümüne bağlı olarak, şifre çözme başarıyla tamamlanana kadar ilgili grubun anahtarları üzerinde yinelenir (sağlama toplamı).  Geriye dönük uyumluluk ve phpShield toplaması nedeniyle birden fazla anahtar (key) vardır:

NTdkNGQ1ZGQxNWY0ZjZhMjc5OGVmNjhiOGMzMjQ5YWY= // public key

MmU1NDRkMGYyNDc1Y2Y0MjU5OTlmZDExNDYwMzcwZDk= // public key

NzkxNThhZDhkOThjYTk3ZDE5NzY4OTRkYzZkYzM3MzU= // license file key

ODI0YzI2YmMyODQ2MWE4MDY3YjgzODQ2YjNjZWJiMzY= // phpShield pub key

YTJmNjc2MDQ3MWU5YzAxMjkxNTkxZGEzMzk2ZWI1ZTE= // phpShield pub key

Korunan dosyanın yürütülmesinin bir sunucunun IP adresiyle veya ana makine adıyla sınırlı olması durumunda, bu değer ikinci header ve body’nin şifre çözme anahtarına eklenir. Dolayısıyla, loader (yükleyici) diğer ortamlarda ikili blok şifresini çözemez ve yürütme başarısız olur. Varsayılan olarak, bir saldırgan statik anahtarları kullanarak şifre çözme işlemini gerçekleştirebilir. Ek anahtar verilerinin (IP veya ana makine adı) daha ileri güvenlik sağlamadığına inanıyoruz, çünkü çalınan bir kaynak kod dosyasının kökeni muhtemelen saldırgan tarafından bilinir veya kaba kuvvetle zorlanabilir.

Başarılı bir şekilde çözülen her blok, üç tam sayı ve gerçek verileri içerir. İlk tam sayı düz veri üzerinde hesaplanan bir sağlama toplamıdır. İkinci tam sayı şifrelenmemiş verilerin uzunluğunu, üçüncü tamsayı dekompresyon (açma) sonrası verilerin boyutunu içerir. Sağlama toplamları daha önce bahsedilen 32bit BSD sağlama toplamıyla hesaplanır. İlk tam sayı hesaplanan sağlama toplamıyla eşleşirse, şifre çözme başarılı olmuş demektir.

Bu noktada veriler çözüldü, ancak yine de Lempel Ziv algoritması ile sıkıştırılır. SourceGuardian, SourceGuardian’ın eski bir sürümüyle kodlanmış dosyalar için lzo1x uygulamasını ve lzss’yi kullanır. İkinci header ve PHP veri blokları bu tekniği kullanarak sıkıştırılmış ve şifrelenmiştir.

Birinci header’a benzer şekilde, bir ayrıştırıcı veri üzerinde yineleme yapar ve ikinci header’ın değerlerini alır. Lisans sahibi, lisans numarası, dosya oluşturma ve dosya son kullanma tarihi gibi çevre kısıtlamaları hakkında bilgi içerir. İkinci header’dan sonra, PHP verileri izler. SourceGuardian, farklı PHP sürümleriyle uyumluluk için birden fazla sürümünü saklayabiliyor. Her bir sürüm için bir veri bloğu kullanılır. Her blok, uyumlu PHP sürümünü ve şifreli verilerin boyutunu ve gerçek PHP verilerini not eden iki tam sayıdan oluşur. O anda çalışan PHP sürümü için uyumlu bir veri bloğu bulunursa, blok çözülür. Değişken isimler gibi şaşırtma ve karmaşıklaştırma olmaz ve çözülen opcode array’i zend_execute() girilir.

Loader (Yükleyici) Uzantılarda Güvenlik Açıkları

Bir PHP uygulamasını korumak, müşteriye gönderildiğinde fikri mülkiyet hırsızlığı ve değiştirilmesini önleyebilir. Ancak aynı zamanda, müşterinin kodu dağıtmadan ve sunucusunda çalıştırmadan önce gözden geçirebilmesini önlemektedir. Riskleri azaltmak için bilinmeyen, korunan PHP kodunu çalıştırırken sistem komutlarını çalıştırmak gibi OS etkileşimini yasaklayan safe_mode ve disable_function gibi PHP mekanizmaları etkinleştirilebilir.

Tersine mühendislik işlemi sırasında, loader (yükleyici) uzantısının her birinde bellek bozulması zafiyetleri tespit ettik. Kötü amaçlı bir PHP dosyası hazırlayarak, yükleyicinin ayrıştırıcısını bozup shellcode’u enjekte etmek mümkündür. Bu güvenlik açıkları uzaktan yararlanılamazken, korunan bir uygulamanın PHP’nin güvenlik mekanizmalarını atlaması ve keyfi sistem komutlarını web sunucusunun kullanıcı ayrıcalıklarıyla yürütmesine izin veriyorlar. Bu konularda ionCube, Zend Guard ve SourceGuardian’ı bilgilendirdik.

Ayrıca, SourceGuardian’da lisans bilgilerinin sızdırılmasına izin veren belgelenmemiş bir özellik tespit ettik. HTTP GET parametresi _sginfo_’yu korumalı bir uygulamaya gönderdik ve enkoder sürümü, kayıt tarihi, lisans sahibi ve kodlama tarihi ile karşılık verdi.

Dinamik Analiz Yöntemiyle Genel Deobüskasyon (Açığa Vurma)

Şimdi korunan PHP uygulamalarını analiz etmek için iki dinamik yaklaşım sunuyoruz. Amacımız dağıtılan şifreleme veya karmaşıklık katmanlarını işeyiş süresinden kaçırarak, orjinal kod hakkında bilgi almaktır. Her iki yaklaşımın ionCube, Zend Guard ve SourceGuardian’a karşı test edildi ve her iki saldırıya karşı savunmasız olduklarını gördük.

Hata Ayıklama

Korunan PHP kodunu analiz etmek için basit bir yaklaşım, onu mevcut işleyiş süresi ortamı hakkında bilgi sızdırmak için PHP’nin yerleşik hata ayıklama fonksiyonlarını kullanan kendi kodu içeriğine dahil etmektir. Örneğin, get_defined_vars (), get_defined_functions (), get_declared_classes () ve get_class_methods () fonksiyonları ve bunların yanı sıra PHP’nin yerleşik ReflectionClass fonksiyonu, tüm değişkenlerin, kullanıcı tanımlı fonksiyonların, klasların ve metotların bir listesini almanıza izin verir. Elde edilen veriler, daha fazla bilgi edinmek için değişken girilebilir ve fonksiyonlar farklı girdileri olan bir kara kutu olarak adlandırılabilir. Test edilen üç araç da, bu analizi önlemek için derlenmiş bir kodun güvenilmeyen bir bağlamda bulunmasını engelleme seçeneğine sahiptir, ancak bu varsayılan olarak devre dışıdır.

Hooking (Kancalama)

Daha sofistike bir yaklaşım, yürütülmeden önce tam baytkodu almak için dahili PHP işlevlerini hook (kanca) etmektir. Bölüm 3’te açıklandığı gibi Zend Guard ve ionCube, zend_compile_file () işlevinin yerini almıştır. Belli bir dosyanın çözülmüş ve şifresiz bayt kodunu dönderir. Yükleyicilerin iç işlerinden haberdar olmaksızın, karmaşık opcode arraylari geri almak için bu fonksiyonları bir kara kutu olarak kullanabiliriz. SourceGuardian dosyalarından gelen kod bu şekilde elde edilemez, çünkü zend_compile_file() ile yer değiştirmez. Bununla birlikte, her ürün çözümlenmiş opcode arraylarini zend_execute () fonksiyonuna argüman olarak aktarır (bkz. Şekil 1. Kesikli ok ile gösterilen). Bu fonksiyonu hook ederek yürütmeyi durdurabilir ve ana opcode arrayini elde edebiliriz. Opcode metot ve fonksiyon arrayleri, PHP’nin dahili derleyicinin genel yapıları yardımıyla yeri tespit edilebilir. Bu şekilde, SourceGuardian ile korunan uygulamaların işlenmemiş PHP bayt kodu doğrudan alınabilir. IonCube ve Zend Guard için daha fazla karmaşıklaştırma, şaşırtma kaldırılmalıdır.

ionCube Opcode damping’i önlemek için, ionCube, çalıştırmadan önce XOR’un tek operasyon kodlarını ve daha sonra XOR’u tekrar çalıştıran bir çalışma zamanı karmaşasını uygular. Bu, bir kerede sadece bir opkodun kaldırılmasını sağlar. Ayrıca, ionCube native PHP motorunun bir kopyasını içerir ve baytkod PHP VM yerine loader (yükleyici) içinde işlenir. Sonuç olarak, Şekil 2’deki son adım dahil edilmemiş ve dinamik analiz için ionCube’un dahili zend_execute () işlevi hook (kanca) edilmelidir.

Gerçekleştirilen yönergeler iki teknikle karıştırılır. İlk olarak opcode numarası, işleyici adresi ve tüm opcode’ların işlenenleri XOR ile şifrelenir. İkincisi, atamalardaki sayısal işlenenler sabitlerle matematiksel işlemlerle karıştırılır. Opcode dizisinin reserve (ayrılmış) edilmiş değişkeni, opcode şifre çözme (bkz. Bölüm 3.2) ve atama açığa çıkarılmış anahtarlarını içeren bir ionCube yapısına atıfta bulunur. Her opcode, opcode indeksi tarafından atıfta bulunulan farklı bir anahtarla XOR edilir. Ardından, işlem kodu çalıştırılır ve aynı XOR işlemi kullanılarak tekrar okunamaz hale getirilir. IonCube yapısındaki tüm anahtarları alarak, tüm opcode’ları gizlenmemiş hale getirebiliriz.

Zend Guard Bir PHP dosyası ayrıştırıldığında opcode numarası, işleyici adresini çözmek için PHP yorumlayıcısı tarafından kullanılır. Bölüm 3.3’te belirtildiği gibi, Zend Guard, baytkodunu zend_execute () işlevine geçirmeden önce opcode numarasını kaldırır. Opcode numarasını tekrar elde etmek için opcode arama tablosundaki o anki işleyici adresini arayabiliriz. Bu, mevcut tüm opkodların indeksini hesaplayarak (bkz. Bölüm 2.2’deki Formül 1) ve mevcut adresin indeksiyle karşılaştırılarak elde edilir.

Karmaşık PHP Baytkod Decompiler (Geri Derleyici)

Bölüm 3’te sunulan bilgiler ve Bölüm 4.2’de sunulan teknikler kullanılarak bir geri derleme uyguladık. Bu amaçla, üç loader (yükleyici) uzantısı ve ayrıca özel bir PHP uzantısı olan bir PHP ortamı kurduk. Geri derleme işlemi 3 adımda gerçekleşti. Önce, bytecode’a erişmek için PHP yürütücüsünü çalıştırdık. İkinci olarak, geriye kalan tüm zor çözümü kaldırıp baytkodu bir dosyaya döküyoruz. Üçüncü olarak, dökülen baytkodu, PHP syntax e geri derledik. Bölüm 3’te sunulan bilgileri kullanarak, PHP baytkodunu yürütülmeden korunan PHP dosyasından statik olarak kurtarmak da mümkündür. Bununla birlikte, dinamik yaklaşım genel olarak uygulanabilirken, her yükleyici uzantısı için sürüm özgü bir ayrıştırıcı (parser) uygulaması gereklidir.

Hooking

PHP uzantısı zend_execute () fonksiyonunu kendi uygulamamızla değiştirerek hook ederiz. Ardından, PHP ortamımızda korunan bir PHP uygulamasının her dosyasını çalıştırırız. Bölüm 3’te açıklandığı gibi, karşılık gelen yükleyici uzantısı artık zend_compile_file () ile hook ediyor ve PHP bayt kodunu özel ikili formattan ayıklıyor. Bayt kodu zend_execute () ‘ya geçirildiğinde, uzantımız yürütmeyi sona erdirir. Çünkü SourceGuardian zend_compile_file () ‘yi hook etmez ve native PHP fonksiyonu olan sg_load() uygular, burada yalnızca zend_execute () fonksiyonunun ikinci çağrısını engelleriz. Bu yolla, baytkodunun başlangıçtaki şifre çözme ve ayrıştırma işlemini gerçekleştiren sg_load () fonksiyonunun zend_execute () fonksiyonuna tekrar geçmeden önce yeniden başlatılmasına izin veriyoruz.

Damping (Boşaltma)

IonCube ve Zend Guard için, Bölüm 4.2’de açıklandığı gibi daha fazla baytkodu açığa çıkarma işlemi gerçekleştiririz. Ardından, baytkod herhangi bir kodlayıcının modifikasyonuna açılır. Her opcode arrayi, operandlar (işlenenler) tarafından yönlendirilen çeşitli veri yapılarını içerir (bkz. Bölüm 2.2). VAR ve CV türündeki operanlar, vars yapı içinde saklanan bir isime sahip değişkenleri belirtir. Sabitler, CONST türündeki operand (işlenen) tarafından kullanılır ve literaller yapısı içinde bulunabilir. Opcodeların kendileri opcode yapısında saklanır. Opcode dizisi bir fonksiyonu temsil ediyorsa, parametreler arg_info yapısında bulunur. Ana opcode arrayini dökmeye başlıyoruz ve kullanıcı tanımlı fonksiyonlarla devam ediyoruz. Klaslar (Class) temelde üye değişkenler ve metot tablosu hakkında bilgi içeren kendi yapısında saklanır. Baytkodunu bir dosyaya attıktan sonra, daha sonraki işlemler için bizim geri derleyici dağıtılabilir.

Decompilation (Geri veya Ters Derleme)

Sonra, her talimat incelenir ve ilgili kaynak kodu gösterime dönüştürülür. Opcodelar üç farklı talimat türünden birine gruplanabilir:

Expressions (İfadeler), diğer talimatlarla operand (işlenen) olarak kullanılan geçici değerleri üreten talimatlardır, örneğin matematiksel bir işlem.

Statements ifade olarak kullanılamayan ve dönüş değeri olmayan, örneğin echo veya break gibi talimatlardır.

Jumps (Atlama) özel durum statement larıdır. Uygulamayı atlayarak erteler ve bir döngü veya koşullu kodu temsil eder.

Genel olarak, baytkodu kaynak koduna geri derlemenin en iyi yolu, ayrı ayrı temel blokları bağlayarak kodun her bir bölümünün ayrı ayrı dönüştürülebileceği bir grafik oluşturmaktır. Bununla birlikte, bu yaklaşım bu yazının kapsamı dışındadır. Konseptimiz için basit bir yaklaşım izliyoruz: Geri derleyicimiz, atlama ve döngü yapıları bulan bir model tanıma yaklaşımına dayanır. Bu yaklaşımın çoğu PHP kaynak kodunu kurtarmak için zaten yeterli olduğunu tespit ettik.

decoder

Tablo 3. Geri Derlenmiş Syntax ile Baytkod Örneği

Yazının Devamı için  ;

 PHP Bytecode Koruma Mekanizmasının Güvenlik Analizi 2

Yukarıdaki belirtilen linke tıklayıp 2. makaleye geçiniz lütfen.

Yorum yapın