Pendahuluan
Pada akhir Maret, kami
melaporkan bahwa kami menemukan kemampuan tersembunyi untuk mengunduh dan menjalankan kode yang tidak diverifikasi di UC Browser. Hari ini kami akan menganalisis secara rinci bagaimana unduhan ini terjadi dan bagaimana peretas dapat menggunakannya untuk tujuan mereka sendiri.
Beberapa waktu yang lalu, UC Browser diiklankan dan didistribusikan dengan sangat agresif: itu dipasang pada perangkat pengguna menggunakan malware, didistribusikan dari berbagai situs dengan kedok file video (mis., Pengguna berpikir mereka mengunduh, misalnya, klip porno, tetapi sebaliknya menerima APK dengan ini browser), digunakan spanduk yang menakutkan dengan pesan yang mengatakan bahwa browser sudah usang, rentan dan hal-hal seperti itu. Grup UC Browser resmi di VK memiliki
topik di mana pengguna dapat mengeluh tentang iklan yang tidak adil, ada banyak contoh. Pada 2016, bahkan ada
iklan video dalam bahasa Rusia (ya, iklan dari browser yang memblokir iklan).
Pada saat penulisan, UC Browser memiliki lebih dari 500.000.000 instalasi di Google Play. Ini mengesankan - hanya Google Chrome yang memiliki lebih. Di antara ulasan Anda dapat melihat banyak keluhan tentang iklan dan pengalihan ke beberapa aplikasi di Google Play. Ini adalah alasan penelitian: kami memutuskan untuk melihat apakah UC Browser melakukan sesuatu yang buruk. Dan ternyata dia melakukannya!
Kode aplikasi mengungkapkan kemampuan untuk mengunduh dan menjalankan kode yang dapat dieksekusi,
yang bertentangan dengan aturan untuk menerbitkan aplikasi di Google Play. Selain fakta bahwa UC Browser mengunduh kode yang dapat dieksekusi, itu membuatnya tidak aman, yang dapat digunakan untuk melakukan serangan MitM. Mari kita lihat apakah kita berhasil melakukan serangan seperti itu.
Segala sesuatu yang ditulis di bawah ini relevan untuk versi UC Browser yang hadir di Google Play pada saat penelitian:
package: com.UCMobile.intl versionName: 12.10.8.1172 versionCode: 10598 sha1 APK-: f5edb2243413c777172f6362876041eb0c3a928c
Vektor serangan
Dalam manifes Peramban UC, Anda dapat menemukan layanan yang disebut
com.uc.deployment.UpgradeDeployService .
<service android:exported="false" android:name="com.uc.deployment.UpgradeDeployService" android:process=":deploy" />
Ketika layanan ini dimulai, browser melakukan permintaan POST ke
puds.ucweb.com/upgrade/index.xhtml , yang dapat dilihat dalam lalu lintas beberapa saat setelah dimulainya. Sebagai tanggapan, ia dapat menerima perintah untuk mengunduh pembaruan atau modul baru. Selama analisis, server tidak memberikan perintah seperti itu, tetapi kami perhatikan bahwa ketika mencoba membuka PDF di browser, itu membuat permintaan kedua di alamat di atas, setelah itu mengunduh perpustakaan asli. Untuk melakukan serangan, kami memutuskan untuk menggunakan fitur UC Browser ini: kemampuan untuk membuka PDF menggunakan pustaka asli, yang tidak ada di APK dan yang, jika perlu, diunduh dari Internet. Perlu dicatat bahwa secara teoritis, UC Browser dapat dipaksa untuk mengunduh sesuatu tanpa interaksi pengguna - jika Anda memberikan respons yang dibentuk dengan benar terhadap permintaan yang dijalankan setelah browser dimulai. Tetapi untuk ini kita perlu mempelajari protokol interaksi dengan server secara lebih detail, jadi kami memutuskan bahwa lebih mudah untuk mengedit respon yang dicegat dan mengganti pustaka untuk bekerja dengan PDF.
Jadi, ketika pengguna ingin membuka PDF langsung di peramban, permintaan berikut dapat dilihat dalam lalu lintas:

Pertama datang permintaan POST ke
puds.ucweb.com/upgrade/index.xhtml , setelah itu
Unduh arsip dengan perpustakaan untuk melihat format PDF dan kantor. Adalah logis untuk mengasumsikan bahwa dalam permintaan pertama informasi tentang sistem ditransmisikan (setidaknya arsitektur untuk memberikan perpustakaan yang diperlukan), dan sebagai tanggapannya browser menerima beberapa informasi tentang perpustakaan yang perlu diunduh: alamat dan, mungkin, sesuatu yang lain. Masalahnya adalah bahwa permintaan ini dienkripsi.
Perpustakaan itu sendiri dikemas dalam ZIP dan tidak dienkripsi.

Cari kode dekripsi lalu lintas
Mari kita coba mendekripsi respons server. Kami melihat kode
com.uc.deployment.UpgradeDeployService kelas: dari metode
onStartCommand, 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(); mb(v3_4); h v4_2 = new h(); mb(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.baacgetCpuArch())); v3_2.add(g.fs("cpu_arch", com.uc.baacPb())); String v4_3 = com.uc.baacPd(); 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.baacPc())); 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.baacJl()))); v3_2.add(g.fs("ram_1", String.valueOf(com.uc.baahPo()))); v3_2.add(g.fs("totalram", String.valueOf(com.uc.baahOL()))); 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 = gi(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.dci 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.dci$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); mb(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 di sini formasi permintaan POST. Kami menarik perhatian pada pembuatan array 16 byte dan isinya: 0x5F, 0, 0x1F, -50 (= 0xCE). Cocok dengan apa yang kami lihat dalam permintaan di atas.
Di kelas yang sama, Anda bisa melihat kelas bersarang di mana ada metode lain yang menarik:
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 = gj(arg11[2], v7); } if(v7 == null) { goto label_57; } if(v5 != 0) { v1_1 = gP(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 = gb(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 menerima array byte sebagai input dan memeriksa bahwa byte nol adalah 0x60 atau byte ketiga adalah 0xD0, dan byte kedua adalah 1, 11 atau 0x1F. Kami melihat jawaban dari server: nol byte - 0x60, kedua - 0x1F, ketiga - 0x60. Sepertinya yang kita butuhkan. Menilai berdasarkan baris ("up_decrypt", misalnya), metode harus dipanggil di sini yang mendekripsi respons server.
Kami lolos ke metode
gj . Perhatikan bahwa byte pada offset 2 (mis., 0x1F dalam kasus kami) ditransfer ke sana sebagai argumen pertama, dan respons server tanpa
16 byte pertama.
public static byte[] j(int arg1, byte[] arg2) { if(arg1 == 1) { arg2 = cc(arg2, c.adu); } else if(arg1 == 11) { arg2 = m.aF(arg2); } else if(arg1 != 0x1F) { } else { arg2 = EncryptHelper.decrypt(arg2); } return arg2; }
Jelas, ada pilihan algoritma dekripsi, dan byte yang sama, yang ada di kami
case adalah 0x1F, menunjukkan satu dari tiga opsi yang memungkinkan.
Kami terus menganalisis kode. Setelah beberapa lompatan, kita masuk ke metode dengan nama berbicara
decryptBytesByKey .
Di sini, dua byte lagi dipisahkan dari jawaban kami, dan sebuah string diperoleh darinya. Jelas bahwa dengan cara ini kunci dipilih untuk mendekripsi pesan.
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];
Ke depan, kami mencatat bahwa pada tahap ini kuncinya belum diperoleh, tetapi hanya "pengidentifikasi" nya. Mendapatkan kuncinya sedikit lebih rumit.
Dalam metode selanjutnya, dua lagi ditambahkan ke parameter yang ada, dan ada empat di antaranya: 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, kami tiba di metode
staticBinarySafeDecryptNoB64 dari
com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent interface
. Tidak ada kelas dalam kode aplikasi utama yang mengimplementasikan antarmuka ini. Kelas semacam ini ada di file
lib / armeabi-v7a / libsgmain.so , yang sebenarnya bukan .so, tapi .jar. Metode yang menarik bagi kami diimplementasikan sebagai berikut:
package com.alibaba.wireless.security.ai;
Di sini, daftar parameter kami dilengkapi oleh dua bilangan bulat lagi: 2 dan 0. Dilihat oleh
untuk semua, 2 berarti dekripsi, seperti pada metode
doFinal dari kelas sistem
javax.crypto.Cipher . Dan semua ini ditransfer ke Router tertentu dengan nomor 10601 - ini, tampaknya, adalah nomor perintah.
Setelah rantai transisi berikutnya, kami menemukan kelas yang mengimplementasikan antarmuka
IRouterComponent dan metode
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
JNICLibrary , di mana metode asli
doCommandNative dinyatakan :
package com.taobao.wireless.security.adapter; public class JNICLibrary { public static native Object doCommandNative(int arg0, Object[] arg1); }
Jadi, kita perlu menemukan metode
doCommandNative dalam kode asli. Dan kemudian kesenangan dimulai.
Kebingungan Kode Mesin
Ada satu perpustakaan asli di file
libsgmain.so (yang sebenarnya .jar dan di mana kami menemukan sedikit implementasi beberapa antarmuka enkripsi yang sedikit lebih tinggi):
libsgmainso-6.4.36.so . Buka di IDA dan dapatkan banyak kotak dialog dengan kesalahan. Masalahnya adalah bahwa tabel header bagian tidak valid. Ini dilakukan khusus untuk mempersulit analisis.

Tetapi tidak diperlukan juga: untuk memuat file ELF dengan benar dan menganalisisnya, tabel header program sudah cukup. Oleh karena itu, kami cukup menghapus tabel bagian, membatalkan bidang yang sesuai di header.

Sekali lagi buka file di IDA.
Ada dua cara untuk memberitahu mesin virtual Java persis di mana di perpustakaan asli implementasi metode yang dinyatakan dalam kode Java sebagai asli berada. Yang pertama adalah untuk memberi nama formulir
Java_package_name_ClassName_Method_name .
Yang kedua adalah mendaftarkannya saat memuat perpustakaan (dalam fungsi
JNI_OnLoad )
dengan memanggil fungsi
RegisterNatives .
Dalam kasus kami, jika Anda menggunakan metode pertama, namanya harus seperti ini:
Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative .
Di antara fungsi yang diekspor, tidak ada yang seperti itu, jadi Anda perlu mencari panggilan ke
RegisterNatives .
Kami pergi ke fungsi
JNI_OnLoad dan melihat gambar berikut:

Apa yang sedang terjadi di sini? Sekilas, awal dan akhir fungsi adalah tipikal arsitektur ARM. Instruksi pertama pada stack menyimpan isi register yang akan digunakan fungsi dalam pekerjaannya (dalam hal ini, R0, R1 dan R2), serta isi register LR, yang berisi alamat pengirim dari fungsi tersebut. Dengan instruksi terakhir, register yang disimpan dikembalikan, dan alamat kembali segera ditempatkan dalam register PC - sehingga kembali dari fungsi. Tetapi jika Anda melihat lebih dekat, Anda akan melihat bahwa instruksi kedua dari belakang mengubah alamat pengirim yang disimpan di tumpukan. Kami menghitung apa yang akan terjadi setelahnya
eksekusi kode. Alamat tertentu 0xB130 dimasukkan ke dalam R1, 5 dikurangkan darinya, kemudian ditransfer ke R0 dan 0x10 ditambahkan ke dalamnya. Ternyata 0xB13B. Dengan demikian, IDA berpikir bahwa dalam instruksi terakhir ada pengembalian normal dari fungsi, tetapi sebenarnya ada transisi ke alamat yang dihitung 0xB13B.
Perlu diingat bahwa prosesor ARM memiliki dua mode dan dua set instruksi: ARM dan Thumb. Bit alamat yang paling signifikan memberitahu prosesor yang set instruksi yang digunakan. Artinya, alamatnya sebenarnya 0xB13A, dan unit dalam bit rendah menunjukkan mode Jempol.
Pada awal setiap fungsi di perpustakaan ini ditambahkan "adaptor" yang mirip dan
kode sampah. Lebih jauh kita tidak akan membahasnya secara rinci - kita hanya ingat
bahwa awal sebenarnya dari hampir semua fungsi sedikit lebih jauh.
Karena tidak ada transisi eksplisit ke 0xB13A dalam kode, IDA itu sendiri tidak mengenali bahwa kode itu ada di tempat ini. Untuk alasan yang sama, itu tidak mengenali sebagian besar kode di perpustakaan sebagai kode, yang membuat analisis agak sulit. Kami memberi tahu IDA bahwa kode itu ada di sini, dan inilah hasilnya:

Pada 0xB144, tabel dengan jelas dimulai. Bagaimana dengan sub_494C?

Ketika fungsi ini dipanggil dalam register LR, kami mendapatkan alamat tabel yang disebutkan di atas (0xB144). Di R0, indeks dalam tabel ini. Artinya, nilai diambil dari tabel, ditambahkan ke LR dan diperoleh
alamat untuk pergi ke. Mari kita coba hitung: 0xB144 + [0xB144 + 8 * 4] = 0xB144 + 0x120 = 0xB264. Kami pergi ke alamat yang diterima dan melihat hanya beberapa petunjuk yang berguna dan lagi transisi ke 0xB140:

Sekarang akan ada pergeseran dengan offset dengan indeks 0x20 dari tabel.
Dilihat dari ukuran tabel, ada banyak transisi dalam kode. Timbul pertanyaan apakah mungkin untuk menangani hal ini secara lebih otomatis, tanpa secara manual menghitung alamat. 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:
Kami meletakkan kursor pada baris 0xB26A, menjalankan skrip dan melihat transisi ke 0xB4B0:

IDA sekali lagi tidak mengenali situs ini sebagai kode. Kami membantunya dan melihat konstruksi lain di sana:

Instruksi setelah BLX tidak terlihat sangat berarti, itu lebih seperti semacam bias. Kami melihat di sub_4964:

Memang, di sini kata diambil di alamat yang terletak di LR, ditambahkan ke alamat ini, setelah itu nilai di alamat yang diterima diambil dan didorong ke tumpukan. Juga, 4 ditambahkan ke LR, sehingga setelah kembali dari fungsi untuk melompat offset yang sama. Kemudian perintah POP {R1} menarik nilai yang diterima dari tumpukan. Jika Anda melihat alamat 0xB4BA + 0xEA = 0xB5A4, Anda dapat melihat sesuatu yang mirip dengan tabel alamat:

Untuk menambal desain ini, Anda perlu mendapatkan dua parameter dari kode: offset dan nomor register tempat Anda ingin meletakkan 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
Kami meletakkan kursor di awal konstruksi yang ingin kami ganti - 0xB4B2 - dan menjalankan skrip:

Selain konstruksi yang sudah disebutkan dalam kode, di sini juga ada:

Seperti pada kasus sebelumnya, setelah instruksi BLX, ada offset:

Kami mengambil offset ke alamat dari LR, menambahkannya ke LR dan pergi ke sana. 0x72044 + 0xC = 0x72050. Script untuk desain ini sangat 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:
Hasil skrip:

Setelah semuanya ditambal dalam fungsi, Anda dapat mengarahkan IDA ke awal sebenarnya. Ini akan mengumpulkan semua kode fungsi dalam beberapa bagian, dan dapat didekompilasi menggunakan HexRays.
String decoding
Kami belajar cara menangani kebingungan kode mesin di
libsgmainso-6.4.36.so pustaka dari UC Browser dan mendapatkan kode untuk fungsi
JNI_OnLoad .
int __fastcall real_JNI_OnLoad(JavaVM *vm) { int result;
Pertimbangkan dengan seksama baris berikut:
sub_73E24(&unk_83EA6, &v6, 49); clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
Fungsi
sub_73E24 secara eksplisit mendekripsi nama kelas. Sebagai parameter dari fungsi ini, sebuah penunjuk ke data yang mirip dengan yang dienkripsi, sebuah buffer dan sebuah angka dilewatkan.
Jelas, setelah fungsi dipanggil, buffer akan berisi baris yang didekripsi, karena dilewatkan ke fungsi FindClass , yang menggunakan nama kelas sebagai parameter kedua. Oleh karena itu, angka adalah ukuran buffer atau panjang string. Mari kita coba menguraikan nama kelas, itu harus memberi tahu kita apakah kita pergi ke arah yang benar. Mari kita lihat lebih dekat apa yang terjadi di sub_73E24. int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size) { int v4;
sub_7AF78 ( ). :
«DcO/lcK+h?m3c*q@» ( , ), — . ,
sub_6115C . 3. , .
int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2) { int v3;
switch , 3. case 3:
sub_6364C , , . . .
sub_6364C , RC4.
. . :
com/taobao/wireless/security/adapter/JNICLibrary . Hebat! .
RegisterNatives ,
doCommandNative . ,
JNI_OnLoad, sub_B7B0 :
int __fastcall sub_B7F6(JNIEnv *env, jclass clazz) { char signature[41];
Memang, metode asli yang disebut doCommandNative terdaftar di sini . Sekarang kita tahu alamatnya. Mari kita lihat apa yang dia lakukan. int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args) { int v5;
, , . 10601.
, :
command / 10000 ,
command % 10000 / 100 command % 10 , . ., , 1, 6 1. ,
JNIEnv , , . ( N1, N2 N3) .
:
JNI_OnLoad .
. . — . , , , ( , ).
, : 0x5F1AC. : UC Browser .
, Java-,
0x4D070. .
R7 R4 :

R11:

, :

, R4. 230 .
Apa yang harus dilakukan? IDA, switch: Edit -> Other -> Specify switch idiom.

. , ,
sub_6115C :

switch, case 3 RC4. ,
doCommandNative . ,
magicInt 16. case – , .

AES!
, : , , , ( AES). -
sub_6115C , , , .
, Android Studio, , , , , , .
UC Browser «». , , . :) , , , . . .
:
Dalam arsitektur ARM, empat parameter pertama dari fungsi dilewatkan melalui register R0-R3, sisanya, jika ada, melalui stack. Dalam register LR alamat pengirim dikirimkan. Semua ini perlu dilestarikan sehingga fungsinya dapat bekerja setelah kita membuang parameternya. Kita juga perlu menyimpan semua register yang akan kita gunakan dalam proses, jadi kita melakukan PUSH.W {R0-R10, LR}. Di R7, kami mendapatkan alamat dari daftar parameter yang diteruskan ke fungsi melalui tumpukan.Menggunakan fungsi fopen , buka file / data / local / tmp / aes dalam mode "ab",. . . R0 , R1 — . , . , , .
fopen .
aes int . ,
fwrite .

, , .

,
aes .
APK , , /, . , , . , . - , . , UC Browser , , , : onCreate .
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
, , , . NullPointerException, . . null.
, : «META-INF/» ".RSA". , . . , , , . , «META-INF/» «BLABLINF/», APK .
, , , . Bingo! !
MitM
, . CBC.

URL , - MD5, «extract_unzipsize» . : MD5 , . . , , Intent «PWNED!». :
puds.ucweb.com/upgrade/index.xhtml . MD5 ( ), .
, . , -
dia tidak menyukainya. Sebagai hasil dari analisis format berlumpur ini, ternyata server masih mentransfer ukuran arsip:
Ini dikodekan dalam LEB128. Setelah tambalan, ukuran arsip dengan perpustakaan berubah sedikit, sehingga browser memutuskan bahwa arsip diunduh dengan bengkok, dan setelah beberapa upaya memberikan kesalahan.Kami mengoreksi ukuran arsip ... Dan - kemenangan! :) Hasilnya di video.https://www.youtube.com/watch?v=Nfns7uH03J8Konsekuensi dan Tanggapan Pengembang
UC Browser, . , . — , , , .
UC Browser , , - . . , , , . 27
UC Browser 12.10.9.1193, HTTPS:
puds.ucweb.com/upgrade/index.xhtml .
, «» PDF «, - !». PDF , , Google Play.