Mencari kelemahan dalam Pelayar UC

Mencari kelemahan dalam Pelayar UC

Pengenalan

Pada akhir bulan Mac kita dilaporkan, bahawa mereka menemui keupayaan tersembunyi untuk memuatkan dan menjalankan kod yang tidak disahkan dalam Pelayar UC. Hari ini kita akan melihat secara terperinci bagaimana muat turun ini berlaku dan bagaimana penggodam boleh menggunakannya untuk tujuan mereka sendiri.

Beberapa ketika dahulu, UC Browser telah diiklankan dan diedarkan dengan sangat agresif: ia dipasang pada peranti pengguna menggunakan perisian hasad, diedarkan daripada pelbagai tapak di bawah nama fail video (iaitu, pengguna menyangka mereka sedang memuat turun, sebagai contoh, video lucah, tetapi sebaliknya menerima APK dengan penyemak imbas ini), menggunakan sepanduk menakutkan dengan mesej bahawa penyemak imbas itu sudah lapuk, terdedah dan sebagainya. Dalam kumpulan Pelayar UC rasmi di VK ada topik, di mana pengguna boleh mengadu tentang pengiklanan yang tidak adil, terdapat banyak contoh di sana. Pada tahun 2016 ada juga pengiklanan video dalam bahasa Rusia (ya, pengiklanan untuk penyemak imbas penyekat iklan).

Pada masa penulisan, UC Browser mempunyai lebih 500 pemasangan di Google Play. Ini mengagumkan - hanya Google Chrome yang mempunyai lebih banyak lagi. Di antara ulasan, anda boleh melihat banyak aduan tentang pengiklanan dan ubah hala ke beberapa aplikasi di Google Play. Inilah sebab penyelidikan kami: kami memutuskan untuk melihat sama ada UC Browser melakukan sesuatu yang buruk. Dan ternyata dia melakukannya!

Dalam kod aplikasi, keupayaan untuk memuat turun dan menjalankan kod boleh laku ditemui, yang bertentangan dengan peraturan penerbitan aplikasi di Google Play. Selain memuat turun kod boleh laku, UC Browser melakukannya dengan cara yang tidak selamat, yang boleh digunakan untuk melancarkan serangan MitM. Mari kita lihat sama ada kita boleh melakukan serangan sedemikian.

Semua yang ditulis di bawah adalah berkaitan untuk versi Penyemak Imbas UC yang tersedia di Google Play pada masa kajian:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-Ρ„Π°ΠΉΠ»Π°: f5edb2243413c777172f6362876041eb0c3a928c

Vektor serangan

Dalam manifes Pelayar UC anda boleh mencari perkhidmatan dengan nama yang menerangkan sendiri com.uc.deployment.UpgradeDeployService.

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

Apabila perkhidmatan ini bermula, penyemak imbas membuat permintaan POST untuk puds.ucweb.com/upgrade/index.xhtml, yang boleh dilihat dalam lalu lintas beberapa lama selepas permulaan. Sebagai tindak balas, dia mungkin menerima arahan untuk memuat turun beberapa kemas kini atau modul baharu. Semasa analisis, pelayan tidak memberikan arahan sedemikian, tetapi kami perhatikan bahawa apabila kami cuba membuka PDF dalam penyemak imbas, ia membuat permintaan kedua ke alamat yang dinyatakan di atas, selepas itu ia memuat turun perpustakaan asli. Untuk melakukan serangan itu, kami memutuskan untuk menggunakan ciri Pelayar UC ini: keupayaan untuk membuka PDF menggunakan perpustakaan asli, yang tiada dalam APK dan yang dimuat turun dari Internet jika perlu. Perlu diingat bahawa, secara teori, Pelayar UC boleh dipaksa untuk memuat turun sesuatu tanpa interaksi pengguna - jika anda memberikan respons yang terbentuk dengan baik kepada permintaan yang dilaksanakan selepas pelayar dilancarkan. Tetapi untuk melakukan ini, kami perlu mengkaji protokol interaksi dengan pelayan dengan lebih terperinci, jadi kami memutuskan bahawa lebih mudah untuk mengedit respons yang dipintas dan menggantikan perpustakaan untuk bekerja dengan PDF.

Jadi, apabila pengguna ingin membuka PDF terus dalam penyemak imbas, permintaan berikut boleh dilihat dalam trafik:

Mencari kelemahan dalam Pelayar UC

Mula-mula ada permintaan POST ke puds.ucweb.com/upgrade/index.xhtml, kemudian
Arkib dengan perpustakaan untuk melihat format PDF dan pejabat dimuat turun. Adalah logik untuk menganggap bahawa permintaan pertama menghantar maklumat tentang sistem (sekurang-kurangnya seni bina untuk menyediakan perpustakaan yang diperlukan), dan sebagai tindak balas kepadanya pelayar menerima beberapa maklumat tentang perpustakaan yang perlu dimuat turun: alamat dan, mungkin. , sesuatu yang lain. Masalahnya ialah permintaan ini disulitkan.

Minta serpihan

Serpihan jawapan

Mencari kelemahan dalam Pelayar UC

Mencari kelemahan dalam Pelayar UC

Perpustakaan itu sendiri dibungkus dalam ZIP dan tidak disulitkan.

Mencari kelemahan dalam Pelayar UC

Cari kod penyahsulitan trafik

Mari cuba tafsirkan respons pelayan. Mari lihat kod kelas com.uc.deployment.UpgradeDeployService: daripada kaedah onStartCommand pergi ke com.uc.deployment.bx, dan daripadanya kepada 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);
}

Kami melihat pembentukan permintaan POST di sini. Kami memberi perhatian kepada penciptaan tatasusunan 16 bait dan pengisiannya: 0x5F, 0, 0x1F, -50 (=0xCE). Bertepatan dengan apa yang kita lihat dalam permintaan di atas.

Dalam kelas yang sama anda boleh melihat kelas bersarang yang mempunyai kaedah menarik lain:

        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");
}
}

Kaedah ini mengambil tatasusunan bait sebagai input dan menyemak bahawa bait sifar ialah 0x60 atau bait ketiga ialah 0xD0, dan bait kedua ialah 1, 11 atau 0x1F. Kami melihat respons dari pelayan: bait sifar ialah 0x60, yang kedua ialah 0x1F, yang ketiga ialah 0x60. Kedengaran seperti apa yang kita perlukan. Berdasarkan baris ("up_decrypt", sebagai contoh), kaedah harus dipanggil di sini yang akan menyahsulit respons pelayan.
Mari kita beralih kepada kaedah gj. Ambil perhatian bahawa hujah pertama ialah bait pada offset 2 (iaitu 0x1F dalam kes kami), dan yang kedua ialah respons pelayan tanpa
16 bait pertama.

     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;
}

Jelas sekali, di sini kami memilih algoritma penyahsulitan, dan bait yang sama yang ada dalam kami
kes sama dengan 0x1F, menandakan satu daripada tiga pilihan yang mungkin.

Kami terus menganalisis kod tersebut. Selepas beberapa lompatan, kami mendapati diri kami berada dalam kaedah dengan nama yang jelas decryptBytesByKey.

Di sini dua lagi bait dipisahkan daripada respons kami, dan rentetan diperoleh daripadanya. Adalah jelas bahawa dengan cara ini kunci untuk menyahsulit mesej dipilih.

    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;
}

Memandang ke hadapan, kami perhatikan bahawa pada peringkat ini kami belum memperoleh kunci, tetapi hanya "pengecam"nya. Mendapatkan kunci adalah sedikit lebih rumit.

Dalam kaedah seterusnya, dua lagi parameter ditambahkan pada yang sedia ada, menjadikan empat daripadanya: nombor ajaib 16, pengecam kunci, data yang disulitkan dan rentetan yang tidak dapat difahami (dalam kes kami, kosong).

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

Selepas beberapa siri peralihan kita sampai pada kaedah staticBinarySafeDecryptNoB64 antara muka com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Tiada kelas dalam kod aplikasi utama yang melaksanakan antara muka ini. Terdapat kelas sedemikian dalam fail lib/armeabi-v7a/libsgmain.so, yang sebenarnya bukan .so, tetapi .jar. Kaedah yang kami minati dilaksanakan seperti berikut:

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);
}
//...
}

Di sini senarai parameter kami ditambah dengan dua lagi integer: 2 dan 0. Berdasarkan
semuanya, 2 bermaksud penyahsulitan, seperti dalam kaedah doFinal kelas sistem javax.crypto.Cipher. Dan semua ini dipindahkan ke Router tertentu dengan nombor 10601 - ini nampaknya nombor arahan.

Selepas rantaian peralihan seterusnya kita dapati kelas yang melaksanakan antara muka IRouterComponent dan kaedah doCommand:

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);
}
}

Dan juga kelas Perpustakaan JNICL, di mana kaedah asli diisytiharkan doCommandNative:

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

Ini bermakna kita perlu mencari kaedah dalam kod asli doCommandNative. Dan di sinilah keseronokan bermula.

Kekeliruan kod mesin

Dalam fail libsgmain.so (yang sebenarnya adalah .jar dan di mana kami mendapati pelaksanaan beberapa antara muka berkaitan penyulitan tepat di atas) terdapat satu perpustakaan asli: libsgmainso-6.4.36.so. Kami membukanya dalam IDA dan mendapatkan sekumpulan kotak dialog dengan ralat. Masalahnya ialah jadual pengepala bahagian tidak sah. Ini dilakukan dengan sengaja untuk merumitkan analisis.

Mencari kelemahan dalam Pelayar UC

Tetapi ia tidak diperlukan: untuk memuatkan fail ELF dengan betul dan menganalisisnya, jadual pengepala program sudah mencukupi. Oleh itu, kami hanya memadamkan jadual bahagian, mensifarkan medan yang sepadan dalam pengepala.

Mencari kelemahan dalam Pelayar UC

Buka fail dalam IDA sekali lagi.

Terdapat dua cara untuk memberitahu mesin maya Java di mana tepatnya dalam perpustakaan asli terdapat pelaksanaan kaedah yang diisytiharkan dalam kod Java sebagai asli. Yang pertama ialah memberinya nama spesies Java_package_name_ClassName_MethodName.

Yang kedua ialah mendaftarkannya apabila memuatkan perpustakaan (dalam fungsi JNI_OnLoad)
menggunakan panggilan fungsi Daftar Orang Asli.

Dalam kes kami, jika kami menggunakan kaedah pertama, nama harus seperti ini: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

Tiada fungsi sedemikian di antara fungsi yang dieksport, yang bermaksud anda perlu mencari panggilan Daftar Orang Asli.
Mari pergi ke fungsi JNI_OnLoad dan kita lihat gambar ini:

Mencari kelemahan dalam Pelayar UC

Apa yang berlaku di sini? Pada pandangan pertama, permulaan dan penghujung fungsi adalah tipikal untuk seni bina ARM. Arahan pertama pada tindanan menyimpan kandungan daftar yang akan digunakan oleh fungsi dalam operasinya (dalam kes ini, R0, R1 dan R2), serta kandungan daftar LR, yang mengandungi alamat pemulangan dari fungsi . Arahan terakhir memulihkan daftar yang disimpan, dan alamat pemulangan segera diletakkan dalam daftar PC - dengan itu kembali dari fungsi. Tetapi jika anda melihat dengan teliti, anda akan melihat bahawa arahan kedua terakhir mengubah alamat pemulangan yang disimpan pada timbunan. Jom kira macam mana nanti
pelaksanaan kod. Alamat tertentu 1xB0 dimuatkan ke dalam R130, 5 ditolak daripadanya, kemudian ia dipindahkan ke R0 dan 0x10 ditambah kepadanya. Ternyata 0xB13B. Oleh itu, IDA berpendapat bahawa arahan terakhir adalah pemulangan fungsi biasa, tetapi sebenarnya ia pergi ke alamat yang dikira 0xB13B.

Perlu diingat di sini bahawa pemproses ARM mempunyai dua mod dan dua set arahan: ARM dan Thumb. Alamat yang paling tidak ketara memberitahu pemproses set arahan yang sedang digunakan. Iaitu, alamatnya sebenarnya 0xB13A, dan satu dalam bit paling tidak ketara menunjukkan mod Ibu jari.

"Penyesuai" yang serupa telah ditambahkan pada permulaan setiap fungsi dalam perpustakaan ini dan
kod sampah. Kami tidak akan membincangkannya secara terperinci - kami hanya ingat
bahawa permulaan sebenar hampir semua fungsi adalah sedikit lebih jauh.

Memandangkan kod itu tidak secara eksplisit melompat ke 0xB13A, IDA sendiri tidak mengenali bahawa kod itu terletak di lokasi ini. Atas sebab yang sama, ia tidak mengenali kebanyakan kod dalam perpustakaan sebagai kod, yang menjadikan analisis agak sukar. Kami memberitahu IDA bahawa ini adalah kod, dan inilah yang berlaku:

Mencari kelemahan dalam Pelayar UC

Jadual bermula dengan jelas pada 0xB144. Apa yang ada dalam sub_494C?

Mencari kelemahan dalam Pelayar UC

Apabila memanggil fungsi ini dalam daftar LR, kami mendapat alamat jadual yang disebutkan sebelumnya (0xB144). Dalam R0 - indeks dalam jadual ini. Iaitu, nilai diambil dari jadual, ditambah kepada LR dan hasilnya adalah
alamat yang hendak dituju. Mari cuba mengiranya: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Kami pergi ke alamat yang diterima dan melihat secara literal beberapa arahan berguna dan sekali lagi pergi ke 0xB140:

Mencari kelemahan dalam Pelayar UC

Sekarang akan ada peralihan pada offset dengan indeks 0x20 daripada jadual.

Berdasarkan saiz jadual, akan terdapat banyak peralihan sedemikian dalam kod. Persoalannya timbul sama ada mungkin untuk menangani perkara ini dengan lebih automatik, tanpa mengira alamat secara manual. Dan skrip dan keupayaan untuk menampal kod dalam IDA membantu kami:

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"

Letakkan kursor pada baris 0xB26A, jalankan skrip dan lihat peralihan kepada 0xB4B0:

Mencari kelemahan dalam Pelayar UC

IDA sekali lagi tidak mengenali kawasan ini sebagai kod. Kami membantunya dan melihat reka bentuk lain di sana:

Mencari kelemahan dalam Pelayar UC

Arahan selepas BLX nampaknya tidak begitu masuk akal, ia lebih kepada sejenis anjakan. Mari lihat sub_4964:

Mencari kelemahan dalam Pelayar UC

Dan sesungguhnya, di sini dword diambil pada alamat yang terletak di LR, ditambah pada alamat ini, selepas itu nilai pada alamat yang terhasil diambil dan diletakkan pada timbunan. Juga, 4 ditambah pada LR supaya selepas kembali daripada fungsi, ofset yang sama ini dilangkau. Selepas itu arahan POP {R1} mengambil nilai yang terhasil daripada timbunan. Jika anda melihat apa yang terletak di alamat 0xB4BA + 0xEA = 0xB5A4, anda akan melihat sesuatu yang serupa dengan jadual alamat:

Mencari kelemahan dalam Pelayar UC

Untuk menampal reka bentuk ini, anda perlu mendapatkan dua parameter daripada kod: offset dan nombor daftar di mana anda ingin meletakkan hasilnya. Untuk setiap pendaftaran yang mungkin, anda perlu menyediakan sekeping kod terlebih dahulu.

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"

Kami meletakkan kursor pada permulaan struktur yang ingin kami gantikan - 0xB4B2 - dan jalankan skrip:

Mencari kelemahan dalam Pelayar UC

Sebagai tambahan kepada struktur yang telah disebutkan, kod itu juga mengandungi yang berikut:

Mencari kelemahan dalam Pelayar UC

Seperti dalam kes sebelumnya, selepas arahan BLX terdapat offset:

Mencari kelemahan dalam Pelayar UC

Kami mengambil offset ke alamat dari LR, menambahnya ke LR dan pergi ke sana. 0x72044 + 0xC = 0x72050. Skrip untuk reka bentuk ini agak mudah:

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"

Hasil pelaksanaan skrip:

Mencari kelemahan dalam Pelayar UC

Setelah semuanya ditampal dalam fungsi, anda boleh menunjukkan IDA ke permulaannya yang sebenar. Ia akan menyatukan semua kod fungsi, dan ia boleh dinyahkompilasi menggunakan HexRays.

Menyahkod rentetan

Kami telah belajar untuk menangani kekeliruan kod mesin di perpustakaan libsgmainso-6.4.36.so daripada UC Browser dan menerima kod fungsi JNI_OnLoad.

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;
}

Mari kita lihat lebih dekat pada baris berikut:

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

Dalam fungsi sub_73E24 nama kelas jelas sedang dinyahsulit. Sebagai parameter kepada fungsi ini, penunjuk kepada data yang serupa dengan data yang disulitkan, penimbal tertentu dan nombor diluluskan. Jelas sekali, selepas memanggil fungsi itu, akan ada baris yang dinyahsulit dalam penimbal, kerana ia dihantar ke fungsi FindClass, yang mengambil nama kelas sebagai parameter kedua. Oleh itu, nombor adalah saiz penimbal atau panjang garisan. Mari cuba tafsirkan nama kelas, ia sepatutnya memberitahu kita sama ada kita menuju ke arah yang betul. Mari kita lihat dengan lebih dekat apa yang berlaku dalam 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;
}

Fungsi sub_7AF78 mencipta contoh bekas untuk tatasusunan bait dengan saiz yang ditentukan (kami tidak akan membincangkan bekas ini secara terperinci). Di sini dua bekas sedemikian dibuat: satu mengandungi baris "DcO/lcK+h?m3c*q@" (mudah untuk meneka bahawa ini adalah kunci), yang lain mengandungi data yang disulitkan. Seterusnya, kedua-dua objek diletakkan dalam struktur tertentu, yang dihantar ke fungsi sub_6115C. Mari kita tandakan medan dengan nilai 3 dalam struktur ini. Mari lihat apa yang berlaku kepada struktur ini seterusnya.

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;
}

Parameter suis ialah medan struktur yang sebelum ini diberikan nilai 3. Lihat kes 3: kepada fungsi sub_6364C parameter diluluskan daripada struktur yang telah ditambahkan di sana dalam fungsi sebelumnya, iaitu kunci dan data yang disulitkan. Jika anda melihat dengan teliti sub_6364C, anda boleh mengenali algoritma RC4 di dalamnya.

Kami mempunyai algoritma dan kunci. Mari cuba tafsirkan nama kelas. Inilah yang berlaku: com/taobao/wireless/security/adapter/JNICLibrary. Hebat! Kami berada di landasan yang betul.

Pokok perintah

Sekarang kita perlu mencari cabaran Daftar Orang Asli, yang akan menunjukkan kita kepada fungsi doCommandNative. Mari lihat fungsi yang dipanggil dari JNI_OnLoad, dan kami dapati di dalamnya 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;
}

Dan sesungguhnya, kaedah asli dengan nama didaftarkan di sini doCommandNative. Sekarang kita tahu alamatnya. Mari lihat apa yang dia lakukan.

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;
}

Dengan nama anda boleh meneka bahawa di sini adalah titik masuk semua fungsi yang pembangun memutuskan untuk memindahkan ke perpustakaan asli. Kami berminat dengan fungsi nombor 10601.

Anda boleh melihat dari kod bahawa nombor arahan menghasilkan tiga nombor: arahan/10000, arahan % 10000 / 100 ΠΈ arahan % 10, iaitu, dalam kes kami, 1, 6 dan 1. Ketiga-tiga nombor ini, serta penunjuk kepada JNIEnv dan hujah yang dihantar ke fungsi ditambahkan pada struktur dan diteruskan. Menggunakan tiga nombor yang diperolehi (mari kita nyatakan mereka N1, N2 dan N3), pepohon arahan dibina.

Sesuatu seperti ini:

Mencari kelemahan dalam Pelayar UC

Pokok itu diisi secara dinamik JNI_OnLoad.
Tiga nombor mengekod laluan dalam pokok. Setiap daun pokok mengandungi alamat pocked bagi fungsi yang sepadan. Kuncinya adalah dalam nod induk. Mencari tempat dalam kod di mana fungsi yang kami perlukan ditambah pada pokok tidak sukar jika anda memahami semua struktur yang digunakan (kami tidak menerangkannya supaya tidak mengembang artikel yang sudah agak besar).

Lebih kekeliruan

Kami menerima alamat fungsi yang sepatutnya menyahsulit trafik: 0x5F1AC. Tetapi masih terlalu awal untuk bergembira: pembangun UC Browser telah menyediakan satu lagi kejutan untuk kami.

Selepas menerima parameter daripada tatasusunan yang telah dibentuk dalam kod Java, kita dapat
kepada fungsi di alamat 0x4D070. Dan di sini satu lagi jenis pengeliruan kod menanti kami.

Kami meletakkan dua indeks dalam R7 dan R4:

Mencari kelemahan dalam Pelayar UC

Kami mengalihkan indeks pertama kepada R11:

Mencari kelemahan dalam Pelayar UC

Untuk mendapatkan alamat daripada jadual, gunakan indeks:

Mencari kelemahan dalam Pelayar UC

Selepas pergi ke alamat pertama, indeks kedua digunakan, iaitu dalam R4. Terdapat 230 elemen dalam jadual.

Apa yang perlu dilakukan mengenainya? Anda boleh memberitahu IDA bahawa ini ialah suis: Edit -> Lain -> Tentukan simpulan bahasa suis.

Mencari kelemahan dalam Pelayar UC

Kod yang terhasil adalah menakutkan. Tetapi, melalui jalan anda melalui hutannya, anda dapat melihat panggilan ke fungsi yang sudah biasa kepada kami sub_6115C:

Mencari kelemahan dalam Pelayar UC

Terdapat suis di mana dalam kes 3 terdapat penyahsulitan menggunakan algoritma RC4. Dan dalam kes ini, struktur yang dihantar ke fungsi diisi daripada parameter yang diluluskan ke doCommandNative. Mari kita ingat apa yang kita ada di sana magicInt dengan nilai 16. Kami melihat kes yang sepadan - dan selepas beberapa peralihan kami menemui kod yang boleh dikenal pasti oleh algoritma.

Mencari kelemahan dalam Pelayar UC

Ini adalah AES!

Algoritma wujud, yang tinggal hanyalah untuk mendapatkan parameternya: mod, kunci dan, mungkin, vektor permulaan (kehadirannya bergantung pada mod operasi algoritma AES). Struktur dengan mereka mesti dibentuk di suatu tempat sebelum panggilan fungsi sub_6115C, tetapi bahagian kod ini sangat dikaburkan, jadi timbul idea untuk menampal kod supaya semua parameter fungsi penyahsulitan dibuang ke dalam fail.

Tampalan

Untuk tidak menulis semua kod tampalan dalam bahasa pemasangan secara manual, anda boleh melancarkan Android Studio, tulis fungsi di sana yang menerima parameter input yang sama seperti fungsi penyahsulitan kami dan tulis pada fail, kemudian salin-tampal kod yang akan pengkompil. menjana.

Rakan-rakan kami dari pasukan UC Browser juga menjaga kemudahan menambah kod. Marilah kita ingat bahawa pada permulaan setiap fungsi kita mempunyai kod sampah yang boleh digantikan dengan yang lain dengan mudah. Sangat mudah πŸ™‚ Walau bagaimanapun, pada permulaan fungsi sasaran tidak ada ruang yang mencukupi untuk kod yang menyimpan semua parameter ke fail. Saya terpaksa membahagikannya kepada beberapa bahagian dan menggunakan blok sampah dari fungsi jiran. Terdapat empat bahagian secara keseluruhan.

Bahagian pertama:

Mencari kelemahan dalam Pelayar UC

Dalam seni bina ARM, empat parameter fungsi pertama diluluskan melalui daftar R0-R3, selebihnya, jika ada, melalui timbunan. Daftar LR membawa alamat pemulangan. Semua ini perlu disimpan supaya fungsi boleh berfungsi selepas kami membuang parameternya. Kami juga perlu menyimpan semua daftar yang akan kami gunakan dalam proses, jadi kami melakukan PUSH.W {R0-R10,LR}. Dalam R7 kita mendapat alamat senarai parameter yang dihantar ke fungsi melalui timbunan.

Menggunakan fungsi fopen mari buka fail /data/local/tmp/aes dalam mod "ab".
iaitu untuk tambahan. Dalam R0 kami memuatkan alamat nama fail, dalam R1 - alamat baris yang menunjukkan mod. Dan di sini kod sampah berakhir, jadi kita beralih ke fungsi seterusnya. Agar ia terus berfungsi, kami meletakkan pada permulaan peralihan kepada kod sebenar fungsi, memintas sampah, dan bukannya sampah kami menambah kesinambungan tampung.

Mencari kelemahan dalam Pelayar UC

Memanggil fopen.

Tiga parameter pertama fungsi aes mempunyai jenis int. Oleh kerana kami menyimpan daftar ke timbunan pada mulanya, kami hanya boleh lulus fungsi fwrite alamat mereka pada timbunan.

Mencari kelemahan dalam Pelayar UC

Seterusnya kita mempunyai tiga struktur yang mengandungi saiz data dan penunjuk kepada data untuk kunci, vektor permulaan dan data yang disulitkan.

Mencari kelemahan dalam Pelayar UC

Pada akhirnya, tutup fail, pulihkan daftar dan pindahkan kawalan ke fungsi sebenar aes.

Kami mengumpul APK dengan perpustakaan yang ditambal, menandatanganinya, memuat naiknya ke peranti/emulator dan melancarkannya. Kami melihat bahawa pembuangan kami sedang dibuat, dan banyak data sedang ditulis di sana. Pelayar menggunakan penyulitan bukan sahaja untuk trafik, dan semua penyulitan melalui fungsi yang dipersoalkan. Tetapi atas sebab tertentu data yang diperlukan tidak ada, dan permintaan yang diperlukan tidak kelihatan dalam trafik. Untuk tidak menunggu sehingga Penyemak Imbas UC berkenan untuk membuat permintaan yang diperlukan, mari ambil respons yang disulitkan daripada pelayan yang diterima sebelum ini dan tampal aplikasi itu semula: tambahkan penyahsulitan pada onCreate aktiviti utama.

    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

Kami memasang, menandatangani, memasang, melancarkan. Kami menerima NullPointerException kerana kaedah mengembalikan null.

Semasa analisis lanjut kod, fungsi ditemui yang mentafsir baris menarik: "META-INF/" dan ".RSA". Nampaknya permohonan itu sedang mengesahkan sijilnya. Atau menjana kunci daripadanya. Saya tidak begitu mahu berurusan dengan apa yang berlaku dengan sijil itu, jadi kami hanya akan menyelitkannya dengan sijil yang betul. Mari kita menampal baris yang disulitkan supaya bukannya "META-INF/" kita mendapat "BLABLINF/", buat folder dengan nama itu dalam APK dan tambahkan sijil penyemak imbas tupai di sana.

Kami memasang, menandatangani, memasang, melancarkan. Bingo! Kami mempunyai kunci!

MitM

Kami menerima kunci dan vektor permulaan yang sama dengan kunci. Mari cuba menyahsulit respons pelayan dalam mod CBC.

Mencari kelemahan dalam Pelayar UC

Kami melihat URL arkib, sesuatu yang serupa dengan MD5, "extract_unzipsize" dan nombor. Kami menyemak: MD5 arkib adalah sama, saiz perpustakaan yang tidak dibungkus adalah sama. Kami cuba menampal perpustakaan ini dan memberikannya kepada penyemak imbas. Untuk menunjukkan bahawa perpustakaan kami yang ditambal telah dimuatkan, kami akan melancarkan Niat untuk membuat SMS dengan teks "PWNED!" Kami akan menggantikan dua respons daripada pelayan: puds.ucweb.com/upgrade/index.xhtml dan untuk memuat turun arkib. Pada yang pertama kami menggantikan MD5 (saiz tidak berubah selepas membongkar), pada yang kedua kami memberikan arkib dengan perpustakaan yang ditambal.

Penyemak imbas cuba memuat turun arkib beberapa kali, selepas itu ia memberikan ralat. Rupa-rupanya sesuatu
dia tidak suka. Hasil daripada menganalisis format keruh ini, ternyata pelayan juga menghantar saiz arkib:

Mencari kelemahan dalam Pelayar UC

Ia dikodkan dalam LEB128. Selepas tampalan, saiz arkib dengan perpustakaan berubah sedikit, jadi penyemak imbas menganggap bahawa arkib telah dimuat turun secara bengkok, dan selepas beberapa percubaan ia menimbulkan ralat.

Kami melaraskan saiz arkib... Dan – kemenangan! πŸ™‚ Hasilnya ada dalam video.

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

Akibat dan reaksi pembangun

Dengan cara yang sama, penggodam boleh menggunakan ciri tidak selamat Pelayar UC untuk mengedar dan menjalankan perpustakaan berniat jahat. Pustaka ini akan berfungsi dalam konteks penyemak imbas, jadi mereka akan menerima semua kebenaran sistemnya. Akibatnya, keupayaan untuk memaparkan tetingkap pancingan data, serta akses kepada fail kerja tupai Cina oren, termasuk log masuk, kata laluan dan kuki yang disimpan dalam pangkalan data.

Kami menghubungi pembangun UC Browser dan memaklumkan mereka tentang masalah yang kami temui, cuba menunjukkan kelemahan dan bahayanya, tetapi mereka tidak membincangkan apa-apa dengan kami. Sementara itu, pelayar terus mempamerkan ciri berbahayanya di hadapan mata. Tetapi sebaik sahaja kami mendedahkan butiran kelemahan, ia tidak lagi mungkin untuk mengabaikannya seperti sebelum ini. 27 Mac adalah
versi baharu UC Browser 12.10.9.1193 telah dikeluarkan, yang mengakses pelayan melalui HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Di samping itu, selepas "pembetulan" dan sehingga masa menulis artikel ini, cubaan membuka PDF dalam penyemak imbas menghasilkan mesej ralat dengan teks "Op, ada masalah!" Permintaan kepada pelayan tidak dibuat semasa cuba membuka PDF, tetapi permintaan dibuat apabila penyemak imbas dilancarkan, yang membayangkan keupayaan berterusan untuk memuat turun kod boleh laku yang melanggar peraturan Google Play.

Sumber: www.habr.com

Tambah komen