Zoeken naar kwetsbaarheden in UC Browser

Zoeken naar kwetsbaarheden in UC Browser

Inleiding

Eind maart hebben we gerapporteerd, dat we een verborgen mogelijkheid hadden ontdekt om niet-geverifieerde code te laden en uit te voeren in UC Browser. Vandaag kijken we in detail hoe deze laadfunctie werkt en hoe hackers deze voor hun doeleinden kunnen gebruiken.

Enige tijd geleden werd UC Browser zeer agressief gepromoot en verspreid: het werd op apparaten van gebruikers geïnstalleerd met behulp van malware, verspreid vanuit verschillende sites onder het mom van videobestanden (d.w.z. gebruikers dachten dat ze een pornofilmpje aan het downloaden waren, maar kregen in plaats daarvan een APK van deze browser), en er werden angstaanjagende banners gebruikt met berichten dat de browser verouderd, kwetsbaar was, en dergelijke. In de officiële UC Browser-groep op VK is er een onderwerp, waar gebruikers verdachte reclame kunnen rapporteren, daar zijn veel voorbeelden. In 2016 was er zelfs videoreclame in het Russisch (ja, reclame voor een browser die advertenties blokkeert).

Op het moment van schrijven had UC Browser meer dan 500.000.000 installaties in Google Play. Dat is indrukwekkend — alleen Google Chrome heeft meer. Onder de recensies zijn er voldoende klachten over advertenties en omleidingen naar bepaalde apps in Google Play. Dit was de aanleiding voor ons onderzoek: we besloten te kijken of UC Browser iets ongehoords deed. En het bleek van wel!

In de code van de applicatie werd een mogelijkheid ontdekt om uitvoerbare code te laden en uit te voeren, wat in strijd is met de richtlijnen voor app-publicatie in Google Play. Naast het feit dat UC Browser uitvoerbare code laadt, doet het dit op een onveilige manier, wat kan worden gebruikt voor een MitM-aanval. Laten we kijken of we zo'n aanval kunnen uitvoeren.

Alles wat hierna is geschreven, is van toepassing op de versie van UC Browser die op Google Play aanwezig was op het moment van het onderzoek:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 van het APK-bestand: f5edb2243413c777172f6362876041eb0c3a928c

Aanval vector

In het manifest van UC Browser is er een service met de veelzeggende naam com.uc.deployment.UpgradeDeployService.

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

Bij het starten van deze service voert de browser een POST-verzoek uit naar puds.ucweb.com/upgrade/index.xhtml, dat na verloop van tijd in het verkeer kan worden opgemerkt na de start. Als reactie kan het een opdracht ontvangen om een bepaalde update of nieuw module te downloaden. Tijdens de analyse gaf de server geen dergelijke opdrachten, maar we merkten op dat bij het proberen om een PDF in de browser te openen, het een herhaalde aanvraag naar het hierboven vermelde adres doet, waarna het de native bibliotheek downloadt. Voor de aanval besloten we deze eigenschap van UC Browser te gebruiken: het vermogen om PDF's te openen met behulp van een native bibliotheek die niet in de APK aanwezig is en die het indien nodig vanuit het internet laadt. Het is de moeite waard te vermelden dat UC Browser theoretisch iets kan laten downloaden zonder interactie van de gebruiker - als het een correct gevormd antwoord op de aanvraag geeft die wordt uitgevoerd na het starten van de browser. Maar daarvoor moet het protocol van de interactie met de server uitgebreider worden bestudeerd, daarom hebben we besloten dat het eenvoudiger was om het onderschepte antwoord te bewerken en de bibliotheek voor het werken met PDF's te vervangen.

Dus als de gebruiker een PDF direct in de browser wil openen, kunnen de volgende aanvragen in het verkeer worden gezien:

Zoeken naar kwetsbaarheden in UC Browser

Eerst is er een POST-aanroep naar puds.ucweb.com/upgrade/index.xhtml, waarna
een archief met de bibliotheek voor het bekijken van PDF's en kantoorformaten wordt gedownload. Het is logisch om aan te nemen dat in de eerste aanvraag informatie over het systeem wordt doorgestuurd (ten minste, de architectuur, zodat de juiste bibliotheek wordt geleverd), en als antwoord ontvangt de browser enige informatie over de bibliotheek die gedownload moet worden: het adres en, mogelijk, nog iets anders. Het probleem is dat deze aanvraag versleuteld is.

Fragment van de aanvraag

Fragment van de respons

Zoeken naar kwetsbaarheden in UC Browser

Zoeken naar kwetsbaarheden in UC Browser

Zelfs de bibliotheek is verpakt in een ZIP en niet versleuteld.

Zoeken naar kwetsbaarheden in UC Browser

Zoek de code voor het ontsleutelen van het verkeer

Laten we proberen het antwoord van de server te ontsleutelen. We bekijken de klascode com.uc.deployment.UpgradeDeployService: van de methode onStartCommand gaan we naar com.uc.deployment.b.x, en daarvandaan naar 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("]product:");
            v4.append(arg9.iGX.ipR);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]version:");
            v4.append(arg9.iGX.iEn);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]upgrade_type:");
            v4.append(arg9.iGX.mMode);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]force_flag:");
            v4.append(arg9.iGX.iEo);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]silent_mode:");
            v4.append(arg9.iGX.iDQ);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]silent_type:");
            v4.append(arg9.iGX.iEr);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]silent_state:");
            v4.append(arg9.iGX.iEp);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]silent_file:");
            v4.append(arg9.iGX.iEq);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apk_md5:");
            v4.append(arg9.iGX.iEl);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]download_type:");
            v4.append(arg9.mDownloadType);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]download_group:");
            v4.append(arg9.mDownloadGroup);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]download_path:");
            v4.append(arg9.iGH);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_child_version:");
            v4.append(arg9.iGX.iEx);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_series:");
            v4.append(arg9.iGX.iEw);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_cpu_arch:");
            v4.append(arg9.iGX.iEt);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_cpu_vfp3:");
            v4.append(arg9.iGX.iEv);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_cpu_vfp:");
            v4.append(arg9.iGX.iEu);
            ArrayList v3_2 = arg9.iGX.iEz;
            if(v3_2 != null && v3_2.size() != 0) {
                Iterator v3_3 = v3_2.iterator();
                while(v3_3.hasNext()) {
                    Object v4_1 = v3_3.next();
                    StringBuilder v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]component_name:");
                    v5.append(((au)v4_1).getName());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]component_ver_name:");
                    v5.append(((au)v4_1).aDA());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]component_ver_code:");
                    v5.append(((au)v4_1).gBl);
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]component_req_type:");
                    v5.append(((au)v4_1).gBq);
                }
            }
 
            j v3_4 = new j();
            m.b(v3_4);
            h v4_2 = new h();
            m.b(v4_2);
            ay v5_1 = new ay();
            v3_4.hS("");
            v3_4.setImsi("");
            v3_4.hV("");
            v5_1.bPQ = v3_4;
            v5_1.bPP = v4_2;
            v5_1.yr(arg9.iGX.ipR);
            v5_1.gBF = arg9.iGX.mMode;
            v5_1.gBI = arg9.iGX.iEz;
            v3_2 = v5_1.gAr;
            c.aBh();
            v3_2.add(g.fs("os_ver", c.getRomInfo()));
            v3_2.add(g.fs("processor_arch", com.uc.b.a.a.c.getCpuArch()));
            v3_2.add(g.fs("cpu_arch", com.uc.b.a.a.c.Pb()));
            String v4_3 = com.uc.b.a.a.c.Pd();
            v3_2.add(g.fs("cpu_vfp", v4_3));
            v3_2.add(g.fs("net_type", String.valueOf(com.uc.base.system.a.Jo())));
            v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
            v3_2.add(g.fs("plugin_ver", arg9.iGX.iEn));
            v3_2.add(g.fs("target_lang", arg9.iGX.iEs));
            v3_2.add(g.fs("vitamio_cpu_arch", arg9.iGX.iEt));
            v3_2.add(g.fs("vitamio_vfp", arg9.iGX.iEu));
            v3_2.add(g.fs("vitamio_vfp3", arg9.iGX.iEv));
            v3_2.add(g.fs("plugin_child_ver", arg9.iGX.iEx));
            v3_2.add(g.fs("ver_series", arg9.iGX.iEw));
            v3_2.add(g.fs("child_ver", r.aVw()));
            v3_2.add(g.fs("cur_ver_md5", arg9.iGX.iEl));
            v3_2.add(g.fs("cur_ver_signature", SystemHelper.getUCMSignature()));
            v3_2.add(g.fs("upgrade_log", i.bjt()));
            v3_2.add(g.fs("silent_install", String.valueOf(arg9.iGX.iDQ)));
            v3_2.add(g.fs("silent_state", String.valueOf(arg9.iGX.iEp)));
            v3_2.add(g.fs("silent_file", arg9.iGX.iEq));
            v3_2.add(g.fs("silent_type", String.valueOf(arg9.iGX.iEr)));
            v3_2.add(g.fs("cpu_archit", com.uc.b.a.a.c.Pc()));
            v3_2.add(g.fs("cpu_set", SystemHelper.getCpuInstruction()));
            boolean v4_4 = v4_3 == null || !v4_3.contains("neon") ? false : true;
            v3_2.add(g.fs("neon", String.valueOf(v4_4)));
            v3_2.add(g.fs("cpu_cores", String.valueOf(com.uc.b.a.a.c.Jl())));
            v3_2.add(g.fs("ram_1", String.valueOf(com.uc.b.a.a.h.Po())));
            v3_2.add(g.fs("totalram", String.valueOf(com.uc.b.a.a.h.OL())));
            c.aBh();
            v3_2.add(g.fs("rom_1", c.getRomInfo()));
            v4_5 = e.getScreenWidth();
            int v6 = e.getScreenHeight();
            StringBuilder v7 = new StringBuilder();
            v7.append(v4_5);
            v7.append("*");
            v7.append(v6);
            v3_2.add(g.fs("ss", v7.toString()));
            v3_2.add(g.fs("api_level", String.valueOf(Build$VERSION.SDK_INT)));
            v3_2.add(g.fs("uc_apk_list", SystemHelper.getUCMobileApks()));
            Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
            while(v4_6.hasNext()) {
                Object v6_1 = v4_6.next();
                v3_2.add(g.fs(((Map$Entry)v6_1).getKey(), ((Map$Entry)v6_1).getValue()));
            }
 
            v3 = v5_1.toByteArray();
        }
 
        if(v3 == null) {
            this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
            return;
        }
 
        v4_5 = this.iGY.iGw ? 0x1F : 0;
        if(v3 == null) {
        }
        else {
            v3 = g.i(v4_5, v3);
            if(v3 == null) {
            }
            else {
                v1 = new byte[v3.length + 16];
                byte[] v6_2 = new byte[16];
                Arrays.fill(v6_2, 0);
                v6_2[0] = 0x5F;
                v6_2[1] = 0;
                v6_2[2] = ((byte)v4_5);
                v6_2[3] = -50;
                System.arraycopy(v6_2, 0, v1, 0, 16);
                System.arraycopy(v3, 0, v1, 16, v3.length);
            }
        }
 
        if(v1 == null) {
            this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
            return;
        }
 
        if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
            this.iGY.iGI.a(arg9, "up_url", "yes", "fail");
            return;
        }
 
        StringBuilder v0 = new StringBuilder("[");
        v0.append(arg9.iGX.ipR);
        v0.append("]url:");
        v0.append(this.iGY.mUpgradeUrl);
        com.uc.browser.core.d.c.i v0_1 = this.iGY.iGI;
        v3_1 = this.iGY.mUpgradeUrl;
        com.uc.base.net.e v0_2 = new com.uc.base.net.e(new com.uc.browser.core.d.c.i$a(v0_1, arg9));
        v3_1 = v3_1.contains("?") ? v3_1 + "&dataver=pb" : v3_1 + "?dataver=pb";
        n v3_5 = v0_2.uc(v3_1);
        m.b(v3_5, false);
        v3_5.setMethod("POST");
        v3_5.setBodyProvider(v1);
        v0_2.b(v3_5);
        this.iGY.iGI.a(arg9, "up_null", "yes", "success");
        this.iGY.iGI.b(arg9);
    }

Hier zien we de formatie van een POST-verzoek. Let op het creƫren van een array van 16 bytes en de invulling ervan: 0x5F, 0, 0x1F, -50 (=0xCE). Dit komt overeen met wat we in het vorige verzoek hebben gezien.

In dezelfde klasse kun je een geneste klasse opmerken, waarin er een andere interessante methode is:

        public final void a(l arg10, byte[] arg11) {
            f v0 = this.iGQ;
            StringBuilder v1 = new StringBuilder("[");
            v1.append(arg10.iGX.ipR);
            v1.append("]:UpgradeSuccess");
            byte[] v1_1 = null;
            if(arg11 == null) {
            }
            else if(arg11.length < 16) {
            }
            else {
                if(arg11[0] != 0x60 && arg11[3] != 0xFFFFFFD0) {
                    goto label_57;
                }
                int v3 = 1;
                int v5 = arg11[1] == 1 ? 1 : 0;
                if(arg11[2] != 1 && arg11[2] != 11) {
                    if(arg11[2] == 0x1F) {
                    }
                    else {
                        v3 = 0;
                    }
                }
                byte[] v7 = new byte[arg11.length - 16];
                System.arraycopy(arg11, 16, v7, 0, v7.length);
                if(v3 != 0) {
                    v7 = g.j(arg11[2], v7);
                }
                if(v7 == null) {
                    goto label_57;
                }
                if(v5 != 0) {
                    v1_1 = g.P(v7);
                    goto label_57;
                }
                v1_1 = v7;
            }
        label_57:
            if(v1_1 == null) {
                v0.iGY.iGI.a(arg10, "up_decrypt", "yes", "fail");
                return;
            }
            q v11 = g.b(arg10, v1_1);
            if(v11 == null) {
                v0.iGY.iGI.a(arg10, "up_decode", "yes", "fail");
                return;
            }
            if(v0.iGY.iGt) {
                v0.d(arg10);
            }
            if(v0.iGY.iGo != null) {
                v0.iGY.iGo.a(0, ((o)v11));
            }
            if(v0.iGY.iGs) {
                v0.iGY.a(((o)v11));
                v0.iGY.iGI.a(v11, "up_silent", "yes", "success");
                v0.iGY.iGI.a(v11);
                return;
            }
            v0.iGY.iGI.a(v11, "up_silent", "no", "success");
        }
    }

De methode ontvangt een byte-array en controleert of de nul-byte gelijk is aan 0x60 of de derde byte gelijk is aan 0xD0, en of de tweede byte gelijk is aan 1, 11 of 0x1F. Laten we het antwoord van de server bekijken: de nul-byte is 0x60, de tweede is 0x1F, de derde is 0x60. Het lijkt erop dat dit is wat we nodig hebben. Uit de regels (bijvoorbeeld "up_decrypt") blijkt dat hier een methode moet worden aangeroepen die het antwoord van de server decodeert.
Laten we verder gaan met de methode g.j. We merken op dat als eerste argument de byte op offset 2 wordt doorgegeven (d.w.z. 0x1F in ons geval), en als tweede argument het antwoord van de server zonder
de eerste 16 bytes.

     public static byte[] j(int arg1, byte[] arg2) {
        if(arg1 == 1) {
            arg2 = c.c(arg2, c.adu);
        }
        else if(arg1 == 11) {
            arg2 = m.aF(arg2);
        }
        else if(arg1 != 0x1F) {
        }
        else {
            arg2 = EncryptHelper.decrypt(arg2);
        }
        return arg2;
    }

Hier lijkt een selectie van het decryptie-algoritme plaats te vinden, en diezelfde byte, die in ons geval
Als het gelijk is aan 0x1F, betekent dit een van de drie mogelijke opties.

We gaan verder met de code-analyse. Na een paar sprongen komen we bij de methode met de sprekende naam decryptBytesByKey.

Hier worden nog eens twee bytes van ons antwoord gescheiden, en daaruit wordt een string gevormd. Het is duidelijk dat op deze manier de sleutel voor het ontsleutelen van het bericht wordt geselecteerd.

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

Terugkijkend merken we op dat er op dit moment nog geen sleutel is verkregen, maar alleen de 'identificator'. Het verkrijgen van de sleutel is iets complexer.

In de volgende methode worden er nog twee parameters toegevoegd aan de bestaande, waardoor er vier worden: het magische getal 16, de sleutelidentificator, de versleutelde gegevens en een onduidelijke string (in ons geval leeg).

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

Na een reeks overgangen komen we bij de methode staticBinarySafeDecryptNoB64 interface com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. In de hoofdcode van de applicatie zijn er geen klassen die deze interface implementeren. Zo'n klasse is te vinden in het bestand lib/armeabi-v7a/libsgmain.so, dat in werkelijkheid geen .so is, maar een .jar. De voor ons interessante methode is als volgt geĆÆmplementeerd:

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

Hier wordt onze lijst van parameters aangevuld met nog twee gehele getallen: 2 en 0. Blijkbaar
beduidt 2 decryptie, zoals in de methode doFinal van de systeemklasse javax.crypto.Cipher. En dit alles wordt doorgegeven aan een bepaalde Router met het nummer 10601 — dat is blijkbaar het commandonummer.

Na een reeks overgangen vinden we de klasse die het interface IRouterComponent en de methode doCommand:

package com.alibaba.wireless.security.mainplugin;

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

En ook de klasse JNICLibrary, waar de native methode wordt gedeclareerd doCommandNative:

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

Dus, we moeten in de native code de methode vinden doCommandNative. En hier begint het echte plezier.

Obfuscatie van machinecode

In het bestand libsgmain.so (dat in feite .jar is en waarin we hierboven de implementatie van enkele interfaces met betrekking tot encryptie hebben gevonden) is er ƩƩn native bibliotheek: libsgmainso-6.4.36.so. We openen het in IDA en krijgen een hoop dialoogvensters met fouten. Het probleem is dat de sectietabel (section header table) – ongeldig is. Dit is opzettelijk gedaan om de analyse te bemoeilijken.

Zoeken naar kwetsbaarheden in UC Browser

Maar het is niet nodig: om een ELF-bestand correct te laden en te analyseren, is het alleen nodig om de segmententabel (program header table) te hebben. Daarom verwijderen we gewoon de sectietabel door de overeenkomstige velden in de header op nul te zetten.

Zoeken naar kwetsbaarheden in UC Browser

We openen het bestand opnieuw in IDA.

Er zijn twee manieren om de virtuele Java-machine te vertellen waar precies in de native bibliotheek de implementatie van de methode bevindt die in Java-code als native is gedeclareerd. De eerste — geef hem een naam als Java_naam_pakket_NaamKlasse_naamMethode.

Ten tweede — deze registreren bij het laden van de bibliotheek (in de functie JNI_OnLoad)
met een functieaanroep RegisterNatives.

In ons geval, als we de eerste manier gebruiken, moet de naam als volgt zijn: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Er is geen dergelijke functie tussen de geƫxporteerde functies, wat betekent dat we naar een aanroep moeten zoeken. RegisterNatives.
Laten we naar de functie gaan JNI_OnLoad en zien we het volgende:

Zoeken naar kwetsbaarheden in UC Browser

Wat is hier aan de hand? Op het eerste gezicht zijn de begin- en eindpunten van de functie typisch voor de ARM-architectuur. De eerste instructie slaat de inhoud van de registers op die de functie in zijn werk zal gebruiken (in dit geval R0, R1 en R2), evenals de inhoud van het LR-register, waarin het retouradres uit de functie zich bevindt. De laatste instructie herstelt de opgeslagen registers, terwijl het retouradres meteen in het PC-register wordt geplaatst — op deze manier vindt de terugkeer uit de functie plaats. Maar als je goed kijkt, kun je merken dat de ƩƩn-na-laatste instructie het retouradres, dat in de stapel is opgeslagen, wijzigt. Laten we berekenen wat het zal zijn na
de uitvoering van de code. In R1 wordt een bepaald adres 0xB130 geladen, daar wordt 5 van afgetrokken, vervolgens wordt dit in R0 geplaatst en er wordt 0x10 bij opgeteld. Dat geeft 0xB13B. Op deze manier denkt IDA dat de laatste instructie een normale terugkeer uit de functie is, terwijl in werkelijkheid een sprong naar het berekende adres 0xB13B plaatsvindt.

Het is belangrijk om te onthouden dat ARM-processors twee modi en twee instructiesets hebben: ARM en Thumb. De laagste bit van het adres vertelt de processor welke instructieset wordt gebruikt. Dat is, het adres is in feite 0xB13A, en de een in de laagste bit geeft de Thumb-modus aan.

Aan het begin van elke functie in deze bibliotheek is een vergelijkbare "overgang" toegevoegd en
onbruikbare code. Laten we hier niet verder op ingaan — laten we gewoon onthouden,
dat het werkelijke begin van bijna alle functies iets verder ligt.

Aangezien er geen expliciete sprong naar 0xB13A in de code is, heeft IDA zelf niet herkend dat er code op deze plek staat. Om dezelfde reden herkent het een groot deel van de code in de bibliotheek niet als code, wat de analyse enigszins bemoeilijkt. We vertellen IDA dat hier code is, en dit is wat we krijgen:

Zoeken naar kwetsbaarheden in UC Browser

Op 0xB144 begint duidelijk de tabel. En wat is er in sub_494C?

Zoeken naar kwetsbaarheden in UC Browser

Bij het aanroepen van deze functie krijgen we het eerder genoemde adres van de tabel (0xB144) in het LR-register. In R0 — de index in deze tabel. Dat betekent dat de waarde uit de tabel wordt genomen, opgeteld bij LR en het resultaat is.
adres waar we heen moeten gaan. Laten we het proberen te berekenen: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. We gaan naar het verkregen adres en zien letterlijk een paar nuttige instructies en weer een overgang naar 0xB140:

Zoeken naar kwetsbaarheden in UC Browser

Nu zal het een overgang zijn op basis van de index 0x20 uit de tabel.

Gezien de grootte van de tabel, zullen er veel van dergelijke overgangen in de code voorkomen. De vraag is, kunnen we hier op een meer geautomatiseerde manier mee omgaan, zonder handmatige adresberekeningen. En scripts en de mogelijkheid om de code in IDA te patchen komen ons te hulp:

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 "Onjuist operandtype op", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
            table = None
        if table is None:
            print "Niet in staat de tabel te vinden"
        else:
            print "tabel =", hex(table)
            offset = get_wide_dword(table + (index << 2))
            put_unconditional_branch(ea, table + offset)
    else:
        print "Onbekende code", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
    print "Niet in staat de eerste instructie te detecteren"

Plaats de muiscursor op regel 0xB26A, voer het script uit en we zien een overgang naar 0xB4B0:

Zoeken naar kwetsbaarheden in UC Browser

IDA herkende dit gedeelte opnieuw niet als code. We helpen haar en zien daar een andere constructie:

Zoeken naar kwetsbaarheden in UC Browser

De instructies na BLX lijken niet erg zinvol, dit lijkt meer op een soort offset. Laten we kijken naar sub_4964:

Zoeken naar kwetsbaarheden in UC Browser

En inderdaad, hier wordt een dword op het adres dat in LR staat genomen, bij dit adres opgeteld, waarna de waarde op het verkregen adres wordt genomen en op de stapel wordt geplaatst. Ook wordt er 4 bij LR opgeteld om na de functieaanroep deze offset te overslaan. Daarna haalt het commando POP {R1} de verkregen waarde van de stapel. Als we kijken naar wat zich op het adres 0xB4BA + 0xEA = 0xB5A4 bevindt, dan kunnen we iets zien dat lijkt op een adressenlijst:

Zoeken naar kwetsbaarheden in UC Browser

Om deze constructie te patchen, moet je twee parameters uit de code halen: de offset en het registernummer waarin je het resultaat wilt plaatsen. Voor elk mogelijk register moet van tevoren een stuk code worden voorbereid.

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-instructie niet gevonden"
    else:
        print "Verkeerd operandtype op +4:", get_operand_type(ea + 4, 0)
else:
    print "Kan de eerste instructies niet detecteren"

Plaats de cursor aan het begin van de constructie die we willen vervangen — 0xB4B2 — en start het script:

Zoeken naar kwetsbaarheden in UC Browser

Naast de reeds genoemde constructies komen in de code ook deze voor:

Zoeken naar kwetsbaarheden in UC Browser

Net als in het vorige geval, volgt na de BLX-instructie een offset:

Zoeken naar kwetsbaarheden in UC Browser

Neem de offset van het adres in LR, tel deze op bij LR en ga daarheen. 0x72044 + 0xC = 0x72050. Het script voor deze constructie is heel eenvoudig:

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 "Kan de eerste instructie niet detecteren"

Resultaat van het script:

Zoeken naar kwetsbaarheden in UC Browser

Nadat de functie is geüpdatet, kunnen we IDA op het werkelijke begin erop aanwijzen. Het zal de hele code van de functie stuk voor stuk verzamelen, en deze kan worden gedecodeerd met HexRays.

Decryptie van strings

We hebben geleerd om obfuscatie van machinecode te bestrijden in de bibliotheek libsgmainso-6.4.36.so van UC Browser en hebben de functiecode verkregen 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;
}

Laten we de volgende regels eens beter bekijken:

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

In de functie sub_73E24 vindt duidelijk de decryptie van de klassenaam plaats. Deze functie neemt een pointer naar gegevens die lijken op versleutelde gegevens, een buffer en een getal als parameters. Het is duidelijk dat na de aanroep van de functie de buffer een gedecryptete string zal bevatten, aangezien deze aan de functie wordt doorgegeven FindClass, die als tweede parameter de klassenaam accepteert. Dit betekent dat het getal de grootte van de buffer of de lengte van de string is. Laten we proberen de klassenaam te decrypten; deze moet ons vertellen of we in de goede richting gaan. Laten we eens kijken naar wat er gebeurt in 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;
}

Functie sub_7AF78 maakt een instantie van een container voor byte-arrays van de opgegeven grootte (we gaan niet in detail in op deze containers). Hier worden er twee van deze containers aangemaakt: in de ene komt de string terecht "DcO/lcK+h?m3c*q@" (het is niet moeilijk te raden dat dit de sleutel is), in de andere – de versleutelde gegevens. Vervolgens worden beide objecten in een bepaalde structuur geplaatst, die aan de functie wordt doorgegeven sub_6115C. We merken ook op dat er in deze structuur een veld met de waarde 3 is. Laten we eens bekijken wat er daarna met deze structuur gebeurt.

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

Als parameter switch wordt een veld van de structuur doorgegeven dat eerder de waarde 3 kreeg. We kijken naar case 3: in de functie sub_6364C worden parameters uit de structuur doorgegeven die daar eerder in de functie zijn geplaatst, dat wil zeggen, de sleutel en de versleutelde gegevens. Als we goed kijken naar sub_6364C, kunnen we het RC4-algoritme hierin vinden.

We hebben een algoritme en een sleutel. Laten we proberen de naam van de klasse te ontcijferen. Dit is wat we kregen: com/taobao/wireless/security/adapter/JNICLibrary. Geweldig! We zijn op de goede weg.

Commandoboom

Nu moeten we de oproep vinden RegisterNatives, die ons naar de functie zal wijzen doCommandNative. We bekijken de functies die worden aangeroepen vanuit JNI_OnLoad, en vinden het in sub_B7B0:

int __fastcall sub_B7F6(JNIEnv *env, jclass clazz)
{
  char signature[41]; // [sp+7h] [bp-55h]
  char name[16]; // [sp+30h] [bp-2Ch]
  JNINativeMethod method; // [sp+40h] [bp-1Ch]
  int v8; // [sp+4Ch] [bp-10h]
 
  v8 = *(_DWORD *)off_8AC00;
  decryptString((unsigned __int8 *)&unk_83ED9, (unsigned __int8 *)name, 0x10u); // doCommandNative
  decryptString((unsigned __int8 *)&unk_83EEA, (unsigned __int8 *)signature, 0x29u); // (I[Ljava/lang/Object;)Ljava/lang/Object;
  method.name = name;
  method.signature = signature;
  method.fnPtr = sub_B69C;
  return ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, int))(*env)->RegisterNatives)(env, clazz, &method, 1) >> 31;
}

En inderdaad, hier wordt de native methode geregistreerd met de naam doCommandNative. Nu kennen we het adres. Laten we bekijken wat het doet.

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

Aan de naam te zien, kunnen we raden dat hier het toegangspunt is voor alle functies die de ontwikkelaars besloten hebben naar de native bibliotheek te verplaatsen. We zijn geĆÆnteresseerd in de functie met nummer 10601.

Uit de code is te zien dat uit het commandonummer drie getallen worden gehaald: command / 10000, command % 10000 / 100 en command % 10, dat wil zeggen, in ons geval, 1, 6 en 1. Deze drie getallen, evenals een pointer naar JNIEnv en de argumenten die aan de functie zijn doorgegeven, worden in een structuur samengevoegd en verder doorgegeven. Met de verkregen drie getallen (laten we ze N1, N2 en N3 noemen) wordt de commandoboom gebouwd.

Ongeveer zo:

Zoeken naar kwetsbaarheden in UC Browser

De boom wordt dynamisch gevuld in JNI_OnLoad.
Drie getallen coderen het pad in de boom. Elke bladknop van de boom bevat het gepersonaliseerde adres van de bijbehorende functie. De sleutel ligt in de bovenliggende knoop. Het is niet moeilijk om de plek in de code te vinden waar de benodigde functie aan de boom wordt toegevoegd, zolang je de gebruikte structuren begrijpt (de beschrijving hiervan laten we achterwegen om de al uitgebreide tekst niet verder uit te breiden).

Meer obfuscatie

We hebben het adres van de functie gekregen die het verkeer moet ontsleutelen: 0x5F1AC. Maar wees nog niet te blij: de ontwikkelaars van UC Browser hebben nog een verrassing voor ons voorbereid.

Na het verkrijgen van parameters uit de array, die in Java-code was gevormd, komen we
in de functie op adres 0x4D070. En hier wacht ons weer een andere vorm van code-obfuscatie.

We plaatsen twee indexen in R7 en R4:

Zoeken naar kwetsbaarheden in UC Browser

We verplaatsen de eerste index naar R11:

Zoeken naar kwetsbaarheden in UC Browser

Om het adres uit de tabel te krijgen, gebruiken we de index:

Zoeken naar kwetsbaarheden in UC Browser

Na de overgang naar het eerste adres gebruiken we de tweede index, die in R4 is. De tabel bevat 230 elementen.

Wat moeten we hiermee doen? We kunnen IDA vertellen dat dit een switch is: Bewerken -> Overige -> Specificeer switch-idioom.

Zoeken naar kwetsbaarheden in UC Browser

De verkregen code is vreselijk. Maar al snuffelend door de jungle ervan, kun je de aanroep van de functie ontdekken die we al kennen. sub_6115C:

Zoeken naar kwetsbaarheden in UC Browser

Er was een switch waarin in case 3 de ontsleuteling met behulp van het RC4-algoritme plaatsvond. In dit geval wordt de structuur die aan de functie wordt doorgegeven, ingevuld met parameters die zijn doorgegeven in doCommandNative. We herinneren ons dat we daar magicInt met de waarde 16 hadden. Kijkend naar de bijbehorende case – en na een paar overgangen vinden we de code waarmee we het algoritme kunnen herkennen.

Zoeken naar kwetsbaarheden in UC Browser

Dit is AES!

Het algoritme is er, het enige wat we nog moeten doen is zijn parameters verkrijgen: modus, sleutel en wellicht een initialisatievector (de aanwezigheid daarvan hangt af van de werkmodus van het AES-algoritme). De structuur met deze parameters moet ergens vóór de aanroep van de functie sub_6115C, maar dit deel van de code is bijzonder goed obfusceerd, dus ontstaat het idee om de code te patchen zodat alle parameters van de ontsleuteling in een bestand worden gedumpt.

Patch

Om niet handmatig de hele patch-code in assemblytaal te hoeven schrijven, kunnen we Android Studio starten, een functie schrijven die dezelfde parameters als onze ontsleutelingsfunctie ontvangt, en in een bestand schrijft, waarna we de code kunnen kopiƫren die door de compiler wordt gegenereerd.

Onze vrienden van het UC Browser-team hebben ook gezorgd voor het gemak van het toevoegen van code. We herinneren ons dat aan het begin van elke functie rommelcode staat, die gemakkelijk door elke andere kan worden vervangen. Heel handig šŸ™‚ Echter, aan het begin van de doel-functie is er niet veel ruimte voor de code die alle parameters naar een bestand opslaat. We moesten het in delen splitsen en gebruik maken van de rommelblokken van aangrenzende functies. Uiteindelijk werden het vier delen.

Eerste deel:

Zoeken naar kwetsbaarheden in UC Browser

In de ARM-architectuur worden de eerste vier functieparameters doorgegeven via de registers R0-R3, de overige, indien aanwezig, via de stack. Het retouradres wordt in het register LR opgeslagen. Dit moet worden bewaard zodat de functie kan blijven werken nadat we zijn parameters hebben gedumpt. We moeten ook alle registers bewaren die we tijdens het proces gaan gebruiken, daarom doen we PUSH.W {R0-R10,LR}. In R7 krijgen we het adres van de lijst met parameters die via de stack aan de functie zijn doorgegeven.

Met de functie fopen openen we het bestand /data/local/tmp/aes in de "ab"-modus,
dat wil zeggen, voor toevoeging. In R0 laden we het adres van de bestandsnaam, in R1 het adres van de string met de modus. En hier eindigt de rommelcode, dus we gaan naar de volgende functie. Om deze functie door te laten gaan, zetten we aan het begin een spron naar de echte code van de functie om de rommel heen, en in plaats van rommel voegen we de voortzetting van de patch toe.

Zoeken naar kwetsbaarheden in UC Browser

We roepen aan fopen.

De eerste drie parameters van de functie aes hebben het type int. Aangezien we in het begin de registers in de stack hebben opgeslagen, kunnen we eenvoudig de adressen van deze parameters in de stack doorgeven aan de functie. fwrite Vervolgens hebben we drie structuren die de grootte van de gegevens en een pointer naar de gegevens bevatten voor de sleutel, de initialisatievector en de versleutelde gegevens.

Zoeken naar kwetsbaarheden in UC Browser

Aan het eind sluiten we het bestand, herstellen we de registers en geven we de controle terug aan de echte functie.

Zoeken naar kwetsbaarheden in UC Browser

We verzamelen de APK met de gepatchte bibliotheek, ondertekenen deze, zetten deze op het apparaat/emulator en starten deze. We zien dat onze dump wordt gemaakt en er veel gegevens worden geschreven. De browser gebruikt encryptie niet alleen voor verkeer, en alle encryptie gaat via de besproken functie. Maar om de een of andere reden ontbreken de benodigde gegevens en is de benodigde aanvraag in het verkeer niet zichtbaar. Om niet te wachten tot UC Browser de nodige aanvraag doet, nemen we het versleutelde antwoord van de server, dat we eerder hebben verkregen, en patchen de applicatie nog een keer: we voegen de decryptie toe in onCreate van de hoofdactiviteit. aes.

We verzamelen de APK met de aangepaste bibliotheek, ondertekenen deze, plaatsen deze op het apparaat/emulator en starten deze. We zien dat onze dump wordt aangemaakt en dat er veel gegevens worden vastgelegd. De browser gebruikt encryptie niet alleen voor het verkeer, en alle encryptie verloopt via de besproken functie. Maar om de een of andere reden zijn de benodigde gegevens er niet, en in het verkeer is de benodigde aanvraag niet zichtbaar. Om niet te wachten totdat UC Browser de benodigde aanvraag doet, nemen we het gecodeerde antwoord van de server, dat we eerder hebben ontvangen, en patchen we de applicatie opnieuw: we voegen decryptie toe in onCreate van de hoofdactiviteit.

    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

We verzamelen, ondertekenen, installeren en starten. We krijgen een NullPointerException omdat de methode null teruggeef.

Tijdens een verdere analyse van de code werd een functie ontdekt waarin interessante regels worden ontcijferd: 'META-INF/' en '.RSA'. Het lijkt erop dat de applicatie zijn certificaat controleert. Of zelfs sleutels hieruit genereert. Het is helemaal niet leuk om te begrijpen wat er met het certificaat gebeurt, dus laten we het gewoon een correct certificaat geven. We patchen de versleutelde regel zo dat in plaats van 'META-INF/' 'BLABLINF/' verschijnt, we maken een map met deze naam aan in de APK en stoppen daar het certificaat van de belbrowser in.

We verzamelen, ondertekenen, installeren en starten. Bingo! We hebben de sleutel!

MitM

We hebben de sleutel en het initialisatievector, gelijk aan de sleutel. Laten we proberen het antwoord van de server te ontcijferen in CBC-modus.

Zoeken naar kwetsbaarheden in UC Browser

We zien de URL van het archief, iets dat lijkt op MD5, 'extract_unzipsize' en een getal. We controleren: de MD5 van het archief komt overeen, de grootte van de uitgepakte bibliotheek komt overeen. We proberen deze bibliotheek te patchen en aan de browser te geven. Om te laten zien dat onze gepatchte bibliotheek is geladen, gaan we een Intent starten om een SMS te creƫren met de tekst 'PWNED!'. We zullen twee antwoorden van de server vervangen: puds.ucweb.com/upgrade/index.xhtml en voor het downloaden van het archief. In het eerste vervangen we de MD5 (de grootte na het uitpakken verandert niet), in het tweede geven we het archief met de gepatchte bibliotheek terug.

De browser probeert het archief meerdere keren te downloaden, waarna het een fout geeft. Blijkbaar is er iets
dat hem niet zint. Uit de analyse van dit vage formaat bleek dat de server ook de grootte van het archief doorgeeft:

Zoeken naar kwetsbaarheden in UC Browser

Het is gecodeerd in LEB128. Na de patch is de grootte van het archief met de bibliotheek iets veranderd, waardoor de browser dacht dat het archief verkeerd was gedownload, en na meerdere pogingen gaf het een fout.

We corrigeren de grootte van het archief... En - overwinning! šŸ™‚ Het resultaat op video.

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

Gevolgen en reactie van de ontwikkelaar

Op dezelfde manier zouden hackers de onveilige functie van UC Browser kunnen gebruiken om kwaadaardige bibliotheken te verspreiden en uit te voeren. Deze bibliotheken werken in de context van de browser, waardoor ze alle systeemrechten verwerven. Dit resulteert in de mogelijkheid om phishing-vensters weer te geven, evenals toegang tot de werkbestanden van de oranje Chinese eekhoorn, inclusief opgeslagen in de database logins, wachtwoorden en cookies.

We hebben contact opgenomen met de ontwikkelaars van UC Browser en hen op de gevonden kwetsbaarheid gewezen, en geprobeerd hen te wijzen op de kwetsbaarheid en het gevaar ervan, maar ze wilden niet met ons in gesprek gaan. Ondertussen bleef de browser pronken met de gevaarlijke functie in het openbaar. Maar zodra we de details van de kwetsbaarheid onthulden, kon dit niet langer genegeerd worden. Op 27 maart werd er
een nieuwe versie van UC Browser 12.10.9.1193 uitgebracht, die verbinding maakte met de server via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Bovendien leidde een poging om een PDF in de browser te openen na de "oplossing" en tot het moment van het schrijven van dit artikel tot een foutmelding met de tekst "Oeps, er is iets misgegaan!". De aanvraag naar de server bij het proberen te openen van de PDF werd niet uitgevoerd, maar de aanvraag bij het starten van de browser werd wel uitgevoerd, wat wijst op een resterende mogelijkheid om uitvoerbare code te laden in strijd met de regels van Google Play.

Bron: habr.com

Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers šŸ”„ Koop betrouwbare webhosting met bescherming tegen DDoS, VPS VDS servers | ProHoster