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.

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.
- 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.
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.0Bu 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.
adb devicesSonra Frida tarafında hedef uygulamanın cihazda görünüp görünmediğine baktım:
frida-ps -Uai | grep viberBu 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ı:
com.viber.voip2. 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:
adb shell pm path com.viber.voipBu komut hedef uygulamanın cihazdaki APK konumunu döndürür. Ardından APK’yı bilgisayarıma çektim:
adb pull /data/app/com.viber.voip-rLR9OmRlPkluCQxsG4wyhg==/base.apk viber.apkBurada ö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:
adb shell pm path com.viber.voip
adb pull <APK_PATH> viber.apkAPK’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:
frida --codeshare fdciabdul/tiktokssl -f com.viber.voip -Ufrida --codeshare kaiserBloo/ssl-and-root-bypass -f com.viber.voip -UBu 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:
pin
public key
certificate
trust anchor
cronet
ssl
x509Bu aramalar sırasında org.chromium.net.impl.CronetEngineBuilderImpl sınıfı dikkatimi çekti. Sınıf adı zaten önemli bir ipucu veriyordu:
org.chromium.net.impl.CronetEngineBuilderImplBu 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:
@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:
- Hostname boş mu kontrol ediyor.
- Pin set’i boş mu kontrol ediyor.
- Expiration date boş mu kontrol ediyor.
- Hostname’i pinning için normalize ediyor.
- Public key pin değerlerini doğruluyor.
- Pin bilgisini mPkps listesine ekliyor.
- Builder instance’ını geri döndürüyor.
Benim için en kritik satır şuydu:
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:
@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:
mPublicKeyPinningBypassForLocalTrustAnchorsEnabledEğ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:
/*
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:
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ı:
const B = Java.use('org.chromium.net.impl.CronetEngineBuilderImpl');Sonra addPublicKeyPins(...) metodunun tüm overload’larını hook’luyorum:
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:
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:
frida -U -f com.viber.voip -l ./viber.jsBuradaki 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:
- Script hata vermeden yükleniyor mu?
- addPublicKeyPins(...) hook’u tetikleniyor mu?
- Console üzerinde skip pins for ... logları görünüyor mu?
- enablePublicKeyPinningBypassForLocalTrustAnchors(...) hook’u tetikleniyor mu?
- Uygulama normal akışında çalışmaya devam ediyor mu?
- 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:
adb devicesfrida-ps -UaiPaket adı yanlış olabilir
Paket adını doğrulamak için:
frida-ps -Uai | grep viberveya:
adb shell pm list packages | grep viberAPK path’i değişmiş olabilir
APK path’i sabit değildir. Önce path bulunmalı, sonra dönen path kullanılmalı:
adb shell pm path com.viber.voipadb pull <APK_PATH> viber.apkClass 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.
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:
YasarKah / viber-26-6-4-0-ssl-pinningBu 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ı.