Reverse Engineering
Reverse Engineering

Viber 26.6.4.0’da Frida ile SSL Pinning Analizi: 101 Walkthrough

Viber 26.6.4.0 üzerinde SSL pinning davranışını Frida ile nasıl analiz ettiğim: APK çıkarma, JADX ile ilgili metodları bulma ve kendi Frida script’imi yazma süreci.

Viber Frida SSL pinning kapak görseli

Bu yazıda Viber 26.6.4.0 üzerinde SSL pinning davranışını Frida ile nasıl analiz ettiğimi adım adım anlatıyorum: APK’yı nasıl çıkardım, JADX üzerinde hangi noktalara baktım ve sonunda kendi Frida script’imi nasıl oluşturdum. Amacım bir uygulamada SSL pinning’in nasıl analiz edilebileceğini anlamak; saldırmak ya da bir şeyi kötüye kullanmak değil.

Kullanılan Aletler
  • Frida 16.4.5runtime instrumentation, server modunda
  • JADXAPK statik analizi / decompile
  • ADBcihaz erişimi ve APK çekme

Lab Ortamı

Bu çalışma için fiziksel bir Android cihaz kullandım.

text
Cihaz: Samsung Galaxy S8
Cihaz durumu: Rootlu
Frida çalışma şekli: Frida server
Frida version: 16.4.5
Hedef paket: com.viber.voip
Hedef uygulama sürümü: Viber 26.6.4.0

Bu tarz analizlerde fiziksel cihaz kullanmayı daha pratik buluyorum. Özellikle Frida server ile çalışırken cihaz davranışını doğrudan görmek, uygulama başlatma/durdurma akışını kontrol etmek ve APK’yı cihazdan çekmek daha rahat oluyor.

1. Cihazda Hedef Uygulamayı Doğrulamak

İlk adımda cihazın bilgisayar tarafından görüldüğünü kontrol ettim.

bash
adb devices

Sonra Frida tarafında hedef uygulamanın cihazda görünüp görünmediğine baktım:

bash
frida-ps -Uai | grep viber

Bu komut bana iki şeyi doğruluyor: Frida cihazla düzgün konuşabiliyor mu ve hedef uygulamanın paket adı beklediğim gibi mi. Bu çalışmada hedef paket adı:

text
com.viber.voip

2. APK Yolunu Bulmak ve APK’yı Çekmek

Statik analiz yapabilmek için APK dosyasına ihtiyacım vardı. Önce cihaz üzerindeki APK path’ini buldum:

bash
adb shell pm path com.viber.voip

Bu komut hedef uygulamanın cihazdaki APK konumunu döndürür. Ardından APK’yı bilgisayarıma çektim:

bash
adb pull /data/app/com.viber.voip-rLR9OmRlPkluCQxsG4wyhg==/base.apk viber.apk

Burada önemli nokta şu: APK path’i cihazdan cihaza veya kurulumdan kuruluma değişebilir. Bu yüzden path ezberlemek yerine önce pm path ile güncel path’i almak daha doğru. Daha genel kullanım şöyle düşünülebilir:

bash
adb shell pm path com.viber.voip
adb pull <APK_PATH> viber.apk

APK’yı aldıktan sonra statik analiz için JADX ile açtım.

3. Önce Hazır CodeShare Script’lerini Denemek

İlk aşamada doğrudan kendi script’imi yazmadım. Önce daha önce paylaşılmış genel SSL bypass script’lerinin bu uygulamada işe yarayıp yaramadığını görmek istedim. Bu amaçla birkaç CodeShare script’i denedim:

bash
frida --codeshare fdciabdul/tiktokssl -f com.viber.voip -U
bash
frida --codeshare kaiserBloo/ssl-and-root-bypass -f com.viber.voip -U

Bu denemeler benim için bir başlangıç noktasıydı. Hazır script’ler bazen hızlı sonuç verir ama her uygulama ve her sürüm için yeterli olmayabilir. Bu noktada şunu gördüm:

Genel script’lerle deneme yapmak faydalı, ama asıl doğru yaklaşım uygulamanın kendi network stack’ini ve pinning davranışını anlamak.

Bu yüzden statik analize dönüp uygulamada gerçekten hangi sınıf ve metodların devrede olduğunu incelemeye başladım.

4. JADX ile İlgili Noktaları Aramak

JADX üzerinde doğrudan tek bir noktaya atlamadım. Önce SSL pinning ve network davranışıyla ilişkili olabilecek anahtar kelimeleri aradım:

text
pin
public key
certificate
trust anchor
cronet
ssl
x509

Bu aramalar sırasında org.chromium.net.impl.CronetEngineBuilderImpl sınıfı dikkatimi çekti. Sınıf adı zaten önemli bir ipucu veriyordu:

java
org.chromium.net.impl.CronetEngineBuilderImpl

Bu sınıf Chromium/Cronet tabanlı network davranışıyla ilişkiliydi. SSL pinning tarafında aradığım mantığın burada olabileceğini düşündüm.

5. addPublicKeyPins(...) Metodunu İncelemek

JADX üzerinde ilk dikkatimi çeken metodlardan biri şuydu:

java
@Override
public CronetEngineBuilderImpl addPublicKeyPins(String str, Set<byte[]> set, boolean z, Date date) {
    if (str == null) {
        throw new NullPointerException("The hostname cannot be null");
    }
    if (set == null) {
        throw new NullPointerException("The set of SHA256 pins cannot be null");
    }
    if (date == null) {
        throw new NullPointerException("The pin expiration date cannot be null");
    }

    String strValidateHostNameForPinningAndConvert = validateHostNameForPinningAndConvert(str);
    HashMap map = new HashMap();

    for (byte[] bArr : set) {
        if (bArr == null || bArr.length != 32) {
            throw new IllegalArgumentException("Public key pin is invalid");
        }
        map.put(Base64.encodeToString(bArr, 0), bArr);
    }

    this.mPkps.add(new Pkp(
        strValidateHostNameForPinningAndConvert,
        (byte[][]) map.values().toArray(new byte[map.size()][]),
        z,
        date
    ));

    return this;
}

Bu metodun yaptığı şey özetle şu sırayı izliyor:

Metodun yaptığı
  1. Hostname boş mu kontrol ediyor.
  2. Pin set’i boş mu kontrol ediyor.
  3. Expiration date boş mu kontrol ediyor.
  4. Hostname’i pinning için normalize ediyor.
  5. Public key pin değerlerini doğruluyor.
  6. Pin bilgisini mPkps listesine ekliyor.
  7. Builder instance’ını geri döndürüyor.

Benim için en kritik satır şuydu:

java
this.mPkps.add(new Pkp(...));

Çünkü burada public key pin bilgisi uygulamanın network builder yapısına ekleniyordu. Bu nedenle Frida tarafında ilk hedeflediğim nokta bu metod oldu. Amacım, bu metod çağrıldığında gerçek gövdesinin çalışmasını engellemek ve pin ekleme adımını atlamaktı.

6. enablePublicKeyPinningBypassForLocalTrustAnchors(...) Metodunu İncelemek

İkinci dikkatimi çeken metod şuydu:

java
@Override
public CronetEngineBuilderImpl enablePublicKeyPinningBypassForLocalTrustAnchors(boolean z) {
    this.mPublicKeyPinningBypassForLocalTrustAnchorsEnabled = z;
    return this;
}

Bu metod oldukça sade. Dışarıdan gelen boolean değeri alıp şu field’a yazıyor:

java
mPublicKeyPinningBypassForLocalTrustAnchorsEnabled
Eğer bu davranış boolean bir değerle yönetiliyorsa, runtime sırasında bu değeri true tarafına zorlayabilirim.

Bu yüzden script’in ikinci parçasında bu metodu hedefledim.

7. Frida Script’ini Yazmak

JADX üzerinde iki önemli noktayı bulduktan sonra Frida script’ini oluşturdum. Amaç iki parçaydı: addPublicKeyPins(...) çağrılarını yakalayıp pin ekleme adımını atlamak, ve enablePublicKeyPinningBypassForLocalTrustAnchors(...) metodunda değeri true olarak zorlamak. Kullandığım script:

javascript
/*
    Android Viber 26.6.4.0 SSL certificate pinning
    by Yasar Kahramaner

    Run with:
    frida -U -f com.viber.voip -l viber-26-6-4-0-ssl-pinning.js
*/

Java.perform(() => {
    const B = Java.use('org.chromium.net.impl.CronetEngineBuilderImpl');

    B.addPublicKeyPins.overloads.forEach(o => {
        o.implementation = function(host, set, enforce, date) {
            console.log('skip pins for', host);
            return this;
        };
    });

    B.enablePublicKeyPinningBypassForLocalTrustAnchors
        .overload('boolean')
        .implementation = function(_) {
            console.log('force bypass local trust anchors');
            return this.enablePublicKeyPinningBypassForLocalTrustAnchors(true);
        };
});

8. Script’in Parça Parça Açıklaması

İlk olarak Java runtime hazır olduğunda kodun çalışmasını sağlıyorum:

javascript
Java.perform(() => {
    // hooks
});

Ardından hedef class’ı alıyorum. Bu, JADX üzerinde incelediğim ve pinning davranışıyla ilişkili olduğunu düşündüğüm class’tı:

javascript
const B = Java.use('org.chromium.net.impl.CronetEngineBuilderImpl');

Sonra addPublicKeyPins(...) metodunun tüm overload’larını hook’luyorum:

javascript
B.addPublicKeyPins.overloads.forEach(o => {
    o.implementation = function(host, set, enforce, date) {
        console.log('skip pins for', host);
        return this;
    };
});

Buradaki amaç şu: metod çağrıldığında log basmak, orijinal gövdeyi çalıştırmamak ve this döndürerek builder chain davranışını bozmamak. Normalde bu metod mPkps listesine pin ekliyordu; ben doğrudan return this; yaparak pin ekleme adımını atlamış oldum.

İkinci hook ise local trust anchor bypass davranışını hedefliyor:

javascript
B.enablePublicKeyPinningBypassForLocalTrustAnchors
    .overload('boolean')
    .implementation = function(_) {
        console.log('force bypass local trust anchors');
        return this.enablePublicKeyPinningBypassForLocalTrustAnchors(true);
    };

Bu bölümde metoda hangi değer verilirse verilsin, değeri true olarak zorladım.

9. Script’i Çalıştırmak

Script’i çalıştırmak için kullandığım komut:

bash
frida -U -f com.viber.voip -l ./viber.js

Buradaki bayraklar kısaca: -U USB ile bağlı cihazı hedefler, -f com.viber.voip uygulamayı spawn ederek başlatır, -l ./viber.js yazdığım script’i yükler. Bu akış benim için daha temizdi çünkü uygulamayı baştan Frida ile başlatıp hook’ların erken aşamada devreye girmesini sağlayabiliyordum.

10. Doğrulama

Bu tarz çalışmalarda sadece script çalıştı demek yeterli değil. Gerçekten doğru noktaya müdahale edip etmediğini doğrulamak gerekiyor. Doğrulama sırasında özellikle şunlara baktım:

Baktığım noktalar
  1. Script hata vermeden yükleniyor mu?
  2. addPublicKeyPins(...) hook’u tetikleniyor mu?
  3. Console üzerinde skip pins for ... logları görünüyor mu?
  4. enablePublicKeyPinningBypassForLocalTrustAnchors(...) hook’u tetikleniyor mu?
  5. Uygulama normal akışında çalışmaya devam ediyor mu?
  6. Müdahale beklenenden daha geniş bir etki oluşturuyor mu?

Burada gerçek host, endpoint, token, request veya response paylaşmıyorum. Benim için önemli olan trafik içeriği değil, runtime sırasında hedef metodların gerçekten tetiklenip tetiklenmediğini görmekti.

11. Neden Hazır Script Değil de Kendi Script’im?

Hazır CodeShare script’leri bazen işe yarayabilir. Fakat benim için asıl öğrenme, uygulamanın kendi davranışını anlayıp hedefe özel bir script yazmakta. Genel SSL bypass script’lerini denedikten sonra şunu fark ettim:

Bir uygulamada hangi network stack kullanılıyor, pinning nerede ekleniyor ve runtime sırasında hangi metodlar tetikleniyor anlamadan yazılan script’ler çoğu zaman tahmine dayalı kalıyor.

Bu yüzden kendi script’imi daha hedefli tuttum. Bu, her uygulamada çalışır bir çözüm değil; özellikle Viber 26.6.4.0 üzerinde gözlemlediğim Cronet tabanlı davranışa göre hazırlanmış bir çalışma.

12. Karşılaşılabilecek Sorunlar

Bu tarz analizlerde birkaç sık problem yaşanabilir.

Frida cihazı görmüyor

Önce cihazın ADB tarafından göründüğünü, sonra Frida tarafını kontrol etmek gerekir:

bash
adb devices
bash
frida-ps -Uai

Paket adı yanlış olabilir

Paket adını doğrulamak için:

bash
frida-ps -Uai | grep viber

veya:

bash
adb shell pm list packages | grep viber

APK path’i değişmiş olabilir

APK path’i sabit değildir. Önce path bulunmalı, sonra dönen path kullanılmalı:

bash
adb shell pm path com.viber.voip
bash
adb pull <APK_PATH> viber.apk

Class veya metod adı değişmiş olabilir

Uygulama sürümü değiştiyse CronetEngineBuilderImpl, addPublicKeyPins veya ilgili metodlar farklı olabilir. Bu yüzden her analizde sürüm bilgisini sabitlemek ve JADX üzerinde tekrar arama yapmak gerekir.

Hook tetiklenmiyor olabilir

Hook tetiklenmiyorsa birkaç ihtimal vardır: uygulama farklı bir network stack kullanıyor olabilir, ilgili metod o akışta çağrılmıyor olabilir, hook geç yükleniyor olabilir, uygulama sürümü değişmiş olabilir veya ilgili davranış native tarafta gerçekleşiyor olabilir.

Öğrendiklerim

Bu çalışma bana birkaç şeyi hatırlattı. Hazır bypass script’leri faydalı başlangıç noktaları olabilir ama her zaman yeterli değildir; gerçek öğrenme uygulamanın kendi davranışını anlamaya çalışınca başlıyor. JADX ve Frida birlikte çok güçlü bir akış oluşturuyor: JADX ile statik tarafta olası noktaları buluyorsun, Frida ile runtime’da bunların gerçekten çalışıp çalışmadığını test ediyorsun. Sürüm bilgisi çok önemli; bu not özellikle Viber 26.6.4.0 için geçerli. Ve script’in çalışmasından daha önemlisi, neye müdahale ettiğini anlayabilmek; aksi halde elinde çalışan ama açıklayamadığın bir sonuç olur.

Public Kayıt

Bu çalışmanın public kaydı Frida CodeShare üzerinde şu adla yer alıyor:

text
YasarKah / viber-26-6-4-0-ssl-pinning

Bu yazı ise CodeShare’deki script’in arkasındaki analiz sürecini, kullandığım araçları ve nasıl ilerlediğimi dokümante etmek için hazırlandı.