Frida ile Android Şifreleme İşlemlerini İnceleme

Mobil uygulamalar, sunucu ile haberleşmek için genellikle HTTP protokolünü kullanırlar. Mobil uygulamalar HTTP protokolü üzerinden iletilen oturum, kullanıcı bilgileri, yapılacak işlem özelindeki parametreleri, bir proxy yazılım ile araya girilmesi durumunda tespit edilmemesi için bu verileri şifreleyerek sunucuya iletir. Sunucu, şifrelenmiş verileri çözer ve işlemi yapmaya devam eder.

RSA ŞİFRELEME

Android uygulamalarda şifreleme işlemlerinin nasıl olduğunu daha iyi anlayabilmek için OWASP MSTG-Hacking-Playground uygulaması incelenecektir. 

Uygulamanın APK dosyası mobil emülatöre veya cihaza kurulduktan sonra uygulama aşağıdaki gibi bir ekran ile açılır:

 

Şifreleme işlemleri için OMTG-DATAST-001-KEYSTORE modülüne giriş yapılması gerekir. 

Modüle giriş yapıldıktan sonra aşağıdaki sayfa açılır.

 

 

Modül android cihaz üzerinden analiz edildiğinde “Clear Text” alanına girilen değeri şifrelediği ve tekrar çözdüğü tespit edilmiştir. 

“Jadx-gui” aracı ile APK dosyası analiz edildiğinde “encrypt” şifreleme işlemini gerçekleştiren fonksiyon(sg.vp.owasp_mobile.OMTG_Android.OMTG_DATAST_001_KeyStore) tespit edilmiştir.

 

 

Kodları satır satır incelemek gerekirse:

145-146: Keystore kullanılarak RSA şifreleme için gereken “public-key” değeri elde edilir.

157: Şifrelenecek veri input alanından okunur.

163: “Cipher” nesnesi gerekli parametreler ile oluşturulur.

164: “Cipher” nesnesinin “init” fonksiyonu çağrılır ve şifreleme işlemi gerçekleştirilir.

166-173: Elde edilen şifreli veri base64 ile encode edilir, ekrana ve loglara basılır.

Şifreleme işleminin gerçekleştirildiği asıl kod bloğu 163. ve 164. satırda bulunmaktadır.

 

 

Cipher” nesnesinin nereden import edildiği kod satırının en üstünde bulunan satırlar incelenerek elde edilebilir.

 

 

“Cipher” nesnesinin kaynak kodlarına http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/tip/src/share/classes/javax/crypto/Cipher.java linkinden erişilebilir.

Frida ile “init” fonksiyonu hooklayarak şifreleme işlemini analiz edilebilir. “init” fonksiyonunu hooklamak için ilk önce fonksiyonun parametrelerinin belirlenmesi gerekmektedir. Yukarıdaki URL de bulunan kaynak kodlar incelendiğinde “init” fonksiyonunun farklı değerler ile “overload” edildiği tespit edilmiştir. Uygulama ise ilk parametresinde “int” ve ikinci parametresinde ise “RSAPublicKey” tipinde bir key değeri almaktadır.

Overload edilen fonksiyonlar analiz edildiğinde “init” fonksiyonunu çağıran kodun parametreleri ile çalışan fonksiyon tespit edilebilir.

 

‘use strict;’

 

Java.perform(() => {

    const cipher = Java.use(‘javax.crypto.Cipher’);

    cipher.init.overload(‘int’,’java.security.Key’).implementation = function(opmode,key) {

        console.log(“Opmode “+opmode);

        console.log(“Key “+key);

    }

})

Frida kodu çalıştırıldığı zaman uygulamanın ve frida arayüzünün çıktısı aşağıdaki ekran görüntüsündeki gibi olmaktadır.

 

 

“init” fonksiyonu başarı ile hooklandı fakat hooklarken fonksiyonun gerçekleştireceği işlemler çalıştırılmadığı için uygulama hata verdi. Bunun önüne geçebilmek için aşağıdaki frida kodu yazılabilir.

‘use strict;’

 

Java.perform(() => {

    const cipher = Java.use(‘javax.crypto.Cipher’);

    cipher.init.overload(‘int’,’java.security.Key’).implementation = function(opmode,key) {

        console.log(“Opmode “+opmode);

        console.log(“Key “+key);

        this.init.overload(‘int’, ‘java.security.Key’).call(this, opmode, key);

    }

})

Yukarıdaki kod init fonksiyonunu tekrar çağıracak ve işlemlerin gerçekleştirilmesini sağlayacaktır. Bu şekilde fonksiyonu hooklanmış ve akış bozulmadan işlemler devam ettirilebilmiştir.

Bu adımdan sonra “javax.crypto.Cipher” nesnesinin tüm fonksiyonları çağrılıp gerekli bilgiler çekilebilir. Örneğin “getOpmodeString” fonksiyonunun çağrılmasını sağlayan frida kodu:

‘use strict;’

 

Java.perform(() => {

    const cipher = Java.use(‘javax.crypto.Cipher’);

    cipher.init.overload(‘int’,’java.security.Key’).implementation = function(opmode,key) {

       

        console.log(“Opmode “+opmode);

        console.log(“Key “+key);

        console.log(“Opmode String: “+ this.getOpmodeString(opmode));

        this.init.overload(‘int’, ‘java.security.Key’).call(this, opmode, key);

    }

})

 

İlgili Java (“javax.crypto.Cipher”) nesnesinin “getAlgorithm” fonksiyonu kullanılarak şifreleme algoritması da elde edilebilir.

“Key” değerini elde etmek için ise “java.security.Key” sınıfının kaynak kodları analiz edilmelidir:

https://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/7fcf35286d52/src/share/classes/java/security/Key.java

Nesnesinin “getEncoded” metodu kullanılarak “Key” değeri elde edilebilir. “Key” değerinin elde edilmesine ait frida kodu:

‘use strict;’

 

Java.perform(() => {

    const cipher = Java.use(‘javax.crypto.Cipher’);

    cipher.init.overload(‘int’,’java.security.Key’).implementation = function(opmode,key) {

       

        console.log(“Opmode “+opmode);

        console.log(“Key “+key.getEncoded());

        console.log(“Opmode String: “+ this.getOpmodeString(opmode));

        console.log(“Algorithm: “+this.getAlgorithm())

        this.init.overload(‘int’, ‘java.security.Key’).call(this, opmode, key);

    }

})

Yukarıda belirtilen frida kodunun çıktısı aşağıda verilmiştir.

 

 

“javax.crypto.Cipher” nesnesi incelendiğinde ilgili fonksiyonun byte dizisi döndürdüğü anlaşılmaktadır:

 

 

Byte dizisinin daha anlaşılır olabilmesi için Java dilinin “java.util.Base64” nesnesinin fonksiyonlarından yararlanılabilir:

‘use strict;’

 

Java.perform(() => {

    const cipher = Java.use(‘javax.crypto.Cipher’);

    cipher.init.overload(‘int’,’java.security.Key’).implementation = function(opmode,key) {

        var base64 = Java.use(‘java.util.Base64’);

        console.log(“Opmode “+opmode);

        console.log(“Key “+base64.getEncoder().encodeToString(key.getEncoded()));

        console.log(“Opmode String: “+ this.getOpmodeString(opmode));

        console.log(“Algorithm: “+this.getAlgorithm())

        this.init.overload(‘int’, ‘java.security.Key’).call(this, opmode, key);

    }

})

 

AES ŞİFRELEME

Mobil uygulamalarda sunucu ve istemci arasındaki verilerin şifrelenmesi için genellikle “AES/CBC” şifreleme algoritması kullanılmaktadır. İlgili algoritmanın nasıl çalıştığını anlamak için https://github.com/Serhatcck/android_aes_encryption adresindeki android uygulamasını analiz etmek yararlı olacaktır.

Uygulama android cihazına yüklendiğinde basit bir arayüz ile açılmaktadır.

 

Uygulama üzerinde bulunan butonlar ile şifreleme işlemi gerçekleştirildiğinde iki farklı durum ortaya çıkmaktadır. “RANDOM KEY ENCRYPT” butonuna tıklandığında her işlemde farklı bir değer ortaya çıkmaktadır. “ENCRYPT” butonuna tıklandığında ise oluşan şifrelenmiş veri aynı olmaktadır.

Uygulamanın kaynak kodları analiz edildiğinde “RANDOM KEY ENCRYPT” butonu ile eşleşen fonksiyonun her işlemde farklı bir “IV Secret” ve “Secret Key” değerleri ürettiği tespit edilmiştir.

 

 

“ENRCYPT” butonu ile eşleşen fonksiyonun ise tek seferliğe mahsus olmak üzere “IV Secret” ve “Secret Key” değerleri ürettiği tespit edilmiştir.

 

 

İlgili fonksiyonlarda bulunan şifreleme paremetrelerini elde edebilmek için frida scripti yazmak doğru olacaktır.

Uygulamanın kaynak kodları incelendiği zaman şifreleme işlemini gerçekleştiren fonksiyonun “encrpyt” fonksiyonu olduğu tespit edilmiştir:

 

 

Yukarıda bulunan görselde de görüldüğü üzere uygulama “javax.crypto.Cipher” nesnesinin 3 değişkenli init fonksiyonunu kullanmaktadır.

İlgili fonksiyonu frida ile hooklamak için aşağıdaki kod bloğu kullanılabilir.

Java.perform(function(){

    var cipher = Java.use(“javax.crypto.Cipher”)

    cipher.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).implementation = function(opmode,key,ivkey){

        this.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).call(this, opmode,key,ivkey)

    }

})

Fonksiyonu hookladıktan sonra fonksiyon ile ilgili bilgileri aşağıdaki frida kodu ile elde edebiliriz:

Java.perform(function(){

    var cipher = Java.use(“javax.crypto.Cipher”)

    cipher.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).implementation = function(opmode,key,algorithmParameter){

        var base64 = Java.use(‘java.util.Base64’);

        console.log(“Key “+base64.getEncoder().encodeToString(key.getEncoded()));

        console.log(“Opmode String: “+ this.getOpmodeString(opmode));

        console.log(“Algorithm: “+this.getAlgorithm())

 

        this.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).call(this,opmode,key,algorithmParameter)

    }

})

Yukarıdaki kod bloğu çalıştırıldığında aşağıdaki gibi bir çıktı verecektir:

 

 

“Secret Key” değerini başarılı bir şekilde elde edilebildi fakat “Iv Key” değeri elde edilemedi. “Iv Key” değerinin elde edilebilmesi için “javax.crypto.spec.IvParameterSpec” nesnesinin de hooklanması gerekir. Uygulamada bulunan kaynak kodlar analiz edildiğinde “Iv Key” değerini oluşturan fonksiyonun parametre olarak tek bir byte dizisi alan bir fonksiyon olduğu tespit edilmiştir:

 

 

Yukarıda belirtilen fonksiyonu hooklamak için kullanılacak olan frida kodu aşağıda verilmiştir:

  Java.perform(function(){

    var base64 = Java.use(‘java.util.Base64’);

    var cipher = Java.use(“javax.crypto.Cipher”)

    cipher.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).implementation = function(opmode,key,algorithmParameter){

       

        console.log(“Key “+base64.getEncoder().encodeToString(key.getEncoded()));

        console.log(“Opmode String: “+ this.getOpmodeString(opmode));

        console.log(“Algorithm: “+this.getAlgorithm())

 

        this.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).call(this,opmode,key,algorithmParameter)

    }

 

    var ivParameter =  Java.use(‘javax.crypto.spec.IvParameterSpec’);

    ivParameter.$init.overload(‘[B’).implementation = function(ivKey){

        console.log(“Iv Key “+base64.getEncoder().encodeToString(ivKey))

 

        this.$init.overload(‘[B’).call(this,ivKey)

    }

})

Frida kodunun çıktısı aşağıdaki gibi olacaktır.

 

 

Önemli bir nokta ise “RANDOM KEY ENCRYPT” butonuna basıldığında her zaman “Iv Key” değeri çıktı olarak verilmesine rağmen “ENCRYPT” butonuna sadece ilk basıldığında verilmektedir. “ENCRPYT” butonun bağlı olduğu fonksiyon her defasında yeni bir “Iv Key” değeri oluşturmadığı için “$init” fonksiyonu tetiklenmemekte ve “Iv Key” değeri de çıktıda görülmemektedir.

Elde edilen “Secret Key” ve “Iv Key” değerleri kullanılarak gönderilen şifreli metin çözümlenebilir veya istenilen bir metin şifrelenebilir.

İlgili frida kodu daha ayrıntılı bilgi vermek üzere değiştirilebilir:

Java.perform(function(){

    var base64 = Java.use(‘java.util.Base64’);

   

    var cipher = Java.use(“javax.crypto.Cipher”)

    cipher.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).implementation = function(opmode,key,algorithmParameter){

       

        send_log(“Key”,base64.getEncoder().encodeToString(key.getEncoded()));

        send_log(“Opmode String”,this.getOpmodeString(opmode));

        send_log(“Algorithm”,this.getAlgorithm())

 

        this.init.overload(‘int’, ‘java.security.Key’, ‘java.security.spec.AlgorithmParameterSpec’).call(this,opmode,key,algorithmParameter)

    }

 

    var ivParameter =  Java.use(‘javax.crypto.spec.IvParameterSpec’);

    ivParameter.$init.overload(‘[B’).implementation = function(ivKey){

        send_log(“Iv Key”,base64.getEncoder().encodeToString(ivKey))

        this.$init.overload(‘[B’).call(this,ivKey)

    }

 

    cipher.doFinal.overload(“[B”).implementation = function(input){

        var input_base64 = base64.getEncoder().encodeToString(input)

        var input_string = byte_to_string(input)

        var output = this.doFinal.overload(‘[B’).call(this,input)

        var output_base64 = base64.getEncoder().encodeToString(output)

        send_log(“Input Base64”,input_base64)

        send_log(“Input String”,input_string)

        send_log(“Output Base64”,output_base64)

        return output;

    }

})

 

function send_log(string, value){

    console.log(“[+] “+string+” : “+value);

}

 

 

 

function byte_to_string(byte_array){

    var StringClass = Java.use(‘java.lang.String’);

    return StringClass.$new(byte_array).toString();

}

İlgili Frida kodunun çıktısı aşağıdaki ekran görüntüsündeki gibi olacaktır:

 

 

Frida kodunun daha özelleştirilmiş halini https://codeshare.frida.re/@Serhatcck/java-crypto-viewer/ adresinden elde edebilirsiniz.

Serhat ÇİÇEK tarafından hazırlanmıştır.

Yorum Yaz

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

*
*

Mail listemize üye olarak eğitim fırsatlarını kaçırmayın!
Eğitim ve ücretsiz etkinliklerizden haberdar olmak için e-posta listesimize üye olun!.