UC Tarayıcısında güvenlik açıkları aranıyor

UC Tarayıcısında güvenlik açıkları aranıyor

Giriş

Mart ayının sonunda biz raporUC Tarayıcısında doğrulanmamış kodu yükleme ve çalıştırmaya yönelik gizli bir yetenek keşfettiklerini söyledi. Bugün bu indirmenin nasıl gerçekleştiğine ve bilgisayar korsanlarının bunu kendi amaçları için nasıl kullanabileceğine ayrıntılı olarak bakacağız.

Bir süre önce, UC Tarayıcı çok agresif bir şekilde reklamı yapıldı ve dağıtıldı: kötü amaçlı yazılım kullanılarak kullanıcıların cihazlarına yüklendi, çeşitli sitelerden video dosyaları kisvesi altında dağıtıldı (yani kullanıcılar, örneğin bir porno video indirdiklerini düşündüler, ancak bunun yerine bu tarayıcıyla bir APK aldı), tarayıcının güncelliğini yitirmiş, savunmasız olduğunu ve buna benzer mesajları içeren korkutucu bannerlar kullandı. VK'daki resmi UC Tarayıcı grubunda konuKullanıcıların haksız reklamlardan şikayetçi olabileceği pek çok örnek var. 2016 yılında bile vardı video reklam Rusça (evet, reklam engelleyici bir tarayıcının reklamı).

Bu yazının yazıldığı sırada UC Tarayıcının Google Play'de 500'den fazla kurulumu vardı. Bu etkileyici; yalnızca Google Chrome'da daha fazlası var. İncelemeler arasında Google Play'deki bazı uygulamalara reklam ve yönlendirmelerle ilgili oldukça fazla şikayet görebilirsiniz. Araştırmamızın nedeni buydu: UC Tarayıcının kötü bir şey yapıp yapmadığını görmeye karar verdik. Ve öyle olduğu ortaya çıktı!

Uygulama kodunda çalıştırılabilir kodu indirme ve çalıştırma yeteneği keşfedildi, bu, başvuru yayınlama kurallarına aykırıdır Google Play'de. UC Tarayıcının yürütülebilir kodu indirmesine ek olarak, bunu güvenli olmayan bir şekilde yapar ve bu, bir MitM saldırısı gerçekleştirmek için kullanılabilir. Bakalım böyle bir saldırıyı gerçekleştirebilecek miyiz?

Aşağıda yazılan her şey, çalışma sırasında Google Play'de mevcut olan UC Tarayıcı sürümüyle ilgilidir:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-файла: f5edb2243413c777172f6362876041eb0c3a928c

Saldırı vektörü

UC Tarayıcı bildiriminde, açıklayıcı bir ada sahip bir hizmet bulabilirsiniz com.uc.deployment.UpgradeDeployService.

    <service android_exported="false" android_name="com.uc.deployment.UpgradeDeployService" android_process=":deploy" />

Bu hizmet başlatıldığında tarayıcı bir POST isteğinde bulunur. puds.ucweb.com/upgrade/index.xhtml, starttan bir süre sonra trafikte görülebiliyor. Yanıt olarak, bir güncelleme veya yeni modül indirme komutu alabilir. Analiz sırasında sunucu bu tür komutlar vermedi ancak tarayıcıda bir PDF açmaya çalıştığımızda yukarıda belirtilen adrese ikinci bir istekte bulunduğunu ve ardından yerel kütüphaneyi indirdiğini fark ettik. Saldırıyı gerçekleştirmek için UC Tarayıcının bu özelliğini kullanmaya karar verdik: APK'da bulunmayan ve gerekirse İnternet'ten indirilen yerel bir kitaplığı kullanarak PDF'yi açma yeteneği. Tarayıcı başlatıldıktan sonra yürütülen bir isteğe iyi biçimlendirilmiş bir yanıt verirseniz, teorik olarak UC Tarayıcının kullanıcı etkileşimi olmadan bir şey indirmeye zorlanabileceğini belirtmekte fayda var. Ancak bunu yapmak için sunucuyla etkileşim protokolünü daha ayrıntılı olarak incelememiz gerekiyor, bu nedenle engellenen yanıtı düzenlemenin ve kitaplığı PDF ile çalışmak üzere değiştirmenin daha kolay olacağına karar verdik.

Yani bir kullanıcı doğrudan tarayıcıda bir PDF açmak istediğinde trafikte aşağıdaki istekler görülebilir:

UC Tarayıcısında güvenlik açıkları aranıyor

İlk önce bir POST isteği var puds.ucweb.com/upgrade/index.xhtmlondan sonra
PDF ve ofis formatlarını görüntülemek için kütüphane içeren bir arşiv indirilir. İlk isteğin sistem hakkında bilgi (en azından gerekli kitaplığı sağlayacak mimari) ilettiğini ve buna yanıt olarak tarayıcının kitaplık hakkında indirilmesi gereken bazı bilgileri aldığını varsaymak mantıklıdır: adres ve muhtemelen , başka bir şey. Sorun bu isteğin şifrelenmiş olmasıdır.

Parça iste

Cevap parçası

UC Tarayıcısında güvenlik açıkları aranıyor

UC Tarayıcısında güvenlik açıkları aranıyor

Kütüphanenin kendisi ZIP olarak paketlenmiştir ve şifrelenmemiştir.

UC Tarayıcısında güvenlik açıkları aranıyor

Trafik şifre çözme kodunu arayın

Sunucu yanıtını deşifre etmeye çalışalım. Sınıf koduna bakalım com.uc.deployment.UpgradeDeployService: yöntemden onStartKomut git com.uc.deployment.bxve ondan şuraya com.uc.browser.core.dcfe:

    public final void e(l arg9) {
int v4_5;
String v3_1;
byte[] v3;
byte[] v1 = null;
if(arg9 == null) {
v3 = v1;
}
else {
v3_1 = arg9.iGX.ipR;
StringBuilder v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]product:");
v4.append(arg9.iGX.ipR);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]version:");
v4.append(arg9.iGX.iEn);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]upgrade_type:");
v4.append(arg9.iGX.mMode);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]force_flag:");
v4.append(arg9.iGX.iEo);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_mode:");
v4.append(arg9.iGX.iDQ);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_type:");
v4.append(arg9.iGX.iEr);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_state:");
v4.append(arg9.iGX.iEp);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]silent_file:");
v4.append(arg9.iGX.iEq);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apk_md5:");
v4.append(arg9.iGX.iEl);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_type:");
v4.append(arg9.mDownloadType);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_group:");
v4.append(arg9.mDownloadGroup);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]download_path:");
v4.append(arg9.iGH);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_child_version:");
v4.append(arg9.iGX.iEx);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_series:");
v4.append(arg9.iGX.iEw);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_arch:");
v4.append(arg9.iGX.iEt);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp3:");
v4.append(arg9.iGX.iEv);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_cpu_vfp:");
v4.append(arg9.iGX.iEu);
ArrayList v3_2 = arg9.iGX.iEz;
if(v3_2 != null && v3_2.size() != 0) {
Iterator v3_3 = v3_2.iterator();
while(v3_3.hasNext()) {
Object v4_1 = v3_3.next();
StringBuilder v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_name:");
v5.append(((au)v4_1).getName());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_name:");
v5.append(((au)v4_1).aDA());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_ver_code:");
v5.append(((au)v4_1).gBl);
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]component_req_type:");
v5.append(((au)v4_1).gBq);
}
}
j v3_4 = new j();
m.b(v3_4);
h v4_2 = new h();
m.b(v4_2);
ay v5_1 = new ay();
v3_4.hS("");
v3_4.setImsi("");
v3_4.hV("");
v5_1.bPQ = v3_4;
v5_1.bPP = v4_2;
v5_1.yr(arg9.iGX.ipR);
v5_1.gBF = arg9.iGX.mMode;
v5_1.gBI = arg9.iGX.iEz;
v3_2 = v5_1.gAr;
c.aBh();
v3_2.add(g.fs("os_ver", c.getRomInfo()));
v3_2.add(g.fs("processor_arch", com.uc.b.a.a.c.getCpuArch()));
v3_2.add(g.fs("cpu_arch", com.uc.b.a.a.c.Pb()));
String v4_3 = com.uc.b.a.a.c.Pd();
v3_2.add(g.fs("cpu_vfp", v4_3));
v3_2.add(g.fs("net_type", String.valueOf(com.uc.base.system.a.Jo())));
v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
v3_2.add(g.fs("plugin_ver", arg9.iGX.iEn));
v3_2.add(g.fs("target_lang", arg9.iGX.iEs));
v3_2.add(g.fs("vitamio_cpu_arch", arg9.iGX.iEt));
v3_2.add(g.fs("vitamio_vfp", arg9.iGX.iEu));
v3_2.add(g.fs("vitamio_vfp3", arg9.iGX.iEv));
v3_2.add(g.fs("plugin_child_ver", arg9.iGX.iEx));
v3_2.add(g.fs("ver_series", arg9.iGX.iEw));
v3_2.add(g.fs("child_ver", r.aVw()));
v3_2.add(g.fs("cur_ver_md5", arg9.iGX.iEl));
v3_2.add(g.fs("cur_ver_signature", SystemHelper.getUCMSignature()));
v3_2.add(g.fs("upgrade_log", i.bjt()));
v3_2.add(g.fs("silent_install", String.valueOf(arg9.iGX.iDQ)));
v3_2.add(g.fs("silent_state", String.valueOf(arg9.iGX.iEp)));
v3_2.add(g.fs("silent_file", arg9.iGX.iEq));
v3_2.add(g.fs("silent_type", String.valueOf(arg9.iGX.iEr)));
v3_2.add(g.fs("cpu_archit", com.uc.b.a.a.c.Pc()));
v3_2.add(g.fs("cpu_set", SystemHelper.getCpuInstruction()));
boolean v4_4 = v4_3 == null || !v4_3.contains("neon") ? false : true;
v3_2.add(g.fs("neon", String.valueOf(v4_4)));
v3_2.add(g.fs("cpu_cores", String.valueOf(com.uc.b.a.a.c.Jl())));
v3_2.add(g.fs("ram_1", String.valueOf(com.uc.b.a.a.h.Po())));
v3_2.add(g.fs("totalram", String.valueOf(com.uc.b.a.a.h.OL())));
c.aBh();
v3_2.add(g.fs("rom_1", c.getRomInfo()));
v4_5 = e.getScreenWidth();
int v6 = e.getScreenHeight();
StringBuilder v7 = new StringBuilder();
v7.append(v4_5);
v7.append("*");
v7.append(v6);
v3_2.add(g.fs("ss", v7.toString()));
v3_2.add(g.fs("api_level", String.valueOf(Build$VERSION.SDK_INT)));
v3_2.add(g.fs("uc_apk_list", SystemHelper.getUCMobileApks()));
Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
while(v4_6.hasNext()) {
Object v6_1 = v4_6.next();
v3_2.add(g.fs(((Map$Entry)v6_1).getKey(), ((Map$Entry)v6_1).getValue()));
}
v3 = v5_1.toByteArray();
}
if(v3 == null) {
this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
return;
}
v4_5 = this.iGY.iGw ? 0x1F : 0;
if(v3 == null) {
}
else {
v3 = g.i(v4_5, v3);
if(v3 == null) {
}
else {
v1 = new byte[v3.length + 16];
byte[] v6_2 = new byte[16];
Arrays.fill(v6_2, 0);
v6_2[0] = 0x5F;
v6_2[1] = 0;
v6_2[2] = ((byte)v4_5);
v6_2[3] = -50;
System.arraycopy(v6_2, 0, v1, 0, 16);
System.arraycopy(v3, 0, v1, 16, v3.length);
}
}
if(v1 == null) {
this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
return;
}
if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
this.iGY.iGI.a(arg9, "up_url", "yes", "fail");
return;
}
StringBuilder v0 = new StringBuilder("[");
v0.append(arg9.iGX.ipR);
v0.append("]url:");
v0.append(this.iGY.mUpgradeUrl);
com.uc.browser.core.d.c.i v0_1 = this.iGY.iGI;
v3_1 = this.iGY.mUpgradeUrl;
com.uc.base.net.e v0_2 = new com.uc.base.net.e(new com.uc.browser.core.d.c.i$a(v0_1, arg9));
v3_1 = v3_1.contains("?") ? v3_1 + "&dataver=pb" : v3_1 + "?dataver=pb";
n v3_5 = v0_2.uc(v3_1);
m.b(v3_5, false);
v3_5.setMethod("POST");
v3_5.setBodyProvider(v1);
v0_2.b(v3_5);
this.iGY.iGI.a(arg9, "up_null", "yes", "success");
this.iGY.iGI.b(arg9);
}

Burada POST isteğinin oluştuğunu görüyoruz. 16 bytelık bir dizinin oluşturulmasına ve doldurulmasına dikkat ediyoruz: 0x5F, 0, 0x1F, -50 (=0xCE). Yukarıdaki talepte gördüğümüzle örtüşüyor.

Aynı sınıfta başka ilginç bir yönteme sahip iç içe geçmiş bir sınıf görebilirsiniz:

        public final void a(l arg10, byte[] arg11) {
f v0 = this.iGQ;
StringBuilder v1 = new StringBuilder("[");
v1.append(arg10.iGX.ipR);
v1.append("]:UpgradeSuccess");
byte[] v1_1 = null;
if(arg11 == null) {
}
else if(arg11.length < 16) {
}
else {
if(arg11[0] != 0x60 && arg11[3] != 0xFFFFFFD0) {
goto label_57;
}
int v3 = 1;
int v5 = arg11[1] == 1 ? 1 : 0;
if(arg11[2] != 1 && arg11[2] != 11) {
if(arg11[2] == 0x1F) {
}
else {
v3 = 0;
}
}
byte[] v7 = new byte[arg11.length - 16];
System.arraycopy(arg11, 16, v7, 0, v7.length);
if(v3 != 0) {
v7 = g.j(arg11[2], v7);
}
if(v7 == null) {
goto label_57;
}
if(v5 != 0) {
v1_1 = g.P(v7);
goto label_57;
}
v1_1 = v7;
}
label_57:
if(v1_1 == null) {
v0.iGY.iGI.a(arg10, "up_decrypt", "yes", "fail");
return;
}
q v11 = g.b(arg10, v1_1);
if(v11 == null) {
v0.iGY.iGI.a(arg10, "up_decode", "yes", "fail");
return;
}
if(v0.iGY.iGt) {
v0.d(arg10);
}
if(v0.iGY.iGo != null) {
v0.iGY.iGo.a(0, ((o)v11));
}
if(v0.iGY.iGs) {
v0.iGY.a(((o)v11));
v0.iGY.iGI.a(v11, "up_silent", "yes", "success");
v0.iGY.iGI.a(v11);
return;
}
v0.iGY.iGI.a(v11, "up_silent", "no", "success");
}
}

Yöntem, girdi olarak bir bayt dizisini alır ve sıfır baytın 0x60 veya üçüncü baytın 0xD0 olduğunu ve ikinci baytın 1, 11 veya 0x1F olduğunu kontrol eder. Sunucudan gelen cevaba bakıyoruz: sıfır bayt 0x60, ikincisi 0x1F, üçüncüsü 0x60. İhtiyacımız olan şeye benziyor. Satırlara bakılırsa (“up_decrypt” örneğin), burada sunucunun yanıtının şifresini çözecek bir yöntemin çağrılması gerekir.
Yönteme geçelim gj. İlk argümanın 2. konumdaki bayt olduğunu (yani bizim durumumuzda 0x1F) ve ikincisinin ise XNUMX. konumdaki bayt olduğunu ve ikincisinin ise sunucu yanıtı olduğunu unutmayın.
ilk 16 bayt.

     public static byte[] j(int arg1, byte[] arg2) {
if(arg1 == 1) {
arg2 = c.c(arg2, c.adu);
}
else if(arg1 == 11) {
arg2 = m.aF(arg2);
}
else if(arg1 != 0x1F) {
}
else {
arg2 = EncryptHelper.decrypt(arg2);
}
return arg2;
}

Açıkçası, burada bir şifre çözme algoritması seçiyoruz ve verimizdeki aynı baytı seçiyoruz.
0x1F'ye eşit durum, üç olası seçenekten birini belirtir.

Kodu analiz etmeye devam ediyoruz. Birkaç atlamadan sonra kendimizi açıklayıcı bir isme sahip bir yöntemin içinde buluyoruz. BytesByKey'in şifresini çöz.

Burada cevabımızdan iki byte daha ayrılır ve onlardan bir string elde edilir. Mesajın şifresini çözme anahtarının bu şekilde seçildiği açıktır.

    private static byte[] decryptBytesByKey(byte[] bytes) {
byte[] v0 = null;
if(bytes != null) {
try {
if(bytes.length < EncryptHelper.PREFIX_BYTES_SIZE) {
}
else if(bytes.length == EncryptHelper.PREFIX_BYTES_SIZE) {
return v0;
}
else {
byte[] prefix = new byte[EncryptHelper.PREFIX_BYTES_SIZE];  // 2 байта
System.arraycopy(bytes, 0, prefix, 0, prefix.length);
String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Выбор ключа
if(keyId == null) {
return v0;
}
else {
a v2 = EncryptHelper.ayL();
if(v2 == null) {
return v0;
}
else {
byte[] enrypted = new byte[bytes.length - EncryptHelper.PREFIX_BYTES_SIZE];
System.arraycopy(bytes, EncryptHelper.PREFIX_BYTES_SIZE, enrypted, 0, enrypted.length);
return v2.l(keyId, enrypted);
}
}
}
}
catch(SecException v7_1) {
EncryptHelper.handleDecryptException(((Throwable)v7_1), v7_1.getErrorCode());
return v0;
}
catch(Throwable v7) {
EncryptHelper.handleDecryptException(v7, 2);
return v0;
}
}
return v0;
}

İleriye baktığımızda, bu aşamada henüz bir anahtar elde etmediğimizi, yalnızca onun "tanımlayıcısını" elde ettiğimizi not ediyoruz. Anahtarı almak biraz daha karmaşıktır.

Bir sonraki yöntemde, mevcut olanlara iki parametre daha eklenir ve bunlardan dördü oluşur: sihirli sayı 16, anahtar tanımlayıcı, şifrelenmiş veriler ve anlaşılmaz bir dize (bizim durumumuzda boş).

    public final byte[] l(String keyId, byte[] encrypted) throws SecException {
return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, "");
}

Bir dizi geçişten sonra yönteme ulaşıyoruz staticBinarySafeDecryptNoB64 arayüz com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Ana uygulama kodunda bu arayüzü uygulayan sınıflar yoktur. Dosyada böyle bir sınıf var lib/armeabi-v7a/libsgmain.so, bu aslında bir .so değil, bir .jar'dır. İlgilendiğimiz yöntem şu şekilde uygulanmaktadır:

package com.alibaba.wireless.security.a.i;
// ...
public class a implements IStaticDataEncryptComponent {
private ISecurityGuardPlugin a;
// ...
private byte[] a(int mode, int magicInt, int xzInt, String keyId, byte[] encrypted, String magicString) {
return this.a.getRouter().doCommand(10601, new Object[]{Integer.valueOf(mode), Integer.valueOf(magicInt), Integer.valueOf(xzInt), keyId, encrypted, magicString});
}
// ...
private byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
return this.a(2, magicInt, 0, keyId, encrypted, magicString);
}
// ...
public byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) throws SecException {
if(keyId != null && keyId.length() > 0 && magicInt >= 0 && magicInt < 19 && encrypted != null && encrypted.length > 0) {
return this.b(magicInt, keyId, encrypted, magicString);
}
throw new SecException("", 301);
}
//...
}

Burada parametre listemize iki tam sayı daha eklenmiştir: 2 ve 0.
her şey, 2 yöntemde olduğu gibi şifre çözme anlamına gelir doFinal sistem sınıfı javax.crypto.Cipher. Ve tüm bunlar 10601 numarasıyla belirli bir Yönlendiriciye aktarılıyor - görünüşe göre bu komut numarası.

Bir sonraki geçiş zincirinden sonra arayüzü uygulayan bir sınıf buluyoruz IRouterBileşeni ve yöntem komut yap:

package com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
public class a implements IRouterComponent {
public a() {
super();
}
public Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}

Ve ayrıca sınıf JNIC Kütüphanesiyerel yöntemin bildirildiği yer doCommandNative:

package com.taobao.wireless.security.adapter;
public class JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}

Bu, yerel kodda bir yöntem bulmamız gerektiği anlamına gelir doCommandNative. Ve eğlencenin başladığı yer burasıdır.

Makine kodunun gizlenmesi

Dosyada libsgmain.so (bu aslında bir .jar'dır ve yukarıda şifrelemeyle ilgili bazı arayüzlerin uygulamasını bulduğumuz) bir yerel kütüphane vardır: libsgmainso-6.4.36.so. Bunu IDA'da açıyoruz ve hatalı bir sürü iletişim kutusu alıyoruz. Sorun, bölüm başlığı tablosunun geçersiz olmasıdır. Bu, analizi zorlaştırmak amacıyla yapılır.

UC Tarayıcısında güvenlik açıkları aranıyor

Ancak buna gerek yok: Bir ELF dosyasını doğru şekilde yüklemek ve analiz etmek için bir program başlık tablosu yeterlidir. Bu nedenle, başlıktaki ilgili alanları sıfırlayarak bölüm tablosunu sileriz.

UC Tarayıcısında güvenlik açıkları aranıyor

Dosyayı IDA'da tekrar açın.

Java sanal makinesine, Java kodunda yerel olarak bildirilen bir yöntemin uygulamasının yerel kitaplıkta tam olarak nerede bulunduğunu söylemenin iki yolu vardır. Birincisi ona bir tür adı vermektir. Java_package_name_ClassName_MethodName.

İkincisi, kütüphaneyi yüklerken onu kaydetmektir (fonksiyonda JNI_Yükte)
işlev çağrısı kullanma Kayıt OlYerliler.

Bizim durumumuzda ilk yöntemi kullanırsak isim şu şekilde olmalıdır: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Dışa aktarılan işlevler arasında böyle bir işlev yoktur, bu da bir çağrı aramanız gerektiği anlamına gelir Kayıt OlYerliler.
Hadi fonksiyona gidelim JNI_Yükte ve şu resmi görüyoruz:

UC Tarayıcısında güvenlik açıkları aranıyor

Burada neler oluyor? İlk bakışta fonksiyonun başlangıcı ve bitişi ARM mimarisine özgüdür. Yığındaki ilk talimat, fonksiyonun işleminde kullanacağı kayıtların içeriğini (bu durumda R0, R1 ve R2) ve ayrıca fonksiyondan dönüş adresini içeren LR kaydının içeriğini saklar. . Son talimat kayıtlı kayıtları geri yükler ve dönüş adresi hemen PC kaydına yerleştirilir ve böylece fonksiyondan geri dönülür. Ancak yakından bakarsanız, sondan bir önceki talimatın yığında saklanan dönüş adresini değiştirdiğini fark edeceksiniz. Sonrası nasıl olacak hesaplayalım
kod yürütme. R1'e belirli bir 0xB130 adresi yüklenir, bundan 5 çıkarılır, ardından R0'a aktarılır ve ona 0x10 eklenir. 0xB13B çıkıyor. Dolayısıyla IDA, son talimatın normal bir fonksiyon dönüşü olduğunu düşünüyor ancak aslında hesaplanan 0xB13B adresine gidiyor.

Burada ARM işlemcilerin iki modu ve iki talimat kümesi olduğunu hatırlamakta fayda var: ARM ve Thumb. Adresin en az anlamlı biti işlemciye hangi talimat setinin kullanıldığını söyler. Yani adres aslında 0xB13A'dır ve en az anlamlı bitteki bir bit Başparmak modunu gösterir.

Bu kütüphanedeki her fonksiyonun başına benzer bir “adaptör” eklenmiş ve
çöp kodu. Bunların üzerinde daha fazla durmayacağız - sadece hatırlıyoruz
hemen hemen tüm fonksiyonların gerçek başlangıcının biraz daha uzakta olduğu.

Kod açıkça 0xB13A'ya atlamadığından, IDA'nın kendisi kodun bu konumda bulunduğunu tanımadı. Aynı sebepten ötürü kütüphanedeki kodların büyük bir kısmını kod olarak tanımıyor, bu da analizi biraz zorlaştırıyor. IDA'ya kodun bu olduğunu söylüyoruz ve şöyle oluyor:

UC Tarayıcısında güvenlik açıkları aranıyor

Tablo açıkça 0xB144'ten başlıyor. sub_494C'de ne var?

UC Tarayıcısında güvenlik açıkları aranıyor

LR yazmacında bu işlevi çağırdığımızda, daha önce bahsedilen tablonun (0xB144) adresini alırız. R0'da - bu tablodaki dizin. Yani değer tablodan alınıp LR'a eklenir ve sonuç
gidilecek adres. Hesaplamaya çalışalım: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Alınan adrese gidiyoruz ve kelimenin tam anlamıyla birkaç yararlı talimat görüyoruz ve tekrar 0xB140'a gidiyoruz:

UC Tarayıcısında güvenlik açıkları aranıyor

Şimdi tablodan 0x20 indeksli ofsette bir geçiş olacak.

Tablonun boyutuna bakılırsa kodda bu tür birçok geçiş olacaktır. Adresleri manuel olarak hesaplamadan, bununla bir şekilde daha otomatik olarak başa çıkmanın mümkün olup olmadığı sorusu ortaya çıkıyor. Komut dosyaları ve IDA'da kod düzeltme yeteneği yardımımıza koşuyor:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 2
if get_wide_word(ea1) == 0xbf00: #NOP
ea1 += 2
if get_operand_type(ea1, 0) == 1 and get_operand_value(ea1, 0) == 0 and get_operand_type(ea1, 1) == 2:
index = get_wide_dword(get_operand_value(ea1, 1))
print "index =", hex(index)
ea1 += 2
if get_operand_type(ea1, 0) == 7:
table = get_operand_value(ea1, 0) + 4
elif get_operand_type(ea1, 1) == 2:
table = get_operand_value(ea1, 1) + 4
else:
print "Wrong operand type on", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
table = None
if table is None:
print "Unable to find table"
else:
print "table =", hex(table)
offset = get_wide_dword(table + (index << 2))
put_unconditional_branch(ea, table + offset)
else:
print "Unknown code", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
print "Unable to detect first instruction"

İmleci 0xB26A satırına yerleştirin, betiği çalıştırın ve 0xB4B0'a geçişi görün:

UC Tarayıcısında güvenlik açıkları aranıyor

IDA yine bu alanı kod olarak tanımadı. Ona yardım ediyoruz ve orada başka bir tasarım görüyoruz:

UC Tarayıcısında güvenlik açıkları aranıyor

BLX'ten sonraki talimatlar pek mantıklı görünmüyor, daha çok bir tür yer değiştirmeye benziyor. Sub_4964'e bakalım:

UC Tarayıcısında güvenlik açıkları aranıyor

Ve aslında burada LR'de bulunan adresten bir dword alınır, bu adrese eklenir ve ardından ortaya çıkan adresteki değer alınır ve yığına konur. Ayrıca LR'ye 4 eklenir, böylece fonksiyondan döndükten sonra aynı ofset atlanır. Bundan sonra POP {R1} komutu elde edilen değeri yığından alır. 0xB4BA + 0xEA = 0xB5A4 adresinde ne olduğuna bakarsanız, adres tablosuna benzer bir şey göreceksiniz:

UC Tarayıcısında güvenlik açıkları aranıyor

Bu tasarımı yamalamak için koddan iki parametre almanız gerekecektir: ofset ve sonucu koymak istediğiniz kayıt numarası. Olası her kayıt için önceden bir kod parçası hazırlamanız gerekecektir.

patches = {}
patches[0] = (0x00, 0xbf, 0x01, 0x48, 0x00, 0x68, 0x02, 0xe0)
patches[1] = (0x00, 0xbf, 0x01, 0x49, 0x09, 0x68, 0x02, 0xe0)
patches[2] = (0x00, 0xbf, 0x01, 0x4a, 0x12, 0x68, 0x02, 0xe0)
patches[3] = (0x00, 0xbf, 0x01, 0x4b, 0x1b, 0x68, 0x02, 0xe0)
patches[4] = (0x00, 0xbf, 0x01, 0x4c, 0x24, 0x68, 0x02, 0xe0)
patches[5] = (0x00, 0xbf, 0x01, 0x4d, 0x2d, 0x68, 0x02, 0xe0)
patches[8] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x80, 0xd8, 0xf8, 0x00, 0x80, 0x01, 0xe0)
patches[9] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0x90, 0xd9, 0xf8, 0x00, 0x90, 0x01, 0xe0)
patches[10] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xa0, 0xda, 0xf8, 0x00, 0xa0, 0x01, 0xe0)
patches[11] = (0x00, 0xbf, 0xdf, 0xf8, 0x06, 0xb0, 0xdb, 0xf8, 0x00, 0xb0, 0x01, 0xe0)
ea = here()
if (get_wide_word(ea) == 0xb082 #SUB SP, SP, #8
and get_wide_word(ea + 2) == 0xb503): #PUSH {R0,R1,LR}
if get_operand_type(ea + 4, 0) == 7:
pop = get_bytes(ea + 12, 4, 0)
if pop[1] == 'xbc':
register = -1
r = get_wide_byte(ea + 12)
for i in range(8):
if r == (1 << i):
register = i
break
if register == -1:
print "Unable to detect register"
else:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
if ea % 4 != 0:
ea += 2
patch_dword(ea, address)
elif pop[:3] == 'x5dxf8x04':
register = ord(pop[3]) >> 4
if register in patches:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
patch_dword(ea, address)
else:
print "POP instruction not found"
else:
print "Wrong operand type on +4:", get_operand_type(ea + 4, 0)
else:
print "Unable to detect first instructions"

İmleci değiştirmek istediğimiz yapının başlangıcına (0xB4B2) yerleştiririz ve betiği çalıştırırız:

UC Tarayıcısında güvenlik açıkları aranıyor

Daha önce bahsedilen yapılara ek olarak kod aşağıdakileri de içerir:

UC Tarayıcısında güvenlik açıkları aranıyor

Önceki durumda olduğu gibi, BLX komutundan sonra bir ofset vardır:

UC Tarayıcısında güvenlik açıkları aranıyor

LR’dan adrese ofset alıp LR’a ekleyip oraya gidiyoruz. 0x72044 + 0xC = 0x72050. Bu tasarımın senaryosu oldukça basittir:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 6
if get_wide_word(ea + 2) == 0xbf00: #NOP
ea1 += 2
offset = get_wide_dword(ea1)
put_unconditional_branch(ea, (ea1 + offset) & 0xffffffff)
else:
print "Unable to detect first instruction"

Komut dosyasının yürütülmesinin sonucu:

UC Tarayıcısında güvenlik açıkları aranıyor

İşlevdeki her şey yamandıktan sonra IDA'yı gerçek başlangıcına yönlendirebilirsiniz. Tüm fonksiyon kodlarını bir araya getirecek ve HexRays kullanılarak kaynak koda dönüştürülebilecek.

Dizelerin kodunu çözme

Kütüphanedeki makine kodunun gizlenmesiyle baş etmeyi öğrendik libsgmainso-6.4.36.so UC Tarayıcısından ve işlev kodunu aldım JNI_Yükte.

int __fastcall real_JNI_OnLoad(JavaVM *vm)
{
int result; // r0
jclass clazz; // r0 MAPDST
int v4; // r0
JNIEnv *env; // r4
int v6; // [sp-40h] [bp-5Ch]
int v7; // [sp+Ch] [bp-10h]
v7 = *(_DWORD *)off_8AC00;
if ( !vm )
goto LABEL_39;
sub_7C4F4();
env = (JNIEnv *)sub_7C5B0(0);
if ( !env )
goto LABEL_39;
v4 = sub_72CCC();
sub_73634(v4);
sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
if ( clazz
&& (sub_9EE4(),
sub_71D68(env),
sub_E7DC(env) >= 0
&& sub_69D68(env) >= 0
&& sub_197B4(env, clazz) >= 0
&& sub_E240(env, clazz) >= 0
&& sub_B8B0(env, clazz) >= 0
&& sub_5F0F4(env, clazz) >= 0
&& sub_70640(env, clazz) >= 0
&& sub_11F3C(env) >= 0
&& sub_21C3C(env, clazz) >= 0
&& sub_2148C(env, clazz) >= 0
&& sub_210E0(env, clazz) >= 0
&& sub_41B58(env, clazz) >= 0
&& sub_27920(env, clazz) >= 0
&& sub_293E8(env, clazz) >= 0
&& sub_208F4(env, clazz) >= 0) )
{
result = (sub_B7B0(env, clazz) >> 31) | 0x10004;
}
else
{
LABEL_39:
result = -1;
}
return result;
}

Şimdi aşağıdaki satırlara daha yakından bakalım:

  sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);

işlevde sub_73E24 sınıf adının şifresi açıkça çözülüyor. Bu fonksiyona parametre olarak, şifrelenmiş verilere benzer verilere yönelik bir işaretçi, belirli bir arabellek ve bir sayı iletilir. Açıkçası, işlevi çağırdıktan sonra, işleve iletildiği için arabellekte şifresi çözülmüş bir satır olacaktır. Sınıfı Bul, ikinci parametre olarak sınıf adını alır. Bu nedenle sayı, arabelleğin boyutu veya satırın uzunluğudur. Sınıf adını deşifre etmeye çalışalım, bu bize doğru yöne gidip gitmediğimizi söylemeli. Gelin neler olduğuna daha yakından bakalım sub_73E24.

int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size)
{
int v4; // r6
int v7; // r11
int v8; // r9
int v9; // r4
size_t v10; // r5
int v11; // r0
struc_1 v13; // [sp+0h] [bp-30h]
int v14; // [sp+1Ch] [bp-14h]
int v15; // [sp+20h] [bp-10h]
v4 = 0;
v15 = *(_DWORD *)off_8AC00;
v14 = 0;
v7 = sub_7AF78(17);
v8 = sub_7AF78(size);
if ( !v7 )
{
v9 = 0;
goto LABEL_12;
}
(*(void (__fastcall **)(int, const char *, int))(v7 + 12))(v7, "DcO/lcK+h?m3c*q@", 16);
if ( !v8 )
{
LABEL_9:
v4 = 0;
goto LABEL_10;
}
v4 = 0;
if ( !in )
{
LABEL_10:
v9 = 0;
goto LABEL_11;
}
v9 = 0;
if ( out )
{
memset(out, 0, size);
v10 = size - 1;
(*(void (__fastcall **)(int, unsigned __int8 *, size_t))(v8 + 12))(v8, in, v10);
memset(&v13, 0, 0x14u);
v13.field_4 = 3;
v13.field_10 = v7;
v13.field_14 = v8;
v11 = sub_6115C(&v13, &v14);
v9 = v11;
if ( v11 )
{
if ( *(_DWORD *)(v11 + 4) == v10 )
{
qmemcpy(out, *(const void **)v11, v10);
v4 = *(_DWORD *)(v9 + 4);
}
else
{
v4 = 0;
}
goto LABEL_11;
}
goto LABEL_9;
}
LABEL_11:
sub_7B148(v7);
LABEL_12:
if ( v8 )
sub_7B148(v8);
if ( v9 )
sub_7B148(v9);
return v4;
}

Fonksiyon sub_7AF78 belirtilen boyuttaki bayt dizileri için bir kapsayıcı örneği oluşturur (bu kapsayıcılar üzerinde ayrıntılı olarak durmayacağız). Burada bu tür iki kap yaratılmıştır: biri satırı içerir "DcO/lcK+h?m3c*q@" (Bunun bir anahtar olduğunu tahmin etmek kolaydır), diğeri şifrelenmiş veriler içerir. Daha sonra her iki nesne de işleve aktarılan belirli bir yapıya yerleştirilir. sub_6115C. Bu yapıda 3 değerine sahip bir alanı da işaretleyelim, bakalım bundan sonra bu yapıya ne olacak.

int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2)
{
int v3; // lr
unsigned int v4; // r1
int v5; // r0
int v6; // r1
int result; // r0
int v8; // r0
*a2 = 820000;
if ( a1 )
{
v3 = a1->field_14;
if ( v3 )
{
v4 = a1->field_4;
if ( v4 < 0x19 )
{
switch ( v4 )
{
case 0u:
v8 = sub_6419C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 3u:
v8 = sub_6364C(a1->field_0, a1->field_10, v3);
goto LABEL_17;
case 0x10u:
case 0x11u:
case 0x12u:
v8 = sub_612F4(
a1->field_0,
v4,
*(_QWORD *)&a1->field_8,
*(_QWORD *)&a1->field_8 >> 32,
a1->field_10,
v3,
a2);
goto LABEL_17;
case 0x14u:
v8 = sub_63A28(a1->field_0, v3);
goto LABEL_17;
case 0x15u:
sub_61A60(a1->field_0, v3, a2);
return result;
case 0x16u:
v8 = sub_62440(a1->field_14);
goto LABEL_17;
case 0x17u:
v8 = sub_6226C(a1->field_10, v3);
goto LABEL_17;
case 0x18u:
v8 = sub_63530(a1->field_14);
LABEL_17:
v6 = 0;
if ( v8 )
{
*a2 = 0;
v6 = v8;
}
return v6;
default:
LOWORD(v5) = 28032;
goto LABEL_5;
}
}
}
}
LOWORD(v5) = -27504;
LABEL_5:
HIWORD(v5) = 13;
v6 = 0;
*a2 = v5;
return v6;
}

Switch parametresi daha önce 3 değeri atanmış bir yapı alanıdır. Durum 3'e bakın: fonksiyona sub_6364C parametreler, önceki fonksiyonda buraya eklenen yapıdan, yani anahtar ve şifrelenmiş verilerden aktarılır. Eğer yakından bakarsanız sub_6364C, içindeki RC4 algoritmasını tanıyabilirsiniz.

Bir algoritmamız ve bir anahtarımız var. Sınıfın adını deşifre etmeye çalışalım. İşte olanlar: com/taobao/kablosuz/güvenlik/adaptör/JNICLibrary. Harika! Biz doğru yoldayız.

Komut ağacı

Şimdi bir meydan okuma bulmalıyız Kayıt OlYerliler, bu bizi fonksiyona yönlendirecek doCommandNative. Çağrılan fonksiyonlara bakalım JNI_Yükte, ve onu içinde buluyoruz sub_B7B0:

int __fastcall sub_B7F6(JNIEnv *env, jclass clazz)
{
char signature[41]; // [sp+7h] [bp-55h]
char name[16]; // [sp+30h] [bp-2Ch]
JNINativeMethod method; // [sp+40h] [bp-1Ch]
int v8; // [sp+4Ch] [bp-10h]
v8 = *(_DWORD *)off_8AC00;
decryptString((unsigned __int8 *)&unk_83ED9, (unsigned __int8 *)name, 0x10u);// doCommandNative
decryptString((unsigned __int8 *)&unk_83EEA, (unsigned __int8 *)signature, 0x29u);// (I[Ljava/lang/Object;)Ljava/lang/Object;
method.name = name;
method.signature = signature;
method.fnPtr = sub_B69C;
return ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, int))(*env)->RegisterNatives)(env, clazz, &method, 1) >> 31;
}

Ve gerçekten de burada isimli yerel bir yöntem kayıtlıdır doCommandNative. Artık adresini biliyoruz. Bakalım ne yapacak.

int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args)
{
int v5; // r5
struc_2 *a5; // r6
int v9; // r1
int v11; // [sp+Ch] [bp-14h]
int v12; // [sp+10h] [bp-10h]
v5 = 0;
v12 = *(_DWORD *)off_8AC00;
v11 = 0;
a5 = (struc_2 *)malloc(0x14u);
if ( a5 )
{
a5->field_0 = 0;
a5->field_4 = 0;
a5->field_8 = 0;
a5->field_C = 0;
v9 = command % 10000 / 100;
a5->field_0 = command / 10000;
a5->field_4 = v9;
a5->field_8 = command % 100;
a5->field_C = env;
a5->field_10 = args;
v5 = sub_9D60(command / 10000, v9, command % 100, 1, (int)a5, &v11);
}
free(a5);
if ( !v5 && v11 )
sub_7CF34(env, v11, &byte_83ED7);
return v5;
}

Adından, geliştiricilerin yerel kütüphaneye aktarmaya karar verdikleri tüm işlevlerin giriş noktasının burası olduğunu tahmin edebilirsiniz. 10601 numaralı fonksiyonla ilgileniyoruz.

Komut numarasının üç sayı ürettiğini koddan görebilirsiniz: komut/10000, komut % 10000 / 100 и komut % 10yani bizim durumumuzda 1, 6 ve 1. Bu üç sayının yanı sıra bir işaretçi JNIEnv ve fonksiyona aktarılan argümanlar bir yapıya eklenir ve iletilir. Elde edilen üç sayıyı kullanarak (bunları N1, N2 ve N3 olarak gösterelim), bir komut ağacı oluşturulur.

Bunun gibi bir şey:

UC Tarayıcısında güvenlik açıkları aranıyor

Ağaç dinamik olarak doldurulur JNI_Yükte.
Üç sayı ağaçtaki yolu kodlar. Ağacın her yaprağı, karşılık gelen fonksiyonun cep adresini içerir. Anahtar ana düğümdedir. Kullanılan tüm yapıları anlarsanız, ihtiyacımız olan işlevin ağaca eklendiği koddaki yeri bulmak zor değildir (zaten oldukça büyük bir makaleyi şişirmemek için bunları açıklamıyoruz).

Daha fazla karışıklık

Trafiğin şifresini çözmesi gereken işlevin adresini aldık: 0x5F1AC. Ancak sevinmek için henüz çok erken: UC Tarayıcının geliştiricileri bizim için başka bir sürpriz hazırladı.

Java kodunda oluşturulan diziden parametreleri aldıktan sonra şunu elde ederiz:
0x4D070 adresindeki fonksiyona. Ve burada başka bir tür kod şaşırtmacası bizi bekliyor.

R7 ve R4'e iki endeks koyduk:

UC Tarayıcısında güvenlik açıkları aranıyor

İlk endeksi R11'e kaydırıyoruz:

UC Tarayıcısında güvenlik açıkları aranıyor

Bir tablodan adres almak için bir dizin kullanın:

UC Tarayıcısında güvenlik açıkları aranıyor

İlk adrese gidildikten sonra R4'teki ikinci indeks kullanılır. Tabloda 230 element bulunmaktadır.

Bu konuda ne yapmalı? IDA'ya bunun bir anahtar olduğunu söyleyebilirsiniz: Düzenle -> Diğer -> Anahtar deyimini belirtin.

UC Tarayıcısında güvenlik açıkları aranıyor

Ortaya çıkan kod korkutucu. Ancak ormanda yol alırken, zaten aşina olduğumuz bir işleve çağrıyı fark edebilirsiniz. sub_6115C:

UC Tarayıcısında güvenlik açıkları aranıyor

Durum 3'te RC4 algoritmasını kullanan bir şifre çözmenin olduğu bir anahtar vardı. Ve bu durumda fonksiyona iletilen yapı, iletilen parametrelerden doldurulur. doCommandNative. Orada neler yaşadığımızı hatırlayalım sihirliInt 16 değeriyle. İlgili duruma bakıyoruz - ve birkaç geçişten sonra algoritmanın tanımlanabileceği kodu buluyoruz.

UC Tarayıcısında güvenlik açıkları aranıyor

Bu AES'tir!

Algoritma mevcuttur, geriye kalan tek şey parametrelerini elde etmektir: mod, anahtar ve muhtemelen başlatma vektörü (varlığı AES algoritmasının çalışma moduna bağlıdır). Onlarla birlikte yapı, işlev çağrısından önce bir yerde oluşturulmalıdır. sub_6115C, ancak kodun bu kısmı özellikle iyi bir şekilde gizlenmiştir, bu nedenle, şifre çözme işlevinin tüm parametrelerinin bir dosyaya dökülmesi için kodu yamalama fikri ortaya çıkar.

Yama

Tüm yama kodlarını Assembly dilinde manuel olarak yazmamak için Android Studio'yu başlatabilir, oraya şifre çözme fonksiyonumuzla aynı giriş parametrelerini alan ve bir dosyaya yazan bir fonksiyon yazabilir, ardından derleyicinin yazacağı kodu kopyalayıp yapıştırabilirsiniz. oluşturmak.

UC Tarayıcı ekibindeki arkadaşlarımız da kod ekleme kolaylığıyla ilgilendiler. Her fonksiyonun başında kolayca başka biriyle değiştirilebilecek çöp kodumuzun bulunduğunu hatırlayalım. Çok kullanışlı 🙂 Ancak hedef fonksiyonun başlangıcında tüm parametreleri bir dosyaya kaydeden kod için yeterli alan yok. Bunu parçalara bölmek ve komşu işlevlerdeki çöp bloklarını kullanmak zorunda kaldım. Toplamda dört bölüm vardı.

İlk bölüm:

UC Tarayıcısında güvenlik açıkları aranıyor

ARM mimarisinde, ilk dört fonksiyon parametresi R0-R3 yazmaçlarından, geri kalanı ise (varsa) yığından geçirilir. LR kaydı dönüş adresini taşır. Parametrelerini attıktan sonra işlevin çalışabilmesi için tüm bunların kaydedilmesi gerekir. Ayrıca işlemde kullanacağımız tüm kaydedicileri kaydetmemiz gerekiyor, bu yüzden PUSH.W {R0-R10,LR} yapıyoruz. R7'de fonksiyona yığın aracılığıyla iletilen parametreler listesinin adresini alırız.

işlevi kullanma açık dosyayı açalım /veri/yerel/tmp/aes "ab" modunda
yani ekleme için. R0'da dosya adının adresini, R1'de ise modu belirten satırın adresini yüklüyoruz. Ve burada çöp kodu bitiyor, böylece bir sonraki fonksiyona geçiyoruz. Çalışmaya devam etmesi için, çöpü atlayarak fonksiyonun gerçek koduna geçişi en başa koyuyoruz ve çöp yerine yamanın devamını ekliyoruz.

UC Tarayıcısında güvenlik açıkları aranıyor

arama açık.

Fonksiyonun ilk üç parametresi aes türü var int. Kayıtları başlangıçta yığına kaydettiğimiz için işlevi kolayca iletebiliriz. yaz yığındaki adresleri.

UC Tarayıcısında güvenlik açıkları aranıyor

Daha sonra, veri boyutunu ve anahtar, başlatma vektörü ve şifrelenmiş veriler için veri işaretçisini içeren üç yapımız var.

UC Tarayıcısında güvenlik açıkları aranıyor

Sonunda dosyayı kapatın, kayıtları geri yükleyin ve kontrolü gerçek fonksiyona aktarın aes.

Yamalı kitaplığa sahip bir APK topluyoruz, imzalıyoruz, cihaza/emülatöre yüklüyoruz ve başlatıyoruz. Dökümümüzün oluşturulduğunu ve oraya birçok veri yazıldığını görüyoruz. Tarayıcı, şifrelemeyi yalnızca trafik için kullanmaz ve tüm şifreleme, söz konusu işlev üzerinden gerçekleştirilir. Ancak bazı nedenlerden dolayı gerekli veriler orada değil ve gerekli istek trafikte görünmüyor. UC Tarayıcısının gerekli isteği yapmaya tenezzül etmesini beklememek için, daha önce sunucudan alınan şifreli yanıtı alalım ve uygulamaya tekrar yama yapalım: şifre çözmeyi ana aktivitenin onCreate dosyasına ekleyin.

    const/16 v1, 0x62
new-array v1, v1, [B
fill-array-data v1, :encrypted_data
const/16 v0, 0x1f
invoke-static {v0, v1}, Lcom/uc/browser/core/d/c/g;->j(I[B)[B
move-result-object v1
array-length v2, v1
invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v2
const-string v0, "ololo"
invoke-static {v0, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

Montajını yapıyor, imzalıyor, kuruyor, lansmanını yapıyoruz. Yöntem null döndürdüğü için bir NullPointerException alıyoruz.

Kodun daha ayrıntılı analizi sırasında, ilginç satırların şifresini çözen bir işlev keşfedildi: “META-INF/” ve “.RSA”. Uygulama sertifikasını doğruluyor gibi görünüyor. Veya hatta ondan anahtarlar üretir. Sertifikayla ilgili olanlarla gerçekten uğraşmak istemiyorum, bu yüzden ona doğru sertifikayı vereceğiz. Şifreli satırı yamalayalım ki “META-INF/” yerine “BLABLINF/” elde edelim, APK'da bu isimde bir klasör oluşturup sincap tarayıcı sertifikasını buraya ekleyelim.

Montajını yapıyor, imzalıyor, kuruyor, lansmanını yapıyoruz. Bingo! Anahtar bizde!

MitM

Bir anahtar ve anahtara eşit bir başlatma vektörü aldık. CBC modunda sunucu yanıtının şifresini çözmeye çalışalım.

UC Tarayıcısında güvenlik açıkları aranıyor

MD5'e benzer bir arşiv URL'si, "extract_unzipsize" ve bir sayı görüyoruz. Kontrol ediyoruz: arşivin MD5'i aynı, paketlenmemiş kütüphanenin boyutu aynı. Bu kütüphaneye yama yapıp tarayıcıya vermeye çalışıyoruz. Yamalı kütüphanemizin yüklendiğini göstermek için “PWNED!” metnini içeren bir SMS oluşturmak için bir Intent başlatacağız. Sunucudan gelen iki yanıtı değiştireceğiz: puds.ucweb.com/upgrade/index.xhtml ve arşivi indirmek için. İlkinde MD5'i değiştiriyoruz (paket açıldıktan sonra boyut değişmiyor), ikincisinde arşivi yamalı kütüphaneyle birlikte veriyoruz.

Tarayıcı birkaç kez arşivi indirmeyi dener ve ardından hata verir. Görünüşe göre bir şey
o hoşlanmaz. Bu karanlık formatın analiz edilmesi sonucunda sunucunun arşiv boyutunu da ilettiği ortaya çıktı:

UC Tarayıcısında güvenlik açıkları aranıyor

LEB128'de kodlanmıştır. Yamadan sonra arşivin ve kütüphanenin boyutu biraz değişti, bu nedenle tarayıcı arşivin hatalı bir şekilde indirildiğini düşündü ve birkaç denemeden sonra bir hata verdi.

Arşivin boyutunu ayarlıyoruz... Ve – zafer! 🙂 Sonuç videoda.

https://www.youtube.com/watch?v=Nfns7uH03J8

Sonuçlar ve geliştirici tepkisi

Aynı şekilde, bilgisayar korsanları da kötü amaçlı kitaplıkları dağıtmak ve çalıştırmak için UC Tarayıcının güvenli olmayan özelliğini kullanabilir. Bu kütüphaneler tarayıcının bağlamında çalışacak, böylece tüm sistem izinlerini alacaklar. Sonuç olarak, kimlik avı pencerelerini görüntüleme yeteneğinin yanı sıra, turuncu Çin sincabının veritabanında saklanan oturum açma bilgileri, şifreler ve çerezler de dahil olmak üzere çalışma dosyalarına erişim mümkün oldu.

UC Tarayıcı geliştiricileriyle iletişime geçerek bulduğumuz sorun hakkında onları bilgilendirdik, güvenlik açığını ve tehlikesini belirtmeye çalıştık ancak bizimle hiçbir şey konuşmadılar. Bu arada tarayıcı, tehlikeli özelliğini açıkça sergilemeye devam etti. Ancak güvenlik açığının ayrıntılarını açıkladığımızda, artık eskisi gibi görmezden gelmek mümkün değildi. 27 Mart
Sunucuya HTTPS üzerinden erişen UC Tarayıcı 12.10.9.1193'ün yeni bir sürümü yayınlandı: puds.ucweb.com/upgrade/index.xhtml.

Ayrıca, "düzeltmeden" sonra ve bu makalenin yazıldığı zamana kadar, bir PDF'yi tarayıcıda açmaya çalışmak, "Oops, bir şeyler ters gitti!" metnini içeren bir hata mesajıyla sonuçlandı. Bir PDF'yi açmaya çalışırken sunucuya bir istek yapılmadı, ancak tarayıcı başlatıldığında bir istek yapıldı; bu, Google Play kurallarını ihlal ederek yürütülebilir kodu indirme yeteneğinin devam ettiğine işaret ediyor.

Kaynak: habr.com

Yorum ekle