Ieškote spragų UC naršyklėje

Ieškote spragų UC naršyklėje

įvedimas

Kovo pabaigoje mes pranešė, kad jie atrado paslėptą galimybę įkelti ir paleisti nepatvirtintą kodą UC naršyklėje. Šiandien mes išsamiai apžvelgsime, kaip šis atsisiuntimas vyksta ir kaip įsilaužėliai gali jį panaudoti savo tikslams.

Prieš kurį laiką „UC Browser“ buvo reklamuojama ir platinama labai agresyviai: ji buvo įdiegta vartotojų įrenginiuose naudojant kenkėjiškas programas, platinamą iš įvairių svetainių, prisidengiant vaizdo failais (t. y. vartotojai manė, kad atsisiunčia, pavyzdžiui, pornografinį vaizdo įrašą, bet vietoj to gavo APK su šia naršykle), naudojo bauginančias reklamjuostes su pranešimais, kad naršyklė pasenusi, pažeidžiama ir panašiai. Oficialioje UC naršyklės grupėje VK yra tema, kuriame vartotojai gali skųstis dėl nesąžiningos reklamos, yra daug pavyzdžių. 2016 metais buvo net vaizdo reklama rusų kalba (taip, reklamą blokuojančiai naršyklei).

Rašymo metu „UC Browser“ sistemoje „Google Play“ įdiegta daugiau nei 500 000 000. Tai įspūdinga – daugiau turi tik „Google Chrome“. Tarp apžvalgų galite pamatyti gana daug skundų dėl reklamos ir peradresavimų į kai kurias programas „Google Play“. Tai buvo mūsų tyrimo priežastis: nusprendėme pažiūrėti, ar UC naršyklė nedaro ką nors blogo. Ir paaiškėjo, kad jis tai daro!

Programos kode buvo aptikta galimybė atsisiųsti ir paleisti vykdomąjį kodą, o tai prieštarauja paraiškų skelbimo taisyklėms „Google Play“. Be vykdomojo kodo atsisiuntimo, UC naršyklė tai daro nesaugiai, o tai gali būti naudojama MitM atakai pradėti. Pažiūrėkime, ar galime surengti tokią ataką.

Viskas, kas parašyta toliau, tinka UC naršyklės versijai, kuri tyrimo metu buvo pasiekiama sistemoje „Google Play“.

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

Atakos vektorius

UC naršyklės apraše galite rasti paslaugą su savaime suprantamu pavadinimu com.uc.deployment.UpgradeDeployService.

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

Kai ši paslauga paleidžiama, naršyklė pateikia POST užklausą puds.ucweb.com/upgrade/index.xhtml, kurį galima pamatyti eisme praėjus šiek tiek laiko po starto. Atsakydamas jis gali gauti komandą atsisiųsti naujinimą arba naują modulį. Analizės metu serveris tokių komandų nedavė, tačiau pastebėjome, kad kai bandome atidaryti PDF naršyklėje, jis pateikia antrą užklausą aukščiau nurodytu adresu, po to atsisiunčia vietinę biblioteką. Norėdami įvykdyti ataką, nusprendėme naudoti šią „UC Browser“ funkciją: galimybę atidaryti PDF naudojant savąją biblioteką, kurios nėra APK ir kurią, jei reikia, ji atsisiunčia iš interneto. Verta paminėti, kad teoriškai UC naršyklė gali būti priversta ką nors atsisiųsti be vartotojo sąveikos – jei pateikiate gerai suformuotą atsakymą į užklausą, kuri vykdoma paleidus naršyklę. Tačiau norėdami tai padaryti, turime išsamiau išnagrinėti sąveikos su serveriu protokolą, todėl nusprendėme, kad būtų lengviau redaguoti perimtą atsakymą ir pakeisti biblioteką darbui su PDF.

Taigi, kai vartotojas nori atidaryti PDF tiesiogiai naršyklėje, sraute gali būti matomos šios užklausos:

Ieškote spragų UC naršyklėje

Pirmiausia yra POST užklausa puds.ucweb.com/upgrade/index.xhtmltada
Atsisiunčiamas archyvas su biblioteka PDF ir biuro formatams peržiūrėti. Logiška manyti, kad pirma užklausa perduoda informaciją apie sistemą (bent jau architektūrą, kad būtų galima pateikti reikiamą biblioteką), o reaguodama į ją naršyklė gauna tam tikrą informaciją apie biblioteką, kurią reikia atsisiųsti: adresą ir galbūt , kažkas kito. Problema ta, kad ši užklausa yra užšifruota.

Prašymo fragmentas

Atsakymo fragmentas

Ieškote spragų UC naršyklėje

Ieškote spragų UC naršyklėje

Pati biblioteka yra supakuota ZIP formatu ir nėra užšifruota.

Ieškote spragų UC naršyklėje

Ieškokite srauto iššifravimo kodo

Pabandykime iššifruoti serverio atsakymą. Pažiūrėkime į klasės kodą com.uc.deployment.UpgradeDeployService: iš metodo onStartCommand eiti į com.uc.deployment.bx, o nuo jo iki 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);
}

Čia matome POST užklausos formavimąsi. Atkreipiame dėmesį į 16 baitų masyvo sukūrimą ir jo užpildymą: 0x5F, 0, 0x1F, -50 (=0xCE). Sutampa su tuo, ką matėme aukščiau pateiktame prašyme.

Toje pačioje klasėje galite pamatyti įdėtą klasę, kuri turi kitą įdomų metodą:

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

Metodas naudoja baitų masyvą kaip įvestį ir patikrina, ar nulinis baitas yra 0x60 arba trečiasis baitas yra 0xD0, o antrasis baitas yra 1, 11 arba 0x1F. Mes žiūrime į atsakymą iš serverio: nulinis baitas yra 0x60, antrasis yra 0x1F, trečias yra 0x60. Skamba taip, kaip mums reikia. Sprendžiant iš eilučių (pvz., „up_decrypt“), čia turėtų būti iškviestas metodas, kuris iššifruos serverio atsakymą.
Pereikime prie metodo gj. Atminkite, kad pirmasis argumentas yra baitas esant 2 poslinkiui (t. y. 0x1F mūsų atveju), o antrasis yra serverio atsakymas be
pirmieji 16 baitų.

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

Akivaizdu, kad čia mes pasirenkame iššifravimo algoritmą ir tą patį baitą, kuris yra mūsų
0x1F, žymi vieną iš trijų galimų parinkčių.

Mes ir toliau analizuojame kodą. Po poros šuolių atsiduriame metode su savaime suprantamu pavadinimu decryptBytesByKey.

Čia nuo mūsų atsakymo atskiriami dar du baitai ir iš jų gaunama eilutė. Aišku, kad tokiu būdu pasirenkamas pranešimo iššifravimo raktas.

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

Žvelgdami į ateitį pastebime, kad šiame etape dar negauname rakto, o tik jo „identifikatorių“. Rakto gavimas yra šiek tiek sudėtingesnis.

Kitu būdu prie esamų pridedami dar du parametrai, iš kurių susidaro keturi: magiškas skaičius 16, rakto identifikatorius, užšifruoti duomenys ir nesuprantama eilutė (mūsų atveju tuščia).

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

Po kelių perėjimų pasiekiame metodą staticBinarySafeDecryptNoB64 sąsaja com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Pagrindiniame programos kode nėra klasių, įgyvendinančių šią sąsają. Byloje yra tokia klasė lib/armeabi-v7a/libsgmain.so, kuris iš tikrųjų yra ne .so, o .jar. Mus dominantis metodas įgyvendinamas taip:

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

Čia mūsų parametrų sąrašas papildytas dar dviem sveikaisiais skaičiais: 2 ir 0. Sprendžiant iš
viskas, 2 reiškia iššifravimą, kaip ir metodu doFinal sistemos klasė javax.crypto.Cipher. Ir visa tai perkeliama į tam tikrą maršrutizatorių su numeriu 10601 – matyt, tai yra komandos numeris.

Po kitos perėjimų grandinės randame klasę, kuri įgyvendina sąsają IRouterComponent ir metodas 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);
}
}

Ir taip pat klasė JNIC biblioteka, kuriame deklaruojamas vietinis metodas doCommandNative:

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

Tai reiškia, kad turime rasti metodą vietiniame kode doCommandNative. Ir čia prasideda linksmybės.

Mašininio kodo sumaišymas

Byloje libsgmain.so (kuri iš tikrųjų yra .jar ir kurioje mes nustatėme, kad yra įdiegtos kai kurios su šifravimu susijusios sąsajos aukščiau) yra viena savoji biblioteka: libsgmainso-6.4.36.so. Atidarome jį IDA ir gauname daugybę dialogo langų su klaidomis. Problema ta, kad skyriaus antraštės lentelė neteisinga. Tai daroma siekiant apsunkinti analizę.

Ieškote spragų UC naršyklėje

Bet to nereikia: norint teisingai įkelti ELF failą ir jį išanalizuoti, pakanka programos antraštės lentelės. Todėl mes tiesiog ištriname skyrių lentelę, panaikindami atitinkamus antraštės laukus.

Ieškote spragų UC naršyklėje

Dar kartą atidarykite failą IDA.

Yra du būdai, kaip nurodyti „Java“ virtualiajai mašinai, kur tiksliai vietinėje bibliotekoje yra „Java“ kode kaip savuoju deklaruoto metodo įgyvendinimas. Pirmasis yra suteikti jai rūšies pavadinimą Java_package_name_ClassName_MethodName.

Antrasis yra užregistruoti jį įkeliant biblioteką (funkcijoje JNI_OnLoad)
naudojant funkcijos skambutį Registruotis Vietiniai.

Mūsų atveju, jei naudojame pirmąjį metodą, pavadinimas turėtų būti toks: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Tarp eksportuotų funkcijų tokios funkcijos nėra, vadinasi, reikia ieškoti skambučio Registruotis Vietiniai.
Pereikime prie funkcijos JNI_OnLoad ir mes matome šį paveikslėlį:

Ieškote spragų UC naršyklėje

Kas čia vyksta? Iš pirmo žvilgsnio funkcijos pradžia ir pabaiga būdinga ARM architektūrai. Pirmoje dėklo instrukcijoje saugomas registrų, kuriuos funkcija naudos veikdama (šiuo atveju R0, R1 ir R2), turinys, taip pat LR registro, kuriame yra funkcijos grąžinimo adresas, turinys. . Paskutinė instrukcija atkuria išsaugotus registrus, o grįžimo adresas iš karto patalpinamas į PC registrą – taip grįžtama iš funkcijos. Bet jei atidžiai pažiūrėsite, pastebėsite, kad priešpaskutinė instrukcija pakeičia grąžos adresą, saugomą krūvoje. Paskaičiuokime, kaip bus po to
kodo vykdymas. Tam tikras adresas 1xB0 įkeliamas į R130, iš jo atimamas 5, tada perkeliamas į R0 ir prie jo pridedamas 0x10. Pasirodo, 0xB13B. Taigi IDA mano, kad paskutinė instrukcija yra normalios funkcijos grąžinimas, tačiau iš tikrųjų ji peršoka į apskaičiuotą adresą 0xB13B.

Čia verta priminti, kad ARM procesoriai turi du režimus ir du instrukcijų rinkinius: ARM ir Thumb. Mažiausiai reikšmingas adreso bitas nurodo procesoriui, kuris komandų rinkinys yra naudojamas. Tai reiškia, kad adresas iš tikrųjų yra 0xB13A, o vienas mažiausiai reikšmingas bitas rodo nykščio režimą.

Panašus „adapteris“ buvo pridėtas prie kiekvienos šios bibliotekos funkcijos pradžios ir
šiukšlių kodas. Detaliau apie juos nesigilinsime – tiesiog prisiminsime
kad tikroji beveik visų funkcijų pradžia yra kiek toliau.

Kadangi kodas aiškiai neperšoka į 0xB13A, pati IDA neatpažino, kad kodas buvo šioje vietoje. Dėl tos pačios priežasties ji neatpažįsta daugumos bibliotekoje esančio kodo kaip kodo, o tai apsunkina analizę. Mes sakome IDA, kad tai yra kodas, ir tai atsitinka:

Ieškote spragų UC naršyklėje

Lentelė aiškiai prasideda 0xB144. Kas yra sub_494C?

Ieškote spragų UC naršyklėje

Iškviečiant šią funkciją LR registre gauname anksčiau minėtos lentelės adresą (0xB144). In R0 – indeksas šioje lentelėje. Tai yra, reikšmė paimama iš lentelės, pridedama prie LR ir gaunamas rezultatas
adresą, kuriuo reikia eiti. Pabandykime jį apskaičiuoti: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Einame gautu adresu ir pažodžiui matome keletą naudingų instrukcijų ir vėl einame į 0xB140:

Ieškote spragų UC naršyklėje

Dabar bus perėjimas prie poslinkio su indeksu 0x20 iš lentelės.

Sprendžiant iš lentelės dydžio, tokių perėjimų kode bus daug. Kyla klausimas, ar galima kažkaip su tuo susitvarkyti automatiškiau, rankiniu būdu neskaičiuojant adresų. Ir scenarijai bei galimybė pataisyti kodą IDA padeda mums:

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"

Perkelkite žymeklį į eilutę 0xB26A, paleiskite scenarijų ir pamatysite perėjimą prie 0xB4B0:

Ieškote spragų UC naršyklėje

IDA vėl neatpažino šios srities kaip kodo. Mes jai padedame ir matome kitą dizainą:

Ieškote spragų UC naršyklėje

Instrukcijos po BLX lyg ir neturi didelės prasmės, tai labiau panašu į kažkokį poslinkį. Pažiūrėkime į sub_4964:

Ieškote spragų UC naršyklėje

Ir tikrai, čia LR esančiu adresu paimamas dword, pridedamas prie šio adreso, po kurio paimama gauto adreso reikšmė ir dedama į krūvą. Taip pat prie LR pridedamas 4, kad grįžus iš funkcijos tas pats poslinkis būtų praleistas. Po to komanda POP {R1} paima gautą reikšmę iš kamino. Jei pažvelgsite į tai, kas yra adresu 0xB4BA + 0xEA = 0xB5A4, pamatysite kažką panašaus į adresų lentelę:

Ieškote spragų UC naršyklėje

Norėdami pataisyti šį dizainą, iš kodo turėsite gauti du parametrus: poslinkį ir registro numerį, kuriame norite įrašyti rezultatą. Kiekvienam galimam registrui turėsite iš anksto paruošti kodo fragmentą.

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"

Mes pastatome žymeklį į norimos pakeisti struktūros pradžią - 0xB4B2 - ir paleidžiame scenarijų:

Ieškote spragų UC naršyklėje

Be jau minėtų struktūrų, kode taip pat yra:

Ieškote spragų UC naršyklėje

Kaip ir ankstesniu atveju, po BLX instrukcijos yra poslinkis:

Ieškote spragų UC naršyklėje

Paimame užskaitą iki adreso iš LR, pridedame prie LR ir einame ten. 0x72044 + 0xC = 0x72050. Šio dizaino scenarijus yra gana paprastas:

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"

Scenarijaus vykdymo rezultatas:

Ieškote spragų UC naršyklėje

Kai viskas bus pataisyta funkcijoje, galite nukreipti IDA į tikrąją jos pradžią. Jis sujungs visą funkcijos kodą ir gali būti dekompiliuotas naudojant HexRays.

Stygų dekodavimas

Išmokome susidoroti su mašininio kodo užtemdymu bibliotekoje libsgmainso-6.4.36.so iš UC Browser ir gavo funkcijos kodą 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;
}

Pažvelkime atidžiau į šias eilutes:

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

Funkcijoje sub_73E24 klasės pavadinimas aiškiai iššifruojamas. Kaip šios funkcijos parametrai perduodami rodyklė į duomenis, panašius į užšifruotus duomenis, tam tikras buferis ir skaičius. Akivaizdu, kad iškvietus funkciją buferyje bus iššifruota eilutė, nes ji perduodama funkcijai FindClass, kuris naudoja klasės pavadinimą kaip antrąjį parametrą. Todėl skaičius yra buferio dydis arba linijos ilgis. Pabandykime iššifruoti klasės pavadinimą, jis turėtų mums pasakyti, ar einame teisinga kryptimi. Pažiūrėkime atidžiau, kas vyksta 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;
}

Funkcija sub_7AF78 sukuria konteinerio egzempliorių nurodyto dydžio baitų masyvams (detaliau ties šiais konteineriais neapsigyvensime). Čia sukuriami du tokie konteineriai: viename yra eilutė „DcO/lcK+h?m3c*q@“ (nesunku atspėti, kad tai yra raktas), kitame yra užšifruoti duomenys. Tada abu objektai dedami į tam tikrą struktūrą, kuri perduodama funkcijai sub_6115C. Taip pat šioje struktūroje pažymėkime lauką su reikšme 3. Pažiūrėkime, kas toliau nutiks šiai struktūrai.

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

Jungiklio parametras yra struktūros laukas, kuriam anksčiau buvo priskirta reikšmė 3. Pažvelkite į 3 atvejį: funkcijai sub_6364C parametrai perduodami iš struktūros, kuri buvo pridėta ankstesnėje funkcijoje, t. y. raktas ir užšifruoti duomenys. Jei atidžiai pažvelgsite į sub_6364C, jame galite atpažinti RC4 algoritmą.

Mes turime algoritmą ir raktą. Pabandykime iššifruoti klasės pavadinimą. Štai kas atsitiko: com/taobao/wireless/security/adapter/JNICLibrary. Puiku! Mes einame teisingu keliu.

Komandų medis

Dabar turime rasti iššūkį Registruotis Vietiniai, kuri nukreips mus į funkciją doCommandNative. Pažvelkime į funkcijas, iškviestas iš JNI_OnLoad, ir mes jį randame 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;
}

Ir iš tikrųjų čia yra užregistruotas vietinis metodas su pavadinimu doCommandNative. Dabar žinome jo adresą. Pažiūrėkime, ką jis daro.

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

Pagal pavadinimą galite atspėti, kad čia yra visų funkcijų, kurias kūrėjai nusprendė perkelti į gimtąją biblioteką, įvesties taškas. Mus domina funkcijos numeris 10601.

Iš kodo matote, kad komandos numeris sukuria tris skaičius: komanda/10000, komanda % 10000/100 и komanda % 10, t.y., mūsų atveju, 1, 6 ir 1. Šie trys skaičiai, taip pat rodyklė į JNIEnv o funkcijai perduoti argumentai pridedami prie struktūros ir perduodami. Naudojant gautus tris skaičius (pažymime juos N1, N2 ir N3), sudaromas komandų medis.

Kažkas panašaus į tai:

Ieškote spragų UC naršyklėje

Medis pildomas dinamiškai JNI_OnLoad.
Trys skaičiai užkoduoja kelią medyje. Kiekviename medžio lape yra atitinkamos funkcijos adresas. Raktas yra pirminiame mazge. Kode rasti vietą, kur prie medžio pridedama mums reikalinga funkcija, nėra sunku, jei suprantate visas naudojamas struktūras (jų neaprašome, kad neišpūstų ir taip didelio straipsnio).

Daugiau sumaišties

Gavome funkcijos, kuri turėtų iššifruoti srautą, adresą: 0x5F1AC. Tačiau džiaugtis dar anksti: UC Browser kūrėjai mums paruošė dar vieną staigmeną.

Gavę parametrus iš masyvo, kuris buvo suformuotas Java kode, gauname
į funkciją adresu 0x4D070. Ir čia mūsų laukia kitoks kodo užmaskavimas.

Į R7 ir R4 įdedame du indeksus:

Ieškote spragų UC naršyklėje

Pirmąjį indeksą perkeliame į R11:

Ieškote spragų UC naršyklėje

Norėdami gauti adresą iš lentelės, naudokite indeksą:

Ieškote spragų UC naršyklėje

Nuėjus į pirmąjį adresą, naudojamas antrasis indeksas, kuris yra R4. Lentelėje yra 230 elementų.

Ką su tuo daryti? Galite pasakyti IDA, kad tai yra jungiklis: Redaguoti -> Kita -> Nurodykite jungiklio idiomą.

Ieškote spragų UC naršyklėje

Gautas kodas yra baisus. Tačiau eidami per jo džiungles galite pastebėti mums jau žinomos funkcijos iškvietimą sub_6115C:

Ieškote spragų UC naršyklėje

Buvo jungiklis, kuriame 3 atveju buvo iššifruotas naudojant RC4 algoritmą. Ir šiuo atveju funkcijai perduota struktūra užpildoma iš parametrų, perduodamų į doCommandNative. Prisiminkime, ką ten turėjome magicInt su reikšme 16. Pažiūrime atitinkamą atvejį – ir po kelių perėjimų randame kodą, pagal kurį galima identifikuoti algoritmą.

Ieškote spragų UC naršyklėje

Tai AES!

Algoritmas egzistuoja, belieka gauti jo parametrus: režimą, raktą ir, galbūt, inicijavimo vektorių (jo buvimas priklauso nuo AES algoritmo darbo režimo). Struktūra su jais turi būti suformuota kažkur prieš funkcijos iškvietimą sub_6115C, tačiau ši kodo dalis yra ypač gerai užmaskuota, todėl kyla mintis kodą pataisyti taip, kad visi iššifravimo funkcijos parametrai būtų išmesti į failą.

Pataisa

Kad nerašytumėte viso pataisos kodo asamblėjos kalba rankiniu būdu, galite paleisti „Android Studio“, ten įrašyti funkciją, kuri gauna tuos pačius įvesties parametrus kaip ir mūsų iššifravimo funkcija, ir įrašo į failą, tada nukopijuokite ir įklijuokite kodą, kurį kompiliatorius sukurs. generuoti.

Mūsų draugai iš UC Browser komandos taip pat pasirūpino kodo pridėjimo patogumu. Prisiminkime, kad kiekvienos funkcijos pradžioje turime šiukšlių kodą, kurį galima lengvai pakeisti bet kuriuo kitu. Labai patogu 🙂 Tačiau tikslinės funkcijos pradžioje neužtenka vietos kodui, kuris išsaugo visus parametrus į failą. Turėjau jį padalinti į dalis ir naudoti šiukšlių blokus iš gretimų funkcijų. Iš viso buvo keturios dalys.

Pirmoji dalis:

Ieškote spragų UC naršyklėje

ARM architektūroje pirmieji keturi funkcijų parametrai perduodami per registrus R0-R3, likusieji, jei tokių yra, perduodami per krūvą. LR registre yra grąžinimo adresas. Visa tai reikia išsaugoti, kad funkcija veiktų po to, kai pašalinsime jos parametrus. Taip pat turime išsaugoti visus registrus, kuriuos naudosime procese, todėl darome PUSH.W {R0-R10,LR}. R7 gauname parametrų sąrašo adresą, perduodamą funkcijai per kaminą.

Naudojant funkciją fopenas atidarykime failą /data/local/tmp/aes „ab“ režimu
y. papildymui. R0 įkeliame failo pavadinimo adresą, R1 - režimą nurodančios eilutės adresą. Ir čia šiukšlių kodas baigiasi, todėl pereiname prie kitos funkcijos. Kad ji veiktų ir toliau, pradžioje įdedame perėjimą prie tikrojo funkcijos kodo, apeinant šiukšles, o vietoj šiukšlių pridedame pataisos tęsinį.

Ieškote spragų UC naršyklėje

Skambina fopenas.

Pirmieji trys funkcijos parametrai AES turėti tipą int. Kadangi pradžioje registrus įrašėme į krūvą, funkciją galime tiesiog perduoti fwrite jų adresus ant krūvos.

Ieškote spragų UC naršyklėje

Toliau turime tris struktūras, kuriose yra duomenų dydis ir rodyklė į rakto duomenis, inicijavimo vektorių ir užšifruotus duomenis.

Ieškote spragų UC naršyklėje

Pabaigoje uždarykite failą, atkurkite registrus ir perkelkite valdymą į tikrąją funkciją AES.

Surenkame APK su pataisyta biblioteka, pasirašome, įkeliame į įrenginį/emuliatorių ir paleidžiame. Matome, kad yra kuriamas mūsų sąvartynas, ten įrašoma daug duomenų. Naršyklė šifravimą naudoja ne tik srautui, o visas šifravimas vyksta per atitinkamą funkciją. Bet kažkodėl reikalingų duomenų nėra, o reikiamos užklausos sraute nematyti. Kad nelauktume, kol UC naršyklė pradės pateikti reikiamą užklausą, paimkime anksčiau gautą šifruotą atsakymą iš serverio ir dar kartą pataisykime programą: pridėkite iššifravimą prie pagrindinės veiklos onCreate.

    const/16 v1, 0x62
new-array v1, v1, [B
fill-array-data v1, :encrypted_data
const/16 v0, 0x1f
invoke-static {v0, v1}, Lcom/uc/browser/core/d/c/g;->j(I[B)[B
move-result-object v1
array-length v2, v1
invoke-static {v2}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v2
const-string v0, "ololo"
invoke-static {v0, v2}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

Surenkame, pasirašome, montuojame, paleidžiame. Gauname NullPointerException, nes metodas grąžino nulį.

Tolesnės kodo analizės metu buvo aptikta funkcija, kuri iššifruoja įdomias eilutes: „META-INF/“ ir „.RSA“. Panašu, kad programa tikrina savo sertifikatą. Arba net generuoja iš jo raktus. Aš tikrai nenoriu nagrinėti to, kas vyksta su sertifikatu, todėl mes tiesiog numesime teisingą pažymėjimą. Pataisykime šifruotą eilutę taip, kad vietoj "META-INF/" gautume "BLABLINF/", sukurkite aplanką tokiu pavadinimu APK ir pridėkite ten voverės naršyklės sertifikatą.

Surenkame, pasirašome, montuojame, paleidžiame. Bingo! Mes turime raktą!

MitM

Gavome raktą ir inicijavimo vektorių, lygų raktui. Pabandykime iššifruoti serverio atsakymą CBC režimu.

Ieškote spragų UC naršyklėje

Matome archyvo URL, kažką panašaus į MD5, „extract_unzipsize“ ir skaičių. Tikriname: archyvo MD5 toks pat, išpakuotos bibliotekos dydis toks pat. Bandome pataisyti šią biblioteką ir perduoti ją naršyklei. Norėdami parodyti, kad mūsų pataisyta biblioteka įkelta, paleisime ketinimą sukurti SMS žinutę su tekstu „PWNED! Mes pakeisime du atsakymus iš serverio: puds.ucweb.com/upgrade/index.xhtml ir atsisiųsti archyvą. Pirmajame pakeičiame MD5 (išpakavus dydis nesikeičia), antruoju atiduodame archyvą su pataisyta biblioteka.

Naršyklė kelis kartus bando atsisiųsti archyvą, po to pateikia klaidą. Matyt, kažkas
jam nepatinka. Išanalizavus šį miglotą formatą, paaiškėjo, kad serveris taip pat perduoda archyvo dydį:

Ieškote spragų UC naršyklėje

Jis užkoduotas LEB128. Po pataisymo šiek tiek pasikeitė archyvo su biblioteka dydis, todėl naršyklė įvertino, kad archyvas atsisiųstas kreivai, ir po kelių bandymų išmetė klaidą.

Koreguojame archyvo dydį... Ir – pergalė! 🙂 Rezultatas – vaizdo įraše.

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

Pasekmės ir kūrėjo reakcija

Taip pat įsilaužėliai gali naudoti nesaugią UC naršyklės funkciją, kad platintų ir paleistų kenkėjiškas bibliotekas. Šios bibliotekos veiks naršyklės kontekste, todėl gaus visus jos sistemos leidimus. Dėl to galimybė rodyti sukčiavimo langus, taip pat prieiga prie oranžinės kiniškos voverės darbo failų, įskaitant prisijungimus, slaptažodžius ir duomenų bazėje saugomus slapukus.

Susisiekėme su UC Browser kūrėjais ir informavome apie rastą problemą, bandėme nurodyti pažeidžiamumą ir jo pavojų, tačiau jie su mumis nieko nekalbėjo. Tuo tarpu naršyklė ir toliau puikavosi savo pavojinga funkcija. Tačiau kai atskleidėme pažeidžiamumo detales, nebebuvo įmanoma jo ignoruoti kaip anksčiau. Buvo kovo 27 d
buvo išleista nauja UC Browser 12.10.9.1193 versija, kuri pasiekė serverį per HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Be to, po „pataisymo“ ir iki šio straipsnio parašymo, bandant atidaryti PDF failą naršyklėje, buvo rodomas klaidos pranešimas su tekstu „Oi, kažkas nutiko! Prašymas serveriui nebuvo pateiktas bandant atidaryti PDF, bet užklausa buvo pateikta paleidus naršyklę, o tai rodo, kad ir toliau galima atsisiųsti vykdomąjį kodą pažeidžiant „Google Play“ taisykles.

Šaltinis: www.habr.com

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