Փնտրվում են խոցելիություններ UC Browser-ում

Փնտրվում են խոցելիություններ UC Browser-ում

Ներածություն

Մարտի վերջին մենք հաղորդում է, որ նրանք հայտնաբերել են UC Browser-ում չստուգված ծածկագիրը բեռնելու և գործարկելու թաքնված հնարավորություն: Այսօր մենք մանրամասն կանդրադառնանք, թե ինչպես է տեղի ունենում այս ներբեռնումը և ինչպես կարող են հաքերներն օգտագործել այն իրենց նպատակների համար:

Որոշ ժամանակ առաջ UC Browser-ը գովազդվեց և տարածվեց շատ ագրեսիվ. այն տեղադրվեց օգտատերերի սարքերի վրա՝ օգտագործելով չարամիտ ծրագրեր, տարածված տարբեր կայքերից՝ վիդեո ֆայլերի քողի տակ (այսինքն՝ օգտատերերը կարծում էին, որ ներբեռնում են, օրինակ, պոռնո տեսանյութ, բայց փոխարենը ստացել է APK այս զննարկիչով), օգտագործել են սարսափազդու պաստառներ՝ հաղորդագրություններով, որ զննարկիչը հնացել է, խոցելի է և նման բաներ: VK-ի պաշտոնական UC Browser խմբում կա թեման, որում օգտատերերը կարող են բողոքել անարդար գովազդից, այնտեղ օրինակները շատ են։ 2016-ին նույնիսկ եղել է վիդեո գովազդ ռուսերեն (այո, գովազդ արգելափակող բրաուզերի գովազդ):

Գրելու պահին UC Browser-ն ունի ավելի քան 500 տեղադրում Google Play-ում: Սա տպավորիչ է. միայն Google Chrome-ն ունի ավելին: Կարծիքների թվում կարող եք տեսնել բավականին շատ բողոքներ գովազդի և Google Play-ի որոշ հավելվածների վերահղումների վերաբերյալ: Սա էր մեր հետազոտության պատճառը. մենք որոշեցինք տեսնել, թե արդյոք UC Browser-ը վատ բան է անում: Եվ պարզվեց, որ նա անում է:

Հավելվածի կոդում հայտնաբերվել է գործարկվող կոդը ներբեռնելու և գործարկելու հնարավորությունը, ինչը հակասում է հայտերի հրապարակման կանոններին Google Play-ում: Բացի գործարկվող կոդ ներբեռնելուց, UC Browser-ը դա անում է անապահով կերպով, որը կարող է օգտագործվել MitM հարձակում գործարկելու համար: Տեսնենք՝ կարո՞ղ ենք նման հարձակում իրականացնել։

Ստորև գրված ամեն ինչ վերաբերում է UC Browser-ի այն տարբերակին, որը հասանելի էր Google Play-ում ուսումնասիրության պահին.

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

Հարձակման վեկտոր

UC Browser-ի մանիֆեստում դուք կարող եք գտնել ծառայություն՝ ինքնաբացատրվող անունով com.uc.deployment.UpgradeDeployService.

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

Երբ այս ծառայությունը սկսվում է, զննարկիչը POST հարցում է կատարում puds.ucweb.com/upgrade/index.xhtml, որը կարելի է տեսնել երթևեկության մեջ մեկնարկից որոշ ժամանակ անց: Ի պատասխան՝ նա կարող է հրաման ստանալ՝ ներբեռնելու ինչ-որ թարմացում կամ նոր մոդուլ։ Վերլուծության ժամանակ սերվերը նման հրամաններ չի տվել, բայց մենք նկատեցինք, որ երբ փորձում ենք PDF բացել բրաուզերում, երկրորդ հարցումն է անում վերը նշված հասցեին, որից հետո ներբեռնում է հայրենի գրադարանը։ Հարձակումն իրականացնելու համար մենք որոշեցինք օգտագործել UC Browser-ի այս հատկությունը՝ PDF-ը բացելու հնարավորություն՝ օգտագործելով բնիկ գրադարան, որը APK-ում չէ, և որը անհրաժեշտության դեպքում այն ​​ներբեռնում է ինտերնետից: Հարկ է նշել, որ տեսականորեն UC Browser-ին կարող են ստիպել ինչ-որ բան ներբեռնել առանց օգտվողի փոխազդեցության, եթե դուք լավ ձևավորված պատասխան եք տալիս այն հարցումին, որը կատարվում է բրաուզերի գործարկումից հետո: Բայց դա անելու համար մենք պետք է ավելի մանրամասն ուսումնասիրենք սերվերի հետ փոխգործակցության արձանագրությունը, ուստի որոշեցինք, որ ավելի հեշտ կլինի խմբագրել գաղտնալսված պատասխանը և փոխարինել գրադարանը PDF-ով աշխատելու համար:

Այսպիսով, երբ օգտվողը ցանկանում է բացել PDF-ը ուղղակիորեն զննարկիչում, տրաֆիկում կարելի է տեսնել հետևյալ հարցումները.

Փնտրվում են խոցելիություններ UC Browser-ում

Նախ կա POST հարցում puds.ucweb.com/upgrade/index.xhtmlորից հետո
Ներբեռնվում է արխիվ՝ գրադարանով PDF և գրասենյակային ձևաչափերը դիտելու համար: Տրամաբանական է ենթադրել, որ առաջին հարցումը տեղեկատվություն է փոխանցում համակարգի մասին (առնվազն անհրաժեշտ գրադարանը տրամադրելու ճարտարապետությունը), և դրան ի պատասխան զննարկիչը ստանում է որոշ տեղեկություններ գրադարանի մասին, որը պետք է ներբեռնվի՝ հասցեն և, հնարավոր է, , ինչ որ այլ բան. Խնդիրն այն է, որ այս հարցումը կոդավորված է:

Հարցրեք հատված

Պատասխանի հատված

Փնտրվում են խոցելիություններ UC Browser-ում

Փնտրվում են խոցելիություններ UC Browser-ում

Գրադարանը ինքնին փաթեթավորված է ZIP-ով և գաղտնագրված չէ:

Փնտրվում են խոցելիություններ UC Browser-ում

Որոնեք երթևեկության վերծանման կոդ

Փորձենք վերծանել սերվերի պատասխանը։ Եկեք նայենք դասի կոդը com.uc.deployment.UpgradeDeployServiceմեթոդից onStartCommand գնալ com.uc.deployment.bx, և դրանից մինչև 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);
}

Այստեղ մենք տեսնում ենք POST հարցման ձևավորումը։ Ուշադրություն ենք դարձնում 16 բայթանոց զանգվածի ստեղծմանը և դրա լրացմանը` 0x5F, 0, 0x1F, -50 (=0xCE): Համընկնում է այն ամենի հետ, ինչ մենք տեսանք վերը նշված խնդրանքում:

Նույն դասում դուք կարող եք տեսնել ներդիր դաս, որն ունի մեկ այլ հետաքրքիր մեթոդ.

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

Մեթոդը վերցնում է բայթերի զանգված՝ որպես մուտքագրում և ստուգում, որ զրոյական բայթը 0x60 է կամ երրորդ բայթը՝ 0xD0, իսկ երկրորդ բայթը՝ 1, 11 կամ 0x1F: Մենք նայում ենք սերվերի պատասխանին՝ զրոյական բայթը 0x60 է, երկրորդը՝ 0x1F, երրորդը՝ 0x60: Հնչում է այն, ինչ մեզ անհրաժեշտ է: Դատելով տողերից (օրինակ՝ «up_decrypt»), այստեղ պետք է կոչվի մեթոդ, որը կվերծանի սերվերի պատասխանը։
Անցնենք մեթոդին գջ. Նկատի ունեցեք, որ առաջին արգումենտը բայթն է օֆսեթ 2-ով (այսինքն՝ 0x1F մեր դեպքում), իսկ երկրորդը սերվերի պատասխանն է՝ առանց
առաջին 16 բայթ.

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

Ակնհայտ է, որ այստեղ մենք ընտրում ենք գաղտնազերծման ալգորիթմ և նույն բայթը, որը կա մեր մեջ
գործը հավասար է 0x1F-ի, նշանակում է երեք հնարավոր տարբերակներից մեկը:

Մենք շարունակում ենք վերլուծել կոդը: Մի երկու ցատկից հետո մենք հայտնվում ենք մի մեթոդի մեջ, որն ունի ինքնին հասկանալի անվանում decryptBytesByKey.

Այստեղ մեր պատասխանից առանձնացվում է ևս երկու բայթ, և դրանցից ստացվում է տող։ Հասկանալի է, որ այս կերպ ընտրվում է հաղորդագրությունը վերծանելու բանալին։

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

Նայելով առաջ՝ մենք նշում ենք, որ այս փուլում մենք դեռ չենք ստանում բանալի, այլ միայն դրա «նույնացուցիչը»: Բանալին ստանալը մի փոքր ավելի բարդ է:

Հաջորդ մեթոդով գոյություն ունեցողներին ավելացվում են ևս երկու պարամետր՝ դարձնելով դրանցից չորսը՝ կախարդական թիվը 16, բանալու նույնացուցիչը, կոդավորված տվյալները և անհասկանալի տողը (մեր դեպքում՝ դատարկ)։

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

Մի շարք անցումներից հետո մենք հասնում ենք մեթոդին staticBinarySafeDecryptNoB64 ինտերֆեյս com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Ծրագրի հիմնական կոդում չկան դասեր, որոնք իրականացնում են այս ինտերֆեյսը: Ֆայլում կա այդպիսի դաս lib/armeabi-v7a/libsgmain.so, որը իրականում ոչ թե .so, այլ .բանկ է: Մեզ հետաքրքրող մեթոդն իրականացվում է հետևյալ կերպ.

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

Այստեղ մեր պարամետրերի ցանկը լրացվում է ևս երկու ամբողջ թվով՝ 2 և 0։ Դատելով ըստ.
ամեն ինչ, 2-ը նշանակում է վերծանում, ինչպես մեթոդում վերջնական համակարգի դաս javax.crypto.Cipher. Եվ այս ամենը փոխանցվում է 10601 համարով որոշակի երթուղիչին. սա, ըստ երևույթին, հրամանի համարն է:

Անցումների հաջորդ շղթայից հետո մենք գտնում ենք մի դաս, որն իրականացնում է ինտերֆեյսը IRouterComponent և մեթոդ 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);
}
}

Եվ նաև դասարան JNICL գրադարան, որում հայտարարված է հայրենի մեթոդը doCommandNative:

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

Սա նշանակում է, որ մենք պետք է մեթոդ գտնենք մայրենի կոդում doCommandNative. Եվ հենց այստեղ է սկսվում զվարճանքը:

Մեքենայի կոդի խաբեություն

Ֆայլում libsgmain.so (որը իրականում .jar է և որում մենք գտանք գաղտնագրման հետ կապված որոշ միջերեսների իրականացում հենց վերևում) կա մեկ բնիկ գրադարան. libsgmainso-6.4.36.so. Մենք բացում ենք այն IDA-ում և ստանում սխալներով երկխոսության տուփեր: Խնդիրն այն է, որ բաժնի վերնագրի աղյուսակը անվավեր է: Դա արվում է միտումնավոր՝ վերլուծությունը բարդացնելու համար:

Փնտրվում են խոցելիություններ UC Browser-ում

Բայց դա անհրաժեշտ չէ. ELF ֆայլը ճիշտ բեռնելու և այն վերլուծելու համար բավական է ծրագրի վերնագրի աղյուսակը: Հետեւաբար, մենք պարզապես ջնջում ենք հատվածի աղյուսակը՝ զրոյացնելով վերնագրի համապատասխան դաշտերը։

Փնտրվում են խոցելիություններ UC Browser-ում

Կրկին բացեք ֆայլը IDA-ում:

Գոյություն ունի Java վիրտուալ մեքենային ասելու երկու եղանակ, թե որտեղ է գտնվում հենց հայրենի գրադարանում Java կոդում որպես բնիկ հայտարարված մեթոդի իրականացումը: Առաջինը նրան տեսակի անուն տալն է Java_package_name_ClassName_MethodName.

Երկրորդը այն գրանցելն է գրադարանը բեռնելիս (գործառույթում JNI_OnLoad)
օգտագործելով ֆունկցիայի կանչ RegisterNatives.

Մեր դեպքում, եթե օգտագործենք առաջին մեթոդը, անունը պետք է լինի այսպիսին. Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

Արտահանված գործառույթների մեջ նման գործառույթ չկա, ինչը նշանակում է, որ դուք պետք է զանգ փնտրեք RegisterNatives.
Անցնենք ֆունկցիային JNI_OnLoad և մենք տեսնում ենք այս նկարը.

Փնտրվում են խոցելիություններ UC Browser-ում

Ինչ է այստեղ կատարվում? Առաջին հայացքից ֆունկցիայի սկիզբն ու ավարտը բնորոշ են ARM ճարտարապետությանը։ Դույլի առաջին հրահանգը պահպանում է ռեգիստրների բովանդակությունը, որոնք գործառույթը կօգտագործի իր գործարկման մեջ (այս դեպքում՝ R0, R1 և R2), ինչպես նաև LR ռեգիստրի բովանդակությունը, որը պարունակում է ֆունկցիայի վերադարձի հասցեն։ . Վերջին հրահանգը վերականգնում է պահպանված ռեգիստրները, և վերադարձի հասցեն անմիջապես տեղադրվում է PC ռեգիստրում, այդպիսով վերադառնալով ֆունկցիայից: Բայց եթե ուշադիր նայեք, կնկատեք, որ նախավերջին հրահանգը փոխում է բուրգի վրա պահված վերադարձի հասցեն։ Եկեք հաշվարկենք, թե հետո ինչ կլինի
կոդի կատարումը: Որոշակի հասցե 1xB0 բեռնվում է R130-ում, դրանից հանվում է 5-ը, այնուհետև այն փոխանցվում է R0-ին և դրան ավելացվում է 0x10: Ստացվում է 0xB13B: Այսպիսով, IDA-ն կարծում է, որ վերջին հրահանգը նորմալ ֆունկցիայի վերադարձ է, բայց իրականում այն ​​գնում է 0xB13B հաշվարկված հասցեով։

Այստեղ հարկ է հիշել, որ ARM պրոցեսորներն ունեն երկու ռեժիմ և հրահանգների երկու հավաքածու՝ ARM և Thumb: Հասցեի ամենաքիչ նշանակալից բիթը պատմում է պրոցեսորին, թե որ հրահանգների հավաքածուն է օգտագործվում: Այսինքն, հասցեն իրականում 0xB13A է, և ամենաքիչ կարևոր բիթից մեկը ցույց է տալիս Thumb ռեժիմը:

Նմանատիպ «ադապտեր» ավելացվել է այս գրադարանի յուրաքանչյուր գործառույթի սկզբում և
աղբի կոդը. Մենք դրանց մասին ավելի մանրամասն չենք անդրադառնա, պարզապես հիշում ենք
որ գրեթե բոլոր գործառույթների իրական սկիզբը մի փոքր հեռու է։

Քանի որ կոդը բացահայտորեն չի ցատկում դեպի 0xB13A, IDA-ն ինքը չի ճանաչել, որ կոդը գտնվում է այս վայրում: Նույն պատճառով այն չի ճանաչում գրադարանի կոդի մեծ մասը որպես կոդ, ինչը որոշ չափով դժվարացնում է վերլուծությունը: Մենք IDA-ին ասում ենք, որ սա ծածկագիր է, և սա տեղի է ունենում.

Փնտրվում են խոցելիություններ UC Browser-ում

Աղյուսակը հստակ սկսվում է 0xB144-ից: Ի՞նչ կա sub_494C-ում:

Փնտրվում են խոցելիություններ UC Browser-ում

Այս ֆունկցիան LR ռեգիստրում կանչելիս ստանում ենք նախկինում նշված աղյուսակի հասցեն (0xB144)։ R0-ում - ինդեքս այս աղյուսակում: Այսինքն, արժեքը վերցվում է աղյուսակից, ավելացվում է LR-ին և ստացվում է
հասցեն, որտեղ պետք է գնալ. Փորձենք հաշվել այն՝ 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264: Մենք գնում ենք ստացված հասցե և տեսնում ենք բառացիորեն մի քանի օգտակար հրահանգ և կրկին գնում ենք 0xB140.

Փնտրվում են խոցելիություններ UC Browser-ում

Այժմ աղյուսակից 0x20 ինդեքսով անցում կլինի օֆսեթում:

Դատելով աղյուսակի չափերից՝ կոդում նման անցումներ շատ կլինեն։ Հարց է առաջանում՝ հնարավո՞ր է ինչ-որ կերպ դրանով զբաղվել ավելի ավտոմատ կերպով՝ առանց հասցեները ձեռքով հաշվելու։ Եվ սցենարները և IDA-ում կոդը կարկատելու հնարավորությունը մեզ օգնության են հասնում.

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"

Տեղադրեք կուրսորը 0xB26A տողի վրա, գործարկեք սկրիպտը և տեսեք անցումը 0xB4B0:

Փնտրվում են խոցելիություններ UC Browser-ում

IDA-ն կրկին չի ճանաչել այս տարածքը որպես ծածկագիր: Մենք օգնում ենք նրան և տեսնում ենք մեկ այլ դիզայն այնտեղ.

Փնտրվում են խոցելիություններ UC Browser-ում

BLX-ից հետո հրահանգները կարծես թե այնքան էլ իմաստ չունեն, դա ավելի շուտ ինչ-որ տեղաշարժի է նման: Եկեք նայենք sub_4964:

Փնտրվում են խոցելիություններ UC Browser-ում

Եվ իսկապես, այստեղ dword-ը վերցվում է LR-ում ընկած հասցեում, ավելացվում այս հասցեին, որից հետո ստացված հասցեի արժեքը վերցվում է և դրվում ստեկի վրա։ Նաև LR-ին ավելացվում է 4, որպեսզի ֆունկցիայից վերադառնալուց հետո այս նույն օֆսեթը բաց թողնվի: Որից հետո POP {R1} հրամանը ստեկից վերցնում է ստացված արժեքը։ Եթե ​​նայեք, թե ինչ է գտնվում 0xB4BA + 0xEA = 0xB5A4 հասցեում, կտեսնեք հասցեների աղյուսակի նման մի բան.

Փնտրվում են խոցելիություններ UC Browser-ում

Այս դիզայնը կարկատելու համար կոդից պետք է ստանաք երկու պարամետր՝ օֆսեթը և գրանցման համարը, որում ցանկանում եք տեղադրել արդյունքը: Յուրաքանչյուր հնարավոր ռեգիստրի համար դուք պետք է նախօրոք պատրաստեք կոդ:

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"

Մենք կուրսորը տեղադրում ենք այն կառուցվածքի սկզբում, որը ցանկանում ենք փոխարինել՝ 0xB4B2, և գործարկում ենք սցենարը.

Փնտրվում են խոցելիություններ UC Browser-ում

Բացի արդեն նշված կառույցներից, ծածկագիրը պարունակում է նաև հետևյալը.

Փնտրվում են խոցելիություններ UC Browser-ում

Ինչպես նախորդ դեպքում, BLX հրահանգից հետո կա օֆսեթ.

Փնտրվում են խոցելիություններ UC Browser-ում

Մենք օֆսեթը վերցնում ենք LR-ից հասցե, ավելացնում ենք LR-ին և գնում այնտեղ: 0x72044 + 0xC = 0x72050: Այս դիզայնի սցենարը բավականին պարզ է.

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"

Սցենարի կատարման արդյունքը.

Փնտրվում են խոցելիություններ UC Browser-ում

Երբ ամեն ինչ կարկատվի գործառույթում, դուք կարող եք մատնանշել IDA-ն իր իրական սկզբին: Այն կմիավորի ամբողջ ֆունկցիայի կոդը, և այն կարելի է ապակոմպիլյացիայի ենթարկել HexRays-ի միջոցով:

Վերծանման տողեր

Մենք սովորել ենք գրադարանում մեքենայական կոդի մշուշման հետ գործ ունենալ libsgmainso-6.4.36.so UC Browser-ից և ստացել ֆունկցիայի կոդը 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;
}

Եկեք մանրամասնորեն նայենք հետևյալ տողերին.

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

Գործառույթի մեջ sub_73E24 դասի անվանումն ակնհայտորեն վերծանվում է: Որպես այս ֆունկցիայի պարամետրեր, փոխանցվում են գաղտնագրված տվյալներին նման տվյալների ցուցիչ, որոշակի բուֆեր և մի շարք: Ակնհայտ է, որ ֆունկցիան կանչելուց հետո բուֆերում կլինի վերծանված տող, քանի որ այն փոխանցվում է ֆունկցիային: FindClass, որն ընդունում է դասի անվանումը որպես երկրորդ պարամետր։ Հետեւաբար, թիվը բուֆերի չափն է կամ գծի երկարությունը: Փորձենք վերծանել դասի անվանումը, այն պետք է մեզ ասի, թե արդյոք ճիշտ ուղղությամբ ենք գնում։ Եկեք ավելի սերտ նայենք, թե ինչ է տեղի ունենում ներսում 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;
}

Ֆունկցիա sub_7AF78 ստեղծում է կոնտեյների օրինակ նշված չափի բայթային զանգվածների համար (մենք մանրամասն չենք անդրադառնա այս բեռնարկղերի վրա): Այստեղ ստեղծվում են երկու այդպիսի կոնտեյներ՝ մեկը պարունակում է տողը «DcO/lcK+h?m3c*q@» (հեշտ է կռահել, որ սա բանալի է), մյուսը պարունակում է կոդավորված տվյալներ։ Հաջորդը, երկու օբյեկտներն էլ տեղադրվում են որոշակի կառուցվածքում, որը փոխանցվում է ֆունկցիային sub_6115C. Եկեք այս կառույցում նշենք նաև 3 արժեքով դաշտը, տեսնենք, թե հետո ինչ կլինի այս կառույցի հետ:

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

Անջատիչի պարամետրը կառուցվածքային դաշտ է, որին նախկինում տրվել է 3 արժեքը: Նայեք գործին 3. ֆունկցիային: sub_6364C պարամետրերը փոխանցվում են կառուցվածքից, որոնք ավելացվել են այնտեղ նախորդ գործառույթում, այսինքն՝ բանալին և գաղտնագրված տվյալները: Եթե ​​ուշադիր նայեք sub_6364C, դրանում կարող եք ճանաչել RC4 ալգորիթմը։

Մենք ունենք ալգորիթմ և բանալի: Փորձենք վերծանել դասի անվանումը։ Ահա թե ինչ է տեղի ունեցել. com/taobao/wireless/security/adapter/JNICLlibrary. Հիանալի Մենք ճիշտ ուղու վրա ենք։

Հրամանի ծառ

Այժմ մենք պետք է մարտահրավեր գտնենք RegisterNatives, որը մեզ ցույց կտա ֆունկցիան doCommandNative. Եկեք նայենք այն գործառույթներին, որոնք կոչվում են JNI_OnLoad, և մենք գտնում ենք այն 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;
}

Եվ իսկապես, այստեղ գրանցված է անվան հետ հայրենի մեթոդ doCommandNative. Այժմ մենք գիտենք նրա հասցեն։ Տեսնենք՝ ինչ է անում։

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

Անունով կարող եք կռահել, որ այստեղ բոլոր գործառույթների մուտքի կետն է, որը մշակողները որոշել են փոխանցել հայրենի գրադարան: Մեզ հետաքրքրում է թիվ 10601 ֆունկցիան։

Կոդից կարող եք տեսնել, որ հրամանի համարը արտադրում է երեք թվեր. հրաման/10000, հրաման % 10000 / 100 и հրաման % 10, այսինքն՝ մեր դեպքում՝ 1, 6 և 1։ Այս երեք թվերը, ինչպես նաև ցուցիչը դեպի JNIEnv և ֆունկցիային փոխանցված արգումենտները ավելացվում են կառուցվածքի մեջ և փոխանցվում: Օգտագործելով ստացված երեք թվերը (նշենք դրանք N1, N2 և N3) կառուցվում է հրամանի ծառ։

Նման մի բան.

Փնտրվում են խոցելիություններ UC Browser-ում

Ծառը լցված է դինամիկ կերպով JNI_OnLoad.
Երեք թվեր կոդավորում են ծառի ուղին: Ծառի յուրաքանչյուր տերևը պարունակում է համապատասխան գործառույթի հասցեն: Բանալին գտնվում է մայր հանգույցում: Կոդի մեջ այն տեղը գտնելը, որտեղ մեզ անհրաժեշտ ֆունկցիան ավելացվում է ծառին, դժվար չէ, եթե հասկանում եք օգտագործված բոլոր կառույցները (մենք չենք նկարագրում դրանք, որպեսզի չփչացնենք արդեն բավականին մեծ հոդվածը):

Ավելի շատ խճճվածություն

Մենք ստացանք այն ֆունկցիայի հասցեն, որը պետք է գաղտնազերծի տրաֆիկը՝ 0x5F1AC: Բայց դեռ վաղ է ուրախանալ. UC Browser-ի մշակողները ևս մեկ անակնկալ են պատրաստել մեզ համար։

Java կոդի մեջ ձևավորված զանգվածից պարամետրերը ստանալուց հետո մենք ստանում ենք
0x4D070 հասցեի գործառույթին: Եվ այստեղ մեզ սպասում է մեկ այլ տիպի կոդի խաբեություն:

Մենք երկու ինդեքս ենք դնում R7 և R4-ում.

Փնտրվում են խոցելիություններ UC Browser-ում

Մենք առաջին ցուցանիշը տեղափոխում ենք R11.

Փնտրվում են խոցելիություններ UC Browser-ում

Աղյուսակից հասցե ստանալու համար օգտագործեք ինդեքս.

Փնտրվում են խոցելիություններ UC Browser-ում

Առաջին հասցե գնալուց հետո օգտագործվում է երկրորդ ինդեքսը, որը գտնվում է R4-ում։ Աղյուսակում կա 230 տարր:

Ի՞նչ անել դրա հետ կապված: Դուք կարող եք IDA-ին ասել, որ սա անջատիչ է՝ Խմբագրել -> Այլ -> Նշել անջատիչի բառապաշար:

Փնտրվում են խոցելիություններ UC Browser-ում

Ստացված ծածկագիրը սարսափելի է։ Բայց, ճանապարհ անցնելով նրա ջունգլիներում, կարող եք նկատել մեզ արդեն ծանոթ ֆունկցիայի կանչ sub_6115C:

Փնտրվում են խոցելիություններ UC Browser-ում

Կար անջատիչ, որի դեպքում 3-ի դեպքում տեղի է ունեցել գաղտնազերծում RC4 ալգորիթմի միջոցով։ Եվ այս դեպքում ֆունկցիային փոխանցված կառուցվածքը լրացվում է դրան փոխանցված պարամետրերից doCommandNative. Հիշենք, թե ինչ ունեինք այնտեղ magicInt 16 արժեքով. Մենք նայում ենք համապատասխան դեպքին - և մի քանի անցումներից հետո գտնում ենք այն կոդը, որով կարելի է նույնականացնել ալգորիթմը։

Փնտրվում են խոցելիություններ UC Browser-ում

Սա AES-ն է:

Ալգորիթմը գոյություն ունի, մնում է միայն ստանալ դրա պարամետրերը՝ ռեժիմը, բանալին և, հնարավոր է, սկզբնավորման վեկտորը (դրա ներկայությունը կախված է AES ալգորիթմի գործառնական ռեժիմից): Նրանց հետ կառուցվածքը պետք է ինչ-որ տեղ ձևավորվի ֆունկցիայի կանչից առաջ sub_6115C, բայց կոդի այս հատվածը հատկապես լավ մշուշված է, ուստի գաղափար է առաջանում կոդն այնպես ուղղել, որպեսզի գաղտնազերծման ֆունկցիայի բոլոր պարամետրերը թափվեն ֆայլի մեջ։

Կարկատել

Որպեսզի ամբողջ կարկատման կոդը ձեռքով չգրվի անսամբլի լեզվով, կարող եք գործարկել Android Studio-ն, այնտեղ գրել գործառույթ, որը ստանում է նույն մուտքային պարամետրերը, ինչ մեր ապակոդավորման գործառույթը և գրում է ֆայլում, այնուհետև պատճենեք-տեղադրեք այն կոդը, որը կկազմողի կողմից: առաջացնել.

UC Browser թիմի մեր ընկերները նույնպես հոգացել են կոդի ավելացման հարմարության մասին։ Հիշենք, որ յուրաքանչյուր ֆունկցիայի սկզբում մենք ունենք աղբի կոդ, որը հեշտությամբ կարելի է փոխարինել ցանկացած այլով։ Շատ հարմար 🙂 Այնուամենայնիվ, թիրախային ֆունկցիայի սկզբում բավականաչափ տեղ չկա կոդի համար, որը պահում է բոլոր պարամետրերը ֆայլում: Ես ստիպված էի բաժանել այն մասերի և օգտագործել աղբի բլոկներ հարևան գործառույթներից: Ընդհանուր առմամբ չորս մաս կար.

Առաջին մասը `

Փնտրվում են խոցելիություններ UC Browser-ում

ARM ճարտարապետության մեջ առաջին չորս ֆունկցիայի պարամետրերը փոխանցվում են R0-R3 ռեգիստրների միջոցով, մնացածը, եթե այդպիսիք կան, փոխանցվում են ստեկի միջով: LR ռեգիստրը կրում է վերադարձի հասցեն: Այս ամենը պետք է պահպանվի, որպեսզի ֆունկցիան աշխատի այն բանից հետո, երբ մենք կթափենք դրա պարամետրերը: Մենք նաև պետք է պահպանենք բոլոր ռեգիստրները, որոնք մենք կօգտագործենք գործընթացում, այնպես որ մենք անում ենք PUSH.W {R0-R10,LR}: R7-ում մենք ստանում ենք ստեկի միջոցով ֆունկցիային փոխանցված պարամետրերի ցանկի հասցեն։

Օգտագործելով գործառույթը բացել եկեք բացենք ֆայլը /տվյալներ/տեղական/tmp/aes «ab» ռեժիմում
այսինքն հավելման համար։ R0-ում մենք բեռնում ենք ֆայլի անվան հասցեն, R1-ում՝ ռեժիմը ցույց տվող տողի հասցեն: Եվ ահա աղբի կոդը ավարտվում է, ուստի մենք անցնում ենք հաջորդ գործառույթին: Որպեսզի այն շարունակի աշխատել, սկզբում դնում ենք անցումը ֆունկցիայի իրական կոդի՝ շրջանցելով աղբը, իսկ աղբի փոխարեն ավելացնում ենք patch-ի շարունակությունը։

Փնտրվում են խոցելիություններ UC Browser-ում

Զանգահարելով բացել.

Ֆունկցիայի առաջին երեք պարամետրերը աես ունեն տեսակ int. Քանի որ սկզբում ռեգիստրները պահել ենք stack-ում, կարող ենք պարզապես փոխանցել ֆունկցիան fwrite նրանց հասցեները կույտում:

Փնտրվում են խոցելիություններ UC Browser-ում

Հաջորդը մենք ունենք երեք կառուցվածք, որոնք պարունակում են տվյալների չափը և բանալու, սկզբնավորման վեկտորի և գաղտնագրված տվյալների ցուցիչ:

Փնտրվում են խոցելիություններ UC Browser-ում

Վերջում փակեք ֆայլը, վերականգնեք ռեգիստրները և կառավարումը փոխանցեք իրական ֆունկցիային աես.

Մենք հավաքում ենք APK՝ կարկատված գրադարանով, ստորագրում ենք այն, վերբեռնում սարքում/էմուլյատորում և գործարկում: Մենք տեսնում ենք, որ մեր աղբանոցը ստեղծվում է, և այնտեղ շատ տվյալներ են գրվում։ Զննարկիչը օգտագործում է կոդավորումը ոչ միայն երթևեկության համար, և ամբողջ գաղտնագրումն անցնում է տվյալ գործառույթի միջոցով: Բայց ինչ-ինչ պատճառներով անհրաժեշտ տվյալները չկան, և պահանջվող հարցումը տեսանելի չէ տրաֆիկում։ Որպեսզի չսպասենք, մինչև UC Browser-ը կարողանա կատարել անհրաժեշտ հարցումը, եկեք վերցնենք գաղտնագրված պատասխանը ավելի վաղ ստացված սերվերից և նորից կարկատենք հավելվածը.

    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-ում և այնտեղ ավելացնենք squirrel բրաուզերի վկայականը:

Մենք հավաքում ենք, ստորագրում, տեղադրում, գործարկում։ Բինգո Մենք ունենք բանալին!

MitM

Մենք ստացանք բանալին և սկզբնականացման վեկտորը, որը հավասար է բանալիին: Փորձենք վերծանել սերվերի պատասխանը CBC ռեժիմում:

Փնտրվում են խոցելիություններ UC Browser-ում

Մենք տեսնում ենք արխիվի URL-ը, որը նման է MD5-ին, «extract_unzipsize»-ին և թվին: Ստուգում ենք՝ արխիվի MD5-ը նույնն է, չփաթեթավորված գրադարանի չափը նույնն է։ Մենք փորձում ենք կարկատել այս գրադարանը և տալ այն դիտարկիչին: Ցույց տալու համար, որ մեր կարկատված գրադարանը բեռնված է, մենք կգործարկենք «PWNED» տեքստով SMS հաղորդագրություն ստեղծելու մտադրություն: Մենք կփոխարինենք երկու պատասխան սերվերից. puds.ucweb.com/upgrade/index.xhtml և արխիվը ներբեռնելու համար։ Առաջինում փոխարինում ենք MD5-ը (չափը բացելուց հետո չի փոխվում), երկրորդում՝ արխիվը տալիս ենք կարկատված գրադարանով։

Բրաուզերը մի քանի անգամ փորձում է ներբեռնել արխիվը, որից հետո սխալ է տալիս։ Ըստ երևույթին, ինչ-որ բան
նա չի սիրում. Այս պղտոր ձևաչափի վերլուծության արդյունքում պարզվեց, որ սերվերը փոխանցում է նաև արխիվի չափը.

Փնտրվում են խոցելիություններ UC Browser-ում

Այն կոդավորված է LEB128-ում: Patch-ից հետո գրադարանի հետ արխիվի չափը մի փոքր փոխվեց, ուստի բրաուզերը համարեց, որ արխիվը ներբեռնվել է ծուռ, և մի քանի փորձից հետո սխալ է թույլ տվել։

Մենք հարմարեցնում ենք արխիվի չափերը... Եվ – հաղթանակ: 🙂 Արդյունքը՝ տեսանյութում։

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

Հետևանքները և մշակողի արձագանքը

Նույն կերպ, հաքերները կարող են օգտագործել UC Browser-ի անապահով գործառույթը՝ վնասակար գրադարաններ տարածելու և գործարկելու համար: Այս գրադարանները կաշխատեն բրաուզերի համատեքստում, ուստի նրանք կստանան նրա բոլոր համակարգի թույլտվությունները: Արդյունքում՝ ֆիշինգի պատուհանները ցուցադրելու հնարավորություն, ինչպես նաև մուտք դեպի նարնջագույն չինական սկյուռի աշխատանքային ֆայլեր, ներառյալ տվյալների բազայում պահվող մուտքերը, գաղտնաբառերը և թխուկները:

Մենք կապվեցինք UC Browser-ի մշակողների հետ և տեղեկացրինք մեր հայտնաբերած խնդրի մասին, փորձեցինք մատնանշել խոցելիությունն ու դրա վտանգը, բայց նրանք մեզ հետ ոչինչ չքննարկեցին։ Միևնույն ժամանակ, զննարկիչը շարունակում էր ցուցադրել իր վտանգավոր առանձնահատկությունը պարզ տեսադաշտում: Բայց երբ մենք բացահայտեցինք խոցելիության մանրամասները, այլևս հնարավոր չէր նախկինի պես անտեսել այն: մարտի 27-ն էր
թողարկվել է UC Browser 12.10.9.1193-ի նոր տարբերակը, որը սերվեր մուտք է գործել HTTPS-ի միջոցով. puds.ucweb.com/upgrade/index.xhtml.

Բացի այդ, «շտկումից» հետո և մինչև այս հոդվածը գրելու պահը, զննարկիչում PDF-ը բացելու փորձը հանգեցրեց սխալի հաղորդագրության՝ «Վա՜յ, ինչ-որ բան սխալ է եղել»: Սերվերին հարցում չի արվել PDF-ը բացելիս, այլ հարցում է արվել, երբ գործարկվել է բրաուզերը, ինչը ակնարկում է Google Play-ի կանոնների խախտմամբ գործարկվող կոդը ներբեռնելու շարունակական ունակության մասին:

Source: www.habr.com

Добавить комментарий