Mencari kerentanan di UC Browser

Mencari kerentanan di UC Browser

pengenalan

Pada akhir bulan Maret kami ΠΎΠΎΠ±Ρ‰Π°Π»ΠΈ, bahwa mereka menemukan kemampuan tersembunyi untuk memuat dan menjalankan kode yang belum diverifikasi di UC Browser. Hari ini kita akan melihat secara detail bagaimana pengunduhan ini terjadi dan bagaimana peretas dapat menggunakannya untuk tujuan mereka sendiri.

Beberapa waktu lalu, UC Browser diiklankan dan didistribusikan dengan sangat agresif: diinstal pada perangkat pengguna menggunakan malware, didistribusikan dari berbagai situs dengan kedok file video (yaitu, pengguna mengira mereka sedang mengunduh, misalnya, video porno, tapi alih-alih menerima APK dengan browser ini), menggunakan spanduk menakutkan dengan pesan bahwa browser tersebut sudah ketinggalan jaman, rentan, dan hal-hal seperti itu. Di grup resmi UC Browser di VK ada tema, di mana pengguna dapat mengeluh tentang iklan yang tidak adil, ada banyak contoh di sana. Bahkan pada tahun 2016 pun ada iklan video dalam bahasa Rusia (ya, beriklan untuk browser pemblokiran iklan).

Pada saat penulisan, UC Browser memiliki lebih dari 500 instalasi di Google Play. Ini mengesankan - hanya Google Chrome yang memiliki lebih banyak. Di antara ulasan tersebut Anda dapat melihat cukup banyak keluhan tentang iklan dan pengalihan ke beberapa aplikasi di Google Play. Inilah alasan penelitian kami: kami memutuskan untuk melihat apakah UC Browser melakukan sesuatu yang buruk. Dan ternyata dia melakukannya!

Dalam kode aplikasi, kemampuan untuk mengunduh dan menjalankan kode yang dapat dieksekusi ditemukan, yang bertentangan dengan aturan penerbitan aplikasi di Google Play. Selain mengunduh kode yang dapat dieksekusi, UC Browser melakukannya dengan cara yang tidak aman, yang dapat digunakan untuk meluncurkan serangan MitM. Mari kita lihat apakah kita bisa melakukan serangan seperti itu.

Semua yang tertulis di bawah ini relevan untuk versi UC Browser yang tersedia di Google Play pada saat penelitian dilakukan:

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

Vektor serangan

Dalam manifes UC Browser Anda dapat menemukan layanan dengan nama yang cukup jelas com.uc.deployment.UpgradeDeployService.

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

Saat layanan ini dimulai, browser membuat permintaan POST ke puds.ucweb.com/upgrade/index.xhtml, yang dapat dilihat di lalu lintas beberapa saat setelah permulaan. Sebagai tanggapan, dia mungkin menerima perintah untuk mengunduh beberapa pembaruan atau modul baru. Selama analisis, server tidak memberikan perintah seperti itu, tetapi kami memperhatikan bahwa ketika kami mencoba membuka PDF di browser, server membuat permintaan kedua ke alamat yang ditentukan di atas, setelah itu mengunduh perpustakaan asli. Untuk melakukan serangan, kami memutuskan untuk menggunakan fitur UC Browser ini: kemampuan untuk membuka PDF menggunakan perpustakaan asli, yang tidak ada dalam APK dan diunduh dari Internet jika diperlukan. Perlu dicatat bahwa, secara teoritis, UC Browser dapat dipaksa untuk mengunduh sesuatu tanpa interaksi pengguna - jika Anda memberikan respons yang baik terhadap permintaan yang dijalankan setelah browser diluncurkan. Namun untuk melakukan ini, kami perlu mempelajari protokol interaksi dengan server secara lebih rinci, jadi kami memutuskan bahwa akan lebih mudah untuk mengedit respons yang disadap dan mengganti perpustakaan untuk bekerja dengan PDF.

Jadi, ketika pengguna ingin membuka PDF langsung di browser, permintaan berikut dapat dilihat di lalu lintas:

Mencari kerentanan di UC Browser

Pertama ada permintaan POST ke puds.ucweb.com/upgrade/index.xhtml, kemudian
Arsip dengan perpustakaan untuk melihat PDF dan format kantor diunduh. Adalah logis untuk mengasumsikan bahwa permintaan pertama mengirimkan informasi tentang sistem (setidaknya arsitektur untuk menyediakan perpustakaan yang diperlukan), dan sebagai tanggapan terhadapnya browser menerima beberapa informasi tentang perpustakaan yang perlu diunduh: alamat dan, mungkin , sesuatu yang lain. Masalahnya adalah permintaan ini dienkripsi.

Fragmen permintaan

Fragmen jawaban

Mencari kerentanan di UC Browser

Mencari kerentanan di UC Browser

Perpustakaan itu sendiri dikemas dalam ZIP dan tidak dienkripsi.

Mencari kerentanan di UC Browser

Cari kode dekripsi lalu lintas

Mari kita coba menguraikan respons server. Mari kita lihat kode kelasnya com.uc.deployment.UpgradeDeployService: dari metode diStartCommand pergi ke com.uc.deployment.bx, dan dari itu ke 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 memperhatikan pembuatan array 16 byte dan pengisiannya: 0x5F, 0, 0x1F, -50 (=0xCE). Bertepatan dengan apa yang kita lihat pada permintaan di atas.

Di kelas yang sama Anda bisa melihat kelas bersarang yang memiliki metode menarik lainnya:

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

Metode ini mengambil array byte sebagai input dan memeriksa apakah byte nol adalah 0x60 atau byte ketiga adalah 0xD0, dan byte kedua adalah 1, 11 atau 0x1F. Kami melihat respon dari server: byte nol adalah 0x60, byte kedua adalah 0x1F, byte ketiga adalah 0x60. Kedengarannya seperti yang kita butuhkan. Dilihat dari baris (β€œup_decrypt”, misalnya), suatu metode harus dipanggil di sini yang akan mendekripsi respons server.
Mari beralih ke metodenya gj. Perhatikan bahwa argumen pertama adalah byte pada offset 2 (yaitu 0x1F dalam kasus kami), dan argumen kedua adalah respons server tanpa
16 byte 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, di sini kita memilih algoritma dekripsi, dan byte yang sama dengan yang kita miliki
case sama dengan 0x1F, menunjukkan salah satu dari tiga opsi yang mungkin.

Kami terus menganalisis kodenya. Setelah beberapa lompatan kita menemukan diri kita dalam sebuah metode dengan nama yang cukup jelas dekripsiBytesByKey.

Di sini dua byte lagi dipisahkan dari respons kami, dan sebuah string diperoleh darinya. Jelas bahwa dengan cara ini kunci untuk mendekripsi pesan 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;
}

Ke depan, kami mencatat bahwa pada tahap ini kami belum mendapatkan kunci, tetapi hanya β€œpengidentifikasinya”. Mendapatkan kuncinya sedikit lebih rumit.

Dalam metode selanjutnya, dua parameter lagi ditambahkan ke parameter yang sudah ada, sehingga menghasilkan empat parameter: angka ajaib 16, pengidentifikasi kunci, data terenkripsi, dan string yang tidak dapat dipahami (dalam kasus kami, kosong).

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

Setelah serangkaian transisi, kita sampai pada metodenya staticBinarySafeDecryptNoB64 antarmuka com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Tidak ada kelas dalam kode aplikasi utama yang mengimplementasikan antarmuka ini. Ada kelas seperti itu di file lib/armeabi-v7a/libsgmain.so, yang sebenarnya bukan .so, melainkan .jar. Metode yang kami minati diimplementasikan sebagai 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 daftar parameter kami dilengkapi dengan dua bilangan bulat lagi: 2 dan 0. Dilihat dari
semuanya, 2 berarti dekripsi, seperti pada metodenya lakukanFinal kelas sistem javax.crypto.Cipher. Dan semua ini ditransfer ke Router tertentu dengan nomor 10601 - tampaknya ini adalah nomor perintah.

Setelah rangkaian transisi berikutnya kita menemukan kelas yang mengimplementasikan antarmuka Komponen Luar IR dan metode lakukanPerintah:

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 JNIC, yang mana metode asli dideklarasikan doCommandNative:

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

Ini berarti kita perlu menemukan metode dalam kode asli doCommandNative. Dan disinilah kesenangan dimulai.

Kebingungan kode mesin

Dalam file libsgmain.so (yang sebenarnya adalah .jar dan di dalamnya kami menemukan implementasi beberapa antarmuka terkait enkripsi tepat di atas) ada satu perpustakaan asli: libsgmainso-6.4.36.so. Kami membukanya di IDA dan mendapatkan banyak kotak dialog dengan kesalahan. Masalahnya adalah tabel header bagian tidak valid. Hal ini dilakukan dengan sengaja untuk mempersulit analisis.

Mencari kerentanan di UC Browser

Tapi itu tidak diperlukan: untuk memuat file ELF dengan benar dan menganalisisnya, tabel header program sudah cukup. Oleh karena itu, kami cukup menghapus tabel bagian, memusatkan perhatian pada bidang terkait di header.

Mencari kerentanan di UC Browser

Buka file di IDA lagi.

Ada dua cara untuk memberi tahu mesin virtual Java di mana tepatnya di perpustakaan asli implementasi metode yang dinyatakan dalam kode Java sebagai asli berada. Yang pertama adalah memberinya nama spesies Java_package_name_ClassName_MethodName.

Yang kedua adalah mendaftarkannya saat memuat perpustakaan (dalam fungsi JNI_OnLoad)
menggunakan pemanggilan fungsi Daftar Asli.

Dalam kasus kita, jika kita menggunakan metode pertama, namanya akan seperti ini: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Tidak ada fungsi seperti itu di antara fungsi yang diekspor, yang berarti Anda perlu mencari panggilan Daftar Asli.
Mari kita pergi ke fungsinya JNI_OnLoad dan kita melihat gambar ini:

Mencari kerentanan di UC Browser

Apa yang terjadi di sini? Sekilas, awal dan akhir fungsinya merupakan ciri khas arsitektur ARM. Instruksi pertama pada stack menyimpan isi register yang akan digunakan fungsi dalam operasinya (dalam hal ini, R0, R1 dan R2), serta isi register LR, yang berisi alamat pengirim dari fungsi tersebut. . Instruksi terakhir mengembalikan register yang disimpan, dan alamat pengirim segera ditempatkan di register PC - sehingga kembali dari fungsinya. Namun jika Anda perhatikan lebih dekat, Anda akan melihat bahwa instruksi kedua dari belakang mengubah alamat pengirim yang disimpan di tumpukan. Mari kita hitung seperti apa jadinya setelahnya
eksekusi kode. Alamat tertentu 1xB0 dimuat ke R130, 5 dikurangi, kemudian ditransfer ke R0 dan 0x10 ditambahkan ke dalamnya. Ternyata 0xB13B. Jadi, IDA menganggap instruksi terakhir adalah pengembalian fungsi normal, namun kenyataannya instruksi tersebut menuju ke alamat terhitung 0xB13B.

Perlu diingat di sini bahwa prosesor ARM memiliki dua mode dan dua set instruksi: ARM dan Thumb. Bit alamat yang paling tidak signifikan memberi tahu prosesor set instruksi mana yang sedang digunakan. Artinya, alamatnya sebenarnya 0xB13A, dan satu bit paling tidak signifikan menunjukkan mode Jempol.

β€œAdaptor” serupa telah ditambahkan ke awal setiap fungsi di perpustakaan ini dan
kode sampah. Kami tidak akan membahasnya lebih jauh - kami hanya mengingatnya
bahwa permulaan sebenarnya dari hampir semua fungsi agak jauh lagi.

Karena kode tidak secara eksplisit melompat ke 0xB13A, IDA sendiri tidak mengenali bahwa kode tersebut terletak di lokasi ini. Untuk alasan yang sama, ia tidak mengenali sebagian besar kode di perpustakaan sebagai kode, sehingga membuat analisis agak sulit. Kami memberi tahu IDA bahwa ini adalah kodenya, dan inilah yang terjadi:

Mencari kerentanan di UC Browser

Tabel jelas dimulai pada 0xB144. Apa yang ada di sub_494C?

Mencari kerentanan di UC Browser

Saat memanggil fungsi ini di register LR, kita mendapatkan alamat tabel yang disebutkan sebelumnya (0xB144). Di R0 - indeks dalam tabel ini. Artinya, nilai diambil dari tabel, ditambahkan ke LR dan hasilnya adalah
alamat yang akan dituju. Mari kita coba menghitungnya: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Kami pergi ke alamat yang diterima dan melihat beberapa instruksi berguna dan kembali ke 0xB140:

Mencari kerentanan di UC Browser

Sekarang akan ada transisi di offset dengan indeks 0x20 dari tabel.

Dilihat dari ukuran tabelnya, akan ada banyak transisi seperti itu dalam kode. Timbul pertanyaan apakah mungkin untuk menangani hal ini secara lebih otomatis, tanpa menghitung alamat secara manual. Dan skrip serta kemampuan untuk menambal kode di 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"

Tempatkan kursor pada baris 0xB26A, jalankan skrip dan lihat transisi ke 0xB4B0:

Mencari kerentanan di UC Browser

IDA lagi-lagi tidak mengenali area ini sebagai kode. Kami membantunya dan melihat desain lain di sana:

Mencari kerentanan di UC Browser

Instruksi setelah BLX sepertinya tidak masuk akal, ini lebih seperti semacam perpindahan. Mari kita lihat sub_4964:

Mencari kerentanan di UC Browser

Dan memang, di sini sebuah kata diambil dari alamat yang terletak di LR, ditambahkan ke alamat ini, setelah itu nilai dari alamat yang dihasilkan diambil dan dimasukkan ke dalam tumpukan. Juga, 4 ditambahkan ke LR sehingga setelah kembali dari fungsi, offset yang sama dilewati. Setelah itu perintah POP {R1} mengambil nilai yang dihasilkan dari tumpukan. Jika Anda melihat apa yang terletak di alamat 0xB4BA + 0xEA = 0xB5A4, Anda akan melihat sesuatu yang mirip dengan tabel alamat:

Mencari kerentanan di UC Browser

Untuk menambal desain ini, Anda perlu mendapatkan dua parameter dari kode: offset dan nomor register yang ingin Anda masukkan hasilnya. Untuk setiap kemungkinan pendaftaran, Anda harus menyiapkan sepotong kode 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 menempatkan kursor di awal struktur yang ingin kami ganti - 0xB4B2 - dan menjalankan skrip:

Mencari kerentanan di UC Browser

Selain struktur yang telah disebutkan, kode tersebut juga berisi yang berikut:

Mencari kerentanan di UC Browser

Seperti pada kasus sebelumnya, setelah instruksi BLX terdapat offset:

Mencari kerentanan di UC Browser

Kami mengambil offset ke alamat dari LR, menambahkannya ke LR dan pergi ke sana. 0x72044 + 0xC = 0x72050. Script untuk desain ini cukup sederhana:

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 eksekusi skrip:

Mencari kerentanan di UC Browser

Setelah semuanya ditambal dalam fungsi tersebut, Anda dapat mengarahkan IDA ke awal sebenarnya. Ini akan mengumpulkan semua kode fungsi, dan dapat didekompilasi menggunakan HexRays.

Menguraikan string

Kami telah belajar menangani kebingungan kode mesin di perpustakaan libsgmainso-6.4.36.so dari UC Browser dan menerima kode 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 baris-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 didekripsi. Sebagai parameter untuk fungsi ini, penunjuk ke data yang mirip dengan data terenkripsi, buffer tertentu, dan nomor diteruskan. Jelasnya, setelah memanggil fungsi tersebut, akan ada baris yang didekripsi di buffer, karena diteruskan ke fungsi tersebut Temukan Kelas, yang menggunakan nama kelas sebagai parameter kedua. Oleh karena itu, angkanya adalah ukuran buffer atau panjang garis. Mari kita coba menguraikan nama kelasnya, ini akan memberi tahu kita apakah kita menuju ke arah yang benar. Mari kita lihat lebih dekat apa yang terjadi 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 membuat instance container untuk array byte dengan ukuran yang ditentukan (kami tidak akan membahas container ini secara detail). Di sini dua wadah dibuat: satu berisi garis "DcO/lcK+h?m3c*q@" (mudah ditebak bahwa ini adalah kuncinya), yang lain berisi data terenkripsi. Selanjutnya, kedua objek ditempatkan dalam struktur tertentu, yang diteruskan ke fungsinya sub_6115C. Mari kita tandai juga bidang dengan nilai 3 pada struktur ini. Mari kita lihat apa yang terjadi selanjutnya pada struktur ini.

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 switch adalah bidang struktur yang sebelumnya diberi nilai 3. Lihat kasus 3: pada fungsi sub_6364C parameter diteruskan dari struktur yang ditambahkan di sana pada fungsi sebelumnya, yaitu kunci dan data terenkripsi. Jika Anda melihat lebih dekat sub_6364C, Anda dapat mengenali algoritma RC4 di dalamnya.

Kami memiliki algoritma dan kunci. Mari kita coba menguraikan nama kelasnya. Inilah yang terjadi: com/taobao/wireless/security/adaptor/JNICLibrary. Besar! Kami berada di jalur yang benar.

Pohon perintah

Sekarang kami perlu menemukan tantangan Daftar Asli, yang akan mengarahkan kita ke fungsinya doCommandNative. Mari kita lihat fungsi yang dipanggil dari JNI_OnLoad, dan kami menemukannya di 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 memang, metode asli dengan nama tersebut terdaftar di sini doCommandNative. Sekarang kita tahu alamatnya. Mari kita 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;
}

Dari namanya Anda dapat menebak bahwa inilah titik masuk dari semua fungsi yang diputuskan oleh pengembang untuk ditransfer ke perpustakaan asli. Kami tertarik dengan fungsi nomor 10601.

Anda dapat melihat dari kode bahwa nomor perintah menghasilkan tiga angka: perintah/10000, perintah% 10000/100 ΠΈ perintah % 10, yaitu, dalam kasus kami, 1, 6 dan 1. Ketiga angka ini, serta penunjuk ke JNIEnv dan argumen yang diteruskan ke fungsi ditambahkan ke struktur dan diteruskan. Dengan menggunakan tiga angka yang diperoleh (sebut saja N1, N2 dan N3), pohon perintah dibangun.

Sesuatu seperti ini:

Mencari kerentanan di UC Browser

Pohon itu diisi secara dinamis JNI_OnLoad.
Tiga angka mengkodekan jalur di pohon. Setiap daun pohon berisi alamat bopeng dari fungsi terkait. Kuncinya ada di node induk. Menemukan tempat dalam kode di mana fungsi yang kita butuhkan ditambahkan ke pohon tidaklah sulit jika Anda memahami semua struktur yang digunakan (kami tidak menjelaskannya agar tidak membuat artikel yang sudah agak besar membengkak).

Lebih banyak kebingungan

Kami menerima alamat fungsi yang harus mendekripsi lalu lintas: 0x5F1AC. Namun masih terlalu dini untuk bersukacita: pengembang UC Browser telah menyiapkan kejutan lain untuk kami.

Setelah menerima parameter dari array yang dibentuk dalam kode Java, kita dapatkan
ke fungsi di alamat 0x4D070. Dan di sini jenis kebingungan kode lainnya menanti kita.

Kami menempatkan dua indeks di R7 dan R4:

Mencari kerentanan di UC Browser

Kami menggeser indeks pertama ke R11:

Mencari kerentanan di UC Browser

Untuk mendapatkan alamat dari tabel, gunakan indeks:

Mencari kerentanan di UC Browser

Setelah menuju alamat pertama digunakan indeks kedua yaitu di R4. Ada 230 elemen dalam tabel.

Apa yang harus dilakukan tentang hal itu? Anda dapat memberi tahu IDA bahwa ini adalah saklar: Edit -> Lainnya -> Tentukan idiom saklar.

Mencari kerentanan di UC Browser

Kode yang dihasilkan menakutkan. Namun saat melewati hutannya, Anda dapat melihat panggilan ke fungsi yang sudah tidak asing lagi bagi kita sub_6115C:

Mencari kerentanan di UC Browser

Ada saklar dimana pada kasus 3 terjadi dekripsi menggunakan algoritma RC4. Dan dalam hal ini, struktur yang diteruskan ke fungsi diisi dari parameter yang diteruskan ke doCommandNative. Mari kita ingat apa yang kita miliki di sana sihirInt dengan nilai 16. Kami melihat kasus yang sesuai - dan setelah beberapa transisi kami menemukan kode yang dapat digunakan untuk mengidentifikasi algoritma.

Mencari kerentanan di UC Browser

Ini adalah AES!

Algoritmanya ada, yang tersisa hanyalah mendapatkan parameternya: mode, kunci dan, mungkin, vektor inisialisasi (keberadaannya tergantung pada mode operasi algoritma AES). Struktur dengan mereka harus dibentuk di suatu tempat sebelum pemanggilan fungsi sub_6115C, tetapi bagian kode ini sangat dikaburkan, sehingga muncul ide untuk menambal kode sehingga semua parameter fungsi dekripsi dibuang ke dalam file.

Tambalan

Agar tidak menulis semua kode patch dalam bahasa assembly secara manual, Anda dapat meluncurkan Android Studio, menulis fungsi di sana yang menerima parameter input yang sama dengan fungsi dekripsi kami dan menulis ke file, lalu salin-tempel kode yang akan dikompilasi oleh kompiler. menghasilkan.

Teman-teman kami dari tim UC Browser juga menjaga kenyamanan penambahan kode. Ingatlah bahwa di awal setiap fungsi kita memiliki kode sampah yang dapat dengan mudah diganti dengan kode lain. Sangat nyaman πŸ™‚ Namun, di awal fungsi target tidak ada cukup ruang untuk kode yang menyimpan semua parameter ke file. Saya harus membaginya menjadi beberapa bagian dan menggunakan blok sampah dari fungsi tetangga. Total ada empat bagian.

Bagian pertama:

Mencari kerentanan di UC Browser

Dalam arsitektur ARM, empat parameter fungsi pertama dilewatkan melalui register R0-R3, sisanya, jika ada, melewati stack. Register LR membawa alamat pengirim. Semua ini perlu disimpan agar fungsinya dapat berfungsi setelah kita membuang parameternya. Kita juga perlu menyimpan semua register yang akan kita gunakan dalam proses tersebut, jadi kita melakukan PUSH.W {R0-R10,LR}. Di R7 kita mendapatkan alamat daftar parameter yang diteruskan ke fungsi melalui stack.

Menggunakan fungsi fopen ayo buka filenya /data/local/tmp/aes dalam mode "ab".
yaitu untuk penambahan. Di R0 kita memuat alamat nama file, di R1 - alamat baris yang menunjukkan mode. Dan di sini kode sampah berakhir, jadi kita beralih ke fungsi berikutnya. Agar dapat terus berfungsi, kami menempatkan transisi ke kode fungsi sebenarnya di awal, melewati sampah, dan sebagai ganti sampah kami menambahkan kelanjutan tambalan.

Mencari kerentanan di UC Browser

Panggilan fopen.

Tiga parameter pertama dari fungsi aes memiliki tipe int. Karena kita menyimpan register ke tumpukan di awal, kita cukup meneruskan fungsinya menulis alamat mereka di tumpukan.

Mencari kerentanan di UC Browser

Selanjutnya kita memiliki tiga struktur yang berisi ukuran data dan penunjuk ke data untuk kunci, vektor inisialisasi, dan data terenkripsi.

Mencari kerentanan di UC Browser

Pada akhirnya, tutup file, pulihkan register dan transfer kontrol ke fungsi sebenarnya aes.

Kami mengumpulkan APK dengan perpustakaan yang dipatch, menandatanganinya, mengunggahnya ke perangkat/emulator, dan meluncurkannya. Kami melihat dump kami sedang dibuat, dan banyak data sedang ditulis di sana. Browser menggunakan enkripsi tidak hanya untuk lalu lintas, dan semua enkripsi melewati fungsi yang dimaksud. Namun karena alasan tertentu, data yang diperlukan tidak ada, dan permintaan yang diperlukan tidak terlihat di lalu lintas. Agar tidak menunggu sampai UC Browser berkenan membuat permintaan yang diperlukan, mari kita ambil respon terenkripsi dari server yang diterima sebelumnya dan patch aplikasi lagi: tambahkan dekripsi ke onCreate dari aktivitas 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 merakit, menandatangani, menginstal, meluncurkan. Kami menerima NullPointerException karena metode ini mengembalikan null.

Selama analisis kode lebih lanjut, ditemukan fungsi yang menguraikan baris menarik: β€œMETA-INF/” dan β€œ.RSA”. Sepertinya aplikasi sedang memverifikasi sertifikatnya. Atau bahkan menghasilkan kunci darinya. Saya sebenarnya tidak ingin berurusan dengan apa yang terjadi dengan sertifikat tersebut, jadi kami hanya akan menyelipkannya dengan sertifikat yang benar. Mari kita tambal baris terenkripsi sehingga alih-alih β€œMETA-INF/” kita mendapatkan β€œBLABLINF/”, buat folder dengan nama itu di APK dan tambahkan sertifikat browser tupai di sana.

Kami merakit, menandatangani, menginstal, meluncurkan. Bingo! Kami punya kuncinya!

MitM

Kami menerima kunci dan vektor inisialisasi yang sama dengan kunci tersebut. Mari kita coba mendekripsi respons server dalam mode CBC.

Mencari kerentanan di UC Browser

Kami melihat URL arsip, mirip dengan MD5, β€œextract_unzipsize” dan nomor. Kami memeriksa: MD5 arsipnya sama, ukuran perpustakaan yang belum dibongkar juga. Kami mencoba menambal perpustakaan ini dan memberikannya ke browser. Untuk menunjukkan bahwa perpustakaan kami yang ditambal telah dimuat, kami akan meluncurkan Intent untuk membuat SMS dengan teks β€œPWNED!” Kami akan mengganti dua respons dari server: puds.ucweb.com/upgrade/index.xhtml dan untuk mengunduh arsip. Yang pertama kita ganti MD5 (ukurannya tidak berubah setelah dibongkar), yang kedua kita berikan arsip dengan perpustakaan yang ditambal.

Browser mencoba mengunduh arsip beberapa kali, setelah itu memberikan kesalahan. Rupanya sesuatu
dia tidak suka. Dari hasil analisa format keruh ini, ternyata server juga mengirimkan ukuran arsip:

Mencari kerentanan di UC Browser

Itu dikodekan dalam LEB128. Setelah patch, ukuran arsip dengan perpustakaan berubah sedikit, sehingga browser menganggap bahwa arsip yang diunduh tidak benar, dan setelah beberapa kali mencoba menimbulkan kesalahan.

Kami menyesuaikan ukuran arsip... Dan – kemenangan! πŸ™‚ Hasilnya ada di video.

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

Konsekuensi dan reaksi pengembang

Dengan cara yang sama, peretas dapat menggunakan fitur tidak aman di UC Browser untuk mendistribusikan dan menjalankan perpustakaan berbahaya. Perpustakaan ini akan bekerja dalam konteks browser, sehingga mereka akan menerima semua izin sistemnya. Hasilnya, kemampuan untuk menampilkan jendela phishing, serta akses ke file kerja tupai Cina oranye, termasuk login, kata sandi, dan cookie yang disimpan dalam database.

Kami menghubungi pengembang UC Browser dan memberi tahu mereka tentang masalah yang kami temukan, mencoba menunjukkan kerentanan dan bahayanya, tetapi mereka tidak membicarakan apa pun dengan kami. Sementara itu, browser terus memamerkan fitur berbahayanya di depan mata. Namun begitu kami mengungkap detail kerentanannya, tidak mungkin lagi mengabaikannya seperti sebelumnya. Tanggal 27 Maret adalah
versi baru UC Browser 12.10.9.1193 dirilis, yang mengakses server melalui HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Selain itu, setelah β€œperbaikan” dan hingga saat artikel ini ditulis, mencoba membuka PDF di browser menghasilkan pesan kesalahan dengan teks β€œUps, ada yang tidak beres!” Permintaan ke server tidak dibuat saat mencoba membuka PDF, namun permintaan dibuat saat browser diluncurkan, yang mengisyaratkan kemampuan lanjutan untuk mengunduh kode yang dapat dieksekusi yang melanggar aturan Google Play.

Sumber: www.habr.com

Tambah komentar