UC brauzerida zaifliklar qidirilmoqda

UC brauzerida zaifliklar qidirilmoqda

kirish

Mart oyining oxirida biz xabar berdi, ular UC brauzerida tasdiqlanmagan kodni yuklash va ishga tushirishning yashirin qobiliyatini aniqladilar. Bugun biz ushbu yuklab olish qanday sodir bo'lishini va xakerlar undan o'z maqsadlari uchun qanday foydalanishlari mumkinligini batafsil ko'rib chiqamiz.

Bir muncha vaqt oldin, UC brauzeri reklama qilingan va juda agressiv tarzda tarqatilgan edi: u zararli dasturlardan foydalangan holda foydalanuvchilarning qurilmalariga o'rnatildi, turli saytlardan videofayllar niqobi ostida tarqatildi (ya'ni, foydalanuvchilar ular, masalan, porno videoni yuklab olmoqda deb o'ylashgan, ammo Buning o'rniga ushbu brauzer bilan APK olingan), brauzerning eskirganligi, zaifligi va shunga o'xshash narsalar haqida xabarlar bilan qo'rqinchli bannerlardan foydalangan. VK-dagi rasmiy UC Brauzer guruhida mavjud mavzusi, unda foydalanuvchilar adolatsiz reklama haqida shikoyat qilishlari mumkin, u erda ko'plab misollar mavjud. 2016 yilda hatto bor edi video reklama rus tilida (ha, reklamani blokirovka qiluvchi brauzer uchun reklama).

Yozish vaqtida UC brauzerida Google Play-da 500 000 000 dan ortiq o'rnatish mavjud. Bu juda ta'sirli - faqat Google Chrome'da ko'proq narsa bor. Sharhlar orasida siz Google Play-dagi ba'zi ilovalarga reklama va yo'naltirishlar haqida juda ko'p shikoyatlarni ko'rishingiz mumkin. Bu bizning tadqiqotimizga sabab bo'ldi: biz UC brauzeri yomon ish qilyaptimi yoki yo'qligini ko'rishga qaror qildik. Va u shunday ekanligi ma'lum bo'ldi!

Ilova kodida bajariladigan kodni yuklab olish va ishga tushirish imkoniyati topildi, bu arizalarni chop etish qoidalariga ziddir Google Play-da. Bajariladigan kodni yuklab olishdan tashqari, UC brauzeri buni xavfsiz tarzda amalga oshiradi, bu esa MitM hujumini boshlash uchun ishlatilishi mumkin. Keling, shunday hujumni amalga oshirishimiz mumkinligini ko'rib chiqaylik.

Quyida yozilganlarning barchasi tadqiqot vaqtida Google Play’da mavjud bo‘lgan UC brauzeri versiyasiga tegishli:

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

Hujum vektori

UC Brauzer manifestida siz o'z-o'zidan tushunarli nomga ega xizmatni topishingiz mumkin com.uc.deployment.UpgradeDeployService.

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

Ushbu xizmat ishga tushganda, brauzer POST so'rovini yuboradi puds.ucweb.com/upgrade/index.xhtml, bu boshlanganidan keyin bir muncha vaqt o'tgach, tirbandlikda ko'rish mumkin. Bunga javoban u yangilanish yoki yangi modulni yuklab olish buyrug'ini olishi mumkin. Tahlil paytida server bunday buyruqlarni bermadi, lekin biz brauzerda PDF-ni ochishga harakat qilganimizda, u yuqorida ko'rsatilgan manzilga ikkinchi so'rov yuborganini, shundan so'ng u mahalliy kutubxonani yuklab olishini payqadik. Hujumni amalga oshirish uchun biz UC brauzerining ushbu xususiyatidan foydalanishga qaror qildik: PDF-ni APK-da bo'lmagan va kerak bo'lganda Internetdan yuklab oladigan mahalliy kutubxona yordamida ochish imkoniyati. Shuni ta'kidlash kerakki, nazariy jihatdan, UC brauzeri foydalanuvchi shovqinisiz biror narsani yuklab olishga majbur bo'lishi mumkin - agar siz brauzer ishga tushirilgandan so'ng bajariladigan so'rovga yaxshi shakllangan javob bersangiz. Ammo buning uchun biz server bilan o'zaro aloqa protokolini batafsilroq o'rganishimiz kerak, shuning uchun biz to'xtatilgan javobni tahrirlash va PDF bilan ishlash uchun kutubxonani almashtirish osonroq bo'ladi, deb qaror qildik.

Shunday qilib, foydalanuvchi PDF-ni to'g'ridan-to'g'ri brauzerda ochmoqchi bo'lsa, trafikda quyidagi so'rovlarni ko'rish mumkin:

UC brauzerida zaifliklar qidirilmoqda

Avval POST so'rovi mavjud puds.ucweb.com/upgrade/index.xhtmlshundan keyin
PDF va ofis formatlarini ko'rish uchun kutubxonaga ega arxiv yuklab olinadi. Birinchi so'rov tizim haqida ma'lumotni uzatadi deb taxmin qilish mantiqan to'g'ri keladi (hech bo'lmaganda kerakli kutubxonani taqdim etish uchun arxitektura) va unga javoban brauzer yuklab olinishi kerak bo'lgan kutubxona haqida ba'zi ma'lumotlarni oladi: manzil va, ehtimol. , boshqa bir narsa. Muammo shundaki, bu so'rov shifrlangan.

So'rov bo'limi

Javob fragmenti

UC brauzerida zaifliklar qidirilmoqda

UC brauzerida zaifliklar qidirilmoqda

Kutubxonaning o'zi ZIP-da paketlangan va shifrlanmagan.

UC brauzerida zaifliklar qidirilmoqda

Trafik shifrini ochish kodini qidiring

Keling, server javobini ochishga harakat qilaylik. Keling, sinf kodini ko'rib chiqaylik com.uc.deployment.UpgradeDeployService: usuldan onStartCommand ga boring com.uc.deployment.bx, va undan 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);
}

Biz bu erda POST so'rovining shakllanishini ko'ramiz. Biz 16 baytlik massivni yaratishga va uni to'ldirishga e'tibor beramiz: 0x5F, 0, 0x1F, -50 (=0xCE). Yuqoridagi so'rovda ko'rganimiz bilan mos keladi.

Xuddi shu sinfda siz boshqa qiziqarli usulga ega bo'lgan ichki sinfni ko'rishingiz mumkin:

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

Usul kirish sifatida baytlar qatorini oladi va nol bayt 0x60 yoki uchinchi bayt 0xD0, ikkinchi bayt esa 1, 11 yoki 0x1F ekanligini tekshiradi. Biz serverdan javobni ko'rib chiqamiz: nol bayt 0x60, ikkinchisi 0x1F, uchinchisi 0x60. Bizga kerak bo'lgan narsaga o'xshaydi. Satrlarga ko'ra (masalan, "yuqoriga_decrypt"), bu erda server javobining shifrini ochadigan usulni chaqirish kerak.
Keling, usulga o'tamiz gj. E'tibor bering, birinchi argument 2 ofsetdagi baytdir (ya'ni, bizning holatlarimizda 0x1F), ikkinchisi esa serverning javobisiz.
birinchi 16 bayt.

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

Shubhasiz, bu erda biz shifrni hal qilish algoritmini tanlaymiz va bizda bo'lgan bir xil bayt
0x1F ga teng bo'lgan holat uchta mumkin bo'lgan variantlardan birini bildiradi.

Biz kodni tahlil qilishni davom ettiramiz. Bir necha sakrashdan keyin biz o'zimizni tushunarli nomga ega bo'lgan usulda topamiz decryptBytesByKey.

Bu erda javobimizdan yana ikkita bayt ajratiladi va ulardan string olinadi. Shu tarzda xabarning shifrini ochish uchun kalit tanlanganligi aniq.

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

Oldinga qarab, shuni ta'kidlaymizki, bu bosqichda biz hali kalitni olmaymiz, faqat uning "identifikatori". Kalitni olish biroz murakkabroq.

Keyingi usulda mavjud bo'lganlarga yana ikkita parametr qo'shiladi, ulardan to'rttasi: sehrli raqam 16, kalit identifikatori, shifrlangan ma'lumotlar va tushunarsiz qator (bizning holimizda bo'sh).

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

Bir qator o'tishlardan so'ng biz usulga kelamiz staticBinarySafeDecryptNoB64 interfeys com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Asosiy dastur kodida ushbu interfeysni amalga oshiradigan sinflar mavjud emas. Faylda shunday sinf mavjud lib/armeabi-v7a/libsgmain.so, bu aslida .so emas, balki .jar. Bizni qiziqtirgan usul quyidagicha amalga oshiriladi:

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

Bu erda parametrlar ro'yxati yana ikkita butun son bilan to'ldiriladi: 2 va 0.
har bir narsa, 2 usulda bo'lgani kabi shifrni ochishni anglatadi doFinal tizim klassi javax.crypto.Cipher. Va bularning barchasi 10601 raqami bilan ma'lum bir Routerga o'tkaziladi - bu, ehtimol, buyruq raqami.

Keyingi o'tish zanjiridan so'ng biz interfeysni amalga oshiradigan sinfni topamiz IRouterComponent va usul 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);
}
}

Va shuningdek, sinf JNICL kutubxonasi, unda mahalliy usul e'lon qilinadi doCommandNative:

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

Bu biz mahalliy kodda usul topishimiz kerakligini anglatadi doCommandNative. Va bu erda o'yin-kulgi boshlanadi.

Mashina kodini xiralashtirish

Fayl ichida libsgmain.so (bu aslida .jar bo'lib, biz shifrlash bilan bog'liq ba'zi interfeyslarni amalga oshirishni yuqorida topdik) bitta mahalliy kutubxona mavjud: libsgmainso-6.4.36.so. Biz uni IDA-da ochamiz va xatolar bilan bir qator dialog oynalarini olamiz. Muammo shundaki, bo'lim sarlavhalari jadvali yaroqsiz. Bu tahlilni murakkablashtirish uchun ataylab amalga oshiriladi.

UC brauzerida zaifliklar qidirilmoqda

Ammo buning hojati yo'q: ELF faylini to'g'ri yuklash va uni tahlil qilish uchun dastur sarlavhasi jadvali etarli. Shuning uchun, biz shunchaki bo'limlar jadvalini o'chirib tashlaymiz, sarlavhadagi tegishli maydonlarni nolga tenglashtiramiz.

UC brauzerida zaifliklar qidirilmoqda

IDA-da faylni yana oching.

Java virtual mashinasiga Java kodida mahalliy deb e'lon qilingan usulning amalga oshirilishi mahalliy kutubxonaning qayerda joylashganligini aniqlashning ikki yo'li mavjud. Birinchisi, unga tur nomini berishdir Java_package_name_ClassName_methodName.

Ikkinchisi, kutubxonani yuklashda uni ro'yxatdan o'tkazish (funktsiyada JNI_OnLoad)
funktsiya chaqiruvidan foydalanish Mahalliylarni ro'yxatdan o'tkazish.

Bizning holatda, agar birinchi usuldan foydalansak, nom quyidagicha bo'lishi kerak: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

Eksport qilingan funktsiyalar orasida bunday funktsiya yo'q, ya'ni siz qo'ng'iroqni qidirishingiz kerak Mahalliylarni ro'yxatdan o'tkazish.
Keling, funktsiyaga o'tamiz JNI_OnLoad va biz ushbu rasmni ko'ramiz:

UC brauzerida zaifliklar qidirilmoqda

Bu yerda nima bo'lyapti? Bir qarashda funktsiyaning boshlanishi va oxiri ARM arxitekturasi uchun xosdir. Stekdagi birinchi ko'rsatma funktsiya o'z ishlashida foydalanadigan registrlar tarkibini (bu holda R0, R1 va R2), shuningdek funktsiyadan qaytish manzilini o'z ichiga olgan LR registrining mazmunini saqlaydi. . Oxirgi ko'rsatma saqlangan registrlarni tiklaydi va qaytish manzili darhol kompyuter registriga joylashtiriladi - bu funktsiyadan qaytadi. Ammo diqqat bilan qarasangiz, oxirgi ko'rsatma stekda saqlangan qaytish manzilini o'zgartirayotganini sezasiz. Keling, keyin qanday bo'lishini hisoblaylik
kodning bajarilishi. R1 ga ma'lum bir manzil 0xB130 yuklanadi, undan 5 chiqariladi, keyin u R0 ga o'tkaziladi va unga 0x10 qo'shiladi. 0xB13B chiqadi. Shunday qilib, IDA oxirgi ko'rsatma normal funktsiyani qaytarish deb o'ylaydi, lekin aslida u 0xB13B hisoblangan manzilga o'tadi.

Shuni esda tutish kerakki, ARM protsessorlarida ikkita rejim va ikkita ko'rsatmalar to'plami mavjud: ARM va Thumb. Manzilning eng kam ahamiyatli biti protsessorga qaysi ko'rsatmalar to'plamidan foydalanilayotganligini bildiradi. Ya'ni, manzil aslida 0xB13A bo'lib, eng kam ahamiyatli bitdan biri Thumb rejimini bildiradi.

Ushbu kutubxonadagi har bir funktsiyaning boshiga shunga o'xshash "adapter" qo'shilgan va
axlat kodi. Biz ular haqida batafsil to'xtalmaymiz - shunchaki eslaymiz
deyarli barcha funktsiyalarning haqiqiy boshlanishi biroz uzoqroqda.

Kod aniq 0xB13A ga o'tmaganligi sababli, IDA o'zi kod ushbu joyda joylashganligini tan olmadi. Xuddi shu sababga ko'ra, u kutubxonadagi kodlarning aksariyatini kod sifatida tan olmaydi, bu esa tahlilni biroz qiyinlashtiradi. Biz IDAga bu kod ekanligini aytamiz va shunday bo'ladi:

UC brauzerida zaifliklar qidirilmoqda

Jadval aniq 0xB144 dan boshlanadi. Sub_494C da nima bor?

UC brauzerida zaifliklar qidirilmoqda

LR registrida ushbu funktsiyani chaqirganda, biz avval aytib o'tilgan jadvalning manzilini olamiz (0xB144). R0 da - ushbu jadvaldagi indeks. Ya'ni, qiymat jadvaldan olinadi, LR ga qo'shiladi va natija bo'ladi
boradigan manzil. Keling, uni hisoblashga harakat qilaylik: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Biz olingan manzilga boramiz va bir nechta foydali ko'rsatmalarni ko'ramiz va yana 0xB140 ga o'tamiz:

UC brauzerida zaifliklar qidirilmoqda

Endi jadvaldan 0x20 indeks bilan ofsetda o'tish bo'ladi.

Jadvalning o'lchamiga qarab, kodda bunday o'tishlar ko'p bo'ladi. Manzillarni qo'lda hisoblamasdan, qandaydir tarzda buni avtomatik ravishda hal qilish mumkinmi degan savol tug'iladi. Va skriptlar va IDA-da kodni tuzatish qobiliyati bizga yordam beradi:

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"

Kursorni 0xB26A qatoriga qo'ying, skriptni ishga tushiring va 0xB4B0 ga o'tishni ko'ring:

UC brauzerida zaifliklar qidirilmoqda

IDA yana bu hududni kod sifatida tan olmadi. Biz unga yordam beramiz va u erda boshqa dizaynni ko'ramiz:

UC brauzerida zaifliklar qidirilmoqda

BLX-dan keyingi ko'rsatmalar unchalik ma'nosiz ko'rinadi, bu ko'proq qandaydir joy o'zgartirishga o'xshaydi. Keling, sub_4964 ni ko'rib chiqaylik:

UC brauzerida zaifliklar qidirilmoqda

Haqiqatan ham, bu erda LRda joylashgan manzilda dword olinadi, bu manzilga qo'shiladi, shundan so'ng olingan manzildagi qiymat olinadi va stekga qo'yiladi. Bundan tashqari, LR ga 4 qo'shiladi, shuning uchun funktsiyadan qaytgandan so'ng, xuddi shu ofset o'tkazib yuboriladi. Shundan so'ng POP {R1} buyrug'i stekdan olingan qiymatni oladi. Agar siz 0xB4BA + 0xEA = 0xB5A4 manzilida joylashgan narsalarni ko'rsangiz, manzillar jadvaliga o'xshash narsani ko'rasiz:

UC brauzerida zaifliklar qidirilmoqda

Ushbu dizaynni tuzatish uchun siz koddan ikkita parametrni olishingiz kerak bo'ladi: ofset va natijani qo'ymoqchi bo'lgan registr raqami. Har bir mumkin bo'lgan ro'yxatga olish uchun siz oldindan kod parchasini tayyorlashingiz kerak bo'ladi.

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"

Biz kursorni o'zgartirmoqchi bo'lgan strukturaning boshiga qo'yamiz - 0xB4B2 - va skriptni ishga tushiramiz:

UC brauzerida zaifliklar qidirilmoqda

Yuqorida aytib o'tilgan tuzilmalarga qo'shimcha ravishda, kod quyidagilarni o'z ichiga oladi:

UC brauzerida zaifliklar qidirilmoqda

Oldingi holatda bo'lgani kabi, BLX ko'rsatmasidan keyin ofset mavjud:

UC brauzerida zaifliklar qidirilmoqda

Biz LR dan manzilga ofsetni olamiz, uni LR ga qo'shamiz va u erga boramiz. 0x72044 + 0xC = 0x72050. Ushbu dizayn uchun skript juda oddiy:

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"

Skriptni bajarish natijasi:

UC brauzerida zaifliklar qidirilmoqda

Funktsiyada hamma narsa yamalgandan so'ng, siz IDA-ni uning haqiqiy boshlanishiga ko'rsatishingiz mumkin. U barcha funktsiya kodlarini birlashtiradi va uni HexRays yordamida dekompilyatsiya qilish mumkin.

Satrlarni dekodlash

Biz kutubxonada mashina kodini chalkashtirib yuborishni o'rgandik libsgmainso-6.4.36.so UC brauzeridan va funksiya kodini oldi 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;
}

Keling, quyidagi qatorlarni batafsil ko'rib chiqaylik:

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

Funktsiyada sub_73E24 sinf nomi aniq shifrlangan. Ushbu funktsiyaning parametrlari sifatida shifrlangan ma'lumotlarga o'xshash ma'lumotlarga ko'rsatgich, ma'lum bir bufer va raqam uzatiladi. Shubhasiz, funktsiyani chaqirgandan so'ng, buferda shifrlangan chiziq bo'ladi, chunki u funktsiyaga uzatiladi. FindClass, bu ikkinchi parametr sifatida sinf nomini oladi. Shuning uchun raqam buferning o'lchami yoki chiziq uzunligi. Keling, sinf nomini ochishga harakat qilaylik, u bizga to'g'ri yo'nalishda ketayotganimizni aytishi kerak. Keling, unda nima sodir bo'lishini batafsil ko'rib chiqaylik 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;
}

vazifa sub_7AF78 belgilangan o'lchamdagi bayt massivlari uchun konteyner namunasini yaratadi (biz bu konteynerlar haqida batafsil to'xtalmaymiz). Bu erda ikkita shunday konteyner yaratiladi: biri chiziqni o'z ichiga oladi "DcO/lcK+h?m3c*q@" (bu kalit ekanligini taxmin qilish oson), ikkinchisida shifrlangan ma'lumotlar mavjud. Keyinchalik, ikkala ob'ekt ham funktsiyaga o'tkaziladigan ma'lum bir tuzilishga joylashtiriladi sub_6115C. Keling, ushbu strukturada 3 qiymatiga ega bo'lgan maydonni ham belgilaymiz.Keyin bu struktura bilan nima sodir bo'lishini ko'rib chiqamiz.

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

Switch parametri avval 3 qiymati tayinlangan struktura maydonidir. 3-holatga qarang: funktsiyaga sub_6364C parametrlar oldingi funktsiyada qo'shilgan strukturadan, ya'ni kalit va shifrlangan ma'lumotlardan uzatiladi. Agar siz diqqat bilan qarasangiz sub_6364C, undagi RC4 algoritmini tanib olishingiz mumkin.

Bizda algoritm va kalit bor. Keling, sinf nomini ochishga harakat qilaylik. Mana nima bo'ldi: com/taobao/simsiz/xavfsizlik/adapter/JNICLlibrary. Ajoyib! Biz to'g'ri yo'ldamiz.

Buyruq daraxti

Endi biz qiyinchilikni topishimiz kerak Mahalliylarni ro'yxatdan o'tkazish, bu bizni funktsiyaga ishora qiladi doCommandNative. dan chaqirilgan funksiyalarni ko'rib chiqamiz JNI_OnLoad, va biz uni ichida topamiz 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;
}

Va haqiqatan ham, bu erda nom bilan mahalliy usul ro'yxatga olingan doCommandNative. Endi biz uning manzilini bilamiz. Keling, u nima qilayotganini ko'rib chiqaylik.

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

Nomi bo'yicha, bu erda ishlab chiquvchilar mahalliy kutubxonaga o'tkazishga qaror qilgan barcha funktsiyalarning kirish nuqtasi ekanligini taxmin qilishingiz mumkin. Biz 10601 raqamiga qiziqamiz.

Koddan buyruq raqami uchta raqamni ishlab chiqarishini ko'rishingiz mumkin: buyruq / 10000, buyruq % 10000 / 100 и buyruq % 10, ya'ni bizning holatlarimizda 1, 6 va 1. Bu uchta raqam, shuningdek, ko'rsatgich JNIEnv va funksiyaga berilgan argumentlar strukturaga qo‘shiladi va uzatiladi. Olingan uchta raqamdan foydalanib (ularni N1, N2 va N3 deb belgilaymiz) buyruq daraxti quriladi.

Shunga o'xshash narsa:

UC brauzerida zaifliklar qidirilmoqda

Daraxt dinamik ravishda to'ldiriladi JNI_OnLoad.
Uchta raqam daraxtdagi yo'lni kodlaydi. Daraxtning har bir bargi tegishli funktsiyaning pocked manzilini o'z ichiga oladi. Kalit asosiy tugunda. Agar siz foydalanilgan barcha tuzilmalarni tushunsangiz, daraxtga kerakli funktsiya qo'shiladigan koddagi joyni topish qiyin emas (biz ularni allaqachon juda katta maqolani shishirmaslik uchun tasvirlamaymiz).

Ko'proq chalkashlik

Biz trafik shifrini ochishi kerak bo'lgan funksiya manzilini oldik: 0x5F1AC. Ammo quvonishga hali erta: UC brauzerini ishlab chiquvchilar biz uchun yana bir syurpriz tayyorladilar.

Java kodida hosil bo'lgan massivdan parametrlarni olgandan so'ng, biz olamiz
0x4D070 manzilidagi funktsiyaga. Va bu erda bizni kodni chalkashtirishning yana bir turi kutmoqda.

R7 va R4 ga ikkita indeks qo'yamiz:

UC brauzerida zaifliklar qidirilmoqda

Biz birinchi indeksni R11 ga o'tkazamiz:

UC brauzerida zaifliklar qidirilmoqda

Jadvaldan manzilni olish uchun indeksdan foydalaning:

UC brauzerida zaifliklar qidirilmoqda

Birinchi manzilga o'tgandan so'ng, R4 da bo'lgan ikkinchi indeks ishlatiladi. Jadvalda 230 ta element mavjud.

Bu haqda nima qilish kerak? Siz IDA ga bu kommutator ekanligini aytishingiz mumkin: Tahrirlash -> Boshqa -> Switch idiomini belgilang.

UC brauzerida zaifliklar qidirilmoqda

Olingan kod qo'rqinchli. Ammo, uning o'rmonidan o'tayotganda, siz bizga allaqachon tanish bo'lgan funktsiyaga qo'ng'iroqni ko'rishingiz mumkin sub_6115C:

UC brauzerida zaifliklar qidirilmoqda

3-holatda RC4 algoritmidan foydalangan holda shifrlash mavjud bo'lgan kalit mavjud edi. Va bu holda, funksiyaga o'tgan struktura o'tkazilgan parametrlardan to'ldiriladi doCommandNative. Keling, u erda nima borligini eslaylik magicInt qiymati bilan 16. Biz mos keladigan holatni ko'rib chiqamiz - va bir nechta o'tishlardan so'ng biz algoritmni aniqlash mumkin bo'lgan kodni topamiz.

UC brauzerida zaifliklar qidirilmoqda

Bu AES!

Algoritm mavjud, faqat uning parametrlarini olish qoladi: rejim, kalit va, ehtimol, ishga tushirish vektori (uning mavjudligi AES algoritmining ishlash rejimiga bog'liq). Ular bilan tuzilma funktsiya chaqiruvidan oldin bir joyda shakllantirilishi kerak sub_6115C, lekin kodning bu qismi ayniqsa yaxshi xiralashgan, shuning uchun shifrni ochish funktsiyasining barcha parametrlari faylga tushirilishi uchun kodni tuzatish g'oyasi paydo bo'ladi.

Patch

Assambleya tilidagi barcha yamoq kodlarini qo'lda yozmaslik uchun siz Android Studio dasturini ishga tushirishingiz, u yerda shifrni ochish funksiyamiz bilan bir xil kiritish parametrlarini qabul qiluvchi funktsiyani yozishingiz va faylga yozishingiz, so'ngra kompilyator ko'rsatadigan kodni nusxa ko'chirishingiz va joylashtirishingiz mumkin. hosil qilish.

UC Browser jamoasidagi do'stlarimiz ham kod qo'shish qulayligi haqida g'amxo'rlik qilishdi. Shuni esda tutingki, har bir funktsiyaning boshida bizda boshqasi bilan osongina almashtirilishi mumkin bo'lgan axlat kodi mavjud. Juda qulay 🙂 Biroq, maqsadli funksiyaning boshida faylga barcha parametrlarni saqlaydigan kod uchun joy yetarli emas. Men uni qismlarga bo'lish va qo'shni funktsiyalardan axlat bloklarini ishlatishim kerak edi. Hammasi bo'lib to'rt qism bor edi.

Birinchi qism:

UC brauzerida zaifliklar qidirilmoqda

ARM arxitekturasida birinchi to'rtta funktsiya parametrlari R0-R3 registrlari orqali, qolganlari, agar mavjud bo'lsa, stek orqali o'tkaziladi. LR registrida qaytish manzili mavjud. Bularning barchasi saqlanishi kerak, shunda biz uning parametrlarini tashlaganimizdan keyin funksiya ishlashi mumkin. Shuningdek, biz jarayonda foydalanadigan barcha registrlarni saqlashimiz kerak, shuning uchun biz PUSH.W {R0-R10, LR} ni bajaramiz. R7 da biz funktsiyaga stek orqali uzatilgan parametrlar ro'yxatining manzilini olamiz.

Funktsiyadan foydalanish fopen faylni ochamiz /data/local/tmp/aes "ab" rejimida
ya'ni qo'shimcha uchun. R0 da biz fayl nomining manzilini yuklaymiz, R1da - rejimni ko'rsatadigan qatorning manzili. Va bu erda axlat kodi tugaydi, shuning uchun biz keyingi funktsiyaga o'tamiz. U ishlashda davom etishi uchun biz boshida axlatni chetlab o'tib, funktsiyaning haqiqiy kodiga o'tishni qo'yamiz va axlat o'rniga yamoqning davomini qo'shamiz.

UC brauzerida zaifliklar qidirilmoqda

Qo'ng'iroq qilish fopen.

Funktsiyaning dastlabki uchta parametri aes turiga ega int. Biz boshida registrlarni stekga saqlaganimiz sababli, biz shunchaki funktsiyani o'tkazishimiz mumkin yozing stekdagi ularning manzillari.

UC brauzerida zaifliklar qidirilmoqda

Keyin bizda uchta tuzilma mavjud bo'lib, ularda ma'lumotlar hajmi va kalit, ishga tushirish vektori va shifrlangan ma'lumotlar uchun ma'lumotlarga ko'rsatgich mavjud.

UC brauzerida zaifliklar qidirilmoqda

Oxirida faylni yoping, registrlarni tiklang va boshqaruvni haqiqiy funktsiyaga o'tkazing aes.

Biz yamalgan kutubxona bilan APK yig'amiz, imzolaymiz, qurilmaga/emulatorga yuklaymiz va ishga tushiramiz. Bizning axlatxonamiz yaratilayotganini va u erda juda ko'p ma'lumotlar yozilayotganini ko'ramiz. Brauzer shifrlashni nafaqat trafik uchun ishlatadi va barcha shifrlash ushbu funktsiyadan o'tadi. Lekin negadir kerakli ma'lumotlar yo'q va kerakli so'rov trafikda ko'rinmaydi. UC brauzeri kerakli so'rovni amalga oshirishni kutmaslik uchun, avval olingan serverdan shifrlangan javobni olib, ilovani yana tuzatamiz: shifrni ochishni asosiy faoliyatning onCreate-ga qo'shing.

    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

Biz yig'amiz, imzolaymiz, o'rnatamiz, ishga tushiramiz. Biz NullPointerExceptionni olamiz, chunki usul nullni qaytardi.

Kodni keyingi tahlil qilish jarayonida qiziqarli qatorlarni shifrlaydigan funksiya topildi: “META-INF/” va “.RSA”. Ilova sertifikatini tekshirayotganga o‘xshaydi. Yoki undan kalitlarni ham yaratadi. Men sertifikat bilan nima sodir bo'layotgani bilan shug'ullanishni xohlamayman, shuning uchun biz unga to'g'ri sertifikat beramiz. “META-INF/” o‘rniga “BLABLINF/” ni olishimiz uchun shifrlangan qatorni tuzatamiz, APK-da shu nomdagi papka yaratamiz va u yerga sincap brauzer sertifikatini qo‘shamiz.

Biz yig'amiz, imzolaymiz, o'rnatamiz, ishga tushiramiz. Bingo! Kalit bizda!

MitM

Biz kalitga teng kalit va ishga tushirish vektorini oldik. Keling, CBC rejimida server javobining shifrini ochishga harakat qilaylik.

UC brauzerida zaifliklar qidirilmoqda

Biz arxiv URL manzilini ko'ramiz, MD5 ga o'xshash narsa, "extract_unzipsize" va raqam. Biz tekshiramiz: arxivning MD5 bir xil, ochilmagan kutubxona hajmi bir xil. Biz ushbu kutubxonani tuzatishga va uni brauzerga berishga harakat qilmoqdamiz. Yamalgan kutubxonamiz yuklanganligini ko'rsatish uchun biz “PWNED!” matni bilan SMS yaratish niyatini ishga tushiramiz. Serverdan ikkita javobni almashtiramiz: puds.ucweb.com/upgrade/index.xhtml va arxivni yuklab olish uchun. Birinchisida biz MD5 ni almashtiramiz (qadoqlashdan keyin o'lcham o'zgarmaydi), ikkinchisida biz arxivni yamalgan kutubxona bilan beramiz.

Brauzer arxivni bir necha marta yuklab olishga harakat qiladi, shundan so'ng u xato beradi. Ko'rinib turibdiki, nimadir
u yoqtirmaydi. Ushbu noaniq formatni tahlil qilish natijasida server arxiv hajmini ham uzatadi:

UC brauzerida zaifliklar qidirilmoqda

U LEB128 da kodlangan. Yamoqdan so'ng kutubxona bilan arxiv hajmi biroz o'zgardi, shuning uchun brauzer arxivni egri yuklab olingan deb hisobladi va bir necha urinishlardan so'ng xatoga yo'l qo'ydi.

Biz arxiv hajmini moslashtiramiz ... Va - g'alaba! 🙂 Natija videoda.

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

Natijalar va ishlab chiquvchining reaktsiyasi

Xuddi shu tarzda, xakerlar zararli kutubxonalarni tarqatish va ishga tushirish uchun UC brauzerining xavfsiz xususiyatidan foydalanishi mumkin. Ushbu kutubxonalar brauzer kontekstida ishlaydi, shuning uchun ular uning barcha tizim ruxsatlarini oladi. Natijada, fishing oynalarini ko'rsatish, shuningdek, ma'lumotlar bazasida saqlanadigan loginlar, parollar va cookie-fayllarni o'z ichiga olgan to'q sariq xitoylik sincapning ishchi fayllariga kirish imkoniyati.

Biz UC Brauzer ishlab chiquvchilari bilan bog‘landik va ularga topilgan muammo haqida ma’lumot berdik, zaiflik va uning xavfini ko‘rsatishga harakat qildik, lekin ular biz bilan hech narsani muhokama qilishmadi. Shu bilan birga, brauzer o'zining xavfli xususiyatini ochiq ko'rinishda namoyish etishda davom etdi. Ammo zaiflikning tafsilotlarini oshkor qilganimizdan so'ng, uni avvalgidek e'tiborsiz qoldirib bo'lmaydi. 27 mart edi
HTTPS orqali serverga kirgan UC brauzerining 12.10.9.1193 yangi versiyasi chiqdi: puds.ucweb.com/upgrade/index.xhtml.

Bundan tashqari, "tuzatish" dan so'ng va ushbu maqola yozilgunga qadar, brauzerda PDF-ni ochishga urinish "Op, nimadir noto'g'ri ketdi!" Matnli xato xabariga olib keldi. PDF-ni ochishga urinayotganda serverga so'rov yuborilmadi, ammo brauzer ishga tushirilganda so'rov yuborildi, bu Google Play qoidalarini buzgan holda bajariladigan kodni yuklab olishning davom etishiga ishora qiladi.

Manba: www.habr.com

a Izoh qo'shish