Letar efter sÄrbarheter i UC Browser

Letar efter sÄrbarheter i UC Browser

Introduktion

I slutet av mars att, upptÀckte vi en dold möjlighet att ladda ner och köra icke verifierad kod i UC Browser. Idag kommer vi att gÄ igenom hur denna nedladdning sker och hur hackare kan anvÀnda den för sina syften.

För en tid sedan marknadsfördes och spreds UC Browser mycket aggressivt: det installerades pĂ„ anvĂ€ndarnas enheter med hjĂ€lp av skadlig programvara, distribuerades frĂ„n olika webbplatser under förespegling av videofiler (dvs. anvĂ€ndarna trodde att de laddade ner en porrfilm, men istĂ€llet fick de en APK med denna webblĂ€sare), anvĂ€ndes skrĂ€mmande banners med meddelanden om att webblĂ€saren var förĂ„ldrad, sĂ„rbar och liknande. I den officiella gruppen för UC Browser pĂ„ VK finns det av forumet., dĂ€r anvĂ€ndare kan klaga pĂ„ oetisk reklam, dĂ€r finns mĂ„nga exempel. År 2016 fanns det till och med videoreklam pĂ„ ryska (ja, reklam för en webblĂ€sare som blockerar reklam).

Vid tidpunkten för denna artikel hade UC Browser fĂ„tt över 500 000 000 installationer pĂ„ Google Play. Det Ă€r imponerande – endast Google Chrome har fler. Bland recensionerna kan man se mĂ„nga klagomĂ„l pĂ„ reklam och omdirigeringar till olika appar pĂ„ Google Play. Detta blev anledningen till vĂ„r forskning: vi ville se om UC Browser gör nĂ„got oetiskt. Och det visade sig att de gör det!

I appens kod upptÀcktes en möjlighet att ladda ner och köra exekverbar kod, vilket strider mot publiceringsreglerna i Google Play. Förutom att UC Browser laddar ner exekverbar kod, gör det detta pÄ ett osÀkert sÀtt, vilket kan anvÀndas för att genomföra en MitM-attack. LÄt oss se om vi kan genomföra en sÄdan attack.

Allt som skrivs hÀrnÀst Àr aktuellt för versionen av UC Browser som fanns pÄ Google Play vid tidpunkten för forskningens genomförande:

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

Angreppsvector

I manifestet för UC Browser kan man upptÀcka en tjÀnst med ett talande namn com.uc.deployment.UpgradeDeployService.

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

NĂ€r denna tjĂ€nst startas utför webblĂ€saren en POST-begĂ€ran till puds.ucweb.com/upgrade/index.xhtml, som kan observeras i trafiken efter en tids anvĂ€ndning. Som svar kan den fĂ„ en kommandon för att ladda ner en uppdatering eller en ny modul. Under analysen gav servern sĂ„dana kommandon inte, men vi mĂ€rkte att nĂ€r vi försökte öppna en PDF i webblĂ€saren, gjorde den en ny begĂ€ran till den angivna adressen, varpĂ„ den laddade ner det inhemska biblioteket. För att genomföra attacken beslutade vi att anvĂ€nda denna egenskap hos UC Browser: förmĂ„gan att öppna PDF-filer med hjĂ€lp av ett inbyggt bibliotek som inte finns i APK:n och som den laddar ner frĂ„n Internet vid behov. Det Ă€r vĂ€rt att notera att teoretiskt kan UC Browser tvingas ladda ner nĂ„got utan anvĂ€ndarinteraktion – om rĂ€tt format svar pĂ„ begĂ€ran som utförs efter att webblĂ€saren startas ges. Men för detta behövs en mer detaljerad studie av protokollet för interaktion med servern, sĂ„ vi beslutade att det var enklare att redigera det avlyssnade svaret och ersĂ€tta biblioteket som anvĂ€nds för PDF-filer.

SÄ, nÀr anvÀndaren vill öppna en PDF direkt i webblÀsaren, kan följande begÀrningar observeras i trafiken:

Letar efter sÄrbarheter i UC Browser

Först görs en POST-begÀran till puds.ucweb.com/upgrade/index.xhtml, efter vilken
ett arkiv laddas ner med biblioteket för att visa PDF- och kontorsformat. Det Àr logiskt att anta att den första begÀran skickar information om systemet (minst arkitektur, för att ge rÀtt bibliotek), och som svar pÄ den receive webblÀsaren viss information om biblioteket som ska laddas ner: adress och eventuellt nÄgot mer. Problemet Àr att denna begÀran Àr krypterad.

Framtagning av begÀran

Framtagning av svaret

Letar efter sÄrbarheter i UC Browser

Letar efter sÄrbarheter i UC Browser

SjÀlva biblioteket Àr packat i ZIP-format och Àr inte krypterat.

Letar efter sÄrbarheter i UC Browser

Sökning av dekrypteringskoden för trafiken

LÄt oss försöka dekryptera serverns svar. Vi ser pÄ klasskoden com.uc.deployment.UpgradeDeployService: frÄn metoden onStartCommand gÄr vi till com.uc.deployment.b.x, och dÀrifrÄn till com.uc.browser.core.d.c.f.e:

    offentlig slutlig void e(l arg9) {
        int v4_5;
        StrÀng v3_1;
        byte[] v3;
        byte[] v1 = null;
        om(arg9 == null) {
            v3 = v1;
        }
        annars {
            v3_1 = arg9.iGX.ipR;
            StringBuilder v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]produkt:");
            v4.append(arg9.iGX.ipR);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]version:");
            v4.append(arg9.iGX.iEn);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]uppgradera_typ:");
            v4.append(arg9.iGX.mMode);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tvinga_flag:");
            v4.append(arg9.iGX.iEo);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tyst_lÀge:");
            v4.append(arg9.iGX.iDQ);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tyst_typ:");
            v4.append(arg9.iGX.iEr);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tyst_tillstÄnd:");
            v4.append(arg9.iGX.iEp);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]tyst_fil:");
            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("]nedladdnings_typ:");
            v4.append(arg9.mDownloadType);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]nedladdnings_grupp:");
            v4.append(arg9.mDownloadGroup);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]nedladdnings_sökvÀg:");
            v4.append(arg9.iGH);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_barn_version:");
            v4.append(arg9.iGX.iEx);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_serie:");
            v4.append(arg9.iGX.iEw);
            v4 = new StringBuilder("[");
            v4.append(v3_1);
            v4.append("]apollo_cpu_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;
            om(v3_2 != null && v3_2.size() != 0) {
                Iterator v3_3 = v3_2.iterator();
                medan(v3_3.hasNext()) {
                    Object v4_1 = v3_3.next();
                    StringBuilder v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_namn:");
                    v5.append(((au)v4_1).getName());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_ver_namn:");
                    v5.append(((au)v4_1).aDA());
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_ver_kod:");
                    v5.append(((au)v4_1).gBl);
                    v5 = new StringBuilder("[");
                    v5.append(((au)v4_1).getName());
                    v5.append("]komponent_req_typ:");
                    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_barn_ver", arg9.iGX.iEx));
            v3_2.add(g.fs("ver_serie", arg9.iGX.iEw));
            v3_2.add(g.fs("barn_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_kÀrnor", String.valueOf(com.uc.b.a.a.c.Jl())));
            v3_2.add(g.fs("ram_1", String.valueOf(com.uc.b.a.a.h.Po())));
            v3_2.add(g.fs("totalram", String.valueOf(com.uc.b.a.a.h.OL())));
            c.aBh();
            v3_2.add(g.fs("rom_1", c.getRomInfo()));
            v4_5 = e.getScreenWidth();
            int v6 = e.getScreenHeight();
            StringBuilder v7 = new StringBuilder();
            v7.append(v4_5);
            v7.append("*");
            v7.append(v6);
            v3_2.add(g.fs("ss", v7.toString()));
            v3_2.add(g.fs("api_nivÄ", String.valueOf(Build$VERSION.SDK_INT)));
            v3_2.add(g.fs("uc_apk_lista", SystemHelper.getUCMobileApks()));
            Iterator v4_6 = arg9.iGX.iEA.entrySet().iterator();
            medan(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();
        }
 
        om(v3 == null) {
            this.iGY.iGI.a(arg9, "up_encode", "yes", "fail");
            return;
        }
 
        v4_5 = this.iGY.iGw ? 0x1F : 0;
        om(v3 == null) {
        }
        annars {
            v3 = g.i(v4_5, v3);
            om(v3 == null) {
            }
            annars {
                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);
            }
        }
 
        om(v1 == null) {
            this.iGY.iGI.a(arg9, "up_encrypt", "yes", "fail");
            return;
        }
 
        om(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);
    }

HÀr ser vi hur en POST-förfrÄgan formas. Vi noterar skapandet av en array av 16 byte och dess fyllning: 0x5F, 0, 0x1F, -50 (=0xCE). Det överensstÀmmer med vad vi sÄg i förfrÄgan ovan.

I denna klass kan vi Àven se en inre klass som har en annan intressant metod:

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

Metoden fÄr en byte-array som indata och kontrollerar att den första byten Àr 0x60 eller den tredje byten Àr 0xD0, samt att den andra byten Àr 1, 11 eller 0x1F. LÄt oss titta pÄ svaret frÄn servern: första byten Àr 0x60, den andra Àr 0x1F, den tredje Àr 0x60. Det verkar vara vad vi behöver. Enligt raderna ("up_decrypt", till exempel) borde en metod som avkrypterar svaret frÄn servern anropas hÀr.
Vi gÄr vidare till metoden g.j. Vi noterar att den första argumentet som förs över Àr byten vid förskjutning 2 (dvs. 0x1F i vÄrt fall), och som andra argument Àr svaret frÄn servern utan
de första 16 byten.

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

Uppenbarligen vÀljs en avkrypteringsalgoritm hÀr, och den aktuella byten som i vÄrt fall
i det hÀr fallet Àr 0x1F, som betyder ett av tre möjliga alternativ.

Vi fortsÀtter med kodanalysen. Efter nÄgra hopp hamnar vi i en metod med det talande namnet decryptBytesByKey.

HÀr separeras ytterligare tvÄ byte frÄn vÄrt svar, och av dem skapas en strÀng. Det Àr tydligt att nyckeln för att dekryptera meddelandet vÀljs pÄ detta sÀtt.

    private static byte[] decryptBytesByKey(byte[] bytes) {
        byte[] v0 = null;
        if(bytes != null) {
            try {
                if(bytes.length < EncryptHelper.PREFIX_BYTES_SIZE) {
                }
                else if(bytes.length == EncryptHelper.PREFIX_BYTES_SIZE) {
                    return v0;
                }
                else {
                    byte[] prefix = new byte[EncryptHelper.PREFIX_BYTES_SIZE];  // 2 byte
                    System.arraycopy(bytes, 0, prefix, 0, prefix.length);
                    String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Val av nyckel
                    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;
    }

Förut nÀmner vi att i detta skede fÄr man Ànnu inte nyckeln, utan bara dess "identifierare". Att fÄ nyckeln Àr lite mer komplicerat.

I nÀsta metod lÀggs ytterligare tvÄ parametrar till de befintliga, vilket gör dem till fyra: det magiska talet 16, nyckelidentifieraren, de krypterade data och en obegriplig strÀng (i vÄrt fall tom).

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

Efter en serie övergÄngar kommer vi till metoden staticBinarySafeDecryptNoB64 grÀnssnittet com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. I huvudkoden för applikationen finns inga klasser som implementerar detta grÀnssnitt. SÄdan klass finns i filen lib/armeabi-v7a/libsgmain.so, som egentligen inte Àr .so, utan .jar. Den metod vi Àr intresserade av implementeras pÄ följande sÀtt:

paket com.alibaba.wireless.security.a.i;
 
// ...
 
public class a implements IStaticDataEncryptComponent {
    privat ISecurityGuardPlugin a;
// ...
    privat byte[] a(int mode, int magicInt, int xzInt, String keyId, byte[] encrypted, String magicString) {
        return this.a.getRouter().doCommand(10601, new Object[]{Integer.valueOf(mode), Integer.valueOf(magicInt), Integer.valueOf(xzInt), keyId, encrypted, magicString});
    }
// ...
    privat byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
        return this.a(2, magicInt, 0, keyId, encrypted, magicString);
    }
// ...
    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);
    }
//...
}

HÀr lÀggs vÄr parameterlista till med ytterligare tvÄ heltal: 2 och 0. Tydligen innebÀr
2 dekryptering, som i metoden doFinal i systemklassen javax.crypto.Cipher. Och allt detta skickas till en viss Router med nummer 10601 — det Ă€r, uppenbarligen, kommandonumret.

Efter en kedja av övergÄngar hittar vi klassen som implementerar grÀnssnittet IRouterComponent och metoden doCommand:

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

Och Àven klassen JNICLibrary, dÀr den natives metoden doCommandNative:

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

Det betyder att vi behöver hitta metoden i den native koden. doCommandNativeOch hÀr börjar det roliga.

Obfuskering av maskinkoden

I filen libsgmain.so (som i verkligheten Ă€r .jar och dĂ€r vi lĂ€ngre upp hittade implementeringen av vissa grĂ€nssnitt relaterade till kryptering) finns en native-bibliotek: libsgmainso-6.4.36.so. Vi öppnar den i IDA och fĂ„r en mĂ€ngd dialogrutor med fel. Problemet Ă€r att sektionstabellen (section header table) – Ă€r ogiltig. Detta har gjorts avsiktligt för att komplicera analysen.

Letar efter sÄrbarheter i UC Browser

Men det Àr inte nödvÀndigt: för att korrekt ladda ELF-filen och analysera den, rÀcker det med programhuvudets tabell (program header table). SÄ vi tar helt enkelt bort sektionstabellen genom att nolla motsvarande fÀlt i rubriken.

Letar efter sÄrbarheter i UC Browser

Öppnar filen i IDA igen.

Det finns tvÄ sÀtt att informera den virtuella Java-maskinen om var i den native-biblioteket implementeringen av metoden, som deklarerades i Java-koden som native, finns. Det första - ge den ett namn som ser ut som Java_namn_paket_NamnKlass_namnMetod.

Det andra steget Àr att registrera det vid inlÀsningen av biblioteket (i funktionen JNI_OnLoad)
genom att anropa funktionen RegisterNatives.

I vÄrt fall, om vi anvÀnder det första sÀttet, bör namnet vara följande: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Bland de exporterade funktionerna finns ingen sÄdan, vilket innebÀr att vi mÄste leta efter anropet. RegisterNatives.
Vi gÄr till funktionen JNI_OnLoad och ser följande bild:

Letar efter sÄrbarheter i UC Browser

Vad hĂ€nder hĂ€r? Vid första anblicken Ă€r början och slutet av funktionen typiska för ARM-arkitektur. Den första instruktionen sparar innehĂ„llet av de register som funktionen kommer att anvĂ€nda (i detta fall R0, R1 och R2), samt innehĂ„llet av registret LR, dĂ€r returadressen frĂ„n funktionen finns. Den sista instruktionen Ă„terstĂ€ller de sparade registren, och returadressen sĂ€tts direkt i registret PC — sĂ„ sker Ă„tergĂ„ngen frĂ„n funktionen. Men om man ser noggrant kan man lĂ€gga mĂ€rke till att nĂ€st sista instruktionen Ă€ndrar returadressen som sparats i stacken. LĂ„t oss berĂ€kna vad den kommer att bli efter
att koden har körts. I R1 laddas en viss adress 0xB130, 5 subtraheras frÄn den, sedan flyttas den till R0 och 0x10 lÀggs till. Resultatet blir 0xB13B. SÄledes tror IDA att den sista instruktionen utför en vanlig ÄtergÄng frÄn funktionen, men i sjÀlva verket sker ett hopp till den berÀknade adressen 0xB13B.

HÀr bör det pÄminnas om att ARM-processorer har tvÄ lÀgen och tvÄ uppsÀttningar instruktioner: ARM och Thumb. Den lÀgsta biten av adressen talar om för processorn vilken uppsÀttning instruktioner som anvÀnds. Det vill sÀga, adressen Àr egentligen 0xB13A, och enan i den lÀgsta biten indikerar Thumb-lÀget.

I början av varje funktion i detta bibliotek har en liknande "adapter" lagts till och
skrĂ€pkod. Vi kommer inte att gĂ„ in pĂ„ dem i detalj hĂ€r – bara kom ihĂ„g,
att den verkliga början av nÀstan alla funktioner ligger lite lÀngre fram.

Eftersom det inte finns nÄgot tydligt hopp till 0xB13A, har IDA inte sjÀlv insett att det hÀr ligger kod. Av samma anledning kÀnner den inte igen stor del av koden i biblioteket som kod, vilket nÄgot försvÄrar analysen. Vi talar om för IDA att det hÀr Àr kod, och sÄ hÀr ser resultatet ut:

Letar efter sÄrbarheter i UC Browser

Vid 0xB144 börjar klart en tabell. Vad finns i sub_494C?

Letar efter sÄrbarheter i UC Browser

Vid anrop av denna funktion fÄr vi adressen till den tidigare nÀmnda tabellen (0xB144) i registret LR. I R0 har vi indexet i denna tabell. Det vill sÀga, ett vÀrde tas frÄn tabellen, lÀggs till LR och vi fÄr
adressen vi ska gÄ till. Vi försöker rÀkna ut den: 0xB144 + [0xB144 + 8 * 4] = 0xB144 + 0x120 = 0xB264. Vi gÄr till den angivna adressen och ser bokstavligen ett par anvÀndbara instruktioner och Äterigen en övergÄng till 0xB140:

Letar efter sÄrbarheter i UC Browser

Nu följer vi förskjutningen med index 0x20 frÄn tabellen.

Med tanke pÄ storleken pÄ tabellen kommer sÄdana övergÄngar att förekomma mycket i koden. FrÄgan uppstÄr om det gÄr att automatisera detta mer, utan att manuellt berÀkna adresser. HÀr kommer skript och möjligheten att patcha koden i IDA till hjÀlp:

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 "Fel operandtyp pÄ", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
            table = None
        if table is None:
            print "Kunde inte hitta tabellen"
        else:
            print "table =", hex(table)
            offset = get_wide_dword(table + (index << 2))
            put_unconditional_branch(ea, table + offset)
    else:
        print "OkÀnd kod", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
    print "Kunde inte upptÀcka första instruktionen"

SÀtt markören pÄ raden 0xB26A, kör skriptet och vi ser en övergÄng till 0xB4B0:

Letar efter sÄrbarheter i UC Browser

IDA identifierade Äterigen inte detta avsnitt som kod. Vi hjÀlper till och ser en annan konstruktion:

Letar efter sÄrbarheter i UC Browser

Instruktionerna efter BLX ser inte sÀrskilt meningsfulla ut, det liknar mer en förskjutning. Vi tittar i sub_4964:

Letar efter sÄrbarheter i UC Browser

Och faktiskt, hÀr tas en dword frÄn adressen som ligger i LR, lÀggs till denna adress, varefter vÀrdet frÄn den resulterande adressen tas och lÀggs pÄ stacken. Dessutom lÀggs 4 till LR för att hoppa över denna förskjutning efter retur frÄn funktionen. Efter detta tar kommandot POP {R1} det erhÄllna vÀrdet frÄn stacken. Om vi ser vad som finns pÄ adressen 0xB4BA + 0xEA = 0xB5A4 kan vi se nÄgot som liknar en adress-tabell:

Letar efter sÄrbarheter i UC Browser

För att patcha denna konstruktion behöver vi hÀmta tvÄ parametrar frÄn koden: förskjutning och registernummer dit resultatet ska placeras. För varje möjligt register mÄste vi i förvÀg förbereda en kodsnutt.

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-instruktion hittades inte"
    else:
        print "Fel operandtyp pÄ +4:", get_operand_type(ea + 4, 0)
else:
    print "Kunde inte upptÀcka första instruktionerna"

Placera markören i början av konstruktionen vi vill ersĂ€tta — 0xB4B2 — och kör skriptet:

Letar efter sÄrbarheter i UC Browser

Förutom de redan nÀmnda konstruktionerna finns det Àven sÄdana hÀr i koden:

Letar efter sÄrbarheter i UC Browser

Som i det föregÄende fallet följer en förskjutning efter instruktionen BLX:

Letar efter sÄrbarheter i UC Browser

Ta förskjutningen frÄn adressen i LR, lÀgg den till LR och gÄ dit. 0x72044 + 0xC = 0x72050. Skriptet för denna konstruktion Àr vÀldigt enkelt:

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 "Kunde inte upptÀcka första instruktionen"

Resultatet av skriptets körning:

Letar efter sÄrbarheter i UC Browser

Efter att funktionen har blivit patchad kan man ange IDA för dess verkliga början. Den kommer att samla hela funktionens kod bit för bit, och det gÄr att dekompilera med hjÀlp av HexRays.

Avkodning av strÀngar

Vi har lÀrt oss att bekÀmpa obfuskering av maskinkod i biblioteket libsgmainso-6.4.36.so frÄn UC Browser och fick funktionens kod JNI_OnLoad.

int __fastcall real_JNI_OnLoad(JavaVM *vm)
{
  int result; // r0
  jclass clazz; // r0 MAPDST
  int v4; // r0
  JNIEnv *env; // r4
  int v6; // [sp-40h] [bp-5Ch]
  int v7; // [sp+Ch] [bp-10h]
  v7 = *(_DWORD *)off_8AC00;
  if ( !vm )
    goto LABEL_39;
  sub_7C4F4();
  env = (JNIEnv *)sub_7C5B0(0);
  if ( !env )
    goto LABEL_39;
  v4 = sub_72CCC();
  sub_73634(v4);
  sub_73E24(&unk_83EA6, &v6, 49);
  clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);
  if ( clazz
    && (sub_9EE4(),
        sub_71D68(env),
        sub_E7DC(env) >= 0
     && sub_69D68(env) >= 0
     && sub_197B4(env, clazz) >= 0
     && sub_E240(env, clazz) >= 0
     && sub_B8B0(env, clazz) >= 0
     && sub_5F0F4(env, clazz) >= 0
     && sub_70640(env, clazz) >= 0
     && sub_11F3C(env) >= 0
     && sub_21C3C(env, clazz) >= 0
     && sub_2148C(env, clazz) >= 0
     && sub_210E0(env, clazz) >= 0
     && sub_41B58(env, clazz) >= 0
     && sub_27920(env, clazz) >= 0
     && sub_293E8(env, clazz) >= 0
     && sub_208F4(env, clazz) >= 0) )
  {
    result = (sub_B7B0(env, clazz) >> 31) | 0x10004;
  }
  else
  {
LABEL_39:
    result = -1;
  }
  return result;
}

LÄt oss ta en nÀrmare titt pÄ följande rader:

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

I funktionen sub_73E24 sker tydligt avkodning av klassnamnet. Som parametrar för denna funktion skickas en pekare till data som liknar krypterade, en slags buffert och ett nummer. Det Àr uppenbart att efter anropet av funktionen kommer bufferten att innehÄlla den avkodade strÀngen, eftersom den skickas till funktionen FindClass, som tar klassnamnet som det andra parametern. DÀrmed Àr numret storleken pÄ bufferten eller lÀngden pÄ strÀngen. LÄt oss försöka avkoda klassnamnet, det borde visa oss om vi Àr pÄ rÀtt vÀg. Vi ska titta nÀrmare pÄ vad som hÀnder i sub_73E24.

int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size)
{
  int v4; // r6
  int v7; // r11
  int v8; // r9
  int v9; // r4
  size_t v10; // r5
  int v11; // r0
  struc_1 v13; // [sp+0h] [bp-30h]
  int v14; // [sp+1Ch] [bp-14h]
  int v15; // [sp+20h] [bp-10h]
  v4 = 0;
  v15 = *(_DWORD *)off_8AC00;
  v14 = 0;
  v7 = sub_7AF78(17);
  v8 = sub_7AF78(size);
  if ( !v7 )
  {
    v9 = 0;
    goto LABEL_12;
  }
  (*(void (__fastcall **)(int, const char *, int))(v7 + 12))(v7, "DcO/lcK+h?m3c*q@", 16);
  if ( !v8 )
  {
LABEL_9:
    v4 = 0;
    goto LABEL_10;
  }
  v4 = 0;
  if ( !in )
  {
LABEL_10:
    v9 = 0;
    goto LABEL_11;
  }
  v9 = 0;
  if ( out )
  {
    memset(out, 0, size);
    v10 = size - 1;
    (*(void (__fastcall **)(int, unsigned __int8 *, size_t))(v8 + 12))(v8, in, v10);
    memset(&v13, 0, 0x14u);
    v13.field_4 = 3;
    v13.field_10 = v7;
    v13.field_14 = v8;
    v11 = sub_6115C(&v13, &v14);
    v9 = v11;
    if ( v11 )
    {
      if ( *(_DWORD *)(v11 + 4) == v10 )
      {
        qmemcpy(out, *(const void **)v11, v10);
        v4 = *(_DWORD *)(v9 + 4);
      }
      else
      {
        v4 = 0;
      }
      goto LABEL_11;
    }
    goto LABEL_9;
  }
LABEL_11:
  sub_7B148(v7);
LABEL_12:
  if ( v8 )
    sub_7B148(v8);
  if ( v9 )
    sub_7B148(v9);
  return v4;
}

Funktionen sub_7AF78 skapar ett instans av en behÄllare för byte-arrayer av angiven storlek (vi gÄr inte in pÄ detaljer om dessa behÄllare). HÀr skapas tvÄ sÄdana behÄllare: en för strÀngen «DcO/lcK+h?m3c*q@» (det Àr inte svÄrt att gissa att detta Àr nyckeln), i den andra Àr de krypterade data. BÄda objekten placeras sedan i en struktur som överförs till funktionen sub_6115C. LÄt oss ocksÄ notera att i denna struktur finns ett fÀlt med vÀrdet 3. LÄt oss se vad som hÀnder med denna struktur vidare.

int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2)
{
  int v3; // lr
  unsigned int v4; // r1
  int v5; // r0
  int v6; // r1
  int result; // r0
  int v8; // r0
  *a2 = 820000;
  if ( a1 )
  {
    v3 = a1->field_14;
    if ( v3 )
    {
      v4 = a1->field_4;
      if ( v4 field_0, a1->field_10, v3);
            goto LABEL_17;
          case 3u:
            v8 = sub_6364C(a1->field_0, a1->field_10, v3);
            goto LABEL_17;
          case 0x10u:
          case 0x11u:
          case 0x12u:
            v8 = sub_612F4(
                   a1->field_0,
                   v4,
                   *(_QWORD *)&a1->field_8,
                   *(_QWORD *)&a1->field_8 >> 32,
                   a1->field_10,
                   v3,
                   a2);
            goto LABEL_17;
          case 0x14u:
            v8 = sub_63A28(a1->field_0, v3);
            goto LABEL_17;
          case 0x15u:
            sub_61A60(a1->field_0, v3, a2);
            return result;
          case 0x16u:
            v8 = sub_62440(a1->field_14);
            goto LABEL_17;
          case 0x17u:
            v8 = sub_6226C(a1->field_10, v3);
            goto LABEL_17;
          case 0x18u:
            v8 = sub_63530(a1->field_14);
LABEL_17:
            v6 = 0;
            if ( v8 )
            {
              *a2 = 0;
              v6 = v8;
            }
            return v6;
          default:
            LOWORD(v5) = 28032;
            goto LABEL_5;
        }
      }
    }
  }
  LOWORD(v5) = -27504;
LABEL_5:
  HIWORD(v5) = 13;
  v6 = 0;
  *a2 = v5;
  return v6;
}

Som parametern switch överförs ett fÀlt i strukturen, som tidigare har tilldelats vÀrdet 3. Vi ser pÄ case 3: i funktionen sub_6364C parametrar överförs frÄn strukturen, som lades dit i den tidigare funktionen, dvs. nyckel och krypterade data. Om man ser noggrant pÄ sub_6364C, kan man fÄ veta algoritmen för RC4.

Vi har algoritmen och nyckeln. LÄt oss försöka dekryptera klassnamnet. HÀr Àr vad vi fick: com/taobao/wireless/security/adapter/JNICLibrary. UtmÀrkt! Vi Àr pÄ rÀtt vÀg.

KommandotrÀd

Nu mÄste vi hitta anropet RegisterNatives, som pekar pÄ funktionen doCommandNative. Vi gÄr igenom funktionerna som anropas frÄn JNI_OnLoad, och hittar det i sub_B7B0:

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

Och faktiskt, hÀr registreras en nativ metod med namnet doCommandNative. Nu vet vi dess adress. LÄt oss se vad den gör.

int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args)
{
  int v5; // r5
  struc_2 *a5; // r6
  int v9; // r1
  int v11; // [sp+Ch] [bp-14h]
  int v12; // [sp+10h] [bp-10h]
  v5 = 0;
  v12 = *(_DWORD *)off_8AC00;
  v11 = 0;
  a5 = (struc_2 *)malloc(0x14u);
  if ( a5 )
  {
    a5->field_0 = 0;
    a5->field_4 = 0;
    a5->field_8 = 0;
    a5->field_C = 0;
    v9 = command % 10000 / 100;
    a5->field_0 = command / 10000;
    a5->field_4 = v9;
    a5->field_8 = command % 100;
    a5->field_C = env;
    a5->field_10 = args;
    v5 = sub_9D60(command / 10000, v9, command % 100, 1, (int)a5, &v11);
  }
  free(a5);
  if ( !v5 && v11 )
    sub_7CF34(env, v11, &byte_83ED7);
  return v5;
}

Av namnet kan man gissa att detta Àr ingÄngspunkten för alla funktioner som utvecklarna har valt att flytta till den inhemska biblioteket. Vi Àr intresserade av funktionen med nummer 10601.

Av koden kan man se att frÄn kommandonumret fÄr man tre tal: command / 10000, command % 10000 / 100 och command % 10, dvs. i vÄrt fall, 1, 6 och 1. Dessa tre tal, tillsammans med pekaren till JNIEnv och argumenten som överförs till funktionen, sÀtts in i en struktur och överförs vidare. Med hjÀlp av de tre erhÄllna talen (som vi kallar N1, N2 och N3) byggs kommandotrÀdet.

UngefÀr sÄ hÀr:

Letar efter sÄrbarheter i UC Browser

TrÀdet fylls i dynamiskt i JNI_OnLoad.
Tre siffror kodar vÀgen i trÀdet. Varje blad i trÀdet innehÄller den resurserade adressen till motsvarande funktion. Nyckeln ligger i förÀldranoden. Att hitta stÀllet i koden dÀr den funktion vi behöver lÀggs till i trÀdet Àr inte sÀrskilt svÄrt om man förstÄr alla anvÀnda strukturer (beskrivningen av dem lÀmnas ut för att inte förlÀnga den redan ganska lÄnga artikeln).

Mer obfuskering

Vi fick adressen till funktionen som ska avkoda trafiken: 0x5F1AC. Men glÀdjen Àr kortvarig: UC Browser utvecklarna har förberett ytterligare en överraskning för oss.

Efter att ha fÄtt parametrarna frÄn arrayen som bildades i Java-koden, hamnar vi
i funktionen vid adress 0x4D070. Och hÀr vÀntar Ànnu en form av kodobfuskering.

Vi lÀgger in tvÄ index i R7 och R4:

Letar efter sÄrbarheter i UC Browser

Flyttar det första indexet till R11:

Letar efter sÄrbarheter i UC Browser

För att fÄ adressen frÄn tabellen anvÀnder vi index:

Letar efter sÄrbarheter i UC Browser

Efter att ha gÄtt till den första adressen anvÀnds det andra indexet som ligger i R4. Tabellen har 230 element.

Vad gör vi med detta? Vi kan sÀga till IDA att det hÀr Àr en switch: Edit -> Other -> Specify switch idiom.

Letar efter sÄrbarheter i UC Browser

Den resulterande koden Àr skrÀmmande. Men genom att navigera genom dess snÄr kan man lÀgga mÀrke till att vi kallar en funktion som vi redan kÀnner till. sub_6115C:

Letar efter sÄrbarheter i UC Browser

Det fanns en switch dĂ€r avkodningen med hjĂ€lp av RC4-algoritmen lĂ„g i case 3. I det hĂ€r fallet fylls strukturen som skickas till funktionen med parametrarna som skickades till doCommandNative. Vi minns att vi hade en magicInt med vĂ€rdet 16. Vi kollar det motsvarande case – och efter nĂ„gra steg hittar vi koden som vi kan identifiera algoritmen med.

Letar efter sÄrbarheter i UC Browser

Det Àr AES!

Algoritmen Àr klar, nu ÄterstÄr att fÄ tag pÄ dess parametrar: lÀge, nyckel och kanske en initialiseringsvektor (dess existens beror pÄ AES-algoritmens arbetslÀge). Strukturen med dessa parametrar bör skapas nÄgonstans innan funktionen sub_6115C, men denna del av koden Àr sÀrskilt vÀl obfuskad, sÄ idén att patcha koden för att dumpa alla dekoderingsfunktionsparametrar till en fil uppstÄr.

Patch

För att inte behöva skriva hela patchkoden manuellt i assembler kan man köra Android Studio, skriva en funktion dÀr som tar emot samma parametrar som vÄr dekoderingsfunktion, och skriver till en fil, och sedan kopiera och klistra in koden som kompileraren genererar.

VĂ„ra vĂ€nner i UC Browser-teamet har ocksĂ„ "tĂ€nkt pĂ„" bekvĂ€mligheten av att lĂ€gga till kod. Vi minns att i början av varje funktion har vi skrĂ€pkod, som lĂ€tt kan ersĂ€ttas med vilken annan kod som helst. Mycket praktiskt 🙂 Faktum Ă€r att det i början av den mĂ„lade funktionen Ă€r lite ont om plats för koden som sparar alla parametrar i en fil. Vi var tvungna att dela upp den i delar och anvĂ€nda skrĂ€pblocken frĂ„n angrĂ€nsande funktioner. Totalt blev det fyra delar.

Första delen:

Letar efter sÄrbarheter i UC Browser

I ARM-arkitekturen överförs de första fyra parametrarna till funktionen via registren R0-R3, övriga, om de finns, via stacken. ÅtervĂ€ndningsadressen överförs i registret LR. Allt detta mĂ„ste sparas för att funktionen ska kunna köras efter att vi har dumpat dess parametrar. Vi mĂ„ste ocksĂ„ spara alla register som vi kommer att anvĂ€nda i processen, sĂ„ vi gör PUSH.W {R0-R10,LR}. I R7 fĂ„r vi adressen till listan över de parametrar som överförts till funktionen via stacken.

Med hjÀlp av funktionen fopen öppnar vi en fil /data/local/tmp/aes i lÀget "ab",
dvs. för tillÀgg. I R0 laddar vi adressen till filnamnet, i R1 - adressen till strÀngen som angiver lÀge. Och hÀr tar skrÀpkoden slut, sÄ vi gÄr vidare till nÀsta funktion. För att den ska fortsÀtta fungera sÀtter vi en övergÄng i början till den riktiga funktionen förbi skrÀpet, och istÀllet för skrÀpet lÀgger vi till fortsÀttningen av patchen.

Letar efter sÄrbarheter i UC Browser

Vi kallar fopen.

De första tre parametrarna till funktionen aes har typen int. Eftersom vi i början sparade registren pÄ stacken kan vi enkelt överföra funktionens fwrite adresser i stacken.

Letar efter sÄrbarheter i UC Browser

DÀrefter har vi tre strukturer som innehÄller datastorleken och en pekare till datan för nyckeln, initialiseringsvektorn och de krypterade uppgifterna.

Letar efter sÄrbarheter i UC Browser

Till slut stÀnger vi filen, ÄterstÀller registren och överför kontrollen till den riktiga funktionen. aes.

Vi samlar APK:n med den patchade biblioteket, signer, laddar upp den till enheten/emulatorn, startar den. Vi ser att vÄr dump skapas, och mycket data skrivs dit. WebblÀsaren anvÀnder kryptering inte bara för trafiken, och all kryptering gÄr genom den övervÀgda funktionen. Men de nödvÀndiga uppgifterna saknas av nÄgon anledning, och den nödvÀndiga förfrÄgan syns inte i trafiken. För att inte vÀnta pÄ att UC Browser ska göra den nödvÀndiga förfrÄgan tar vi det krypterade svaret frÄn servern som fick tidigare och patchar applikationen igen: vi lÀgger till avkryptering i onCreate för huvudaktiviteten.

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

Vi samlar, signerar, installerar, kör. FÄr NullPointerException, eftersom metoden returnerade null.

Under en vidare analys av koden upptÀcktes en funktion som avkodar intressanta rader: "META-INF/" och ".RSA". Det verkar som att applikationen kontrollerar sitt certifikat. Eller till och med genererar nycklar frÄn det. Att grÀva i vad som hÀnder med certifikatet kÀnns inte lockande, sÄ vi kommer bara att ge det rÀtt certifikat. Vi patchar den krypterade raden sÄ att istÀllet för "META-INF/" fÄr vi "BLABLINF/", skapar en mapp med det namnet i APK:en och lÀgger dÀr certifikatet frÄn belkobrowsern.

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

MitM

Vi har fÄtt nyckeln och initialiseringsvektorn, som Àr lika med nyckeln. Vi ska försöka avkoda svaret frÄn servern i CBC-lÀge.

Letar efter sÄrbarheter i UC Browser

Vi ser URL:en till arkivet, nÄgot som liknar MD5, "extract_unzipsize" och ett nummer. Kontrollerar: MD5 för arkivet stÀmmer, storleken pÄ den utpackade biblioteken stÀmmer. Vi försöker patcha denna bibliotek och ge den till webblÀsaren. För att visa att vÄr patchade bibliotek har laddats, kommer vi att starta en Intent för att skapa ett SMS med texten "PWNED!". Vi kommer att Àndra tvÄ svar frÄn servern: puds.ucweb.com/upgrade/index.xhtml och för nedladdning av arkivet. I den första Àndrar vi MD5 (storleken efter utpakning förÀndras inte), i den andra ger vi arkivet med den patchade biblioteken.

WebblÀsaren försöker ladda ner arkivet flera gÄnger, varefter den ger ett felmeddelande. Tydligen Àr det nÄgot
som inte passar. Efter att ha analyserat detta oklara format visade det sig att servern Àven skickar storleken pÄ arkivet:

Letar efter sÄrbarheter i UC Browser

Den Àr kodad i LEB128. Efter patchen har storleken pÄ arkivet med biblioteket förÀndrats nÄgot, dÀrför ansÄg webblÀsaren att arkivet laddades fel och efter flera försök gav den ett fel.

Vi justerar storleken pĂ„ arkivet ... och – seger! 🙂 Resultatet pĂ„ video.

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

Konsekvenser och reaktion frÄn utvecklaren

PÄ samma sÀtt kunde hackare anvÀnda den osÀkra funktionen i UC Browser för att sprida och köra skadliga bibliotek. Dessa bibliotek skulle fungera i webblÀsarens sammanhang och fÄ alla dess systembehörigheter. Som en följd av detta skulle de kunna visa phishing-fönster samt fÄ Ätkomst till arbetsfiler för den orangea kinesiska ekorren, inklusive inloggningar, lösenord och cookies som lagras i databasen.

Vi har kontaktat utvecklarna av UC Browser och informerat dem om den funna problematiken, och försökt pÄpeka sÄrbarheten och dess farlighet, men de ville inte diskutera nÄgot med oss. Under tiden fortsatte webblÀsaren att visa sin farliga funktion för alla. Men sÄ snart vi avslöjade detaljerna om sÄrbarheten, gick det inte lÀngre att ignorera detta som tidigare. Den 27 mars slÀpptes
en ny version av UC Browser 12.10.9.1193, som kommunicerade med servern via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Dessutom, efter "fixen" och fram till tidpunkten för skrivandet av denna artikel, ledde försök att öppna en PDF i webblÀsaren till ett felmeddelande med texten "OjdÄ, nÄgot gick fel!". BegÀran till servern vid försök att öppna PDF:en genomfördes inte, men begÀran vid uppstart av webblÀsaren genomfördes, vilket antyder en kvarstÄende möjlighet att ladda ner körbar kod i strid med Google Plays regler.

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster