Man In The Middle – 1

Amacımız: Karşıdaki bir sunucuya HTTPS ile bağlanan bir browser’ın oluşturduğu trafiği decrypted olarak görebilmek.

PROLOGUE

Bu yöntemi mümkün olduğunca bizim ilerleyişimize paralel olarak anlatmaya çalışacağım. Çünkü başlarda çok önemli görünmeyen ayrıntıların ileride ciddi tasarım değişikliklerine yol açması veya zor farkedilen detaylara neden ihtiyaç duyulduğunun farkedilmesi açısından bu daha faydalı olacaktır. Ayrıca bu süreç boyunca ilerleyişimizi de çözüme en çabuk ulaşan yolu izleyerek yapmadığımızı önceden belirtmek gerek. Konunun detaylarına başlarda hakim olmamamızdan veya inline çalışma ve ssl ile ilgili tecrübe eksikliğimizden kaynaklanan hatalar ve öğrenme odaklı yaptığımız çalışmalardan dolayı aralarda önemli  değişiklikler yapmak zorunda kalacağız.

NOT JUST ANOTHER SSL PROXY..

Önceki yazılarda anlattığım üzere, bir ssl client’ın encrypted trafiğinin çözülmesi için gereken master-key’e öğrenmek, ssl handshake sırasında kullanılan sertifikanın ait olduğu private-key’e (PrK) sahip olmaktan geçiyor. Bunun için ya kullanıcıya giden gerçek sertifikanın PrK’sini çalmanız , ya da trafiğin arasına girerek kullanıcının gerçek server ile değil sizinle konuşmasını, dolayısıyla da göndereceği verileri gerçek sertifikada değil, sizin yolladığınız sertifikadaki bilgilere göre şifrelenmiş bir şekilde yollamasını sağlamanız lazım. Bu ikinci yönteme Man In the Middle yöntemi diyoruz.

İlk gereksinimimiz, warez sitelerinde bolca bulunabilen ssl-proxy programlarının aksine, bizim bütün bağlantıları tek bir process içinde tek bir thread ile kontrol etme zorunluluğumuz. Normalde bu programlarda server için accept(), client için de connect() metodlarının her kullanımında yeni bir thread açıp, programın normal akışını etkilemeden blocking socketler ile gerekli işler halledilebiliyor. Ayrıca her thread kendi bağlantısıyla ilgili bilgileri tuttuğu için, daha sonra değineceğim tcp_header bilgilerinin hesaplanması ve session resumption durumları için ekstra bir iş yapmaya gerek kalmıyor.

Önce tek bir SSL Session için çalışabilecek bir modül hazırlamayı deniyoruz.

Aradaki adam olarak, yapmamız gereken temel iş, şifreli gelen verilerin deşifre edilip, tekrar şifrelenerek gönderilebilmesi. Bunun için OpenSSL kütüphanesini kullanıyoruz. Bu kütüphanenin genel kullanımı da ssl-proxy programlarına benzer şekilde client ve server socketleri açarak çalışıyor. Ama biz socket kullanamayacağımız için farklı bir yol izliyoruz. Kütüphanenin IO handler olarak kullanılabilecek farklı yapıları var. Genel olarak BIO_* şeklinde isimlendirilen bu yapılardan bizim kullanacağımızın ismi BIO_s_mem. Bu interface ile OpenSSL’in socketler üzerinden değil, memory’den okuyup tekrar memory’ye yazacak şekilde çalışmasını ayalayabiliyoruz.

OPENSSL FOR DUMMIES

OpenSSL içinde bilinmesi gereken iki önemli yapı var. SSL ve SSL_CTX. Environment bilgileri, yani sertifika, ssl versiyonu, gibi bilgiler context içinde tutuluyor. Yapılacak her bağlantı için de (client veya server) bu context bilgileri kullanılarak yeni bir SSL oluşturuluyor. Bu yapı temelde bir SSL Session’u temsil ediyor. Bunu SSL Connection ile karıştırmamak lazım. Bir session’a yapılan ilk bağlantıdan sonra, farklı ‘resumption’ mekanizmaları kullanılarak yeni bağlantılar da oluşturulabilir. Bizim kullandığımız SSL yapısı bu devam eden Session olacak. Bunun için client ile konuşacak bir fake server(FS), server ile konuşacak bir fake client(FC) olarak kullanılmak üzere iki SSL objesi oluşturup, her ikisinin de input ve output BIO’larını BIO_s_mem kullanacak şekilde ayarlıyoruz. Bu 4 BIO_s_mem, session’lara şifreli verinin verilmesi ve şifrelenmiş verinin alınması için kullanılacak. OpenSSL tarafından çözülen açık verilerin okunması ve tekrar yazılması için de SSL_read()/write() metodlarını kullanabiliriz.

Kısaca şu şekildeki gibi bir tasarımımız var:

Memory BIO’lar içinde şifreli veriler, SSL_read metodlarında da çözülmüş veriler bulunuyor.

PEKİ SORUN NE?

Bu noktadaki tasarımımız, biraz “ssl accelerator” tadındaydı. FS ve FC’ye gelen her mesaj, direk olarak ilgili SSL’e verilip oluşan cevap geri dönüyordu. Bu yöntemin bazı avantajları olsa da pratikte pek işe yaramayacağı belli aslında. Her iki taraf da anında cevap döndüğü için, tcp header state’lerini kaydetmek zorunda kalmıyoruz. Gelen paketin seq ve ack numaralarını paketteki payload_len ile toplayarak yeni paketin header’ını kolay bir şekilde hesaplamak mümkün. Ancak iki tarafın da senkronize bir şekilde gitmesi bu tasarımda kolay değil, çünkü gelen paketlere üretilen cevaplar karşı tarafı etkilemeden olduğu gibi geri dönüyor. Dolayısıyla bir tarafta ortaya çıkacak farklılıkların (session resumption, alert) manuel olarak kontrol edilip diğer tarafa yansıtılması gerekiyor ki, bu tür temel işleri mümkün olduğunca OpenSSL’in kendisinin halletmesini istediğimizden pek tercih edilir bir durum değil.

Asıl önemli sorun ise sertifikanın nasıl oluşturulacağı. Client üzerindeki browser hangi adrese gitmek istediğini önceden biliyor. Bu adresle ilgili DNS sorgusunu yaptıktan sonra bulduğu IP’ye 443 portundan bağlanıp SSL Handshake işlemlerine başlıyor. Dolayısıyla bu handshake tamamlanıp “HTTP GET” paketi gidene kadar hangi siteye bağlanmak istediğini trafiğin ortasında iken bilmek mümkün değil. Tabiki ReverseDNS ile veya önceden bazı sitelerin IP’lerini kaydedip bunların içinden bir arama yapılabilir, ancak bu tür sorgular bizi fazlasıyla yavaşlatacağı için bu mümkün değil.

Aslında bilmemiz gereken zaten sitenin adresi de değil. Amacımız client’ı server olduğumuza inandırmak olduğu için, server’ın göndereceği sertifikadaki bilgileri gönderiyor olmak bize yetecektir. Dolayısıyla kendi sertifikamızı oluşturmadan önce server’dan gerçek sertifikayı almamız gerekiyor. Bunun için de client’dan ClientHello(CH) geldikten sonra geri dönüş yapmadan FC’yi tetikleyip karşıdan ServerHello(SH) ve Certificate(CRT) mesajlarının alınmasını beklemek gerek.

Sertifika konusunu bi tarafa, öncelikle taraflar arası bağlantıyı sağlayacak bir tasarıma geçtik..

Bu noktada basitlik adına yaptığımız bir varsayım, iki tarafta da eşit sayıda mesaj ile handshake’in tamamlanacağı şeklindeydi. Dolayısıyla iki taraftaki SSL nesnelerinin de state’leri ortak olarak devam edecekti. Bunun için yapılması gereken, client’dan ilk CH mesajını aldıktan sonra bunu FC’ye vermek. Bunu verdiğimiz anda FC içinde bu mesaja cevap olarak üretilen SH mesajı hazırlanıyor ama mem_from_fs üzerinden bu veriyi hemen okumuyoruz. Çünkü gerçek server’dan bir cevap gelmeden client’a dönmek istemiyoruz. Ayrıca bu yaklaşım, CH’den sonra hem client’a SH dönüp, aynı zamanda server’a da CH yollamak gibi tek pakete karşılık iki paket yollamak gibi bir gereksinimden de kurtarıyor.

Tüm bu varsayımlara ve tasarımlara girmeden önce, daha alt seviyede çözülmesi gereken çok daha öncelikli bir konu var:

TCP HEADER: THE NIGHTMARE

Ethernet ve IP katmanından geçen veriler, işletim sistemi tarafından TCP katmanına verilir. Burada paketin hangi process’e ait olduğu (port numarasından) bu stream’deki offset’i (seq no) ve gönderilen verilerin ne kadarının karşı tarafa ulaştığı (ack no) belirlendikten sonra checksum kontrolü yapılıp ilgili process tarafından tcp header’dan sonraki kısım işlenir. Bu bizim senaryomuzda SSL verisinin ilk byte’ına denk geliyor. Yani içinde payload olmayan paketlerden uygulamanın haberi olmuyor ama ack/seq numaraları gidip gelmeye devam ediyor.

Peki işletim sistemi bu sayıları ne için kullanıyor?

tcp içinde gönderilen verilerin işletim sistemi açısından bakıldığında bir sınırı yoktur. Yani bir tcp session’ı boyunca bir tarafa giden tüm veri, sanki tek bir büyük veri bloğu gibi muamele görür. İşletim sisteminin görevi tcp header içindeki bilgileri kullanarak, bu verilerin doğru bir sırayla(1), tam olarak(2) ve bozulmadan(3) uygulamalara iletilmesini sağlamaktır.

Verilerin sıralanması için kullanılan alanlar ack ve seq numaraları. Her paketin başlangıcında, bu paketteki verilerin, stream’in ilk başındaki paketten kaç byte sonra başladığı ‘seq no’ alanında belirtilir. Burada dikkat edilmesi gereken bir nokta, bu sayının net bir offset olarak verilmez. Yani ilk pakette seq no sıfır değil, random 32bit’lik bir sayıdır. Ve tabiki network ordered olarak gelir. Dolayısıyla tam olarak offset’i hesaplamak için gelen paketteki host order’a çevrilmiş değerin ilk paketteki seq numarasının host order’a çevrilmiş halinden ne kadar fazla olduğuna bakılabilir. Burada ilk pakete göre değil bir önceki pakete göre offset alınarak da sıralama kontrolü yapılabilir tabi.

Bizim gönderdiğimiz verilerin ne kadarının karşı tarafa ulaştığı ise ack değeri ile kontrol edilir. Stream’in ilk başından şu ana kadar karşı tarafın aldığı toplam veri miktarı (byte olarak) son gelen paketteki ack değerine yine yukardaki gibi bir offset hesaplaması uygulanarak bulunur. Bu noktadan sonra gönderilecek pakete bu sonuca göre karar verilir. Şöyle ki:

Eğer bu değer bizim gönderdiğimiz son seq_no+payload’dan küçük ise, son paketlerin bir kısmı gitmemiş demektir. İşletim sistemindeki implementasyona bağlı olarak, paketin kalan kısmı veya tamamı tekrar gönderilir. Bu bizim için önemli çünkü eğer asıl paket aradaki bir makineden (bu biz oluyoruz) daha önce geçtiyse, tekrar gönderilen paketi bizim uygulamamızın çalıştığı işletim sistemi retransmission olarak algılayıp bize yollamayacaktır. Ack/seq değerleri yeniden düzenlenmeden server’a gönderilen bu retransmitted paket server tarafındaki state’i etkileyebilir. Bizim ssl state’lerimizin consistent kalması için zaten retransmitted paketleri işleme sokmadan discard edeceğiz, ama uygulamanın ihtiyaçlarına göre bu durumu göz önünde bulundurmakta fayda var.

Peki bu durum mümkün mü? Sonuçta paketin tamamı karşıya gittiyse karşıdan gelen ack değerinin de sorunsuz olarak bunu onaylaması gerekmez mi?

Normalde, paket kaybı veya başka bir aksaklık olmadığı sürece “decent” bir network içinde olmaz tabiki. Ne var ki; “these are not decent places”

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s