Vi leitar eftir veikleikum í UC Browser

Vi leitar eftir veikleikum í UC Browser

Innledning

Í lok mars mánaðar rapportert, sem við uppgötvuðum falinn möguleika á að hlaða niður og framkvæma óprófun kóða í UC Browser. Í dag skoðum við í smáatriðum hvernig þessi niðurhali fer fram og hvernig tölvuþrjótar geta nýtt sér hana.

Fyrir nokkru var UC Browser auglýst og dreift mjög ágenglega: það var sett á tæki notenda með skaðlegum forritum, dreift frá ýmsum vefsíðum í gegnum myndskrá (þ.e. notendur héldu að þeir væru að hlaða niður, til dæmis, pornómyndbandi, en fengu í staðinn APK með þessum vafra), notaðar voru skelfingarauglýsingar með skilaboðum um að vafrinn væri útrunninn, veikur og eitthvað í þeim dúr. Í opinberu hópinu fyrir UC Browser í VK er efni, þar sem notendur geta kvartað yfir óheiðarlegri auglýsingu, þar eru mörg dæmi. Árið 2016 kom jafnvel myndbandasýning á rússnesku (já, auglýsing fyrir vafra sem blokkir auglýsingar).

Við skrifum þessa grein hafði UC Browser meira en 500.000.000 niðurhallir í Google Play. Þetta er mjög mögnuð tala – aðeins Google Chrome hefur meira. Á meðal umsagnanna má sjá töluverðan fjölda kvartana um auglýsingar og endurvísanir á einhvers konar forrit í Google Play. Þetta varð að ástæðu fyrir rannsókn: við ákváðum að kanna hvort UC Browser væri að gera eitthvað óheiðarlegt. Og það reyndist vera!

Í kóðanum á forritinu fannst möguleiki á að hlaða niður og framkvæma keyrandi kóða, sem brýtur gegn reglum um útgáfu forrita í Google Play. Að auki, það sem UC Browser hleður niður keyrandi kóða, gerir þetta á óöruggan hátt, sem má nýta til að framkvæma MitM árás. Við skulum sjá hvort við getum framkvæmt slíka árás.

Allt sem skrifað er hér að neðan er í gildi fyrir útgáfu UC Browser, sem var til staðar í Google Play þegar rannsóknin fór fram:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-skrár: f5edb2243413c777172f6362876041eb0c3a928c

Árásarvektor

Í manifesinu fyrir UC Browser má finna þjónustu með því nafninu com.uc.deployment.UpgradeDeployService.

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

við að framkvæma þessa þjónustu sendir vafrinn POST beiðni til puds.ucweb.com/upgrade/index.xhtml, som kan bli sett i trafikken etter en stund etter oppstarten. I svaret kan den få en kommando om å laste ned en oppdatering eller en ny modul. Under analysen ga serveren ikke slike kommandoer, men vi la merke til at når vi prøvde å åpne en PDF i nettleseren, gjorde den en ny forespørsel til adressen nevnt over, hvoretter den laster ned det native biblioteket. For å gjennomføre angrepet bestemte vi oss for å bruke denne egenskapen til UC Browser: evnen til å åpne PDF-er ved hjelp av et native bibliotek som ikke finnes i APK-en, og som det laster ned fra Internett om nødvendig. Det er verdt å merke seg at teoretisk sett kan UC Browser tvinges til å laste noe ned uten brukerinteraksjon – dersom man gir et riktig formulert svar på forespørselen som utføres etter at nettleseren er startet. Men for dette må vi studere protokollen for interaksjon med serveren mer detaljert. Derfor bestemte vi oss for at det var lettere å redigere det fangede svaret og bytte ut biblioteket for håndtering av PDF.

Så, når brukeren ønsker å åpne en PDF direkte i nettleseren, kan følgende forespørsel bli sett i trafikken:

Vi leitar eftir veikleikum í UC Browser

Først kommer det en POST-forespørsel til puds.ucweb.com/upgrade/index.xhtml, deretter
lastes et arkiv ned med biblioteket for visning av PDF-er og kontorformater. Det er rimelig å anta at den første forespørselen sender informasjon om systemet (minst arkitektur, for å gi det nødvendige biblioteket), og som svar får nettleseren noe informasjon om biblioteket som må lastes ned: adressen og muligens noe mer. Problemet er at denne forespørselen er kryptert.

Utdrag av forespørselen

Utdrag av svaret

Vi leitar eftir veikleikum í UC Browser

Vi leitar eftir veikleikum í UC Browser

Selve biblioteket er pakket i ZIP og er ikke kryptert.

Vi leitar eftir veikleikum í UC Browser

Søke etter dekrypteringskode for trafikk

La oss prøve å dekryptere svaret fra serveren. Vi ser på klassen com.uc.deployment.UpgradeDeployService: fra metoden onStartCommand går vi til com.uc.deployment.b.x, og derfra til com.uc.browser.core.d.c.f.e:

    offentlig sluttflyt void e(l arg9) {
        int v4_5;
        String v3_1;
        byte[] v3;
        byte[] v1 = null;
        hvis(arg9 == null) {
            v3 = v1;
        }
        ellers {
            v3_1 = arg9.iGX.ipR;
            StringBuilder v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]produkt:");
            v4.append(arg9.iGX.ipR);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]versjon:");
            v4.append(arg9.iGX.iEn);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]oppgradertype:");
            v4.append(arg9.iGX.mMode);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tvangsflagg:");
            v4.append(arg9.iGX.iEo);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tystmodus:");
            v4.append(arg9.iGX.iDQ);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tysttype:");
            v4.append(arg9.iGX.iEr);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tyststatus:");
            v4.append(arg9.iGX.iEp);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tystfil:");
            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("]nedlastningstype:");
            v4.append(arg9.mDownloadType);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]nedlastingsgruppe:");
            v4.append(arg9.mDownloadGroup);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]nedlastingsbane:");
            v4.append(arg9.iGH);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_barnversjon:");
            v4.append(arg9.iGX.iEx);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo-serie:");
            v4.append(arg9.iGX.iEw);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_cpu_arkitektur:");
            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;
            hvis(v3_2 != null && v3_2.size() != 0) {
                Iterator v3_3 = v3_2.iterator();
                mens(v3_3.hasNext()) {
                    Object v4_1 = v3_3.next();
                    StringBuilder v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_navn:");
                    v5.append(((au)v4_1).getName());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_ver_navn:");
                    v5.append(((au)v4_1).aDA());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_ver_kode:");
                    v5.append(((au)v4_1).gBl);
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_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("prosessor_arkitektur", com.uc.b.a.a.c.getCpuArch()));
            v3_2.add(g.fs("cpu_arkitektur", 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("nett_type", String.valueOf(com.uc.base.system.a.Jo())));
            v3_2.add(g.fs("fravert", arg9.iGX.iEm));
            v3_2.add(g.fs("plugin_ver", arg9.iGX.iEn));
            v3_2.add(g.fs("mål_språk", arg9.iGX.iEs));
            v3_2.add(g.fs("vitamio_cpu_arkitektur", 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_barnversjon", arg9.iGX.iEx));
            v3_2.add(g.fs("versjon_serie", arg9.iGX.iEw));
            v3_2.add(g.fs("barn_ver", r.aVw()));
            v3_2.add(g.fs("nåver_md5", arg9.iGX.iEl));
            v3_2.add(g.fs("nåver_signatur", SystemHelper.getUCMSignature()));
            v3_2.add(g.fs("oppgraderingslogg", i.bjt()));
            v3_2.add(g.fs("tyst_installasjon", String.valueOf(arg9.iGX.iDQ)));
            v3_2.add(g.fs("tyst_status", String.valueOf(arg9.iGX.iEp)));
            v3_2.add(g.fs("tyst_fil", arg9.iGX.iEq));
            v3_2.add(g.fs("tyst_type", String.valueOf(arg9.iGX.iEr)));
            v3_2.add(g.fs("cpu_arkitektur", com.uc.b.a.a.c.Pc()));
            v3_2.add(g.fs("cpu_sett", 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_kjerner", 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_nivå", String.valueOf(Build$VERSION.SDK_INT)));
            v3_2.add(g.fs("uc_apk_liste", SystemHelper.getUCMobileApks()));
            Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
            mens(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();
        }
 
        hvis(v3 == null) {
            this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
            return;
        }
 
        v4_5 = this.iGY.iGw ? 0x1F : 0;
        hvis(v3 == null) {
        }
        ellers {
            v3 = g.i(v4_5, v3);
            hvis(v3 == null) {
            }
            ellers {
                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);
            }
        }
 
        hvis(v1 == null) {
            this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
            return;
        }
 
        hvis(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);
    }

Vi ser her dannelsen av en POST-forespørsel. Merk opprettelsen av en matrise på 16 byte og dens fylling: 0x5F, 0, 0x1F, -50 (=0xCE). Dette samsvarer med det vi så i forespørselen ovenfor.

I denne klassen kan man observere en innebygd klasse, som har en annen interessant metode:

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

Metoden tar imot en byte-matrise og sjekker at nullbyte er lik 0x60 eller den tredje byte er lik 0xD0, mens andre byte er 1, 11 eller 0x1F. Vi ser svaret fra serveren: nullbyte er 0x60, andre er 0x1F, tredje er 0x60. Det ser ut til at dette er det vi trenger. Ifølge linjene ("up_decrypt", for eksempel), bør det kalles en metode som avkoder svaret fra serveren.
La oss gå videre til metoden g.j. Vi merk at som første argument blir et byte på offset 2 (dvs. 0x1F i vårt tilfelle) sendt, og som andre — svaret fra serveren uten
de første 16 byte.

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

Åpenbart skjer det et valg av dekrypteringsalgoritme her, og den aktuelle byte, som er i vår
verdien som er 0x1F, representerer en av tre mulige alternativer.

La oss fortsette analysen av koden. Etter et par hopp kommer vi til metoden med det talende navnet decryptBytesByKey.

Her skilles det ut to flere byte fra svaret vårt, og fra dem dannes det en streng. Det er klart at på denne måten velges nøkkelen for dekryptering av meldingen.

    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 byte
                    System.arraycopy(bytes, 0, prefix, 0, prefix.length);
                    String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Valg av nøkkel
                    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;
    }

For å skynde oss fremover, la oss merke at vi på dette stadiet ikke får nøkkelen, men bare dens «identifikator». Å få nøkkelen er litt mer komplisert.

I den neste metoden blir det lagt til to flere parametere, og de blir fire: det magiske tallet 16, nøkkelidentifikatoren, de krypterte dataene og en uforståelig streng (i vårt tilfelle tom).

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

Etter en rekke overganger kommer vi til metoden staticBinarySafeDecryptNoB64 grensesnittet com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. I hovedkoden til applikasjonen finnes det ingen klasser som implementerer dette grensesnittet. En slik klasse finnes i filen lib/armeabi-v7a/libsgmain.so, som i virkeligheten ikke er .so, men .jar. Metoden vi er interessert i er implementert som følger:

pakke com.alibaba.wireless.security.a.i;
 
// ...
 
offentlig klasse a implementerer IStaticDataEncryptComponent {
    privat ISecurityGuardPlugin a;
// ...
    privat 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});
    }
// ...
    privat byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
        return this.a(2, magicInt, 0, keyId, encrypted, magicString);
    }
// ...
    offentlig byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) kaster SecException {
        if(keyId != nul && keyId.length() > 0 && magicInt >= 0 && magicInt < 19 && encrypted != nul && encrypted.length > 0) {
            return this.b(magicInt, keyId, encrypted, magicString);
        }
 
        kast en SecException("", 301);
    }
//...
}

Her vår liste over parametere supplert med to heltall: 2 og 0. Ifølge
alt tyder på at 2 betyr dekryptering, som i metoden doFinal systemklasse javax.crypto.Cipher. Og alt dette sendes til en eller annen Router med tallet 10601 — det er antagelig kommandoidentifikatoren.

Etter en rekke overganger finner vi klassen som implementerer grensesnittet IRouterComponent og metoden doCommand:

pakke com.alibaba.wireless.security.mainplugin;

import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
 
 offentlig klasse a implementerer IRouterComponent {
    offentlig a() {
        super();
    }
    offentlig Objekt doCommand(int arg2, Object[] arg3) {
        return JNICLibrary.doCommandNative(arg2, arg3);
    }
}

Og også klassen JNICLibrary, hvor den native metoden doCommandNative:

pakke com.taobao.wireless.security.adapter;
 
 offentlig klasse JNICLibrary {
    offentlig statisk native Objekt doCommandNative(int arg0, Object[] arg1);
}

Så vi må finne metoden i den native koden. doCommandNative. Og her begynner moroa.

Obfuskering av maskinkoden

I filen libsgmain.so (som faktisk er .jar og hvor vi tidligere fant implementeringen av noen grensesnitt relatert til kryptering) er det én native bibliotekt: libsgmainso-6.4.36.so. Vi åpner den i IDA og får en haug med feilmeldinger. Problemet er at seksjonshodet (section header table) – er ugyldig. Dette er gjort med vilje for å komplisere analysen.

Vi leitar eftir veikleikum í UC Browser

Men den er unødvendig: For å laste inn ELF-filen korrekt og analysere den, er det ganske nok med programhodeseksjonen (program header table). Derfor sletter vi bare seksjonshodet ved å nullstille de aktuelle feltene i overskriften.

Vi leitar eftir veikleikum í UC Browser

Vi åpner filen i IDA igjen.

Det er to måter å informere den virtuelle Java-maskinen hvor i den native biblioteken metodens implementering, erklært i Java-koden som native, finnes. Den første — gi det et navn som Java_pakkenavn_KlasseNavn_metodeNavn.

Den andre - registrere den ved lastingen av biblioteket (i funksjonen JNI_OnLoad)
ved å kalle funksjonen RegisterNatives.

I vårt tilfelle, hvis vi bruker den første metoden, må navnet være slik: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Blant de eksporterte funksjonene finnes det ingen slik, så vi må lete etter kall RegisterNatives.
Vi går inn i funksjonen JNI_OnLoad og ser et slik bilde:

Vi leitar eftir veikleikum í UC Browser

Hva skjer her? Ved første øyekast er starten og slutten av funksjonen typiske for ARM-arkitekturen. Den første instruksjonen lagrer innholdet i registrene, som funksjonen vil bruke i sitt arbeid (i dette tilfellet R0, R1 og R2), samt innholdet i registeret LR, hvor adresse for retur fra funksjonen er. Den siste instruksjonen gjenoppretter de lagrede registrene, der returadressen umiddelbart plasseres i registeret PC - slik skjer det en retur fra funksjonen. Men ved nærmere ettersyn kan man legge merke til at nest siste instruksjon endrer returadressen lagret i stakken. La oss beregne hva den vil være etter
utførelsen av koden. I R1 lastes en adresse 0xB130, der det blir trukket fra 5, deretter blir den flyttet til R0 og 0x10 blir lagt til. Det blir 0xB13B. Slik sett tror IDA at den siste instruksjonen utfører en vanlig retur fra funksjonen, mens det i virkeligheten skjer et hopp til den beregnede adressen 0xB13B.

Her er det verdt å minne om at ARM-prosessorer har to moduser og to sett med instruksjoner: ARM og Thumb. Den laveste biten av adressen forteller prosessoren hvilket sett med instruksjoner som brukes. Altså, adressen er faktisk 0xB13A, og eneren i den laveste biten indikerer Thumb-modus.

I starten av hver funksjon i dette biblioteket er det lagt til en lignende "overgang" og
søppel kode. Videre vil vi ikke gå i detalj på dem - bare husk,
at den virkelige starten av nesten alle funksjoner ligger litt lenger.

Siden det ikke er noe eksplisitt hopp til 0xB13A, gjenkjente ikke IDA selv at det på dette stedet er kode. Av samme grunn gjenkjenner den ikke det meste av koden i biblioteket som kode, noe som gjør analysen noe vanskeligere. Vi sier til IDA at dette er kode, og her er hva vi får:

Vi leitar eftir veikleikum í UC Browser

På 0xB144 begynner tabellen å bli åpenbar. Hva med sub_494C?

Vi leitar eftir veikleikum í UC Browser

Ved å kalle denne funksjonen får vi adressen til den nevnte tabellen (0xB144) i registeret LR. I R0 - indeksen i denne tabellen. Det vil si at verdien fra tabellen blir hentet, lagt til LR og resultatet oppnås.
adressen som du må gå til. Vi prøver å beregne dette: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vi går til den beregnede adressen og ser bokstavelig talt et par nyttige instruksjoner og igjen en overgang til 0xB140:

Vi leitar eftir veikleikum í UC Browser

Nå blir det en overgang ved offset med indeksering 0x20 fra tabellen.

Basert på størrelsen på tabellen vil det være mange slike overganger i koden. Spørsmålet oppstår: kan vi på en eller annen måte håndtere dette mer automatisert, uten manuelle adresseberegninger? Og skript og muligheten til å patch koden i IDA kommer til vår hjelp:

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 "Feil operandtype på", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
            table = None
        if table is None:
            print "Ikke mulig å finne tabellen"
        else:
            print "tabellen =", hex(table)
            offset = get_wide_dword(table + (index << 2))
            put_unconditional_branch(ea, table + offset)
    else:
        print "Ukjent kode", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
    print "Uklart å oppdage første instruksjon"

Sett markøren på linje 0xB26A, kjør skriptet, og se overgangen til 0xB4B0:

Vi leitar eftir veikleikum í UC Browser

IDA oppdaget igjen ikke dette området som kode. Vi hjelper den og ser en annen konstruksjon:

Vi leitar eftir veikleikum í UC Browser

Instruksjonene etter BLX ser ikke veldig meningsfulle ut, det ligner mer på en eller annen offset. La oss se på sub_4964:

Vi leitar eftir veikleikum í UC Browser

Og faktisk, her blir det hentet en dword fra adressen som ligger i LR, legges til denne adressen, hvoretter verdien fra den berettede adressen tas og legges på stakken. LR legges også til 4, for å hoppe over denne offseten etter retur fra funksjonen. Deretter henter POP {R1} den mottatte verdien fra stakken. Hvis vi ser hva som ligger på adressen 0xB4BA + 0xEA = 0xB5A4, kan vi se noe som ligner på en adresse-tabell:

Vi leitar eftir veikleikum í UC Browser

For patching this structure, you will need to obtain two parameters from the code: the offset and the register number where the result should be placed. You must prepare a snippet of code in advance for each possible register.

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"

Position the cursor at the beginning of the construct you want to replace — 0xB4B2 — and run the script:

Vi leitar eftir veikleikum í UC Browser

In addition to the already mentioned constructs, the code also contains such ones:

Vi leitar eftir veikleikum í UC Browser

As in the previous case, there is an offset after the BLX instruction:

Vi leitar eftir veikleikum í UC Browser

Take the offset from the address in LR, add it to LR, and jump there. 0x72044 + 0xC = 0x72050. The script for this construct is quite simple:

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"

The result of the script execution:

Vi leitar eftir veikleikum í UC Browser

Når alt i funksjonen er patcha, kan vi peke IDA på den virkelige starten. Den vil samle all koden i funksjonen bit for bit, og den kan dekompileres med HexRays.

Dekryptering av strenger

Vi har lært å håndtere obfuskering av maskinkode i biblioteket libsgmainso-6.4.36.so fra UC Browser og fått koden til funksjonen 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;
}

La oss se nærmere på følgende linjer:

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

I funksjonen sub_73E24 er det åpenbart at avkoding av klasse-navnet skjer. Som parametre til denne funksjonen sendes en peker til data som ser ut til å være kryptert, en type buffer, og et tall. Det er tydelig at etter funksjonskallet vil bufferen inneholde den dekodede strengen, ettersom den sendes til funksjonen FindClass, som tar klasse-navnet som det andre parameteret. Dermed er tallet størrelsen på bufferen eller lengden på strengen. La oss forsøke å dekode klasse-navnet, det bør indikere om vi er på riktig vei. La oss se nærmere på hva som skjer i 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;
}

kan sub_7AF78 oppretter en instans av en container for byte-arrayer av angitt størrelse (vi vil ikke gå i detalj om disse containerne). To slike containere blir opprettet: én for teksten «DcO/lcK+h?m3c*q@» (det er ikke vanskelig å gjette at dette er nøkkelen), den andre for de krypterte dataene. Begge objektene legges deretter inn i en struktur som sendes til funksjonen sub_6115C. Vi legger også merke til et felt i denne strukturen med verdien 3. La oss se hva som skjer med denne strukturen videre.

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

Som som parameter switch sender du et felt fra strukturen, som tidligere var tildelt værdien 3. Lad os se case 3: til funktionen sub_6364C de sendes parametre fra strukturen, der blev lagt der i den forrige funktion, dvs. nøgle og krypterede data. Hvis du ser nøje på sub_6364C, kan du finde algoritmen for RC4 i den.

Vi har algoritmen og nøglen. Lad os prøve at dechifrere klassens navn. Her er resultatet: com/taobao/wireless/security/adapter/JNICLibrary. Fremragende! Vi er på rette vej.

Kommandotræet

Nu skal vi finde kaldet RegisterNatives, som vil pege på funktionen doCommandNative. Vi gennemgår funktionerne, der kaldes fra JNI_OnLoad, og finder den i 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;
}

Og faktisk registreres der en natív metode med navnet doCommandNative. Nu kender vi dens adresse. Lad os se, hvad den gør.

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

Ud fra navnet kan vi gætte, at der her ligger indgangspunktet for alle de funktioner, som udviklerne har besluttet at flytte til den natív biblioteker. Vi er interesserede i funktionen nummer 10601.

Fra koden kan vi se, at der fra kommandonummeret findes tre tal: command / 10000, command % 10000 / 100 og command % 10, altså, i vores tilfælde, 1, 6 og 1. Disse tre tal, samt pegeren til JNIEnv og argumenterne sendt til funktionen, lægges i en struktur og sendes videre. Med de tre tal (lad os kalde dem N1, N2 og N3) bygges kommandotræet.

Sådan cirka:

Vi leitar eftir veikleikum í UC Browser

Træet fyldes dynamisk i JNI_OnLoad.
Tre tall kodar vegen i treet. Kvar blad i treet inneheld den tilsvarande funksjonen sin pokserte adresse. Nøkkelen ligg i forelderknuten. Å finne staden i koden der den funksjonen vi treng blir lagt til i treet, er ikkje vanskeleg, viss ein set seg inn i dei strukturar som blir nytta (me gjev ingen omtale av dei, for ikkje å utvide den altfor store artikkelen).

Endå obfuskering

Vi har fått adressen til funksjonen som skal dekryptere trafikken: 0x5F1AC. Men det er for tidleg å glede seg: utviklarane av UC Browser har ein ny overrasking til oss.

Etter å ha motteke parameter frå matrisen som ble danna i Java-koden, kjem vi til
funksjonen med adressen 0x4D070. Og her ventar det oss en ny type obfuskering av koden.

Vi legg to indeksar i R7 og R4:

Vi leitar eftir veikleikum í UC Browser

Vi flyttar den første indeksen til R11:

Vi leitar eftir veikleikum í UC Browser

For å få adressen frå tabellen, nyttar vi indeksen:

Vi leitar eftir veikleikum í UC Browser

Etter å ha gått til den første adressen, blir den andre indeksen som er i R4, nytta. Det er 230 element i tabellen.

Kva skal vi gjere med dette? Vi kan seie til IDA at dette er ein slik switch: Edit -> Other -> Specify switch idiom.

Vi leitar eftir veikleikum í UC Browser

Den resulterande koden er forferdeleg. Men når vi navigerer gjennom den, kan vi merke oss oppropet til den kjente funksjonen vår. sub_6115C:

Vi leitar eftir veikleikum í UC Browser

Der var det en switch der dekrypteringa med å bruke RC4-algoritmen låg i case 3. I dette tilfellet blir strukturen som blir sendt til funksjonen, fylt ut med parameter som blir sendt til doCommandNative. Vi hugsar at vi hadde der magicInt med verdien 16. Vi ser på den tilsvarande casen – og etter fleire overganger finn vi koden som gjer at vi kan identifisere algoritmen.

Vi leitar eftir veikleikum í UC Browser

Det er AES!

Algoritmen er på plass, no gjenstår det å få parameterne: modus, nøkkel og, kanskje, initialiseringsvektor (om det finst, kjem an på modusen til AES-algoritmen). Strukturen med dei må formast før funksjonskallet sub_6115C, men denne delen av koden er spesielt godt obfuskert, så det kjem ideen om å patche koden for å dumpa alle parametrane til dekrypteringsfunksjonen til ei fil.

Patch

For ikkje å måtte skrive heile patch-koden på assembler sjølv, kan ein starte Android Studio, skrive ei funksjon der som tek inn dei same parameterane som dekrypteringsfunksjonen vår, og skriver til ei fil, før ein kopierar koden som kompilatoren genererer.

Vennligst legg merke til at våre venner fra UC Browser-teamet også har "tatt vare på" bekvemmeligheten ved å legge til koden. Vi husker at i starten av hver funksjon har vi søppel kode, som lett kan erstattes med hvilken som helst annen. Veldig praktisk 🙂 Men i begynnelsen av den målrettede funksjonen er det lite plass til koden som lagrer alle parametere i en fil. Vi måtte dele den opp i deler og bruke søppelblokker fra nabofunksjoner. Totalt ble det fire deler.

Første del:

Vi leitar eftir veikleikum í UC Browser

I ARM-arkitekturen overføres de første fire parametrene til funksjonen gjennom registrene R0-R3, de andre, hvis det er noen, går gjennom stakken. Returadressen overføres i LR-registeret. Alle disse må lagres slik at funksjonen kan fungere etter at vi har dumpet dens parametere. Vi må også lagre alle registrene som vi skal bruke i prosessen, så vi gjør PUSH.W {R0-R10,LR}. I R7 får vi adressen til parameterlisten som ble sendt til funksjonen via stakken.

Ved hjelp av funksjonen fopen åpner vi filen /data/local/tmp/aes i "ab"-modus,
dvs. for tillegg. I R0 laster vi adressen til filnavnet, i R1 adressen til strengen med modusangivelsen. Og her slutter søppel koden, så vi går videre til neste funksjon. For at den skal fortsette å fungere, setter vi i begynnelsen en overgang til den ekte kodene i funksjonen forbi søppelen, og erstatter søppel med fortsettelsen av patchen.

Vi leitar eftir veikleikum í UC Browser

Vi kaller fopen.

De første tre parametrene til funksjonen aes har typen int. Siden vi i begynnelsen lagret registrene i stakken, kan vi bare overføre funksjonen fwrite adressene deres i stakken.

Vi leitar eftir veikleikum í UC Browser

Videre har vi tre strukturer som inneholder datastørrelsen og en peker til data for nøkkelen, initialiseringsvektoren og de krypterte dataene.

Vi leitar eftir veikleikum í UC Browser

Til slutt stenger vi filen, gjenoppretter registrene og overfører kontrollen til den ekte funksjonen. aes.

Vi samler APK-en med den patchede biblioteket, signerer, laster opp til enheten/emulatoren, og starter. Vi ser at dumpen vår opprettes, og mye data skrives dit. Nettleseren bruker kryptering ikke bare for trafikken, og all kryptering går gjennom den vurderte funksjonen. Men de nødvendige dataene mangler av en eller annen grunn, og den nødvendige forespørselen er ikke synlig i trafikken. For ikke å vente på at UC Browser skal behage å sende den nødvendige forespørselen, tar vi den krypterte svaret fra serveren, mottatt tidligere, og patcher applikasjonen igjen: vi legger til dekryptering i onCreate av hovedaktiviteten.

    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

Vi samlar, signerar, installerar och kör. Vi får NullPointerException eftersom metoden returnerar null.

Vid en vidare analys av koden upptäcktes en funktion där intressanta rader dekrypteras: «META-INF/» och «.RSA». Det verkar som om applikationen kontrollerar sitt certifikat. Eller till och med genererar nycklar från det. Att gräva i vad som händer med certifikatet känns inte lockande, så vi kommer helt enkelt att ge det rätt certifikat. Vi patchar den krypterade raden så att istället för «META-INF/» blir det «BLABLINF/», skapar en mapp med det namnet i APK:n och lägger dit certifikatet från belkobrowser.

Vi samlar, signerar, installerar och kör. Bingo! Vi har nyckeln!

MitM

Vi har fått nyckeln och initialiseringsvektorn, som är lika med nyckeln. Låt oss försöka dekryptera serverns svar i CBC-läge.

Vi leitar eftir veikleikum í UC Browser

Vi ser URL:en till arkivet, något som liknar MD5, «extract_unzipsize» och ett nummer. Vi kontrollerar: MD5 för arkivet stämmer, storleken på den extraherade biblioteken stämmer. Vi försöker patcha detta bibliotek och ge det till webbläsaren. För att visa att vårt patchade bibliotek har laddats, kommer vi att skicka en Intent för att skapa ett sms med texten «PWNED!». Vi kommer att byta ut två svar från servern: puds.ucweb.com/upgrade/index.xhtml och för nedladdning av arkivet. I det första byter vi MD5 (storleken efter extraheringen ändras inte), i det andra ger vi arkivet med det patchade biblioteket.

Webbläsaren försöker ladda ner arkivet flera gånger, varefter den ger ett fel. Det verkar som att något
inte är rätt. Efter analys av detta luddiga format kom vi fram till att servern också skickar storleken på arkivet:

Vi leitar eftir veikleikum í UC Browser

Den är kodad i LEB128. Efter patchen har storleken på arkivet med biblioteket ändrats något, så webbläsaren trodde att arkivet laddades ner fel och efter några försök gav den ett fel.

Vi justerar storleken på arkivet… Och – seger! 🙂 Resultatet i video.

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

Konsekvenserna och utvecklarens reaktion

På samme måte kunne hackere utnytte den usikre funksjonen i UC Browser for å distribuere og kjøre skadelig programvare. Disse bibliotekene vil fungere innenfor nettleseren, og derfor få alle systemtillatelser. Som et resultat — muligheten til å vise phishing-vinduer, samt tilgang til arbeidsfiler fra den oransje kinesiske ekornet, inkludert lagrede brukernavn, passord og informasjonskapsler i databasen.

Vi tok kontakt med utviklerne av UC Browser og informerte dem om det oppdagede problemet, forsøkte å påpeke sårbarheten og dens farer, men de nektet å diskutere noe med oss. I mellomtiden fortsatte nettleseren å vise frem den farlige funksjonen åpenlyst. Men så snart vi avdekket detaljene om sårbarheten, kunne dette ikke ignoreres som før. Den 27. mars ble
en ny versjon av UC Browser 12.10.9.1193 utgitt, som kontaktet serveren via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

I tillegg, etter «fikseren» og frem til tidspunktet for skrivingen av artikkelen, førte forsøk på å åpne PDF i nettleseren til en feilmelding med teksten «Oisann, noe gikk galt!». Forespørselen til serveren ved forsøk på å åpne PDF ble ikke utført, men forespørselen ved oppstart av nettleseren ble gjennomført, noe som antyder en fortsatt mulighet for å laste opp kjørbar kode i strid med reglene for Google Play.

Kilde: habr.com

Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere 🔥 Kjøp pålitelig hosting for nettsteder med DDoS-beskyttelse, VPS VDS-servere | ProHoster