Otsime UC Browser'is haavatavusi.

Otsime UC Browser'is haavatavusi.

Sissejuhatus

MĂ€rtsi lĂ”puks olime me teatanud, et oleme avastanud varjatud vĂ”imaluse laadida ja kĂ€ivitada kontrollimata koodi UC Browseris. TĂ€na vaatame ĂŒksikasjalikult, kuidas see laadimine toimub ja kuidas hĂ€kkerid seda enda huvides kasutada saavad.

MĂ”nda aega tagasi reklaamiti ja levitati UC Browserit vĂ€ga agressiivselt: seda installeeriti kasutajate seadmetesse pahavara kaudu, jagati erinevatelt veebisaitidelt videofailidena (st kasutajad arvasid, et laadivad nĂ€iteks pornovideot, kuid said selle asemel APK selle brauseriga), kasutati hirmuĂ€ratavaid bĂ€nnerid, mis teatasid, et brauser on vananenud, haavatav ja midagi sellist. UC Browseri ametlikus grupis VK-s on teema, kus kasutajad saavad kaevata ebaausate reklaamide ĂŒle, seal on palju nĂ€iteid. 2016. aastal oli isegi videoreklaam venekeeles (jah, reklaam brauserile, mis blokeerib reklaame).

Artikli kirjutamise ajal oli UC Browseri Google Play's kogunenud ĂŒle 500 000 000 installatsiooni. See on muljetavaldav — rohkem on ainult Google Chrome'il. Arvustustes on palju kaebusi reklaamide ja suunamiste kohta mĂ”nedes rakendustes Google Play's. See andis meile impulsi uurimiseks: otsustasime vaadata, kas UC Browser teeb midagi kahtlast. Ja selgus, et teebki!

Rakenduse koodist avastati vĂ”imalus laadida ja kĂ€ivitada tĂ€itmist vajavat koodi, mis on vastuolus rakenduste avaldamise reeglitega Google Play's. Peale selle, et UC Browser laadib tĂ€itmist vajavat koodi, teeb ta seda ebaturvaliselt, mida saab kasutada MitM-rĂŒnnaku teostamiseks. Vaatame, kas suudame sellise rĂŒnnaku lĂ€bi viia.

KÔik, mis jÀrgneb, kehtib UC Browseri versiooni kohta, mis oli Google Play's olemas uuringu lÀbiviimise ajal:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-faili: f5edb2243413c777172f6362876041eb0c3a928c

RĂŒnnaku vektori

UC Browseri manifestis on teenus, millel on jutukas nimi com.uc.deployment.UpgradeDeployService.

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

Selle teenuse kĂ€ivitamisel saadab brauser POST-pĂ€ringu puds.ucweb.com/upgrade/index.xhtml, mida saab mĂ€rgata liikluses pĂ€rast mĂ”ningast aega pĂ€rast kĂ€ivitamist. Vastuseks vĂ”ib ta saada kĂ€su laadida mĂ”ni uuendus vĂ”i uus moodul. Server selliseid kĂ€ske analĂŒĂŒsi kĂ€igus ei andnud, kuid me mĂ€rkisime, et kui prooviti avada PDF-d brauseris, tehti korduv pĂ€ring ĂŒlaltoodud aadressile, pĂ€rast mida laaditi alla kohalik raamatukogu. RĂŒnnaku lĂ€biviimiseks otsustasime kasutada UC Browserva omadust: vĂ”imalust avada PDF kohaliku raamatukogu abil, mida APK-s ei ole ja mille ta vajadusel Internetist alla laadib. Tasub mĂ€rkida, et teoreetiliselt on UC Browserit vĂ”imalik sundida midagi allalaadima ka ilma kasutajaga suhtlemata – kui esitada Ă”igesti vormistatud vastus pĂ€ringule, mis kĂ€ivitatakse brauseri kĂ€ivitamisel. Kuid selleks on vaja tĂ€psemalt uurida serveriga suhtlemise protokolli, mistĂ”ttu otsustasime, et on lihtsam redigeerida salvestatud vastust ja asendada PDF-iga töötamiseks vajalik raamatukogu.

Nii et kui kasutaja soovib PDF-d otse brauseris avada, vÔib liikluses nÀha jÀrgmisi pÀringuid:

Otsime UC Browser'is haavatavusi.

Esiteks toimub POST-pÀring puds.ucweb.com/upgrade/index.xhtml, pÀrast mida
Laaditakse alla arhiiv PDF ja kontoriformaatide vaatamiseks. On mĂ”istlik eeldada, et esimeses pĂ€ringus edastatakse teave sĂŒsteemi kohta (vĂ€hemalt arhitektuur, et anda Ă”ige teek), ja sellele vastuseks saab brauser mingit teavet teekide kohta, mis tuleb alla laadida: aadress ja vĂ”ib-olla veel midagi. Probleem seisneb selles, et see pĂ€ring on krĂŒpteeritud.

PĂ€ringu fragment

Vastuse fragment

Otsime UC Browser'is haavatavusi.

Otsime UC Browser'is haavatavusi.

Ise teek on pakitud ZIP-faili ja ei ole krĂŒpteeritud.

Otsime UC Browser'is haavatavusi.

Liikluse dekrĂŒpteerimise koodi otsimine

Proovime dekrĂŒpteerida serveri vastust. Vaatame klassi koodi com.uc.deployment.UpgradeDeployService: meetodist onStartCommand liigume com.uc.deployment.b.x, sealt edasi com.uc.browser.core.d.c.f.e:

    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("]toode:");
            v4.append(arg9.iGX.ipR);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]versioon:");
            v4.append(arg9.iGX.iEn);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]uuenduse_tĂŒĂŒp:");
            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("]komponent_nimi:");
                    v5.append(((au)v4_1).getName());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_versioon_nimi:");
                    v5.append(((au)v4_1).aDA());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_versioon_kood:");
                    v5.append(((au)v4_1).gBl);
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_req_tĂŒĂŒp:");
                    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_versioon", c.getRomInfo()));
            v3_2.add(g.fs("protsessori_arhitektuur", com.uc.b.a.a.c.getCpuArch()));
            v3_2.add(g.fs("cpu_arhitektuur", 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_tĂŒĂŒp", String.valueOf(com.uc.base.system.a.Jo())));
            v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
            v3_2.add(g.fs("plugin_versioon", arg9.iGX.iEn));
            v3_2.add(g.fs("target_lang", arg9.iGX.iEs));
            v3_2.add(g.fs("vitamio_cpu_arhitektuur", 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_laste_versioon", arg9.iGX.iEx));
            v3_2.add(g.fs("versioon_seeria", arg9.iGX.iEw));
            v3_2.add(g.fs("laste_versioon", r.aVw()));
            v3_2.add(g.fs("praegune_versioon_md5", arg9.iGX.iEl));
            v3_2.add(g.fs("praegune_versioon_signature", SystemHelper.getUCMSignature()));
            v3_2.add(g.fs("uuenduse_logi", 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_arhitektuur", com.uc.b.a.a.c.Pc()));
            v3_2.add(g.fs("cpu_seadistus", 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_tuumad", 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("kokku_ram", 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_tase", String.valueOf(Build$VERSION.SDK_INT)));
            v3_2.add(g.fs("uc_apk_loend", 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, "uuenda_encode", "jah", "eba");
            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, "uuenda_encrypt", "jah", "eba");
            return;
        }
 
        if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
            this.iGY.iGI.a(arg9, "uuenda_url", "jah", "eba");
            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, "uuenda_null", "jah", "edukalt");
        this.iGY.iGI.b(arg9);
    }

Siit nÀeme POST-pÀringu koostamist. JÀtame meelde 16 baitisest massiivi loomise ja selle tÀitmise: 0x5F, 0, 0x1F, -50 (=0xCE). See vastab sellele, mida nÀgime eespool olevas pÀringus.

Samas klassis on mĂ€rgata ka sisemist klassi, kus on veel ĂŒks huvitav meetod:

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

Meetod vĂ”tab sisendiks baitide massiivi ja kontrollib, et nullbait oleks 0x60 vĂ”i kolmas bait oleks 0xD0, ning teine bait oleks 1, 11 vĂ”i 0x1F. Vaatame serveri vastust: nullbait – 0x60, teine – 0x1F, kolmas – 0x60. Tundub, et see on see, mida vajame. Kohe nĂ€ha, et stringides („up_decrypt”, nĂ€iteks) peaks olema meetod, mis dekodeerib serveri vastuse.
Liigume meetodi juurde g.j. TĂ€helepanu, et esimese argumendina antakse sellele bait nihkega 2 (st 0x1F meie puhul), ja teise argumendina – serveri vastus ilma
esimese 16 baitideta.

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

Ilmselt toimub siin dekrĂŒpteerimisalgoritmi valik, ja see konkreetne bait, mis meie
juhtumil on 0x1F, tĂ€histab ĂŒhte kolmest vĂ”imalikust variandist.

JĂ€tkame koodi analĂŒĂŒsimist. PĂ€rast paari hĂŒpet satume meetodisse, mille nimi on decryptBytesByKey.

Siin eraldatakse meie vastusest veel kaks baiti, millest moodustatakse string. On selge, et sellel viisil valitakse sĂ”numi dekrĂŒpteerimise vĂ”ti.

    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 baiti
                    System.arraycopy(bytes, 0, prefix, 0, prefix.length);
                    String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // VÔti valimine
                    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;
    }

Tuleb mÀrkida, et selle etapi jooksul ei saa me veel vÔtme, vaid ainult selle «identifikaatori». VÔtme saamine on aga veidi keerulisem.

JĂ€rgmises meetodis lisatakse olemasolevatele parameetritele veel kaks, muutes neid kokku neljaks: maagiline number 16, vĂ”tme identifikaator, krĂŒpteeritud andmed ja arusaamatu string (meie puhul tĂŒhi).

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

PĂ€rast mitmeid ĂŒleminekuid jĂ”uame meetodini staticBinarySafeDecryptNoB64 liidese com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. PĂ”hikoodis ei ole klasse, mis rakendaks seda liidest. Selline klass on failis lib/armeabi-v7a/libsgmain.so, mis tegelikult ei ole .so, vaid .jar. Meid huvitav meetod on rakendatud jĂ€rgmiselt:

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  0) {
            return this.b(magicInt, keyId, encrypted, magicString);
        }
 
        throw new SecException("", 301);
    }
//...
}

Siin meie parameetrite nimekiri tÀieneb veel kahe tÀisarvuga: 2 ja 0. Oletatavasti
2 tĂ€hendab dekrĂŒptimist, nagu meetodis doFinal sĂŒsteemne klass javax.crypto.Cipher. Ja kĂ”ik see edastatakse mingisse Routerisse, mille numbriks on 10601 — see on nĂ€htavasti kĂ€su number.

PĂ€rast veel ĂŒhte ĂŒleminekujĂ€rjekorda leiame klassi, mis rakendab liidest IRouterComponent ja meetod 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);
    }
}

Ja klass JNICLibrary, kus on deklareeritud kohalik meetod doCommandNative:

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

Seega peame leidma kohalikust koodist meetodi doCommandNative. Ja siin algab tÔeline lÔbu.

Masinkoodi obfutseerimine

Failis libsgmain.so (mis on tegelikult .jar ja milles leidsime natuke varem mĂ”ned krĂŒpteerimisega seotud liideste rakendused) on ĂŒks kohalik raamatukogu: libsgmainso-6.4.36.so. Avame selle IDAs ja saame hulga veahoiatusi. Probleem on selles, et sektsioonide tabel (section header table) on kehtetu. See on tehtud tahtlikult, et analĂŒĂŒsi raskendada.

Otsime UC Browser'is haavatavusi.

Aga see pole vajalik: et ELF-fail korrektselt laadida ja analĂŒĂŒsida, piisab tĂ€iesti segmentide tabelist (program header table). Seega kustutame lihtsalt sektsioonide tabeli, nullides vastavad vĂ€ljad pĂ€ises.

Otsime UC Browser'is haavatavusi.

Avame faili taas IDAs.

On kaks viisi, kuidas jagada virtuaalsele Java-masinale teavet selle kohta, kus tÀpselt on natiivses teegis meetodi teostus, mis on Java-koodis mÀÀratletud kui native. Esimene on anda sellele nimi kujul Java_paketi_nimi_KlassiNimi_meetodiNimi.

Teine on registreerida see teegi laadimisel (funktsiooni JNI_OnLoad,)
kasutades funktsiooni RegisterNatives..

Meie puhul, kui kasutada esimest meetodit, peab nimi olema selline: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative..

Eksportivate funktsioonide seas puudub selline, mis tÀhendab, et peame otsima kutset RegisterNatives..
Liigume funktsiooni JNI_OnLoad, ja nÀeme sellist pilti:

Otsime UC Browser'is haavatavusi.

Mis siin toimub? Esmapilgul tundub, et funktsiooni algus ja lĂ”pp on tĂŒĂŒpilised ARM-architektuurile. Esimese kĂ€su puhul salvestatakse virna registrite sisu, mida funktsioon oma töös kasutab (antud juhul R0, R1 ja R2), samuti LR registreeritud sisu, kus asub tagasipöördumise aadress funktsioonist. Viimase kĂ€su puhul taastatakse salvestatud registrid, samal ajal kui tagasipöördumise aadress pannakse otse PC registrisse — nii toimub funktsioonist tagasipöördumine. Kuid kui tĂ€helepanelikult vaadata, vĂ”ib mĂ€rgata, et eelviimane kĂ€sk muudab virnas salvestatud tagasipöördumise aadressi. Arvutame vĂ€lja, mis see pĂ€rast
koodi tĂ€itmist olema hakkab. R1 laaditakse mingisuguse aadressiga 0xB130, sellest lahutatakse 5, siis pannakse see R0-sse ja sellele lisatakse 0x10. Tulemus on 0xB13B. Nii peab IDA viimases kĂ€sus toimuma tavaline funktsioonist tagasirenderimine, kuid tegelikult toimub ĂŒleminek arvutatud aadressile 0xB13B.

Siin tasub meenutada, et ARM protsessoritel on kaks reĆŸiimi ja kaks kĂ€skude kogumit: ARM ja Thumb. Madalam bitt aadressil mĂ€rgib protsessorile, millist kĂ€skude kogumit kasutatakse. See tĂ€hendab, et aadress on tegelikult 0xB13A, ja ĂŒks madalas bitis tĂ€histab Thumb reĆŸiimi.

Iga selle raamatukogu funktsiooni algusesse on lisatud sarnane „adapter“ ja
prĂŒgikood. Edasi ei peatu nende juures pikemalt – lihtsalt peame meeles,
et peaaegu kÔikide funktsioonide tÔeline algus asub veidi kaugemal.

Kuna koodis ei ole selget ĂŒleminekut aadressile 0xB13A, ei suutnud IDA ise tuvastada, et seal asub kood. Sama pĂ”hjusel ei tunne ta suurema osa koodist raamatukogus koodina Ă€ra, mis teeb analĂŒĂŒsi keerukamaks. Ütleme IDA-le, et siin on kood, ja tulemus on jĂ€rgmine:

Otsime UC Browser'is haavatavusi.

Aadressil 0xB144 algab selgelt tabel. Ja mis on sub_494C?

Otsime UC Browser'is haavatavusi.

Selle funktsiooni vÀljakutsumise korral saame registris LR aadressi eespool mainitud tabelist (0xB144). R0-s on indekseerimine selles tabelis. See tÀhendab, et tabelist vÔetakse vÀÀrtus, lisatakse LR-ile ja saadakse
aadress, kuhu tuleb minna. Proovime selle arvutada: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. LĂ€hme saadud aadressile ja nĂ€eme sĂ”na-sĂ”nalt paar kasulikku juhist ning jĂ€lle ĂŒleminek 0xB140-le:

Otsime UC Browser'is haavatavusi.

NĂŒĂŒd toimub ĂŒleminek nihkega, mille indeks on 0x20 tabelist.

Arvestades tabeli suurust, selliseid ĂŒleminekuid koodis kohtab palju. Tekkib kĂŒsimus, kas on vĂ”imalik sellega kuidagi automaatsemalt toime tulla, ilma aadresside kĂ€sitsi arvutamiseta. Meile tulevad appi skriptid ja vĂ”imalus koodi IDAs patchida:

def put_unconditional_branch(source, destination):
    offset = (destination - source - 4) >> 1
    if offset > 2097151 or offset  1023 or 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"

Aseta kursor rida 0xB26A, kĂ€ivita skript ja nĂ€ed ĂŒleviimist 0xB4B0:

Otsime UC Browser'is haavatavusi.

IDA ei tuvastanud seda ala taas koodina. Aitame tal ja nÀeme seal teist struktuuri:

Otsime UC Browser'is haavatavusi.

BLX-i juhised ei tundu vÀga loogilised, see meenutab pigem mingit nihget. Vaadakem sub_4964:

Otsime UC Browser'is haavatavusi.

Ja tÔepoolest, siin vÔetakse dword aadressilt, mis asub LR-is, sellele aadressile liidetakse, pÀrast mida vÔetakse saadud aadressi vÀÀrtus ja pannakse kuhugi. Samuti lisatakse LR-ile 4, et pÀrast funktsioonist naasmist see nihke vÀltida. PÀrast seda kÀsk POP {R1} toob saadud vÀÀrtuse vÀlja kuhjast. Kui vaadata, mis asub aadressil 0xB4BA + 0xEA = 0xB5A4, siis vÔib nÀha midagi, mis sarnaneb aadresside tabeliga:

Otsime UC Browser'is haavatavusi.

Selle konstruktsiooni patchimiseks on vaja koodi kaitsta kahe parameetriga: nihke ja registreerimise numbri, kuhu tuleks tulemus panna. Iga vÔimaliku registri jaoks tuleb eelnevalt ette valmistada koodilÔik.

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 <> 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"

Asetame kursor konstruktsiooni algusesse, mille tahame asendada — 0xB4B2 — ja kĂ€ivitame skripti:

Otsime UC Browser'is haavatavusi.

Peale juba nimetatud konstruktsioonide leidub koodis ka jÀrgmisi:

Otsime UC Browser'is haavatavusi.

Nagu ka varasemal juhul, jÀrgneb juhisele BLX nihke:

Otsime UC Browser'is haavatavusi.

VÔtame LR-lt aadressi pÔhjal nihke, liidame selle LR-iga ja liigume sinna. 0x72044 + 0xC = 0x72050. Selle konstruktsiooni skript on tÀiesti lihtne:

def put_unconditional_branch(source, destination):
    offset = (destination - source - 4) >> 1
    if offset > 2097151 or offset  1023 or 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"

Skripti tÀitmise tulemus:

Otsime UC Browser'is haavatavusi.

PĂ€rast seda, kui funktsioonis kĂ”ik on propatchitud, saab IDA-t suunata selle tegelikku algusesse. See kogub kogu funktsiooni koodi tĂŒkkhaaval, mida saab dekompileerida HexRayse abil.

Stringide dekodeerimine

Oleme Ôppinud vÔitlema masinkoodi obfuskatsiooniga teegis. libsgmainso-6.4.36.so UC Browserist ning funktsioonikood 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;
}

Vaatame lÀhemalt jÀrgmisi ridu:

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

funktsioonis sub_73E24 toimub klassinime dekrĂŒpteerimine. Selle funktsiooni parameetritena antakse edasi nĂ€itaja krĂŒpteeritud andmetele, mingi puhversalv ja number. Ilmselt on pĂ€rast funktsiooni vĂ€ljakutsumist puhversalves dekrĂŒpteeritud string, kuna see edastatakse funktsioonile. FindClass, mis vĂ”tab teiseks parameetriks klassinime. Seega number on puhversalvi suurus vĂ”i stringi pikkus. Proovime klasini nime dekrĂŒpteerida, see peaks nĂ€itama meile, kas me liikume Ă”igesse suunda. Vaatame lĂ€hemalt, mis toimub 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 loob instantsi konteineri byte massiivide mÀÀratud suuruses (ei kavatse nende konteinerite ĂŒle pikalt peatuda). Siia luuakse kaks sellist konteinerit: ĂŒhte pannakse string «DcO/lcK+h?m3c*q@» (ei ole raske oletada, et see on vĂ”ti), teise — krĂŒpteeritud andmed. Edasi koos mĂ”lemad objektid pannakse mingisse struktuuri, mis edastatakse funktsioonile sub_6115C. Samuti mĂ€rkige selles struktuuris vĂ€lja vÀÀrtusega 3 olev vĂ€li. Vaatame, mis juhtub selle struktuuriga edasi.

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

Parameeterina switch antakse edasi struktuuri vĂ€li, millele on varem mÀÀratud vÀÀrtus 3. Vaadake case 3: funktsioon sub_6364C edastatakse struktuurist parameetrid, mis olid sinna eelnevas funktsioonis pandud, st vĂ”ti ja krĂŒpteeritud andmed. Kui vaadata tĂ€helepanelikult sub_6364C, saab teada RC4 algoritmi.

Meil on algorithm ja vĂ”ti. Proovime klassi nime dekrĂŒpteerida. Siin on, mis saadud: com/taobao/wireless/security/adapter/JNICLibrary. SuurepĂ€rane! Oleme Ă”igel teel.

KĂ€skude puu

NĂŒĂŒd tuleb leida kutse RegisterNatives., mis osutab meile funktsioonile doCommandNative. Vaadake funktsioone, mida kutsutakse JNI_OnLoad, ja leiame selle 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;
}

Ja tĂ”epoolest, siin registreeritakse natiivne meetod nimega doCommandNative. NĂŒĂŒd teame selle aadressi. Vaadake, mida ta teeb.

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

Nime jĂ€rgi vĂ”ib arvata, et siin asub kĂ”ikide funktsioonide sissetoomise koht, mille arendajad otsustasid natiivsete raamatukogudesse ĂŒle viia. Meid huvitab funktsioon numbriga 10601.

Koodist on nÀha, et kÀskude numbrist saadakse kolm numbrit: command / 10000, command % 10000 / 100 ja command % 10, st meie puhul 1, 6 ja 1. Need kolm numbrit, samuti nÀidikud JNIEnv ja funktsioonile edastatud argumendid kogutakse struktuuri ja edastatakse edasi. Saadud kolme numbri (nimetame need N1, N2 ja N3) abil ehitatakse kÀskude puu.

Umbes selline:

Otsime UC Browser'is haavatavusi.

Puu tĂ€idetakse dĂŒnaamiliselt JNI_OnLoad,.
Kolm numbrit kodeerivad teed puus. Iga puu leht sisaldab vastava funktsiooni adresseerimist. VÔti asub vanemas sÔlmes. Leida koht koodis, kus puusse lisatakse vajalik funktsioon, ei ole keeruline, kui selgitatakse kÔiki kasutatavaid struktuure (me ei tooda nende kirjeldusi, et mitte venitada juba praegu mahukat artiklit).

Veel obfuskatsioonist

Me saime funktsiooni aadressi, mis peaks dekodeerima liiklust: 0x5F1AC. Kuid rÔÔmustamiseks on veel vara: UC Browseri arendajad on meile valmistanud veel ĂŒhe ĂŒllatuse.

PÀrast parameetrite saamist massiivist, mis loodi Java koodis, jÔuame
funktsiooni aadressile 0x4D070. Ja siin ootab meid veel ĂŒks koodi obfuskatsiooni tĂŒĂŒp.

Paneme R7 ja R4 kahele indeksile:

Otsime UC Browser'is haavatavusi.

Kandame esimese indeksi R11-sse:

Otsime UC Browser'is haavatavusi.

Aadressi saamiseks tabelist kasutame indeksit:

Otsime UC Browser'is haavatavusi.

PĂ€rast esimesest aadressist liikumist kasutatakse teist indeksit, mis on R4. Tabelis on 230 elementi.

Mida selle kÔige kohta teha? VÔime IDA-le öelda, et see on switch: Redigeeri -> Muu -> TÀpsustage switch idiom.

Otsime UC Browser'is haavatavusi.

Tulemuslik kood on hirmutav. Kuid tema rÀgastikku lÀbi minnes vÔib mÀrgata juba tuttava funktsiooni kÔnet. sub_6115C:

Otsime UC Browser'is haavatavusi.

Seal oli switch, kus case 3-s oli dekodeerimine RC4 algoritmi abil. Sel juhul tĂ€idetakse funktsiooni struktuur parameetritega, mis on edastatud doCommandNative. Meenutame, et meil oli seal magicInt vÀÀrtusega 16. Vaatame vastavat case'i – ja pĂ€rast mitmeid siirdeid leiame koodi, mille abil saab algoritmi tuvastada.

Otsime UC Browser'is haavatavusi.

See on AES!

Algoritm on olemas, nĂŒĂŒd peab saama selle parameetrid: reĆŸiim, vĂ”ti ja vĂ”imalik, et initsialiseerimise vektor (kasutamise olemasolu sĂ”ltub AES-i algoritmi reĆŸiimist). Struktuur nendega peaks moodustuma kuskil enne funktsiooni vĂ€ljakutset sub_6115C, kuid see koodijupp on eriti hĂ€sti obfuskeeritud, seega tekib idee koodi patĆĄida, et kĂ”ik dekrĂŒpteerimisfunktsiooni parameetrid dumpitaks faili.

PatĆĄ

Kuna ei soovi kirjutada kogu patĆĄi koodi kĂ€sitsi assembleri keeles, saab avada Android Studio, kirjutada sinna funktsioon, mis saab sisse samasuguseid parameetreid, nagu meie dekrĂŒpteerimisfunktsioon, ja kirjutab faili, pĂ€rast mida saab kopeerida koodi, mille genereerib kompilaator.

Meie sĂ”brad UC Browerist on ka “mugavuse” ĂŒle hoolitsenud. Peame meeles, et iga funktsiooni alguses on meil prĂŒgi kood, mille saab hĂ”lpsasti asendada mĂ”ne teisega. See on vĂ€ga mugav 🙂 TĂ”si, sihtfunktsiooni alguses on koht koodile, mis salvestab kĂ”ik parameetrid faili, ĂŒsna vĂ€ike. Pidi selle jagama osadeks ja kasutama kĂ”rvaliste funktsioonide prĂŒgi bloke. Kokku tuli neli osa.

Esimene osa:

Otsime UC Browser'is haavatavusi.

ARM arhitektuuris edastatakse funktsiooni esimesed neli parameetrit registrite R0-R3 kaudu, ĂŒlejÀÀnud, kui neid on, edastatakseé€šèż‡ć †æ ˆ. Registreeritavas LR-s edastatakse tagasipöördumise aadress. KĂ”ik see tuleb salvestada, et funktsioon saaks töötada pĂ€rast seda, kui me dumpime selle parameetrid. Samuti peame salvestama kĂ”ik registrid, mida me protsessi kĂ€igus kasutame, seega teeme PUSH.W {R0-R10,LR}. R7-s on meil aadress parameetrite nimekirjale, mis edastatakse funktsioonile lĂ€bi steki.

Funktsiooni fopen avatakse fail /data/local/tmp/aes moodul 'ab',
st. e. lisamiseks. R0-s laadime faili nime aadressi, R1-s reĆŸiimi stringi aadressi. Siin lĂ”peb prĂŒkkood, seega liigume jĂ€rgmisse funktsiooni. Selle töötamiseks paneme alguses ĂŒlemineku tĂ”elise funktsiooni koodile prĂŒgikasti vĂ€ltimiseks, ja prĂŒgikoodi asemele lisame patĆĄi jĂ€tku.

Otsime UC Browser'is haavatavusi.

Kutsume fopen.

Esimesed kolm funktsiooni parameetrit aes on tĂŒĂŒpi int. Kuna me alguses salvestasime registreid kuhja, saame lihtsalt edastada funktsioonile fwrite nende aadresse kuhjas.

Otsime UC Browser'is haavatavusi.

Edasi on meil kolm struktuuri, mis sisaldavad andmete suurust ja andmeviidikut vÔtme, alguse vektori ja ƥifreeritud andmete jaoks.

Otsime UC Browser'is haavatavusi.

LÔpus sulgeme faili, taastame registreid ja edastame juhtimise tÔelisele funktsioonile. aes.

Kogume APK, kus on patĆĄitud teek, allkirjastame selle, laadime seadmesse/emulaatorisse ja kĂ€ivitame. NĂ€eme, et meie dump luuakse ja sinna kirjutatakse palju andmeid. Brauser kasutab krĂŒpteerimist mitte ainult liikluse jaoks, vaid kogu krĂŒpteerimine kulgeb lĂ€bi arutatava funktsiooni. Kuid vajalikud andmed on kuidagi puudulikud ja liikluses pole Ă”iget pĂ€ringut nĂ€ha. Et mitte oodata, kuni UC Browser soovi pĂ€ringu teha, vĂ”tame varasema serverilt saadud krĂŒpteeritud vastuse ja patĆĄime rakenduse uuesti: lisame dekrĂŒpteerimise peamise tegevuse onCreate'i.

    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

Kogume, allkirjastage, installige ja kÀivitage. Saame NullPointerException, kuna meetod tagastas nulli.

Edasi liikudes koodi analĂŒĂŒsi kĂ€igus leidsime funktsiooni, kus dekodeeritakse huvitavaid ridu: "META-INF/" ja ".RSA". Tundub, et rakendus kontrollib oma sertifikaati. VĂ”i isegi genereerib sealt vĂ”tmeid. Sertifikaadi kĂ€itlemisega tegelemine ei ole siiski meelepĂ€rane, seega pakume lihtsalt Ă”iget sertifikaati. Murdame Ă€ra krĂŒpteeritud rea nii, et "META-INF/" asemel saame "BLABLINF/", loome APK-s sellise nimega kausta ja lisame sinna sellega sertifikaadi.

Kogume, allkirjastame, installime, kÀivitame. Bingo! VÔti on meil!

MitM

Oleme saanud vĂ”tme ja initsialiseerimise vektori, mis on vĂ”rdne vĂ”tmega. Proovime dekrĂŒpteerida serveri vastust CBC-reĆŸiimis.

Otsime UC Browser'is haavatavusi.

NÀeme arhiivi URL-i, mis meenutab MD5-d, "extract_unzipsize" ja numbrit. Kontrollime: arhiivi MD5 vastab, lahti pakitud teegi suurus on Ôige. Proovime murda see teek ja anda see brauserile. Et nÀidata, et meie murtud teek on laaditud, kÀivitame Intent'i, et luua SMS tekstiga "PWNED!". Asendame kaks vastust serverilt: puds.ucweb.com/upgrade/index.xhtml ja ja allalaadimise arhiivi. Esmalt vahetame MD5 (lahtipakkimise kaudu ei muutu), teiseks anname vÀlja arhiivi, kus on patcheeritud raamatukogu.

Brauser proovib mitu korda arhiivi alla laadida, pÀrast mida viskab vea. Tundub, et midagi
ei meeldi talle. Selle uduste formaatide analĂŒĂŒsi tulemusel selgus, et server edastab ka arhiivi suuruse:

Otsime UC Browser'is haavatavusi.

See on kodeeritud LEB128 kujul. PÀrast patchimist muutus arhiivi suurus natuke, seetÔttu arvutas brauser, et arhiiv laaditi valesti alla ja pÀrast mitmeid katseid viskas vea.

Korrigeerime arhiivi suuruse... Ja - vĂ”it! 🙂 Tulemus videos.

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

TagajÀrjed ja arendaja reaktsioon

Just sellisel viisil saaksid hĂ€kkerid kasutada UC Browser'i turvakaalutluste kĂ”rvalekalduvat funktsiooni, et levitada ja kĂ€ivitada pahatahtlikke raamatukogusid. Need raamatukogud töötaksid brauseri kontekstis, seega omaksid kĂ”ik sĂŒsteemi Ă”igused. TagajĂ€rjeks - nĂ€idata vĂ€ljapetmisaknaid ja samuti juurdepÀÀs töötavatele failidele oranĆŸi Hiina orava kohta, sealhulgas andmebaasis hoitavad logid, paroolid ja kĂŒpsised.

Oleme pöördunud UC Browseri arendajate poole, teavitades neid avastatud probleemist, pĂŒĂŒdes osutada haavatavusele ja selle ohtlikkusele, kuid nad ei soovinud meiega millestki arutada. Samas jĂ€tkas brauser ohtlikku funktsiooni uhkelt nĂ€itamist. Kuid kui me paljastasime haavatavuse ĂŒksikasjad, ei saanud seda enam eirata nagu varem. 27. mĂ€rtsil
vÀlja anti uus versioon UC Browser 12.10.9.1193, mis suhtles serveriga HTTPS-protokolli kaudu: puds.ucweb.com/upgrade/index.xhtml.

Lisaks toimus pÀrast 'paranduse' tegemist ja enne artikli kirjutamist katse avada PDF-brauseris, mis pÔhjustas veateate, mille tekst oli 'Ups, midagi lÀks valesti!'. Serverisse tehtud pÀring PDF-i avamise katses ei tÀitunud, kuid brauseri avamisel tÀideti pÀring, mis viitab sellele, et vÔimalus tÀitefailide laadimiseks Google Play reegleid rikkudes on endiselt olemas.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster