Op syk nei kwetsberens yn UC Browser

Op syk nei kwetsberens yn UC Browser

Ynlieding

Ein maart we melde, dat se in ferburgen mooglikheid ûntdutsen om net ferifiearre koade te laden en út te fieren yn UC Browser. Hjoed sille wy yn detail sjen hoe't dizze download bart en hoe't hackers it kinne brûke foar har eigen doelen.

In skoft lyn waard UC Browser tige agressyf advertearre en ferspraat: it waard ynstalleare op apparaten fan brûkers mei malware, ferspraat fan ferskate siden ûnder it mom fan fideobestannen (d.w.s. brûkers tochten dat se bygelyks in pornofideo downloade, mar krige ynstee in APK mei dizze browser), brûkte enge banners mei berjochten dat de browser ferâldere, kwetsber wie, en soksoarte dingen. Yn 'e offisjele UC Browser-groep op VK is d'r it ûnderwerp, wêryn brûkers kinne kleie oer ûnearlike reklame, binne der in protte foarbylden. Yn 2016 wie der sels video reklame yn it Russysk (ja, reklame foar in advertinsjeblokkearjende browser).

Op it stuit fan dit skriuwen hat UC Browser mear dan 500 ynstallaasjes op Google Play. Dit is yndrukwekkend - allinich Google Chrome hat mear. Under de resinsjes kinne jo in soad klachten sjen oer reklame en trochferwizings nei guon applikaasjes op Google Play. Dit wie de reden foar ús ûndersyk: wy besletten om te sjen oft UC Browser wat min die. En it die bliken dat hy docht!

Yn 'e applikaasjekoade waard de mooglikheid ûntdutsen om útfierbere koade te downloaden en út te fieren, wat yn striid is mei de regels foar it publisearjen fan oanfragen op Google Play. Neist it downloaden fan útfierbere koade, docht UC Browser dat op in ûnfeilige manier, dy't kin wurde brûkt om in MitM-oanfal te starten. Lit sjen oft wy sa'n oanfal útfiere kinne.

Alles dat hjirûnder skreaun is is relevant foar de ferzje fan UC Browser dy't te krijen wie op Google Play op it stuit fan 'e stúdzje:

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

Attack vector

Yn it UC Browser-manifest kinne jo in tsjinst fine mei in selsferklearjende namme com.uc.deployment.UpgradeDeployService.

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

As dizze tsjinst begjint, makket de browser in POST-fersyk oan puds.ucweb.com/upgrade/index.xhtml, dy't in skoft nei de start yn it ferkear te sjen is. As antwurd kin hy in kommando krije om wat update of nije module te downloaden. Tidens de analyze joech de tsjinner sokke kommando's net, mar wy hawwe opmurken dat as wy besykje in PDF yn 'e browser te iepenjen, it in twadde fersyk docht oan it hjirboppe spesifisearre adres, wêrnei't it de native bibleteek downloadt. Om de oanfal út te fieren, hawwe wy besletten dizze funksje fan UC Browser te brûken: de mooglikheid om PDF te iepenjen mei in native bibleteek, dy't net yn 'e APK is en dy't it as nedich fan it ynternet downloadt. It is de muoite wurdich op te merken dat, teoretysk, UC Browser kin wurde twongen om wat te downloaden sûnder brûkersynteraksje - as jo in goed foarme antwurd leverje op in fersyk dat wurdt útfierd neidat de browser is lansearre. Mar om dit te dwaan, moatte wy it protokol fan ynteraksje mei de tsjinner yn mear detail studearje, sadat wy besletten dat it makliker wêze soe om it ûnderskepte antwurd te bewurkjen en de bibleteek te ferfangen foar wurkjen mei PDF.

Dus, as in brûker in PDF direkt yn 'e browser wol iepenje, kinne de folgjende oanfragen yn it ferkear sjoen wurde:

Op syk nei kwetsberens yn UC Browser

Earst is der in POST fersyk oan puds.ucweb.com/upgrade/index.xhtml, doe
In argyf mei in bibleteek foar it besjen fan PDF- en kantoarformaten wurdt ynladen. It is logysk om oan te nimmen dat it earste fersyk ynformaasje oer it systeem trochstjoert (op syn minst de arsjitektuer om de fereaske bibleteek te leverjen), en as antwurd dêrop krijt de browser wat ynformaasje oer de bibleteek dy't downloade moat: it adres en, evt. , wat oars. It probleem is dat dit fersyk is fersifere.

Fragmint oanfreegje

Antwurd fragmint

Op syk nei kwetsberens yn UC Browser

Op syk nei kwetsberens yn UC Browser

De bibleteek sels is yn ZIP ferpakt en is net fersifere.

Op syk nei kwetsberens yn UC Browser

Sykje nei ferkearsûntsiferingskoade

Litte wy besykje it tsjinnerantwurd te ûntsiferjen. Litte wy nei de klassekoade sjen com.uc.deployment.UpgradeDeployService: út metoade onStartCommand gean nei com.uc.deployment.bx, en fan it nei 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);
}

Wy sjogge hjir de foarming fan in POST-fersyk. Wy jouwe oandacht oan it meitsjen fan in array fan 16 bytes en har ynfoljen: 0x5F, 0, 0x1F, -50 (=0xCE). Komt oerien mei wat wy seagen yn it fersyk hjirboppe.

Yn deselde klasse kinne jo in nestele klasse sjen dy't in oare nijsgjirrige metoade hat:

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

De metoade nimt in array fan bytes as ynfier en kontrolearret dat de nul byte is 0x60 of de tredde byte is 0xD0, en de twadde byte is 1, 11 of 0x1F. Wy sjogge nei it antwurd fan 'e tsjinner: de nul byte is 0x60, de twadde is 0x1F, de tredde is 0x60. Klinkt as wat wy nedich binne. Te oardieljen nei de rigels ("up_decrypt", bygelyks), moat hjir in metoade neamd wurde dy't it antwurd fan de tsjinner ûntsiferje sil.
Litte wy nei de metoade gean gj. Tink derom dat it earste argumint de byte is by offset 2 (d.w.s. 0x1F yn ús gefal), en it twadde is de serverantwurd sûnder
earste 16 bytes.

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

Fansels selektearje wy hjir in ûntsiferingsalgoritme, en deselde byte dy't yn ús is
gefal gelyk oan 0x1F, jout ien fan trije mooglike opsjes oan.

Wy geane fierder te analysearjen de koade. Nei in pear sprongen fine wy ​​ússels yn in metoade mei in selsferklearjende namme decryptBytesByKey.

Hjir binne twa mear bytes skieden fan ús antwurd, en in tekenrige wurdt krigen fan harren. It is dúdlik dat op dizze manier de kaai foar it ûntsiferjen fan it berjocht selektearre is.

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

Foarútsjen konstatearje wy dat wy op dit stadium noch gjin kaai krije, mar allinich syn "identifikaasje". De kaai krije is wat yngewikkelder.

Yn 'e folgjende metoade wurde noch twa parameters tafoege oan' e besteande, wêrtroch't fjouwer dêrfan binne: it magyske nûmer 16, de kaai-identifikaasje, de fersifere gegevens, en in ûnbegryplike tekenrige (yn ús gefal, leech).

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

Nei in rige oergongen komme wy by de metoade staticBinarySafeDecryptNoB64 ynterface com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. D'r binne gjin klassen yn 'e haadapplikaasjekoade dy't dizze ynterface ymplementearje. Der is sa'n klasse yn it bestân lib/armeabi-v7a/libsgmain.so, dat is net eins in .so, mar in .jar. De metoade wêryn wy ynteressearre binne wurdt as folget ymplementearre:

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

Hjir is ús list fan parameters oanfolle mei noch twa hiele getallen: 2 en 0. Rjochtsjen op
alles, 2 betsjut ûntsiferjen, lykas yn 'e metoade doFinal systeem klasse javax.crypto.Cipher. En dit alles wurdt oerdroegen oan in bepaalde router mei it nûmer 10601 - dit is blykber it kommando nûmer.

Nei de folgjende keatling fan transysjes fine wy ​​in klasse dy't de ynterface ymplementearret IRouterComponent en metoade 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);
}
}

En ek klasse JNICLibrary, wêryn de native metoade wurdt ferklearre doCommandNative:

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

Dit betsjut dat wy in metoade moatte fine yn 'e native koade doCommandNative. En dit is wêr't de wille begjint.

Obfuscation fan masine koade

Yn triem libsgmain.so (dat is eins in .jar en wêryn wy fûnen de ymplemintaasje fan guon fersifering-relatearre ynterfaces krekt boppe) der is ien native bibleteek: libsgmainso-6.4.36.so. Wy iepenje it yn IDA en krije in bosk dialoochfinsters mei flaters. It probleem is dat de seksjekoptabel ûnjildich is. Dit wurdt mei doel dien om de analyze te komplisearjen.

Op syk nei kwetsberens yn UC Browser

Mar it is net nedich: om in ELF-bestân goed te laden en te analysearjen, is in kopteksttafel fan it programma genôch. Dêrom wiskje wy gewoan de seksjetabel, nulearje de oerienkommende fjilden yn 'e koptekst.

Op syk nei kwetsberens yn UC Browser

Iepenje it bestân opnij yn IDA.

D'r binne twa manieren om de Java-firtuele masine te fertellen wêr't krekt yn 'e native bibleteek de ymplemintaasje fan in metoade is ferklearre yn Java-koade as native. De earste is om it in soarte namme te jaan Java_package_name_ClassName_MethodName.

De twadde is om it te registrearjen by it laden fan de bibleteek (yn 'e funksje JNI_OnLoad)
mei help fan in funksje oprop RegisterNatives.

Yn ús gefal, as wy de earste metoade brûke, moat de namme sa wêze: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

D'r is gjin sa'n funksje ûnder de eksporteare funksjes, wat betsjut dat jo moatte sykje nei in oprop RegisterNatives.
Litte wy nei de funksje gean JNI_OnLoad en wy sjogge dizze foto:

Op syk nei kwetsberens yn UC Browser

Wat bart hjir? Op it earste each binne it begjin en ein fan 'e funksje typysk foar ARM-arsjitektuer. De earste ynstruksje op 'e stapel bewarret de ynhâld fan' e registers dy't de funksje sil brûke yn har wurking (yn dit gefal R0, R1 en R2), lykas de ynhâld fan it LR-register, dat it weromadres fan 'e funksje befettet . De lêste ynstruksje herstelt de bewarre registers, en it weromadres wurdt fuortendaliks pleatst yn it PC-register - dus werom fan 'e funksje. Mar as jo sjogge goed, do silst merken dat de foarlêste ynstruksje feroaret it weromkommen adres opslein op 'e steapel. Lit ús berekkenje hoe't it sil wêze nei
koade útfiering. In bepaald adres 1xB0 wurdt laden yn R130, 5 wurdt derfan ôflutsen, dan wurdt it oerdroegen oan R0 en 0x10 wurdt tafoege oan it. It docht bliken 0xB13B. Sa, IDA tinkt dat de lêste ynstruksje is in normale funksje werom, mar yn feite giet it nei it berekkene adres 0xB13B.

It is it wurdich om hjir te ûnthâlden dat ARM-processors twa modi en twa sets ynstruksjes hawwe: ARM en Thumb. It minst signifikante bit fan it adres fertelt de prosessor hokker ynstruksjeset wurdt brûkt. Dat is, it adres is eins 0xB13A, en ien yn 'e minste signifikante bit jout de Thumb-modus oan.

In ferlykbere "adapter" is tafoege oan it begjin fan elke funksje yn dizze bibleteek en
garbage koade. Wy sille net fierder op har yn detail gean - wy ûnthâlde gewoan
dat it echte begjin fan hast alle funksjes wat fierder fuort is.

Om't de koade net eksplisyt nei 0xB13A springt, erkende IDA sels net dat de koade op dizze lokaasje lei. Om deselde reden werkent it it grutste part fan 'e koade yn' e bibleteek net as koade, wat analyse wat dreech makket. Wy fertelle IDA dat dit de koade is, en dit is wat der bart:

Op syk nei kwetsberens yn UC Browser

De tabel begjint dúdlik op 0xB144. Wat is yn sub_494C?

Op syk nei kwetsberens yn UC Browser

By it oproppen fan dizze funksje yn it LR-register krije wy it adres fan 'e earder neamde tabel (0xB144). Yn R0 - yndeks yn dizze tabel. Dat is, de wearde wurdt nommen út 'e tabel, tafoege oan LR en it resultaat is
it adres om nei te gean. Litte wy besykje it te berekkenjen: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Wy geane nei it ûntfongen adres en sjogge letterlik in pear nuttige ynstruksjes en gean opnij nei 0xB140:

Op syk nei kwetsberens yn UC Browser

No sil d'r in oergong wêze by offset mei yndeks 0x20 fan 'e tabel.

Beoardielje troch de grutte fan 'e tafel, sille d'r in protte sokke transysjes yn' e koade wêze. De fraach ûntstiet oft it mooglik is om dit op ien of oare manier mear automatysk om te gean, sûnder de hân fan adressen te berekkenjen. En skripts en de mooglikheid om koade te patchjen yn IDA komme ús te helpen:

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"

Plak de rinnerke op rigel 0xB26A, rinne it skript en sjoch de oergong nei 0xB4B0:

Op syk nei kwetsberens yn UC Browser

IDA wer net werkenne dit gebiet as in koade. Wy helpe har en sjogge dêr in oar ûntwerp:

Op syk nei kwetsberens yn UC Browser

De ynstruksjes nei BLX lykje net folle sin, it is mear as in soarte fan ferpleatsing. Litte wy nei sub_4964 sjen:

Op syk nei kwetsberens yn UC Browser

En yndied wurdt hjir in dword nommen op it adres dat yn LR leit, oan dit adres tafoege, wêrnei't de wearde op it resultearjende adres nommen wurdt en op 'e steapel set wurdt. Ek wurdt 4 tafoege oan LR sadat nei weromkomst fan 'e funksje dizze selde offset wurdt oerslein. Dêrnei nimt it POP {R1} kommando de resultearjende wearde fan 'e stapel. As jo ​​​​sjogge nei wat leit op adres 0xB4BA + 0xEA = 0xB5A4, sille jo wat sjen dat ferlykber is mei in adrestabel:

Op syk nei kwetsberens yn UC Browser

Om dit ûntwerp te patchjen moatte jo twa parameters fan 'e koade krije: de offset en it registernûmer wêryn jo it resultaat wolle pleatse. Foar elk mooglik register moatte jo fan tefoaren in stikje koade tariede.

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"

Wy pleatse de rinnerke oan it begjin fan 'e struktuer dy't wy ferfange wolle - 0xB4B2 - en rinne it skript:

Op syk nei kwetsberens yn UC Browser

Neist de al neamde struktueren befettet de koade ek it folgjende:

Op syk nei kwetsberens yn UC Browser

Lykas yn it foarige gefal, nei de BLX-ynstruksje is d'r in offset:

Op syk nei kwetsberens yn UC Browser

Wy nimme de offset nei it adres fan LR, foegje it ta oan LR en gean dêr. 0x72044 + 0xC = 0x72050. It skript foar dit ûntwerp is frij simpel:

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"

Resultaat fan skriptútfiering:

Op syk nei kwetsberens yn UC Browser

Sadree't alles is patched yn 'e funksje, kinne jo wize IDA nei syn echte begjin. It sil alle funksjekoade gearwurkje, en it kin wurde dekompilearre mei HexRays.

Dekodearjen fan snaren

Wy hawwe leard om te gean mei obfuscation fan masinekoade yn 'e bibleteek libsgmainso-6.4.36.so fan UC Browser en krige de funksjekoade 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;
}

Litte wy de folgjende rigels in tichterby besjen:

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

Yn funksje sub_73E24 de klasse namme wurdt dúdlik ûntsifere. As parameters foar dizze funksje wurdt in oanwizer nei gegevens fergelykber mei fersifere gegevens, in bepaalde buffer en in nûmer trochjûn. Fansels, nei it oproppen fan de funksje, sil d'r in dekodearre rigel yn 'e buffer wêze, om't it wurdt trochjûn nei de funksje FindClass, dy't de klassenamme nimt as de twadde parameter. Dêrom is it nûmer de grutte fan 'e buffer of de lingte fan' e line. Litte wy besykje de klassenamme te ûntsiferjen, it moat ús fertelle oft wy de goeie rjochting geane. Litte wy in tichterby besjen wat der bart yn 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;
}

function sub_7AF78 makket in eksimplaar fan in kontener foar byte-arrays fan 'e oantsjutte grutte (wy sille net yn detail op dizze konteners wenje). Hjir binne twa sokke konteners makke: ien befettet de line "DcO/lcK+h?m3c*q@" (it is maklik te rieden dat dit in kaai is), de oare befettet fersifere gegevens. Dêrnei wurde beide objekten yn in bepaalde struktuer pleatst, dy't trochjûn wurdt oan 'e funksje sub_6115C. Litte wy yn dizze struktuer ek in fjild markearje mei de wearde 3. Lit ús sjen wat der dan mei dizze struktuer bart.

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

De switch parameter is in struktuer fjild dat waard earder tawiisd de wearde 3. Sjoch op gefal 3: nei de funksje sub_6364C parameters wurde trochjûn út de struktuer dy't waarden tafoege dêr yn de foarige funksje, dat is de kaai en fersifere gegevens. As jo ​​sjogge goed nei sub_6364C, kinne jo it RC4-algoritme dêryn werkenne.

Wy hawwe in algoritme en in kaai. Litte wy besykje de namme fan 'e klasse te ûntsiferjen. Hjir is wat der bard is: com/taobao/wireless/security/adapter/JNICLibrary. Grut! Wy binne op it goede spoar.

Kommandobeam

No moatte wy in útdaging fine RegisterNatives, dy't ús sil wize op de funksje doCommandNative. Litte wy sjen nei de funksjes neamd fan JNI_OnLoad, en wy fine it yn 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;
}

En yndie, in lânseigen metoade mei de namme is hjir registrearre doCommandNative. No witte wy syn adres. Litte wy sjen wat hy docht.

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

Troch de namme kinne jo riede dat hjir it yngongspunt is fan alle funksjes dy't de ûntwikkelders besletten hawwe oer te bringen nei de native bibleteek. Wy binne ynteressearre yn funksje nûmer 10601.

Jo kinne út 'e koade sjen dat it kommandonûmer trije nûmers produseart: kommando / 10000, kommando % 10000 / 100 и kommando % 10, dus yn ús gefal, 1, 6 en 1. Dizze trije nûmers, en ek in oanwizer nei JNIEnv en de arguminten trochjûn oan de funksje wurde tafoege oan in struktuer en trochjûn. Mei help fan de trije sifers krigen (litte ús oantsjutte se N1, N2 en N3), in kommando beam boud.

Soksawat:

Op syk nei kwetsberens yn UC Browser

De beam wurdt dynamysk ynfolle JNI_OnLoad.
Trije sifers kodearje it paad yn 'e beam. Elk blêd fan 'e beam befettet it pocked adres fan' e oerienkommende funksje. De kaai is yn de âlder node. It plak fine yn 'e koade wêr't de funksje dy't wy nedich binne tafoege oan' e beam is net dreech as jo alle brûkte struktueren begripe (wy beskriuwe se net om in al frij grut artikel net op te blazen).

Mear obfuscation

Wy krigen it adres fan de funksje dy't ferkear ûntsiferje moat: 0x5F1AC. Mar it is te betiid om bliid te wêzen: de ûntwikkelders fan UC Browser hawwe in oare ferrassing foar ús taret.

Nei it ûntfangen fan de parameters fan 'e array dy't waard foarme yn' e Java-koade, krije wy
nei de funksje op adres 0x4D070. En hjir wachtet ús in oare soarte fan koade-obfuscation.

Wy sette twa yndeksen yn R7 en R4:

Op syk nei kwetsberens yn UC Browser

Wy ferskowe de earste yndeks nei R11:

Op syk nei kwetsberens yn UC Browser

Om in adres fan in tabel te krijen, brûk in yndeks:

Op syk nei kwetsberens yn UC Browser

Nei it gean nei it earste adres, wurdt de twadde yndeks brûkt, dy't yn R4 is. D'r binne 230 eleminten yn 'e tabel.

Wat der oan te dwaan? Jo kinne IDA fertelle dat dit in skeakel is: Bewurkje -> Oare -> Skeakelidioom oantsjutte.

Op syk nei kwetsberens yn UC Browser

De resultearjende koade is ferskriklik. Mar, troch de jungle te gean, kinne jo in oprop fernimme nei in funksje dy't ús al bekend is sub_6115C:

Op syk nei kwetsberens yn UC Browser

D'r wie in skeakel wêryn yn gefal 3 in dekodearring wie mei it RC4-algoritme. En yn dit gefal, de struktuer trochjûn oan de funksje wurdt ynfolle út de parameters trochjûn oan doCommandNative. Lit ús ûnthâlde wat wy dêr hiene magicInt mei de wearde 16. Wy sjogge nei it korrespondearjende gefal - en nei ferskate transysjes fine wy ​​de koade wêrmei it algoritme kin wurde identifisearre.

Op syk nei kwetsberens yn UC Browser

Dit is AES!

It algoritme bestiet, alles wat oerbliuwt is om syn parameters te krijen: modus, kaai en, mooglik, de inisjalisaasjevektor (syn oanwêzigens hinget ôf fan 'e operaasjemodus fan it AES-algoritme). De struktuer mei harren moat wurde foarme earne foar de funksje oprop sub_6115C.

Patch

Om net alle patchkoade yn gearstallingstaal mei de hân te skriuwen, kinne jo Android Studio starte, dêr in funksje skriuwe dy't deselde ynfierparameters ûntfangt as ús ûntsiferfunksje en skriuwt nei in bestân, kopiearje en plakke dan de koade dy't de kompilator sil generearje.

Us freonen fan it UC Browser-team soargen ek foar it gemak fan it tafoegjen fan koade. Lit ús betinke dat oan it begjin fan elke funksje wy hawwe garbage koade dy't kin maklik wurde ferfongen troch in oare. Hiel handich 🙂 Oan it begjin fan 'e doelfunksje is der lykwols net genôch romte foar de koade dy't alle parameters opslaan yn in bestân. Ik moast split it yn dielen en brûk garbage blokken fan oanbuorjende funksjes. Yn totaal wiene der fjouwer dielen.

Foarste diel:

Op syk nei kwetsberens yn UC Browser

Yn 'e ARM-arsjitektuer wurde de earste fjouwer funksjeparameters troch de registers R0-R3 trochjûn, de rest, as der binne, wurde troch de stapel trochjûn. It LR-register draacht it weromadres. Dit alles moat wurde bewarre sadat de funksje kin wurkje nei't wy syn parameters dumpe. Wy moatte ek alle registers bewarje dy't wy yn it proses sille brûke, dus dogge wy PUSH.W {R0-R10,LR}. Yn R7 krije wy it adres fan 'e list fan parameters dy't trochjûn binne nei de funksje fia de stapel.

Mei help fan de funksje fopen litte wy it bestân iepenje /data/local/tmp/aes yn "ab" modus
dus foar tafoeging. Yn R0 laden wy it adres fan 'e triemnamme, yn R1 - it adres fan' e line dy't de modus oanjout. En hjir einiget de jiskefetkoade, dus geane wy ​​nei de folgjende funksje. Om it fierder te wurkjen, sette wy oan it begjin de oergong nei de echte koade fan 'e funksje, it omgean fan it jiskefet, en ynstee fan it jiskefet foegje wy in fuortsetting fan' e patch ta.

Op syk nei kwetsberens yn UC Browser

Calling fopen.

De earste trije parameters fan de funksje AES hawwe type int. Om't wy de registers oan it begjin opslein hawwe yn 'e stapel, kinne wy ​​​​de funksje gewoan trochjaan fwrit harren adressen op 'e steapel.

Op syk nei kwetsberens yn UC Browser

Folgjende hawwe wy trije struktueren dy't befetsje de gegevens grutte en in oanwizer nei de gegevens foar de kaai, inisjalisaasje vector en fersifere gegevens.

Op syk nei kwetsberens yn UC Browser

Slút it bestân oan 'e ein, restaurearje de registers en oerdrage kontrôle nei de echte funksje AES.

Wy sammelje in APK mei in patched bibleteek, tekenje it, upload it nei it apparaat / emulator, en starte it. Wy sjogge dat ús dump oanmakke wurdt, en dêr wurde in protte gegevens skreaun. De browser brûkt fersifering net allinnich foar ferkear, en alle fersifering giet troch de funksje yn kwestje. Mar om ien of oare reden binne de nedige gegevens der net, en it fereaske fersyk is net sichtber yn it ferkear. Om net te wachtsjen oant de UC-blêder ferachtet om it nedige fersyk te meitsjen, litte wy it fersifere antwurd nimme fan 'e earder ûntfongen tsjinner en de applikaasje opnij patchje: foegje de ûntsifering ta oan onCreate fan 'e haadaktiviteit.

    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

Wy sammelje, tekenje, ynstallearje, lansearje. Wy ûntfange in NullPointerException omdat de metoade nul weromjûn.

By fierdere analyze fan de koade waard in funksje ûntdutsen dy't nijsgjirrige rigels ûntsiferet: "META-INF/" en ".RSA". It liket derop dat de applikaasje har sertifikaat ferifiearret. Of genereart der sels kaaien fan. Ik wol net echt omgean mei wat der bart mei it sertifikaat, dus wy sille it gewoan it juste sertifikaat slipje. Litte wy de fersifere rigel patchje, sadat wy ynstee fan "META-INF/" "BLABLINF/" krije, in map meitsje mei dy namme yn 'e APK en dêr it iikhoarnblêder-sertifikaat taheakje.

Wy sammelje, tekenje, ynstallearje, lansearje. Bingo! Wy hawwe de kaai!

MitM

Wy krigen in kaai en in initialisaasjevektor lyk oan de kaai. Litte wy besykje it tsjinnerantwurd te ûntsiferjen yn CBC-modus.

Op syk nei kwetsberens yn UC Browser

Wy sjogge de argyf-URL, wat fergelykber mei MD5, "extract_unzipsize" en in nûmer. Wy kontrolearje: de MD5 fan it argyf is itselde, de grutte fan 'e útpakte bibleteek is itselde. Wy besykje dizze bibleteek te patchjen en it oan de browser te jaan. Om sjen te litten dat ús patched bibleteek is laden, sille wy in yntinsje lansearje om in SMS te meitsjen mei de tekst "PWNED!" Wy sille twa antwurden fan 'e tsjinner ferfange: puds.ucweb.com/upgrade/index.xhtml en om it argyf te downloaden. Yn 'e earste ferfange wy MD5 (de grutte feroaret net nei it útpakken), yn' e twadde jouwe wy it argyf mei de patched bibleteek.

De browser besiket it argyf ferskate kearen te downloaden, wêrnei't it in flater jout. Blykber wat
hy hâldt net fan. As gefolch fan it analysearjen fan dit tsjustere formaat, die bliken dat de tsjinner ek de grutte fan it argyf stjoert:

Op syk nei kwetsberens yn UC Browser

It is kodearre yn LEB128. Nei de patch feroare de grutte fan it argyf mei de bibleteek in bytsje, sadat de browser tocht dat it argyf krom ynladen wie, en nei ferskate besykjen smiet it in flater.

Wy oanpasse de grutte fan it argyf ... En - oerwinning! 🙂 It resultaat is yn 'e fideo.

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

Gefolgen en ûntwikkeldersreaksje

Op deselde manier kinne hackers de ûnfeilige funksje fan UC Browser brûke om kweade bibleteken te fersprieden en út te fieren. Dizze bibleteken sille wurkje yn 'e kontekst fan' e browser, sadat se al har systeemmooglikheden krije. As resultaat is de mooglikheid om phishing-finsters wer te jaan, lykas tagong ta de wurkbestannen fan 'e oranje Sineeske iikhoarntsje, ynklusyf logins, wachtwurden en koekjes opslein yn 'e databank.

Wy kontakten de ûntwikkelders fan UC Browser en ynformeare har oer it probleem dat wy fûnen, besochten de kwetsberens en it gefaar te wizen, mar se hawwe neat mei ús besprutsen. Underwilens bleau de browser syn gefaarlike funksje yn it gewoane sicht pronkje. Mar as wy ienris de details fan 'e kwetsberens iepenbiere hawwe, wie it net mear mooglik om it te negearjen lykas earder. 27 maart wie
in nije ferzje fan UC Browser 12.10.9.1193 waard frijlitten, dy't tagong ta de tsjinner fia HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Derneist, nei de "fix" en oant de tiid fan it skriuwen fan dit artikel, besykjen om in PDF yn in browser te iepenjen resultearre yn in flaterberjocht mei de tekst "Oeps, der gie wat mis!" In fersyk oan de tsjinner waard net dien by it besykjen om in PDF te iepenjen, mar in fersyk waard makke doe't de browser waard lansearre, dy't hints op 'e trochgeande mooglikheid om útfierbere koade te downloaden yn striid mei Google Play-regels.

Boarne: www.habr.com

Add a comment