Pohojmë dobësitë në UC Browser

Pohojmë dobësitë në UC Browser

Hyrje

Në fund të marsit ne njoftuam, se zbuluam një mundësi të fshehtë për ngarkimin dhe ekzekutimin e kodit të paautorizuar në UC Browser. Sot do të shqyrtojmë në detaje se si ndodh ky ngarkim dhe si mund ta përdorin hakerët këtë për qëllimet e tyre.

Disa kohë më parë, UC Browser reklamohej dhe shpërndahej shumë agresivisht: e instalonin në pajisjet e përdoruesve me ndihmën e programeve të dëmshme, e shpërndanin nga faqe të ndryshme si skedarë video (pra, përdoruesit mendonin se po shkarkonin, për shembull, një video për të rritur, por në vend të saj merrnin një APK me këtë shfletues), përdornin banera frikësuese me mesazhe që tregonin se shfletuesi ishte i vjetër, i dobët dhe të gjitha ato. Në grupin zyrtar të UC Browser në VK ka temë, ku përdoruesit mund të ankohen për reklamim të pabesueshëm, ka shumë shembuj. Në vitin 2016 kishte madje reklama video në gjuhën ruse (po, reklama për një shfletues që bllokon reklamat).

Në momentin e shkruarjes së këtij artikulli, UC Browser kishte më shumë se 500,000,000 instalime në Google Play. Kjo është e impresionueshme - më shumë ka vetëm Google Chrome. Ndër komentet, mund të shihen mjaft ankesa për reklamat dhe redirektimet në disa aplikacione në Google Play. Kjo ishte arsyeja për një hulumtim: ne vendosëm të shikojmë nëse UC Browser po bënte diçka të keqe. Dhe rezultoi se po bënte!

Në kodin e aplikacionit u zbulua mundësia e ngarkimit dhe ekzekutimit të kodit të ekzekutueshëm, çka është në kundërshtim me rregullat e publikimit të aplikacioneve në Google Play. Përveç faktit se UC Browser ngarkon kod të ekzekutueshëm, ai e bën këtë në mënyrë të pasigurt, çka mund të përdoret për të realizuar një sulm MitM. Le të shohim nëse mund ta realizojmë një sulm të tillë.

E gjithë ajo që do të shkruhet më poshtë është e rëndësishme për versionin e UC Browser që ishte i pranishëm në Google Play në momentin e hulumtimit:

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

Vektori i sulmit

Në manifestin e UC Browser mund të zbuloni një shërbim me emrin e qartë com.uc.deployment.UpgradeDeployService.

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

Kur aktivizohet ky shĂ«rbim, ndihmĂ«sja bĂ«n njĂ« kĂ«rkesĂ« POST nĂ« puds.ucweb.com/upgrade/index.xhtml, i cili mund tĂ« vĂ«rehet nĂ« trafik pas njĂ« kohe pas fillimit. Si pĂ«rgjigje, ai mund tĂ« marrĂ« njĂ« komandĂ« pĂ«r tĂ« shkarkuar ndonjĂ« pĂ«rditĂ«sim ose moduli tĂ« ri. GjatĂ« analizĂ«s, serveri nuk dha tĂ« tilla komanda, por ne vĂ«mĂ« re se kur pĂ«rpiqet tĂ« hapĂ« njĂ« PDF nĂ« shfletues, ai bĂ«n njĂ« kĂ«rkesĂ« tĂ« pĂ«rsĂ«ritur nĂ« adresĂ«n e mĂ«sipĂ«rme, pas sĂ« cilĂ«s shkarkon librarinĂ« native. PĂ«r tĂ« kryer sulmin, vendosĂ«m tĂ« pĂ«rdorim kĂ«tĂ« veçori tĂ« UC Browser: aftĂ«sinĂ« pĂ«r tĂ« hapur PDF me ndihmĂ«n e njĂ« librarie native, tĂ« cilĂ«n nuk e ka nĂ« APK dhe qĂ« e shkarkon nga Interneti kur Ă«shtĂ« e nevojshme. Duhet theksuar se teorikisht UC Browser mund tĂ« detyrohet tĂ« shkarkojĂ« diçka edhe pa ndĂ«rveprimin e pĂ«rdoruesit – nĂ«se jepni njĂ« pĂ«rgjigje tĂ« formuar siç duhet pĂ«r kĂ«rkesĂ«n, qĂ« ekzekutohet pas nisjes sĂ« shfletuesit. Por pĂ«r kĂ«tĂ« nevojitet njĂ« studim mĂ« i hollĂ«sishĂ«m i protokollit tĂ« ndĂ«rveprimit me serverin, prandaj vendosĂ«m se ishte mĂ« e lehtĂ« tĂ« redaktonim pĂ«rgjigjen e kapur dhe tĂ« zĂ«vendĂ«sojmĂ« bibliotekĂ«n pĂ«r punĂ«n me PDF.

Pra, kur përdoruesi dëshiron të hapë një PDF direkt në shfletues, në trafik mund të shihen të tilla kërkesa:

Pohojmë dobësitë në UC Browser

Fillimisht, ka një kërkesë POST për puds.ucweb.com/upgrade/index.xhtml, pas së cilës
shkarkohet njĂ« arkiv me bibliotekĂ«n pĂ«r shikimin e PDF dhe formateve tĂ« zyrĂ«s. ËshtĂ« logjike tĂ« supozohet se nĂ« kĂ«rkesĂ«n e parĂ« dĂ«rgohet informacion mbi sistemin (tĂ« paktĂ«n, arkitektura, pĂ«r tĂ« dorĂ«zuar bibliotekĂ«n e duhur), dhe nĂ« pĂ«rgjigje tĂ« saj, shfletuesi merr ndonjĂ« informacion mbi bibliotekĂ«n qĂ« duhet shkarkuar: adresĂ«n dhe, ndoshta, diçka tjetĂ«r. Problemi Ă«shtĂ« se kjo kĂ«rkesĂ« Ă«shtĂ« e enkriptuar.

Fragmenti i kërkesës

Fragmenti i përgjigjes

Pohojmë dobësitë në UC Browser

Pohojmë dobësitë në UC Browser

Biblioteka vetë është e paketuar në ZIP dhe nuk është e enkriptuar.

Pohojmë dobësitë në UC Browser

Kërkimi i kodit për dekodimin e trafikut

Le të përpiqemi të dekodojmë përgjigjen e serverit. Shikojmë kodin e klasës com.uc.deployment.UpgradeDeployService: nga metoda onStartCommand kalojmë te com.uc.deployment.b.x, dhe nga ai te 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("]produkti:");
            v4.append(arg9.iGX.ipR);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]versioni:");
            v4.append(arg9.iGX.iEn);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tipi_për_hapjen:");
            v4.append(arg9.iGX.mMode);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]flag_detyrueshëm:");
            v4.append(arg9.iGX.iEo);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]modaliteti_në_heshtje:");
            v4.append(arg9.iGX.iDQ);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tipi_në_heshtje:");
            v4.append(arg9.iGX.iEr);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]statusi_në_heshtje:");
            v4.append(arg9.iGX.iEp);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]skedari_në_heshtje:");
            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("]tipi_i_shkarkimit:");
            v4.append(arg9.mDownloadType);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]grupi_i_shkarkimit:");
            v4.append(arg9.mDownloadGroup);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]rruga_e_shkarkimit:");
            v4.append(arg9.iGH);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]versioni_child_apollo:");
            v4.append(arg9.iGX.iEx);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]seria_apollo:");
            v4.append(arg9.iGX.iEw);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]arkitektura_cpu_apollo:");
            v4.append(arg9.iGX.iEt);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]cpu_vfp3_apollo:");
            v4.append(arg9.iGX.iEv);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]cpu_vfp_apollo:");
            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("]emri_komponentit:");
                    v5.append(((au)v4_1).getName());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]emri_versionit_komponentit:");
                    v5.append(((au)v4_1).aDA());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]kod_versioni_komponentit:");
                    v5.append(((au)v4_1).gBl);
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]tipo_i_kërkesës_komponentit:");
                    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("arkitektura_procesorit", 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("tipo_rrjeti", String.valueOf(com.uc.base.system.a.Jo())));
            v3_2.add(g.fs("nga_hosti", arg9.iGX.iEm));
            v3_2.add(g.fs("versioni_plugin", arg9.iGX.iEn));
            v3_2.add(g.fs("gjuha_target", 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("versioni_child_plugin", arg9.iGX.iEx));
            v3_2.add(g.fs("seria_versionit", arg9.iGX.iEw));
            v3_2.add(g.fs("child_version", r.aVw()));
            v3_2.add(g.fs("md5_versioni_kapri", arg9.iGX.iEl));
            v3_2.add(g.fs("nënshkrimi_versionit_kapri", SystemHelper.getUCMSignature()));
            v3_2.add(g.fs("logun_e_hapjes", i.bjt()));
            v3_2.add(g.fs("instalimi_në_heshtje", String.valueOf(arg9.iGX.iDQ)));
            v3_2.add(g.fs("statusi_në_heshtje", String.valueOf(arg9.iGX.iEp)));
            v3_2.add(g.fs("skedari_në_heshtje", arg9.iGX.iEq));
            v3_2.add(g.fs("tipi_në_heshtje", String.valueOf(arg9.iGX.iEr)));
            v3_2.add(g.fs("arkitektura_cpu", com.uc.b.a.a.c.Pc()));
            v3_2.add(g.fs("grupi_cpu", 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("në_dytë_cpu", 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("ram_totale", 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("niveli_api", String.valueOf(Build$VERSION.SDK_INT)));
            v3_2.add(g.fs("lista_apk_uc", 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", "po", "dështim");
            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", "po", "dështim");
            return;
        }
 
        if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
            this.iGY.iGI.a(arg9, "up_url", "po", "dështim");
            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", "po", "sukses");
        this.iGY.iGI.b(arg9);
    }

Këtu shqyrtojmë formimin e një POST-requests. Vëmendje e veçantë i kushtohet krijimit të një matrice prej 16 bajtëve dhe mbushjes së saj: 0x5F, 0, 0x1F, -50 (=0xCE). Kjo përputhet me atë që pamë në request-in e mëparshëm.

Në këtë klasë mund të vërehet gjithashtu një klasë e brendshme, në të cilën ka një metodë tjetër interesante:

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

Metoda merr si input njĂ« array bytes dhe kontrollon qĂ« byte i parĂ« tĂ« jetĂ« 0x60 ose byte i tretĂ« tĂ« jetĂ« 0xD0, ndĂ«rsa byte i dytĂ« tĂ« jetĂ« 1, 11 ose 0x1F. ShikojmĂ« pĂ«rgjigjen nga serveri: byte i parĂ« Ă«shtĂ« 0x60, byte i dytĂ« 0x1F, dhe byte i tretĂ« Ă«shtĂ« 0x60. Duket se kjo Ă«shtĂ« ajo qĂ« na nevojitet. Sipas linjave (“up_decrypt”, pĂ«r shembull), duhet tĂ« thirret njĂ« metodĂ« qĂ« dekripton pĂ«rgjigjen nga serveri.
Kalojmë në metodën g.j. Vërejmë se si argumenti i parë i kalon një byte me offset 2 (dmth, 0x1F në rastin tonë), dhe si argumenti i dytë - përgjigjen e serverit pa
16 byte-të e parë.

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

Evidentisht, këtu po bëhet një zgjedhje e algoritmit të dekriptimit, dhe ai byte-i, që në rastin tonë është 0x1F, përfaqëson një nga tre mundësitë.
Vazhdojmë analizën e kodit. Pasi bëjmë disa kërcime, arrijmë në metodën me emrin e qartë

decryptBytesByKey KĂ«tu ndahen edhe dy byte tĂ« tjerĂ« nga pĂ«rgjigja jonĂ«, dhe nga ata formohet njĂ« string. ËshtĂ« e qartĂ« se nĂ« kĂ«tĂ« mĂ«nyrĂ« zgjidhet çelĂ«si pĂ«r dekriptimin e mesazhit..

KĂ«tu ndaj pĂ«rgjigjes sonĂ«, ndahen edhe dy byte tĂ« tjera, dhe prej tyre formohet njĂ« varg. ËshtĂ« e qartĂ« se nĂ« kĂ«tĂ« mĂ«nyrĂ« zgjidhet çelĂ«si pĂ«r dekriptimin e mesazhit.

    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()); // Key selection
                    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;
    }

Duke përpara, do të theksojmë se në këtë fazë nuk po marrim ende çelësin, por vetëm "identifikuesin" e tij. Marrja e çelësit është pak më e komplikuar.

Në metodën e ardhshme, dy parametra shtohen te ato ekzistuese, duke bërë që të tjera të bëhen katër: numri magjik 16, identifikuesi i çelësit, të dhënat e enkriptuara dhe një varg i pakuptimtë (në rastin tonë, i zbrazët).

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

Pas një sërë kalimesh, arrijmë te metoda staticBinarySafeDecryptNoB64 interface com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Në kodin kryesor të aplikacionit nuk ka klasa që implementojnë këtë ndërfaqe. Një klasë e tillë ka në skedarin lib/armeabi-v7a/libsgmain.so, i cili në të vërtetë nuk është .so, por .jar. Metoda që na intereson implementohet si në vijim:

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

Këtu lista jonë e parametrave plotësohet me dy numra të plotë: 2 dhe 0. Duke u gjykuar nga
tĂ« gjitha, 2 do tĂ« thotĂ« dekriptim, si nĂ« metodĂ«n doFinal klasa sistemore javax.crypto.Cipher. Dhe tĂ« gjitha kĂ«to dĂ«rgohen nĂ« njĂ« Router me numrin 10601 — duket se Ă«shtĂ« numri i komandĂ«s.

Pas një seri kalimesh gjejmë klasën që implementon ndërfaqen IRouterComponent dhe metodën 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);
    }
}

Dhe gjithashtu klasa JNICLibrary, në të cilën është shpallur metoda native doCommandNative:

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

Kështu, na nevojitet që në kodin native të gjejmë metodën doCommandNative. Dhe këtu fillon gjithë argëtimi.

Obfuskimi i kodit makinerik

NĂ« skedar libsgmain.so (i cili nĂ« tĂ« vĂ«rtetĂ« Ă«shtĂ« .jar dhe nĂ« tĂ« cilin sapo gjetĂ«m implementimin e disa interfazave lidhur me enkriptimin) ka njĂ« bibliotekĂ« nativĂ«: libsgmainso-6.4.36.so. E hapim atĂ« nĂ« IDA dhe marrim njĂ« mori dritareve dialog me gabime. Problemi Ă«shtĂ« se tabela e seksioneve (section header table) – Ă«shtĂ« e pavlefshme. Kjo Ă«shtĂ« bĂ«rĂ« qĂ«llimisht, pĂ«r ta bĂ«rĂ« analizĂ«n mĂ« tĂ« vĂ«shtirĂ«.

Pohojmë dobësitë në UC Browser

Por nuk ka nevojë për të: që të ngarkohet saktë një skedari ELF dhe të analizohet, është mjaft e mjaftueshme tabela e segmenteve (program header table). Prandaj thjesht e fshijmë tabelën e seksioneve, duke zerozuar fushat përkatëse në krye.

Pohojmë dobësitë në UC Browser

E hapim sërish skedarin në IDA.

Ka janë dy mënyra për t'i treguar makinës virtuale Java se ku ndodhet implementimi i metodës në bibliotekën native, e cila është deklaruar në kodin Java si native. E para është të jepet një emër si Java_emri_i_paketës_EmriKlases_emriMetodes.

E dyta është ta regjistrosh atë gjatë ngarkimit të bibliotekës (në funksionin JNI_OnLoad)
me thirrjen e funksionit RegisterNatives.

Në rastin tonë, nëse përdorim mënyrën e parë, emri duhet të jetë si më poshtë: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Mes funksioneve të eksportuara nuk ka një të tillë, pra duhet të kërkojmë thirrjen RegisterNatives.
Shkojmë në funksionin JNI_OnLoad dhe shohim një pamje të tillë:

Pohojmë dobësitë në UC Browser

ÇfarĂ« po ndodh kĂ«tu? NĂ« pamje tĂ« parĂ«, fillimi dhe fundi i funksionit janĂ« tipik pĂ«r arkitekturĂ«n ARM. Instruksioni i parĂ« ruan pĂ«rmbajtjen e regjistrave qĂ« funksioni do tĂ« pĂ«rdorĂ« nĂ« punĂ«n e tij (nĂ« kĂ«tĂ« rast R0, R1 dhe R2), si dhe pĂ«rmbajtjen e regjistrit LR, ku ndodhet adresa e kthimit nga funksioni. Instruksioni pĂ«rfundimtar rikthen regjistrat e ruajtur, ndĂ«rsa adresa e kthimit vendoset menjĂ«herĂ« nĂ« regjistrin PC — kĂ«shtu ndodh kthimi nga funksioni. Por nĂ«se e shikoni me kujdes, mund tĂ« vini re se instruksioni parafundit ndryshon adresĂ«n e kthimit tĂ« ruajtur nĂ« stash. Le tĂ« llogarisim se cila do tĂ« jetĂ« ajo pas
ekzekutimit të kodit. Në R1 ngarkohet një adresë e caktuar 0xB130, nga e cila zbritet 5, pastaj ajo e transferohet në R0 dhe i shtohet 0x10. Kështu del 0xB13B. Pra, IDA mendon se në instruksionin e fundit po ndodh një kthim normal nga funksioni, ndërsa në të vërtetë po ndodh një kalim në adresën e llogaritur 0xB13B.

Këtu është e rëndësishme të theksohet se procesorët ARM kanë dy modet dhe dy grupe instrukcionesh: ARM dhe Thumb. Biti më i vogël i adresës tregon procesorit se cili grup instrukcionesh përdoret. Pra, adresa në fakt është 0xB13A, dhe njësi në bitin më të vogël nënkupton modin Thumb.

Në fillim të çdo funksioni në këtë bibliotekë është shtuar një "adapter" të tillë dhe
kodin e mbetur. MĂ« vonĂ« nuk do tĂ« ndalemi shumĂ« mbi to – thjesht mbajmĂ« mend,
se fillimi i vërtetë i pothuajse të gjithë funksioneve ndodhet pak më tutje.

Duke e ditur se në kod nuk ka një kalim të qartë në 0xB13A, IDA vetë nuk e ka identifikuar se në këtë pikë ndodhet kodi. Për këtë arsye, një pjesë e madhe e kodit në bibliotekë nuk është e njohur si kod, që vë në vështirësi analizën. I themi IDA-s se këtu është kod, dhe ja çfarë rezultati kemi:

Pohojmë dobësitë në UC Browser

Në 0xB144 fillon qartë tabela. Por çfarë ndodhet në sub_494C?

Pohojmë dobësitë në UC Browser

Kur thirrim kĂ«tĂ« funksion, nĂ« regjistrin LR do tĂ« marrim adresĂ«n e tabelĂ«s sĂ« pĂ«rmendur mĂ« parĂ« (0xB144). NĂ« R0 – indeksi nĂ« kĂ«tĂ« tabelĂ«. Pra, merret vlera nga tabela, shtohet nĂ« LR dhe rezulton
Adresa në të cilën duhet të kalojmë. Le t'i llogarisim: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Kalojmë në adresën e marra dhe shohim në të vërtetë disa udhëzime të dobishme dhe sërish kalimi në 0xB140:

Pohojmë dobësitë në UC Browser

Tani do të kalojmë në offset me indeks 0x20 nga tabela.

Duke parë madhësinë e tabelës, do të hasim shumë kalime të tilla në kod. Lind pyetja, a është e mundur të luftojmë këtë më automatizuar, pa llogaritje manuale të adresave. Dhe për këtë na vijnë në ndihmë skriptet dhe mundësia për të patch-uar kodin në IDA:

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 "indeksi =", 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 "Tipi i papërshtatshëm i operandit në", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
            table = None
        if table is None:
            print "Nuk mund të gjejmë tabelën"
        else:
            print "tabela =", hex(table)
            offset = get_wide_dword(table + (index << 2))
            put_unconditional_branch(ea, table + offset)
    else:
        print "Kode e panjohur", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
    print "Nuk mund të zbulojmë instruktionen e parë"

Vendosim kursorin në rreshtin 0xB26A, aktivizojmë skritpn dhe shohim kalimin në 0xB4B0:

Pohojmë dobësitë në UC Browser

IDA sërish nuk e ka njohur këtë pjesë si kod. E ndihmojmë dhe shohim një strukturë tjetër:

Pohojmë dobësitë në UC Browser

Udhëzimet pas BLX duken jo shumë kuptimplote, më shumë si një shpërndarje. Po shikojmë në sub_4964:

Pohojmë dobësitë në UC Browser

Dhe në të vërtetë, këtu merret një dword nga adresa që ndodhet në LR, i cili shtohet në këtë adresë, pas së cilës merret vlera nga adresa e marrë dhe vendoset në stek. Po ashtu, në LR i shtohet 4 që të shmanget ky shpërndarje pas kthimit nga funksioni. Pas kësaj, komanda POP {R1} merr vlerën e marrë nga steka. Nëse shikoni se çfarë ndodhet në adresën 0xB4BA + 0xEA = 0xB5A4, mund të shihni diçka të ngjashme me një tabelë adresash:

Pohojmë dobësitë në UC Browser

Për të patch këtë konstrukcion, do të duhet të merrni dy parametra nga kodi: shpërndarja dhe numri i regjistrit në të cilin duhet të vendoset rezultati. Për çdo regjistër të mundshëm do të duhet të përgatitet paraprakisht një copë kod.

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 "Instruksioni POP nuk u gjet"
    else:
        print "Lloji i operandit të gabuar në +4:", get_operand_type(ea + 4, 0)
else:
    print "Nuk mund të zbulohet instruksioni i parë"

Vendosim kursori nĂ« fillimin e konstrukcionit qĂ« duam tĂ« zĂ«vendĂ«sojmĂ« — 0xB4B2 — dhe startojmĂ« skriptin:

Pohojmë dobësitë në UC Browser

Përveç konstrukcioneve të përmendura tashmë, në kodin gjithashtu hasen këto:

Pohojmë dobësitë në UC Browser

Siç ndodh edhe në rastin e mëparshëm, pas udhëzimit BLX vjen një zhvendosje:

Pohojmë dobësitë në UC Browser

Marrim zhvendosjen nga adresa në LR, e shtojmë atë në LR dhe kalojmë aty. 0x72044 + 0xC = 0x72050. Skripti për këtë konstrukcion është krejtësisht i thjeshtë:

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"

Rezultati i ekzekutimit të skriptit:

Pohojmë dobësitë në UC Browser

Pasi të jetë patchuar funksioni, mund të tregojmë IDA për fillimin e tij të vërtetë. Ajo do të mbledhë gjithë kodin e funksionit në pjesë, dhe mund të dekodifikohet përmes HexRays.

Dekriptimi i vargjeve

Ne mësuam të luftojmë me obfuscimin e kodit mahnitor në bibliotekë libsgmainso-6.4.36.so nga nga UC Browser dhe morëm kodin e funksionit 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;
}

Le të shohim më ngushtë rreshtat e mëposhtëm:

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

NĂ« funksionin sub_73E24 qartazi poqoni emrin e klasit. Si parametra, kjo funksion merr njĂ« tregues pĂ«r tĂ« dhĂ«nat qĂ« duken si tĂ« koduara, njĂ« tampon dhe njĂ« numĂ«r. ËshtĂ« e qartĂ« se pas thirrjes sĂ« funksionit, nĂ« tampon do tĂ« ketĂ« njĂ« varg tĂ« dekriptuar, pasi ai i dĂ«rgohet funksionit. FindClass, i cili merr si parametrin e dytĂ« emrin e klasit. Pra, numri Ă«shtĂ« madhĂ«sia e tamponit ose gjatĂ«sia e vargut. Le tĂ« pĂ«rpiqemi tĂ« dekriptojmĂ« emrin e klasit, ai duhet tĂ« na tregojĂ« nĂ«se jemi nĂ« drejtimin e duhur. Le tĂ« shqyrtojmĂ« mĂ« nĂ« detaje se çfarĂ« ndodh nĂ« 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;
}

Funksioni sub_7AF78 krijon njĂ« instancĂ« tĂ« kontenierit pĂ«r array-t e byte-ve me madhĂ«si tĂ« caktuar (nuk do tĂ« hyjmĂ« nĂ« detaje pĂ«r kĂ«ta kontenierĂ«). KĂ«tu krijohen dy tĂ« tillĂ«: nĂ« njĂ« vendoset njĂ« varg «DcO/lcK+h?m3c*q@» (nuk Ă«shtĂ« e vĂ«shtirĂ« tĂ« kuptohet se ky Ă«shtĂ« çelĂ«si), nĂ« tjetrin — tĂ« dhĂ«nat e koduara. MĂ« pas tĂ« dy objektet vendosen nĂ« njĂ« strukturĂ«, e cila kalon nĂ« funksionin sub_6115C. Gjithashtu, le tĂ« theksojmĂ« nĂ« kĂ«tĂ« strukturĂ« fushĂ«n me vlerĂ«n 3. TĂ« shohim se çfarĂ« ndodh me kĂ«tĂ« strukturĂ« mĂ« tej.

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

Si parametri switch dërgohet fusha e strukturës, të cilës më parë iu dha vlera 3. Shikojmë case 3: në funksionin sub_6364C dërgohen parametrat nga struktura, të cilat u vendosën aty në funksionin e mëparshëm, dmth çelësi dhe të dhënat e kriptuara. Nëse shikoni me kujdes sub_6364C, mund të mësoni në të algoritmin RC4.

Kemi algoritmin dhe çelësin. Të provojmë të deshifrojmë emrin e klasës. Ja çfarë arritëm: com/taobao/wireless/security/adapter/JNICLibrary. Shumë mirë! Jemi në rrugën e duhur.

Pema e komandave

Tani duhet të gjejmë thirrjen RegisterNatives, e cila do të na tregojë për funksionin doCommandNative. Shikojmë funksionet e thirrura nga JNI_OnLoad, dhe e gjejmë atë në 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;
}

Dhe me të vërtetë, këtu regjistrohet një metodë natyrore me emrin doCommandNative. Tani e dimë adresën e saj. Le të shohim se çfarë bën ajo.

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

Duke me titullin mund të kuptohet se ky është pika e hyrjes për të gjitha funksionet që zhvilluesit vendosën t'i transferojnë në bibliotekën native. Na intereson funksioni me numrin 10601.

Nga kodi mund të shohim se numri i komandës nxjerr tre numra: command / 10000, command % 10000 / 100 dhe command % 10, dmth, në rastin tonë, 1, 6 dhe 1. Këta tre numra, si dhe treguesi për JNIEnv dhe argumentet e dërguara në funksion, grumbullohen në një strukturë dhe dërgohen më tej. Me ndihmën e këtyre tre numrave të marrë (të quajtur N1, N2 dhe N3) ndërtohet një pemë komandash.

Diçka e tillë:

Pohojmë dobësitë në UC Browser

Pema mbushet dinamikisht në JNI_OnLoad.
Tre numra kodifikojnĂ« rrugĂ«n nĂ« pemĂ«. Çdo gjethe e pemĂ«s pĂ«rmban adresĂ«n e koduar tĂ« funksionit pĂ«rkatĂ«s. ÇelĂ«si ndodhet te nodi prind. TĂ« gjejmĂ« vendin nĂ« kod ku shtohet funksioni qĂ« na nevojitet, nuk Ă«shtĂ« shumĂ« e vĂ«shtirĂ«, nĂ«se kuptojmĂ« tĂ« gjitha struktura qĂ« pĂ«rdoren (pĂ«rshkrimi i tyre nuk Ă«shtĂ« pĂ«rfshirĂ«, pĂ«r tĂ« mos e zgjeruar kĂ«tĂ« artikull tashmĂ« tĂ« gjatĂ«).

Akoma obfuskimi

Ne kemi marrë adresën e funksionit që duhet të zbërthejë trafikun: 0x5F1AC. Por nuk është koha për të festuar akoma: zhvilluesit e UC Browser na kanë përgatitur një surprizë tjetër.

Pas marrjes së parametrave nga një masë, që ishte formuar në kodin Java, ne arrijmë
në funksionin me adresë 0x4D070. Dhe këtu na pret një tjetër formë obfuskimi të kodit.

Vendosim në R7 dhe R4 dy indekse:

Pohojmë dobësitë në UC Browser

Shkëmbejmë indeksi i parë në R11:

Pohojmë dobësitë në UC Browser

Për të marrë adresën nga tabela, përdorim indekset:

Pohojmë dobësitë në UC Browser

Pas kalimit në adresën e parë, përdoret indeksi i dytë, që është në R4. Në tabelë ka 230 elemente.

ÇfarĂ« tĂ« bĂ«jmĂ« me kĂ«tĂ«? Mund tĂ« themi IDA se ky Ă«shtĂ« njĂ« switch: Edit -> Other -> Specify switch idiom.

Pohojmë dobësitë në UC Browser

Kodi i rezultuar është i frikshëm. Por, duke u përpjekur përmes wreaks të tij, mund të vërejmë thirrjen e funksionit që na është njohur më parë. sub_6115C:

Pohojmë dobësitë në UC Browser

Ishte një switch, ku në case 3 ndodhej dekriptimi duke përdorur algoritmin RC4. Në këtë rast struktura që është kaluar në funksion plotësohet nga parametrat e kaluar në doCommandNative. Kujtojmë se atje kishte magicInt me vlerën 16. Shikojmë case përkatës - dhe pas disa kalimeve gjejmë kodin që lejon identifikimin e algoritmit.

Pohojmë dobësitë në UC Browser

ËshtĂ« AES!

Algoritmi është, tani na duhen parametrat e tij: mënyra, çelësi dhe, ndoshta, vektori i inicializimit (prania e tij varet nga mënyra e funksionimit të algoritmit AES). Struktura që përmban ato duhet të formohet diku para thirrjes së funksionit sub_6115C, por kjo pjesë e kodit është obfuskatuar veçanërisht mirë, prandaj lind ideja për të bërë një patch të kodit, në mënyrë që të gjithë parametrat e funksionit të dekriptimit të shkruhen në skedar.

Patch

Për të mos e shkruar të gjithë kodin e patch-it në Assembler manualisht, mund të ndash Android Studio, të shkruash atje një funksion që merr si input parametrat e njëjtë si funksioni ynë i dekriptimit dhe shkruan në skedar, pastaj të kopjosh kodin që do të gjeneronte kompajleri.

PĂ«r sa i pĂ«rket lehtĂ«sisĂ« sĂ« shtimit tĂ« kodit, miqtĂ« tanĂ« nga ekipi UC Browser gjithashtu kanĂ« «kujdesur» pĂ«r kĂ«tĂ«. KujtojmĂ« se nĂ« fillim tĂ« çdo funksioni kemi kod tĂ« padobishĂ«m, i cili lehtĂ«sisht mund tĂ« zĂ«vendĂ«sohet me ndonjĂ« tjetĂ«r. ËshtĂ« shumĂ« e pĂ«rshtatshme 🙂 MegjithatĂ«, nĂ« fillimin e funksionit tĂ« synuar, nuk ka shumĂ« vend pĂ«r kodin qĂ« ruan tĂ« gjitha parametrat nĂ« skedarin, prandaj duhet ta ndajmĂ« atĂ« nĂ« pjesĂ« dhe tĂ« pĂ«rdorim blloqe tĂ« padobishme nga funksionet fqinje. NĂ« total rezultuan katĂ«r pjesĂ«.

Pjesa e parë:

Pohojmë dobësitë në UC Browser

Në arkitekturën ARM, katër parametrat e parë të funksionit transmetohen përmes regjistrave R0-R3, të tjerët, nëse ka, përmes stack-ut. Në regjistrin LR dërgohet adresa e rikthimit. E gjithë kjo duhet të ruhet në mënyrë që funksioni të mund të punojë pas dump-it të parametrave të tij. Po ashtu, duhet të ruajmë të gjithë regjistrat që do të përdorim gjatë procesit, prandaj bëjmë PUSH.W {R0-R10,LR}. Në R7 na del adresa e listës së parametrave të dërguar funksionit përmes stack-ut.

Duke përdorur funksionin fopen do të hapim skedarin /data/local/tmp/aes në modalitetin «ab»,
dmth. qĂ« do tĂ« shtohet. NĂ« R0 ngarkohet adresa e emrit tĂ« skedarit, nĂ« R1 – adresa e vargut qĂ« tregon modin. Dhe kĂ«tu pĂ«rfundojnĂ« kodet e plehrave, prandaj kalojmĂ« nĂ« funksionin tjetĂ«r. QĂ« tĂ« vazhdojĂ« tĂ« punojĂ«, vendosim nĂ« fillim kalimin nĂ« kodin e vĂ«rtetĂ« tĂ« funksionit duke u anashkaluar plehrat, dhe nĂ« vend tĂ« plehrave shtojmĂ« vazhdimin e patch-it.

Pohojmë dobësitë në UC Browser

Thërrasim fopen.

Tre parametrat e parë të funksionit aes kanë tip int. Pasi në fillim ruajtëm regjistrat në stack, mund ta kalojmë thjesht funksionit fwrite adresat e tyre në stack.

Pohojmë dobësitë në UC Browser

Më pas kemi tre struktura, të cilat përmbajnë madhësinë e të dhënave dhe treguesin për të dhënat për çelësin, vektorin e inicimit dhe të dhënat e enkriptuara.

Pohojmë dobësitë në UC Browser

Në fund mbyllim skedarin, rikthejmë regjistrat dhe i japim kontrollet funksionit të vërtetë aes.

Krijojmë APK me bibliotekën e patçuar, e nënshkruajmë, e ngarkojmë në pajisje/emulator, e fillojmë. Shohim që dumpi ynë krijohet dhe aty shkruhen shumë të dhëna. Shfletuesi përdor enkriptimin jo vetëm për trafik, dhe e gjithë enkriptimi kalon përmes funksionit të shqyrtuar. Ndërsa të dhënat e nevojshme ndodhen diku, dhe në trafik nuk shihet kërkesa e nevojshme. Për të mos pritur derisa UC Browser t'i marrë përsipër të bëjë kërkesën e nevojshme, do të marrim përgjigjen e enkriptuar nga serveri, e marrë më parë, dhe do ta patçojmë aplikacionin një herë tjetër: do të shtojmë dekryptimin në onCreate të aktivitetit kryesor.

    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

Krijojmë, nënshkruajmë, instalojmë, fillojmë. Marrim NullPointerException, sepse metoda ktheu null.

Gjatë analizës së mëtejshme të kodit, u zbulua një funksion ku dekriptohen vargje interesante: «META-INF/» dhe «.RSA». Duket se aplikacioni kontrollon certifikatën e tij. Ose madje gjeneron çelësa nga ajo. Nuk dua të merrem me atë që ndodh me certifikatën, prandaj do t'ia ofrojmë një certifikatë të saktë. Do ta patch-ojmë vargun e enkriptuar në mënyrë që, në vend të «META-INF/», të kemi «BLABLINF/», do të krijojmë një dosje me emrin e tillë në APK dhe do t'i dërgojmë atje certifikatën e shfletuesit.

Mblidhen, nĂ«nshkruhen, instalohet, startohet. Bingo! ÇelĂ«si Ă«shtĂ« kĂ«tu!

MitM

Kemi marrë çelësin dhe vektorin e inicializimit, të barabartë me çelësin. Do të përpiqemi të dekriptojmë përgjigjen e serverit në modalitetin CBC.

Pohojmë dobësitë në UC Browser

Shohim URL-në e arkivit, diçka që ngjan me MD5, «extract_unzipsize» dhe një numër. Kontrollojmë: MD5 i arkivit përputhet, madhësia e bibliotekës së dekompresuar përputhet. Do të provojmë të patch-ojmë këtë bibliotekë dhe t'ia dorëzojmë shfletuesit. Për të treguar që biblioteka jonë e patch-uar është ngarkuar, do të nisnim një Intent për të krijuar një SMS me tekstin «PWNED!». Do të zëvendësojmë dy përgjigje nga serveri: puds.ucweb.com/upgrade/index.xhtml dhe për shkarkimin e arkivës. Në të parin zëvendësojmë MD5 (përmasat pas shpërndarjes nuk ndryshojnë), ndërsa në të dytin ofrojmë arkiv me bibliotekën e modifikuar.

Shfletuesi përpiqet disa herë të shkarkojë arkivën, pas së cilës shfaq një gabim. Dukshëm, diçka
nuk i pëlqen. Si rezultat i analizës së këtij formati të çuditshëm u zbulua se serveri po transmeton gjithashtu përmasën e arkivës:

Pohojmë dobësitë në UC Browser

Ajo është e koduar në LEB128. Pas modifikimit, përmasa e arkivës me bibliotekën ndryshoi pak, prandaj shfletuesi konsideroi se arkiva u shkarkua jo siç duhej, dhe pas disa përpjekjesh dha një gabim.

KorrigjojmĂ« pĂ«rmasĂ«n e arkivĂ«s
 Dhe – fitore! 🙂 Rezultati nĂ« video.

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

Pasojat dhe reagimi i zhvilluesit

Sikurse, hakerĂ«t mund tĂ« pĂ«rdorin funksionin e pasigurt UC Browser pĂ«r tĂ« shpĂ«rndarĂ« dhe ekzekutuar biblioteka malinj. KĂ«to biblioteka do tĂ« punojnĂ« nĂ« kontekstin e shfletuesit, prandaj do tĂ« marrin tĂ« gjitha lejet e sistemit tĂ« tij. Si pasojĂ« — mundĂ«sia pĂ«r tĂ« shfaqur dritare phishing, si dhe akses nĂ« skedarĂ«t operativĂ« tĂ« minjve tĂ« portokalltĂ« kinezĂ«, pĂ«rfshirĂ« loginat, fjalĂ«kalimet dhe cookies qĂ« ruhen nĂ« bazĂ«n e tĂ« dhĂ«nave.

Ne kemi kontaktuar zhvilluesit e UC Browser dhe u kemi raportuar për problemin e gjetur, duke u përpjekur të theksojmë dobësinë dhe rrezikun e saj, por ata nuk deshën të diskutojnë ndonjë gjë me ne. Ndërkohë, shfletuesi vazhdoi të mostrante funksionin e rrezikshëm për të gjithë. Megjithatë, sapo ne zbuluam detajet e dobësisë, nuk ishte më e mundur ta injoronim si më parë. Më 27 mars, u publikua
një version i ri i UC Browser 12.10.9.1193, i cili iu drejtohej serverit përmes HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Përveç kësaj, pas "rregullimit" dhe deri në momentin e shkruajtur të artikullit, përpjekja për të hapur një PDF në shfletues shkaktonte shfaqjen e një mesazhi gabimi me tekstin "Oups, diçka shkoi keq!". Kërkesa drejtuar serverit gjatë përpjekjes për të hapur PDF-në nuk u ekzekutua, por kërkesa gjatë lansimit të shfletuesit u ekzekutua, çka sugjeron një mundësi të mbetur për të ngarkuar kod ekzekutiv në shkelje të rregullave të Google Play.

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster