Letar efter sÄrbarheter i UC Browser

Letar efter sÄrbarheter i UC Browser

Inledning

I slutet av mars rapporterad, att de upptÀckte en dold förmÄga att ladda och köra overifierad kod i UC Browser. Idag kommer vi att titta i detalj pÄ hur denna nedladdning sker och hur hackare kan anvÀnda den för sina egna syften.

För en tid sedan annonserades och distribuerades UC Browser mycket aggressivt: den installerades pÄ anvÀndarnas enheter med skadlig programvara, distribuerad frÄn olika webbplatser under sken av videofiler (dvs anvÀndare trodde att de laddade ner till exempel en porrfilm, men fick istÀllet en APK med den hÀr webblÀsaren), anvÀnde skrÀmmande banners med meddelanden om att webblÀsaren var förÄldrad, sÄrbar och sÄnt. I den officiella UC Browser-gruppen pÄ VK finns det tema, dÀr anvÀndare kan klaga pÄ orÀttvis reklam, det finns mÄnga exempel dÀr. 2016 var det jÀmnt videoreklam pÄ ryska (ja, reklam för en annonsblockerande webblÀsare).

I skrivande stund har UC Browser över 500 000 000 installationer pÄ Google Play. Detta Àr imponerande - bara Google Chrome har mer. Bland recensionerna kan du se ganska mÄnga klagomÄl om reklam och omdirigeringar till vissa applikationer pÄ Google Play. Detta var anledningen till vÄr forskning: vi bestÀmde oss för att se om UC Browser gjorde nÄgot dÄligt. Och det visade sig att han gör det!

I applikationskoden upptÀcktes möjligheten att ladda ner och köra körbar kod, vilket strider mot reglerna för publicering av ansökningar pÄ Google Play. Förutom att ladda ner körbar kod gör UC Browser det pÄ ett osÀkert sÀtt, vilket kan anvÀndas för att starta en MitM-attack. LÄt oss se om vi kan utföra en sÄdan attack.

Allt som stÄr nedan Àr relevant för versionen av UC Browser som var tillgÀnglig pÄ Google Play vid tidpunkten för studien:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-фаĐčла: f5edb2243413c777172f6362876041eb0c3a928c

Attackvektor

I UC Browser-manifestet kan du hitta en tjÀnst med ett sjÀlvförklarande namn com.uc.deployment.UpgradeDeployService.

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

NÀr den hÀr tjÀnsten startar gör webblÀsaren en POST-begÀran till puds.ucweb.com/upgrade/index.xhtml, som kan ses i trafiken en tid efter starten. Som svar kan han fÄ ett kommando för att ladda ner nÄgon uppdatering eller ny modul. Under analysen gav inte servern sÄdana kommandon, men vi mÀrkte att nÀr vi försöker öppna en PDF i webblÀsaren gör den en andra begÀran till adressen som anges ovan, varefter den laddar ner det inbyggda biblioteket. För att utföra attacken bestÀmde vi oss för att anvÀnda den hÀr funktionen i UC Browser: möjligheten att öppna PDF med ett inbyggt bibliotek, som inte finns i APK och som det laddar ner frÄn Internet vid behov. Det Àr vÀrt att notera att UC Browser teoretiskt sett kan tvingas ladda ner nÄgot utan anvÀndarinteraktion - om du ger ett vÀlformaterat svar pÄ en begÀran som exekveras efter att webblÀsaren har startat. Men för att göra detta mÄste vi studera interaktionsprotokollet med servern mer i detalj, sÄ vi bestÀmde oss för att det skulle vara lÀttare att redigera det avlyssnade svaret och ersÀtta biblioteket för att arbeta med PDF.

SÄ nÀr en anvÀndare vill öppna en PDF direkt i webblÀsaren kan följande förfrÄgningar ses i trafiken:

Letar efter sÄrbarheter i UC Browser

Först finns det en POST-förfrÄgan till puds.ucweb.com/upgrade/index.xhtml, dÄ
Ett arkiv med ett bibliotek för visning av PDF- och kontorsformat laddas ner. Det Àr logiskt att anta att den första begÀran sÀnder information om systemet (Ätminstone arkitekturen för att tillhandahÄlla det erforderliga biblioteket), och som svar pÄ det fÄr webblÀsaren viss information om biblioteket som behöver laddas ner: adressen och ev. , nÄgot annat. Problemet Àr att denna begÀran Àr krypterad.

BegÀr fragment

Svarsfragment

Letar efter sÄrbarheter i UC Browser

Letar efter sÄrbarheter i UC Browser

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

Letar efter sÄrbarheter i UC Browser

Sök efter trafikdekrypteringskod

LÄt oss försöka dechiffrera serverns svar. LÄt oss titta pÄ klasskoden com.uc.deployment.UpgradeDeployService: frÄn metod onStartCommand gÄ till com.uc.deployment.bx, och frÄn det till com.uc.browser.core.dcfe:

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

Vi ser bildandet av en POST-förfrÄgan hÀr. Vi uppmÀrksammar skapandet av en array pÄ 16 byte och dess fyllning: 0x5F, 0, 0x1F, -50 (=0xCE). Sammanfaller med vad vi sÄg i förfrÄgan ovan.

I samma klass kan du se en kapslad 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 tar en array av byte som indata och kontrollerar att nollbyten Àr 0x60 eller den tredje byten Àr 0xD0, och den andra byten Àr 1, 11 eller 0x1F. Vi tittar pÄ svaret frÄn servern: nollbyten Àr 0x60, den andra Àr 0x1F, den tredje Àr 0x60. LÄter som vad vi behöver. Att döma av raderna ("up_decrypt", till exempel), bör en metod anropas hÀr som kommer att dekryptera serverns svar.
LÄt oss gÄ vidare till metoden gj. Observera att det första argumentet Àr byten vid offset 2 (dvs. 0x1F i vÄrt fall), och det andra Àr serversvaret utan
första 16 bytes.

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

SjÀlvklart vÀljer vi hÀr en dekrypteringsalgoritm och samma byte som finns i vÄr
fall lika med 0x1F, anger ett av tre möjliga alternativ.

Vi fortsÀtter att analysera koden. Efter ett par hopp befinner vi oss i en metod med ett sjÀlvförklarande namn decryptBytesByKey.

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

    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 баĐčта
System.arraycopy(bytes, 0, prefix, 0, prefix.length);
String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // Đ’Ń‹Đ±ĐŸŃ€ Đșлюча
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;
}

NÀr vi blickar framÄt noterar vi att vi i detta skede Ànnu inte fÄr nÄgon nyckel, 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 fyra av dem: det magiska numret 16, nyckelidentifieraren, den krypterade datan 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 rad övergÄngar kommer vi fram till metoden staticBinarySafeDecryptNoB64 grÀnssnitt com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Det finns inga klasser i huvudapplikationskoden som implementerar detta grÀnssnitt. Det finns en sÄdan klass i filen lib/armeabi-v7a/libsgmain.so, som egentligen inte Àr en .so, utan en .jar. Metoden vi Àr intresserade av implementeras enligt följande:

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

HÀr kompletteras vÄr lista över parametrar med ytterligare tvÄ heltal: 2 och 0. Att döma av
allt, 2 betyder dekryptering, som i metoden doFinal systemklass javax.crypto.Cipher. Och allt detta överförs till en viss router med numret 10601 - detta Àr tydligen kommandonumret.

Efter nÀsta kedja av övergÄngar hittar vi en klass som implementerar grÀnssnittet IRouterComponent och metod 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);
}
}

Och Àven klass JNICLibrary, dÀr den ursprungliga metoden deklareras doCommandNative:

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

Det betyder att vi mÄste hitta en metod i den ursprungliga koden doCommandNative. Och det Àr hÀr det roliga börjar.

Obfuskering av maskinkod

I fil libsgmain.so (som faktiskt Àr en .jar och dÀr vi hittade implementeringen av nÄgra krypteringsrelaterade grÀnssnitt precis ovan) finns det ett inbyggt bibliotek: libsgmainso-6.4.36.so. Vi öppnar den i IDA och fÄr en massa dialogrutor med fel. Problemet Àr att sektionsrubriktabellen Àr ogiltig. Detta görs med avsikt för att komplicera analysen.

Letar efter sÄrbarheter i UC Browser

Men det behövs inte: för att korrekt ladda en ELF-fil och analysera den rÀcker det med en programhuvudtabell. DÀrför tar vi helt enkelt bort sektionstabellen och nollstÀller motsvarande fÀlt i rubriken.

Letar efter sÄrbarheter i UC Browser

Öppna filen i IDA igen.

Det finns tvÄ sÀtt att berÀtta för den virtuella Java-maskinen var exakt i det inbyggda biblioteket implementeringen av en metod som deklarerats i Java-kod som ursprunglig finns. Den första Àr att ge den ett artnamn Java_package_name_ClassName_MethodName.

Det andra Àr att registrera det nÀr du laddar biblioteket (i funktionen JNI_OnLoad)
med hjÀlp av ett funktionsanrop Registrera infödda.

I vÄrt fall, om vi anvÀnder den första metoden, bör namnet vara sÄ hÀr: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Det finns ingen sÄdan funktion bland de exporterade funktionerna, vilket innebÀr att du mÄste leta efter ett samtal Registrera infödda.
LÄt oss gÄ till funktionen JNI_OnLoad och vi ser denna 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 pÄ stacken lagrar innehÄllet i registren som funktionen kommer att anvÀnda i sin funktion (i detta fall R0, R1 och R2), sÄvÀl som innehÄllet i LR-registret, som innehÄller returadressen frÄn funktionen . Den sista instruktionen ÄterstÀller de sparade registren, och returadressen placeras omedelbart i PC-registret - och ÄtergÄr dÀrmed frÄn funktionen. Men om du tittar noga kommer du att mÀrka att den nÀst sista instruktionen Àndrar returadressen som Àr lagrad pÄ stacken. LÄt oss rÀkna ut hur det kommer att bli efterÄt
kodexekvering. En viss adress 1xB0 laddas in i R130, 5 subtraheras frÄn den, sedan överförs den till R0 och 0x10 lÀggs till den. Det visar sig 0xB13B. SÄledes tror IDA att den sista instruktionen Àr en normal funktionsretur, men i sjÀlva verket gÄr den till den berÀknade adressen 0xB13B.

Det Àr vÀrt att komma ihÄg hÀr att ARM-processorer har tvÄ lÀgen och tvÄ uppsÀttningar instruktioner: ARM och Thumb. Den minst signifikanta biten av adressen talar om för processorn vilken instruktionsuppsÀttning som anvÀnds. Det vill sÀga, adressen Àr faktiskt 0xB13A, och en i den minst signifikanta biten indikerar tumlÀget.

En liknande "adapter" har lagts till i början av varje funktion i detta bibliotek och
sopkod. Vi kommer inte att uppehÄlla oss mer i detalj - vi kommer bara ihÄg
att den verkliga början av nÀstan alla funktioner Àr lite lÀngre bort.

Eftersom koden inte explicit hoppar till 0xB13A, kÀnde inte IDA sjÀlv igen att koden fanns pÄ denna plats. Av samma anledning kÀnner den inte igen det mesta av koden i biblioteket som kod, vilket gör analysen nÄgot svÄr. Vi sÀger till IDA att detta Àr koden, och det hÀr Àr vad som hÀnder:

Letar efter sÄrbarheter i UC Browser

Tabellen börjar helt klart pÄ 0xB144. Vad finns i sub_494C?

Letar efter sÄrbarheter i UC Browser

Vid anrop av denna funktion i LR-registret fÄr vi adressen till den tidigare nÀmnda tabellen (0xB144). I R0 - index i denna tabell. Det vill sÀga att vÀrdet tas frÄn tabellen, lÀggs till LR och resultatet Àr
adressen att gÄ till. LÄt oss försöka berÀkna det: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Vi gÄr till den mottagna adressen och ser bokstavligen ett par anvÀndbara instruktioner och gÄr igen till 0xB140:

Letar efter sÄrbarheter i UC Browser

Nu blir det en övergÄng vid offset med index 0x20 frÄn tabellen.

Att döma av tabellens storlek kommer det att finnas mÄnga sÄdana övergÄngar i koden. FrÄgan uppstÄr om det Àr möjligt att pÄ nÄgot sÀtt hantera detta mer automatiskt, utan att manuellt berÀkna adresser. Och skript och möjligheten att patcha kod i IDA kommer till vÄr hjÀlp:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((offset >> 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 2
if get_wide_word(ea1) == 0xbf00: #NOP
ea1 += 2
if get_operand_type(ea1, 0) == 1 and get_operand_value(ea1, 0) == 0 and get_operand_type(ea1, 1) == 2:
index = get_wide_dword(get_operand_value(ea1, 1))
print "index =", hex(index)
ea1 += 2
if get_operand_type(ea1, 0) == 7:
table = get_operand_value(ea1, 0) + 4
elif get_operand_type(ea1, 1) == 2:
table = get_operand_value(ea1, 1) + 4
else:
print "Wrong operand type on", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
table = None
if table is None:
print "Unable to find table"
else:
print "table =", hex(table)
offset = get_wide_dword(table + (index << 2))
put_unconditional_branch(ea, table + offset)
else:
print "Unknown code", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
print "Unable to detect first instruction"

Placera markören pÄ rad 0xB26A, kör skriptet och se övergÄngen till 0xB4B0:

Letar efter sÄrbarheter i UC Browser

IDA kÀnde igen inte detta omrÄde som en kod. Vi hjÀlper henne och ser en annan design dÀr:

Letar efter sÄrbarheter i UC Browser

Instruktionerna efter BLX verkar inte vara sÄ vettiga, det Àr mer som nÄgon form av förskjutning. LÄt oss titta pÄ sub_4964:

Letar efter sÄrbarheter i UC Browser

Och faktiskt, hÀr tas ett dord pÄ adressen som ligger i LR, adderat till denna adress, varefter vÀrdet pÄ den resulterande adressen tas och lÀggs pÄ stacken. Dessutom lÀggs 4 till LR sÄ att samma offset hoppas över efter att ha ÄtervÀnt frÄn funktionen. DÀrefter tar POP {R1}-kommandot det resulterande vÀrdet frÄn stacken. Om du tittar pÄ vad som finns pÄ adressen 0xB4BA + 0xEA = 0xB5A4, kommer du att se nÄgot som liknar en adresstabell:

Letar efter sÄrbarheter i UC Browser

För att patcha den hÀr designen mÄste du fÄ tvÄ parametrar frÄn koden: offset och registernumret dÀr du vill lÀgga resultatet. För varje eventuellt register mÄste du förbereda en kodbit i förvÀg.

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 << i):
register = i
break
if register == -1:
print "Unable to detect register"
else:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
if ea % 4 != 0:
ea += 2
patch_dword(ea, address)
elif pop[:3] == 'x5dxf8x04':
register = ord(pop[3]) >> 4
if register in patches:
address = get_wide_dword(ea + 8) + ea + 8
for b in patches[register]:
patch_byte(ea, b)
ea += 1
patch_dword(ea, address)
else:
print "POP instruction not found"
else:
print "Wrong operand type on +4:", get_operand_type(ea + 4, 0)
else:
print "Unable to detect first instructions"

Vi placerar markören i början av strukturen som vi vill ersÀtta - 0xB4B2 - och kör skriptet:

Letar efter sÄrbarheter i UC Browser

Förutom de redan nÀmnda strukturerna innehÄller koden Àven följande:

Letar efter sÄrbarheter i UC Browser

Som i föregÄende fall, efter BLX-instruktionen finns det en offset:

Letar efter sÄrbarheter i UC Browser

Vi tar offseten till adressen frÄn LR, lÀgger till den i LR och gÄr dit. 0x72044 + 0xC = 0x72050. Skriptet för denna design Àr ganska enkelt:

def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset < -2097152:
raise RuntimeError("Invalid offset")
if offset > 1023 or offset < -1024:
instruction1 = 0xf000 | ((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"

Resultat av skriptkörning:

Letar efter sÄrbarheter i UC Browser

NÀr allt Àr lappat i funktionen kan du peka IDA till dess verkliga början. Det kommer att lÀgga ihop all funktionskod, och den kan dekompileras med hjÀlp av HexRays.

Avkodning av strÀngar

Vi har lÀrt oss att hantera obfuskering av maskinkod i biblioteket libsgmainso-6.4.36.so frÄn UC Browser och fick funktionskoden 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 titta nÀrmare pÄ följande rader:

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

I funktion sub_73E24 klassnamnet dekrypteras tydligt. Som parametrar för denna funktion skickas en pekare till data liknande krypterad data, en viss buffert och ett nummer. Uppenbarligen, efter att ha anropat funktionen, kommer det att finnas en dekrypterad rad i bufferten, eftersom den skickas till funktionen FindClass, som tar klassnamnet som den andra parametern. DÀrför Àr siffran storleken pÄ bufferten eller lÀngden pÄ linjen. LÄt oss försöka dechiffrera klassnamnet, det borde berÀtta för oss om vi gÄr i rÀtt riktning. LÄt oss ta en nÀrmare titt 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;
}

Funktion sub_7AF78 skapar en instans av en behÄllare för byte-arrayer av den angivna storleken (vi kommer inte att uppehÄlla oss i dessa behÄllare i detalj). HÀr skapas tvÄ sÄdana behÄllare: en innehÄller raden "DcO/lcK+h?m3c*q@" (det Àr lÀtt att gissa att detta Àr en nyckel), den andra innehÄller krypterad data. DÀrefter placeras bÄda objekten i en viss struktur, som skickas till funktionen sub_6115C. LÄt oss ocksÄ markera ett fÀlt med vÀrdet 3 i denna struktur. LÄt oss se vad som hÀnder med den hÀr strukturen hÀrnÀst.

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 < 0x19 )
{
switch ( v4 )
{
case 0u:
v8 = sub_6419C(a1->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;
}

Switch-parametern Àr ett strukturfÀlt som tidigare tilldelats vÀrdet 3. Titta pÄ fall 3: till funktionen sub_6364C parametrar skickas frÄn strukturen som lades till dÀr i föregÄende funktion, dvs nyckeln och krypterad data. Om man tittar noga pÄ sub_6364C, kan du kÀnna igen RC4-algoritmen i den.

Vi har en algoritm och en nyckel. LÄt oss försöka tyda klassnamnet. HÀr Àr vad som hÀnde: com/taobao/wireless/security/adapter/JNICLibrary. Bra! Vi Àr pÄ rÀtt vÀg.

KommandotrÀd

Nu mÄste vi hitta en utmaning Registrera infödda, som pekar oss pÄ funktionen doCommandNative. LÄt oss titta pÄ funktionerna som kallas frÄn JNI_OnLoad, och vi 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, en infödd metod med namnet Àr registrerad hÀr doCommandNative. Nu vet vi hans adress. LÄt oss se vad han 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;
}

Med namnet kan du gissa att hÀr Àr ingÄngspunkten för alla funktioner som utvecklarna bestÀmde sig för att överföra till det inhemska biblioteket. Vi Àr intresserade av funktionsnummer 10601.

Du kan se frÄn koden att kommandonumret ger tre siffror: kommando/10000, kommando % 10000 / 100 О kommando % 10, dvs i vÄrt fall, 1, 6 och 1. Dessa tre siffror, samt en pekare till JNIEnv och argumenten som skickas till funktionen lÀggs till i en struktur och skickas vidare. Med hjÀlp av de tre erhÄllna siffrorna (lÄt oss beteckna dem N1, N2 och N3), byggs ett kommandotrÀd.

NÄgot som det hÀr:

Letar efter sÄrbarheter i UC Browser

TrÀdet fylls i dynamiskt JNI_OnLoad.
Tre siffror kodar sökvÀgen i trÀdet. Varje blad i trÀdet innehÄller den pockade adressen för motsvarande funktion. Nyckeln finns i förÀldranoden. Att hitta platsen i koden dÀr funktionen vi behöver lÀggs till i trÀdet Àr inte svÄrt om du förstÄr alla strukturer som anvÀnds (vi beskriver dem inte för att inte svÀlla upp en redan ganska stor artikel).

Mer förvirring

Vi fick adressen till funktionen som ska dekryptera trafik: 0x5F1AC. Men det Àr för tidigt att glÀdjas: utvecklarna av UC Browser har förberett ytterligare en överraskning Ät oss.

Efter att ha tagit emot parametrarna frÄn arrayen som bildades i Java-koden fÄr vi
till funktionen pÄ adress 0x4D070. Och hÀr vÀntar oss en annan typ av kodförvirring.

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

Letar efter sÄrbarheter i UC Browser

Vi flyttar det första indexet till R11:

Letar efter sÄrbarheter i UC Browser

För att fÄ en adress frÄn en tabell, anvÀnd ett index:

Letar efter sÄrbarheter i UC Browser

Efter att ha gÄtt till den första adressen anvÀnds det andra indexet, vilket Àr i R4. Det finns 230 element i tabellen.

Vad ska man göra Ă„t det? Du kan tala om för IDA att detta Ă€r en vĂ€xel: Redigera -> Övrigt -> Ange vĂ€xlingssprĂ„k.

Letar efter sÄrbarheter i UC Browser

Den resulterande koden Àr fruktansvÀrd. Men nÀr du tar dig igenom dess djungel kan du mÀrka ett samtal till en funktion som vi redan kÀnner till sub_6115C:

Letar efter sÄrbarheter i UC Browser

Det fanns en switch dÀr det i fall 3 fanns en dekryptering med RC4-algoritmen. Och i det hÀr fallet fylls strukturen som skickas till funktionen frÄn parametrarna som skickas till doCommandNative. LÄt oss komma ihÄg vad vi hade dÀr magicInt med vÀrdet 16. Vi tittar pÄ motsvarande fall - och efter flera övergÄngar hittar vi koden med vilken algoritmen kan identifieras.

Letar efter sÄrbarheter i UC Browser

Detta Àr AES!

Algoritmen finns, allt som ÄterstÄr Àr att erhÄlla dess parametrar: lÀge, nyckel och eventuellt initialiseringsvektorn (dess nÀrvaro beror pÄ AES-algoritmens driftslÀge). Strukturen med dem mÄste bildas nÄgonstans före funktionsanropet sub_6115C, men den hÀr delen av koden Àr sÀrskilt vÀl obfuskerad, sÄ idén uppstÄr att lappa koden sÄ att alla parametrar för dekrypteringsfunktionen dumpas i en fil.

Lappa

För att inte skriva all patchkod i assemblersprÄk manuellt kan du starta Android Studio, skriva en funktion dÀr som tar emot samma inmatningsparametrar som vÄr dekrypteringsfunktion och skriver till en fil, kopiera och klistra sedan in koden som kompilatorn ska generera.

VĂ„ra vĂ€nner frĂ„n UC Browser-teamet tog ocksĂ„ hand om bekvĂ€mligheten med att lĂ€gga till kod. LĂ„t oss komma ihĂ„g att i början av varje funktion har vi skrĂ€pkod som enkelt kan ersĂ€ttas med vilken annan som helst. Mycket bekvĂ€mt 🙂 I början av mĂ„lfunktionen finns det dock inte tillrĂ€ckligt med utrymme för koden som sparar alla parametrar till en fil. Jag var tvungen att dela upp den i delar och anvĂ€nda sopblock frĂ„n nĂ€rliggande funktioner. Det blev fyra delar totalt.

Första delen:

Letar efter sÄrbarheter i UC Browser

I ARM-arkitekturen skickas de första fyra funktionsparametrarna genom registren R0-R3, resten, om nÄgra, passeras genom stacken. LR-registret bÀr returadressen. Allt detta mÄste sparas sÄ att funktionen kan fungera 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 med parametrar som skickas till funktionen via stacken.

AnvÀnder funktionen öppen lÄt oss öppna filen /data/local/tmp/aes i "ab"-lÀge
dvs för tillÀgg. I R0 laddar vi adressen till filnamnet, i R1 - adressen till raden som indikerar lÀget. Och hÀr slutar sopkoden, sÄ vi gÄr vidare till nÀsta funktion. För att det ska fortsÀtta att fungera lÀgger vi i början övergÄngen till den verkliga koden för funktionen, förbi skrÀpet, och istÀllet för skrÀpet lÀgger vi till en fortsÀttning pÄ lappen.

Letar efter sÄrbarheter i UC Browser

Kallelse öppen.

Funktionens tre första parametrar aes har typ int. Eftersom vi sparade registren i stacken i början kan vi helt enkelt skicka funktionen fwrite deras adresser pÄ högen.

Letar efter sÄrbarheter i UC Browser

DÀrefter har vi tre strukturer som innehÄller datastorleken och en pekare till data för nyckeln, initialiseringsvektorn och krypterad data.

Letar efter sÄrbarheter i UC Browser

I slutet, stÀng filen, ÄterstÀll registren och överför kontrollen till den verkliga funktionen aes.

Vi samlar in en APK med ett lappat bibliotek, signerar det, laddar upp det till enheten/emulatorn och startar det. Vi ser att vÄr soptipp skapas, och det skrivs mycket data dÀr. WebblÀsaren anvÀnder kryptering inte bara för trafik, och all kryptering gÄr via den aktuella funktionen. Men av nÄgon anledning finns inte den nödvÀndiga informationen dÀr, och den begÀrda begÀran Àr inte synlig i trafiken. För att inte vÀnta tills UC Browser Àgnar sig Ät att göra den nödvÀndiga begÀran, lÄt oss ta det krypterade svaret frÄn servern som mottogs tidigare och lappa applikationen igen: lÀgg till dekrypteringen till onCreate av 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 monterar, signerar, installerar, lanserar. Vi fÄr ett NullPointerException eftersom metoden returnerade null.

Vid vidare analys av koden upptÀcktes en funktion som dechiffrerar intressanta rader: "META-INF/" och ".RSA". Det verkar som att programmet verifierar sitt certifikat. Eller till och med genererar nycklar frÄn den. Jag vill egentligen inte ta itu med vad som hÀnder med certifikatet, sÄ vi skickar bara det rÀtta certifikatet. LÄt oss patcha den krypterade raden sÄ att istÀllet för "META-INF/" fÄr vi "BLABLINF/", skapa en mapp med det namnet i APK:n och lÀgg till ekorrwebblÀsarcertifikatet dÀr.

Vi monterar, signerar, installerar, lanserar. Bingo! Vi har nyckeln!

MITM

Vi fick en nyckel och en initialiseringsvektor lika med nyckeln. LÄt oss försöka dekryptera serversvaret i CBC-lÀge.

Letar efter sÄrbarheter i UC Browser

Vi ser arkivets URL, nÄgot som liknar MD5, "extract_unzipsize" och ett nummer. Vi kontrollerar: MD5 för arkivet Àr densamma, storleken pÄ det uppackade biblioteket Àr densamma. Vi försöker patcha det hÀr biblioteket och ge det till webblÀsaren. För att visa att vÄrt patchade bibliotek har laddats kommer vi att lansera en avsikt att skapa ett SMS med texten "PWNED!" Vi kommer att ersÀtta tvÄ svar frÄn servern: puds.ucweb.com/upgrade/index.xhtml och ladda ner arkivet. I den första ersÀtter vi MD5 (storleken Àndras inte efter uppackning), i den andra ger vi arkivet med det patchade biblioteket.

WebblÀsaren försöker ladda ner arkivet flera gÄnger, varefter det ger ett felmeddelande. Tydligen nÄgot
Han gillar inte. Som ett resultat av att analysera detta grumliga format visade det sig att servern ocksÄ överför storleken pÄ arkivet:

Letar efter sÄrbarheter i UC Browser

Den Àr kodad i LEB128. Efter patchen Àndrades storleken pÄ arkivet med biblioteket lite, sÄ webblÀsaren ansÄg att arkivet laddades ner snett och efter flera försök gav det ett fel.

Vi anpassar storleken pĂ„ arkivet... Och – seger! 🙂 Resultatet finns i videon.

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

Konsekvenser och utvecklares reaktion

PÄ samma sÀtt kan hackare anvÀnda den osÀkra funktionen i UC Browser för att distribuera och köra skadliga bibliotek. Dessa bibliotek kommer att fungera i webblÀsarens sammanhang, sÄ de kommer att fÄ alla dess systembehörigheter. Som ett resultat, möjligheten att visa nÀtfiskefönster, samt tillgÄng till arbetsfilerna för den orange kinesiska ekorren, inklusive inloggningar, lösenord och cookies lagrade i databasen.

Vi kontaktade utvecklarna av UC Browser och informerade dem om problemet vi hittade, försökte pÄpeka sÄrbarheten och dess fara, men de diskuterade ingenting med oss. Under tiden fortsatte webblÀsaren att visa sin farliga funktion i klarsynt. Men nÀr vi vÀl avslöjade detaljerna om sÄrbarheten var det inte lÀngre möjligt att ignorera den som tidigare. 27 mars var
en ny version av UC Browser 12.10.9.1193 slÀpptes, som fick Ätkomst till servern via HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Dessutom, efter "fixen" och fram till tidpunkten för att skriva den hÀr artikeln, resulterade ett försök att öppna en PDF i en webblÀsare i ett felmeddelande med texten "Hoppsan, nÄgot gick fel!" En begÀran till servern gjordes inte nÀr man försökte öppna en PDF, men en begÀran gjordes nÀr webblÀsaren startades, vilket antyder den fortsatta möjligheten att ladda ner körbar kod i strid med Google Plays regler.

KĂ€lla: will.com

LĂ€gg en kommentar