Meklē ievainojamības UC pārlūkprogrammā

Meklē ievainojamības UC pārlūkprogrammā

Ievads

Marta beigās mēs ziņots, ka viņi atklāja slēptu iespēju ielādēt un palaist nepārbaudītu kodu UC pārlūkprogrammā. Šodien mēs detalizēti aplūkosim, kā notiek šī lejupielāde un kā hakeri to var izmantot saviem mērķiem.

Pirms kāda laika UC Browser tika reklamēts un izplatīts ļoti agresīvi: tas tika instalēts lietotāju ierīcēs, izmantojot ļaunprātīgu programmatūru, kas izplatīta no dažādām vietnēm video failu aizsegā (t.i., lietotāji domāja, ka lejupielādē, piemēram, porno video, bet tā vietā saņēma APK ar šo pārlūkprogrammu), izmantoja biedējošus reklāmkarogus ar ziņojumiem, ka pārlūkprogramma ir novecojusi, neaizsargāta un tamlīdzīgi. Oficiālajā UC Browser grupā VK ir tēma, kurā lietotāji var sūdzēties par negodīgu reklāmu, ir daudz piemēru. 2016. gadā bija pat video reklāma krievu valodā (jā, reklāmu bloķējošas pārlūkprogrammas reklāma).

Rakstīšanas laikā UC pārlūkprogrammā Google Play ir vairāk nekā 500 000 000 instalāciju. Tas ir iespaidīgi — tikai Google Chrome ir vairāk. Starp atsauksmēm var redzēt diezgan daudz sūdzību par reklamēšanu un novirzīšanu uz dažām lietojumprogrammām pakalpojumā Google Play. Tas bija iemesls mūsu pētījumam: mēs nolēmām noskaidrot, vai UC Browser nedara kaut ko sliktu. Un izrādījās, ka viņš to dara!

Lietojumprogrammas kodā tika atklāta iespēja lejupielādēt un palaist izpildāmo kodu, kas ir pretrunā ar pieteikumu publicēšanas noteikumiem pakalpojumā Google Play. Papildus izpildāmā koda lejupielādei UC Browser to dara nedrošā veidā, ko var izmantot, lai uzsāktu MitM uzbrukumu. Paskatīsimies, vai varam veikt šādu uzbrukumu.

Viss, kas rakstīts tālāk, attiecas uz UC Browser versiju, kas pētījuma laikā bija pieejama pakalpojumā Google Play:

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

Uzbrukuma vektors

UC Browser manifestā varat atrast pakalpojumu ar pašsaprotamu nosaukumu com.uc.deployment.UpgradeDeployService.

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

Kad šis pakalpojums tiek startēts, pārlūkprogramma veic POST pieprasījumu puds.ucweb.com/upgrade/index.xhtml, ko satiksmē var redzēt kādu laiku pēc starta. Atbildot uz to, viņš var saņemt komandu lejupielādēt kādu atjauninājumu vai jaunu moduli. Analīzes laikā serveris nedeva šādas komandas, taču mēs pamanījām, ka, mēģinot pārlūkprogrammā atvērt PDF failu, tas veic otru pieprasījumu uz iepriekš norādīto adresi, pēc kura lejupielādē vietējo bibliotēku. Lai veiktu uzbrukumu, mēs nolēmām izmantot šo UC Browser funkciju: iespēju atvērt PDF, izmantojot vietējo bibliotēku, kuras nav APK un kuru tas vajadzības gadījumā lejupielādē no interneta. Ir vērts atzīmēt, ka teorētiski UC Browser var būt spiests kaut ko lejupielādēt bez lietotāja mijiedarbības - ja sniedzat labi noformētu atbildi uz pieprasījumu, kas tiek izpildīts pēc pārlūkprogrammas palaišanas. Bet, lai to izdarītu, mums ir sīkāk jāizpēta mijiedarbības protokols ar serveri, tāpēc nolēmām, ka būs vieglāk rediģēt pārtverto atbildi un aizstāt bibliotēku darbam ar PDF.

Tātad, ja lietotājs vēlas atvērt PDF failu tieši pārlūkprogrammā, datplūsmā var redzēt šādus pieprasījumus:

Meklē ievainojamības UC pārlūkprogrammā

Vispirms tiek nosūtīts POST pieprasījums puds.ucweb.com/upgrade/index.xhtmlpēc tam
Tiek lejupielādēts arhīvs ar bibliotēku PDF un biroja formātu apskatei. Ir loģiski pieņemt, ka pirmais pieprasījums pārraida informāciju par sistēmu (vismaz arhitektūru, lai nodrošinātu nepieciešamo bibliotēku), un, atbildot uz to, pārlūkprogramma saņem kādu informāciju par bibliotēku, kas jālejupielādē: adrese un, iespējams, informācija. , kaut kas cits. Problēma ir tā, ka šis pieprasījums ir šifrēts.

Pieprasījuma fragments

Atbildes fragments

Meklē ievainojamības UC pārlūkprogrammā

Meklē ievainojamības UC pārlūkprogrammā

Pati bibliotēka ir iesaiņota ZIP formātā un nav šifrēta.

Meklē ievainojamības UC pārlūkprogrammā

Meklēt trafika atšifrēšanas kodu

Mēģināsim atšifrēt servera atbildi. Apskatīsim klases kodu com.uc.deployment.UpgradeDeployService: no metodes onStartCommand iet uz com.uc.deployment.bx, un no tā līdz 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);
}

Šeit mēs redzam POST pieprasījuma veidošanos. Mēs pievēršam uzmanību 16 baitu masīva izveidei un tā aizpildīšanai: 0x5F, 0, 0x1F, -50 (=0xCE). Sakrīt ar to, ko redzējām iepriekš minētajā pieprasījumā.

Tajā pašā klasē varat redzēt ligzdotu klasi, kurai ir vēl viena interesanta metode:

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

Metode izmanto baitu masīvu kā ievadi un pārbauda, ​​vai nulles baits ir 0x60 vai trešais baits ir 0xD0 un otrais baits ir 1, 11 vai 0x1F. Mēs skatāmies uz atbildi no servera: nulles baits ir 0x60, otrais ir 0x1F, trešais ir 0x60. Izklausās pēc tā, kas mums vajadzīgs. Spriežot pēc rindām (piemēram, “up_decrypt”), šeit ir jāizsauc metode, kas atšifrēs servera atbildi.
Pāriesim pie metodes gj. Ņemiet vērā, ka pirmais arguments ir baits 2. nobīdē (t.i., mūsu gadījumā — 0x1F), bet otrais ir servera atbilde bez
pirmie 16 baiti.

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

Acīmredzot šeit mēs izvēlamies atšifrēšanas algoritmu un to pašu baitu, kas ir mūsu
reģistrs ir vienāds ar 0x1F, apzīmē vienu no trim iespējamajām opcijām.

Mēs turpinām analizēt kodu. Pēc pāris lēcieniem atrodamies metodē ar pašsaprotamu nosaukumu atšifrētBytesByKey.

Šeit no mūsu atbildes tiek atdalīti vēl divi baiti, un no tiem tiek iegūta virkne. Ir skaidrs, ka šādā veidā tiek izvēlēta atslēga ziņojuma atšifrēšanai.

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

Raugoties nākotnē, mēs atzīmējam, ka šajā posmā mēs vēl neiegūstam atslēgu, bet tikai tās “identifikatoru”. Atslēgas iegūšana ir nedaudz sarežģītāka.

Nākamajā metodē esošajiem parametriem tiek pievienoti vēl divi parametri, veidojot četrus no tiem: maģiskais skaitlis 16, atslēgas identifikators, šifrētie dati un nesaprotama virkne (mūsu gadījumā tukša).

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

Pēc virknes pāreju mēs nonākam pie metodes staticBinarySafeDecryptNoB64 interfeiss com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Galvenās lietojumprogrammas kodā nav klases, kas ievieš šo saskarni. Failā ir šāda klase lib/armeabi-v7a/libsgmain.so, kas patiesībā nav .so, bet gan .jar. Mūs interesējošā metode tiek īstenota šādi:

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

Šeit mūsu parametru saraksts ir papildināts ar vēl diviem veseliem skaitļiem: 2 un 0. Spriežot pēc
viss, 2 nozīmē atšifrēšanu, tāpat kā metodē doFinal sistēmas klase javax.crypto.Cipher. Un tas viss tiek pārsūtīts uz noteiktu maršrutētāju ar numuru 10601 - tas acīmredzot ir komandas numurs.

Pēc nākamās pāreju ķēdes mēs atrodam klasi, kas ievieš saskarni IRouterComponent un metode 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);
}
}

Un arī klase JNICBibliotēka, kurā ir deklarēta vietējā metode doCommandNative:

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

Tas nozīmē, ka mums ir jāatrod metode vietējā kodā doCommandNative. Un šeit sākas jautrība.

Mašīnkoda apmulsināšana

Failā libsgmain.so (kas patiesībā ir .jar un kurā mēs atradām dažu ar šifrēšanu saistītu saskarņu ieviešanu tieši iepriekš) ir viena vietējā bibliotēka: libsgmainso-6.4.36.so. Mēs to atveram IDA un iegūstam virkni dialoglodziņu ar kļūdām. Problēma ir tā, ka sadaļas galvenes tabula nav derīga. Tas tiek darīts ar nolūku, lai sarežģītu analīzi.

Meklē ievainojamības UC pārlūkprogrammā

Bet tas nav nepieciešams: lai pareizi ielādētu ELF failu un analizētu to, pietiek ar programmas galvenes tabulu. Tāpēc mēs vienkārši izdzēšam sadaļu tabulu, noņemot atbilstošos laukus galvenē.

Meklē ievainojamības UC pārlūkprogrammā

Atkal atveriet failu IDA.

Ir divi veidi, kā norādīt Java virtuālajai mašīnai, kur tieši vietējā bibliotēkā atrodas Java kodā kā vietējās deklarētās metodes ieviešana. Pirmais ir piešķirt tai sugas nosaukumu Java_package_name_ClassName_MethodName.

Otrais ir reģistrēt to, ielādējot bibliotēku (funkcijā JNI_OnLoad)
izmantojot funkciju zvanu Reģistrēties Natives.

Mūsu gadījumā, ja mēs izmantojam pirmo metodi, nosaukumam jābūt šādam: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Starp eksportētajām funkcijām šādas funkcijas nav, kas nozīmē, ka jums ir jāmeklē zvans Reģistrēties Natives.
Dosimies uz funkciju JNI_OnLoad un mēs redzam šo attēlu:

Meklē ievainojamības UC pārlūkprogrammā

Kas šeit notiek? No pirmā acu uzmetiena funkcijas sākums un beigas ir raksturīgas ARM arhitektūrai. Pirmajā steka instrukcijā tiek saglabāts reģistru saturs, kurus funkcija izmantos savā darbībā (šajā gadījumā R0, R1 un R2), kā arī LR reģistra saturs, kurā ir funkcijas atgriešanas adrese. . Pēdējā instrukcija atjauno saglabātos reģistrus, un atgriešanas adrese nekavējoties tiek ievietota datora reģistrā - tādējādi atgriežoties no funkcijas. Bet, ja paskatās uzmanīgi, jūs pamanīsit, ka priekšpēdējā instrukcija maina kaudzē saglabāto atgriešanas adresi. Parēķināsim, kā būs pēc tam
koda izpilde. R1 tiek ielādēta noteikta adrese 0xB130, no tās tiek atņemta 5, pēc tam tā tiek pārnesta uz R0 un tai tiek pievienota 0x10. Izrādās 0xB13B. Tādējādi IDA domā, ka pēdējā instrukcija ir normāla funkcijas atgriešana, bet patiesībā tā dodas uz aprēķināto adresi 0xB13B.

Šeit ir vērts atgādināt, ka ARM procesoriem ir divi režīmi un divas instrukciju kopas: ARM un Thumb. Vismazāk nozīmīgais adreses bits norāda procesoram, kura instrukciju kopa tiek izmantota. Tas nozīmē, ka adrese faktiski ir 0xB13A, un viens vismazāk nozīmīgajā bitā norāda īkšķa režīmu.

Līdzīgs “adapteris” ir pievienots katras šīs bibliotēkas funkcijas sākumam un
atkritumu kods. Mēs par tiem sīkāk nepakavēsimies - mēs vienkārši atceramies
ka gandrīz visu funkciju īstais sākums ir nedaudz tālāk.

Tā kā kods nepāriet uz 0xB13A, IDA pati neatzina, ka kods atrodas šajā vietā. Tā paša iemesla dēļ tā neatpazīst lielāko daļu bibliotēkas koda kā kodu, kas padara analīzi nedaudz sarežģītu. Mēs sakām IDA, ka šis ir kods, un notiek šādi:

Meklē ievainojamības UC pārlūkprogrammā

Tabula skaidri sākas ar 0xB144. Kas ir sub_494C?

Meklē ievainojamības UC pārlūkprogrammā

Izsaucot šo funkciju LR reģistrā, mēs iegūstam iepriekš minētās tabulas adresi (0xB144). In R0 - indekss šajā tabulā. Tas ir, vērtība tiek ņemta no tabulas, pievienota LR un rezultāts ir
adrese, uz kuru doties. Mēģināsim to aprēķināt: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Mēs ejam uz saņemto adresi un burtiski redzam pāris noderīgus norādījumus un atkal dodamies uz 0xB140:

Meklē ievainojamības UC pārlūkprogrammā

Tagad no tabulas būs pāreja nobīdē ar indeksu 0x20.

Spriežot pēc tabulas izmēra, šādu pāreju kodā būs daudz. Rodas jautājums, vai ar to ir iespējams kaut kā tikt galā automātiskāk, bez manuālas adrešu aprēķināšanas. Un mums palīdz skripti un iespēja labot kodu IDA:

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"

Novietojiet kursoru rindā 0xB26A, palaidiet skriptu un skatiet pāreju uz 0xB4B0:

Meklē ievainojamības UC pārlūkprogrammā

IDA atkal neatzina šo apgabalu kā kodu. Mēs viņai palīdzam un redzam citu dizainu:

Meklē ievainojamības UC pārlūkprogrammā

Norādījumiem pēc BLX, šķiet, nav lielas jēgas, tas vairāk atgādina kaut kādu pārvietošanu. Apskatīsim sub_4964:

Meklē ievainojamības UC pārlūkprogrammā

Un tiešām, šeit tiek ņemts dword adresē, kas atrodas LR, pievienots šai adresei, pēc kura tiek ņemta vērtība iegūtajā adresē un ievietota kaudzē. Tāpat LR tiek pievienots 4, lai pēc atgriešanās no funkcijas šī pati nobīde tiktu izlaista. Pēc tam komanda POP {R1} iegūst iegūto vērtību no steka. Ja paskatās uz to, kas atrodas adresē 0xB4BA + 0xEA = 0xB5A4, jūs redzēsit kaut ko līdzīgu adrešu tabulai:

Meklē ievainojamības UC pārlūkprogrammā

Lai ielāpītu šo dizainu, no koda būs jāiegūst divi parametri: nobīde un reģistra numurs, kurā vēlaties ievietot rezultātu. Katram iespējamajam reģistram jums būs iepriekš jāsagatavo koda fragments.

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"

Mēs novietojam kursoru tās struktūras sākumā, kuru vēlamies aizstāt - 0xB4B2 - un palaižam skriptu:

Meklē ievainojamības UC pārlūkprogrammā

Papildus jau minētajām struktūrām kods satur arī sekojošo:

Meklē ievainojamības UC pārlūkprogrammā

Tāpat kā iepriekšējā gadījumā, pēc BLX instrukcijas ir nobīde:

Meklē ievainojamības UC pārlūkprogrammā

Ņemam nobīdi uz adresi no LR, pievienojam LR un dodamies uz turieni. 0x72044 + 0xC = 0x72050. Šī dizaina skripts ir diezgan vienkāršs:

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"

Skripta izpildes rezultāts:

Meklē ievainojamības UC pārlūkprogrammā

Kad funkcijā viss ir izlabots, varat norādīt IDA uz tās īsto sākumu. Tas apkopos visu funkcijas kodu, un to var dekompilēt, izmantojot HexRays.

Stīgu dekodēšana

Mēs esam iemācījušies tikt galā ar mašīnkoda apmulsināšanu bibliotēkā libsgmainso-6.4.36.so no UC Browser un saņēma funkcijas kodu 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;
}

Sīkāk apskatīsim šādas rindas:

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

Funkcijā sub_73E24 klases nosaukums acīmredzami tiek atšifrēts. Kā šīs funkcijas parametri tiek nodots rādītājs uz datiem, kas līdzīgi šifrētiem datiem, noteikts buferis un skaitlis. Acīmredzot pēc funkcijas izsaukšanas buferī būs atšifrēta līnija, jo tā tiek nodota funkcijai FindClass, kas izmanto klases nosaukumu kā otro parametru. Tāpēc skaitlis ir bufera izmērs vai līnijas garums. Mēģināsim atšifrēt klases nosaukumu, tam vajadzētu mums pateikt, vai mēs ejam pareizajā virzienā. Apskatīsim tuvāk, kas notiek iekšā 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;
}

Funkcija sub_7AF78 izveido konteinera gadījumu norādītā izmēra baitu masīviem (sīkāk pie šiem konteineriem nepakavēsimies). Šeit tiek izveidoti divi šādi konteineri: vienā ir līnija "DcO/lcK+h?m3c*q@" (ir viegli uzminēt, ka šī ir atslēga), otrā satur šifrētus datus. Tālāk abi objekti tiek ievietoti noteiktā struktūrā, kas tiek nodota funkcijai sub_6115C. Šajā struktūrā atzīmēsim arī lauku ar vērtību 3. Redzēsim, kas ar šo struktūru notiks tālāk.

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

Slēdža parametrs ir struktūras lauks, kuram iepriekš tika piešķirta vērtība 3. Apskatiet funkcijas 3. gadījumu sub_6364C parametri tiek nodoti no struktūras, kas tur tika pievienota iepriekšējā funkcijā, t.i., atslēga un šifrētie dati. Ja paskatās cieši uz sub_6364C, jūs tajā varat atpazīt RC4 algoritmu.

Mums ir algoritms un atslēga. Mēģināsim atšifrēt klases nosaukumu. Lūk, kas notika: com/taobao/wireless/security/adapter/JNICLibrary. Lieliski! Mēs esam uz pareizā ceļa.

Komandu koks

Tagad mums ir jāatrod izaicinājums Reģistrēties Natives, kas mūs norādīs uz funkciju doCommandNative. Apskatīsim funkcijas, kas tiek izsauktas no JNI_OnLoad, un mēs to atrodam 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;
}

Un patiešām, šeit ir reģistrēta dzimtā metode ar nosaukumu doCommandNative. Tagad mēs zinām viņa adresi. Paskatīsimies, ko viņš dara.

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

Pēc nosaukuma jūs varat uzminēt, ka šeit ir visu to funkciju ieejas punkts, kuras izstrādātāji nolēma pārsūtīt uz vietējo bibliotēku. Mūs interesē funkcijas numurs 10601.

No koda var redzēt, ka komandas numurs rada trīs skaitļus: komanda/10000, komanda % 10000/100 и komanda % 10, t.i., mūsu gadījumā, 1, 6 un 1. Šie trīs skaitļi, kā arī rādītājs uz JNIEnv un funkcijai nodotie argumenti tiek pievienoti struktūrai un nodoti tālāk. Izmantojot trīs iegūtos skaitļus (apzīmēsim tos N1, N2 un N3), tiek izveidots komandu koks.

Kaut kas tamlīdzīgs:

Meklē ievainojamības UC pārlūkprogrammā

Koks tiek dinamiski aizpildīts JNI_OnLoad.
Trīs cipari kodē ceļu kokā. Katra koka lapa satur atbilstošās funkcijas ielikto adresi. Atslēga atrodas vecāku mezglā. Kodā atrast vietu, kur mums vajadzīgā funkcija ir pievienota kokam, nav grūti, ja saprotat visas izmantotās struktūras (mēs tās neaprakstām, lai neuzpūstu jau tā diezgan lielu rakstu).

Vairāk apmulsuma

Mēs saņēmām funkcijas, kurai vajadzētu atšifrēt trafiku, adresi: 0x5F1AC. Bet vēl ir pāragri priecāties: UC Browser izstrādātāji mums ir sagatavojuši vēl vienu pārsteigumu.

Pēc parametru saņemšanas no masīva, kas tika izveidots Java kodā, mēs iegūstam
uz funkciju ar adresi 0x4D070. Un šeit mūs sagaida cita veida koda apmulsināšana.

Mēs ievietojām divus indeksus R7 un R4:

Meklē ievainojamības UC pārlūkprogrammā

Mēs pārnesam pirmo indeksu uz R11:

Meklē ievainojamības UC pārlūkprogrammā

Lai iegūtu adresi no tabulas, izmantojiet indeksu:

Meklē ievainojamības UC pārlūkprogrammā

Pēc došanās uz pirmo adresi tiek izmantots otrais indekss, kas atrodas R4. Tabulā ir 230 elementi.

Ko darīt ar to? Varat pateikt IDA, ka tas ir slēdzis: Rediģēt -> Cits -> Norādīt slēdža idiomu.

Meklē ievainojamības UC pārlūkprogrammā

Iegūtais kods ir šausmīgs. Bet, izejot cauri džungļiem, jūs varat pamanīt izsaukumu uz mums jau pazīstamu funkciju sub_6115C:

Meklē ievainojamības UC pārlūkprogrammā

Bija slēdzis, kurā 3. gadījumā notika atšifrēšana, izmantojot RC4 algoritmu. Un šajā gadījumā funkcijai nodotā ​​struktūra tiek aizpildīta no parametriem, kas nodoti uz doCommandNative. Atcerēsimies, kas mums tur bija magicInt ar vērtību 16. Apskatām atbilstošo gadījumu - un pēc vairākām pārejām atrodam kodu, pēc kura var identificēt algoritmu.

Meklē ievainojamības UC pārlūkprogrammā

Tas ir AES!

Algoritms pastāv, atliek tikai iegūt tā parametrus: režīmu, taustiņu un, iespējams, inicializācijas vektoru (tā klātbūtne ir atkarīga no AES algoritma darbības režīma). Struktūra ar tiem ir jāveido kaut kur pirms funkcijas izsaukuma sub_6115C, taču šī koda daļa ir īpaši labi aptumšota, tāpēc rodas doma lāpīt kodu, lai visi atšifrēšanas funkcijas parametri tiktu izmesti failā.

Plāksteris

Lai nerakstītu visu ielāpa kodu montāžas valodā manuāli, varat palaist Android Studio, tur ierakstīt funkciju, kas saņem tādus pašus ievades parametrus kā mūsu atšifrēšanas funkcija un raksta failā, pēc tam kopējiet un ielīmējiet kodu, ko kompilators veiks. ģenerēt.

Par koda pievienošanas ērtībām parūpējās arī mūsu draugi no UC Browser komandas. Atcerēsimies, ka katras funkcijas sākumā mums ir atkritumu kods, ko var viegli aizstāt ar jebkuru citu. Ļoti ērti 🙂 Tomēr mērķa funkcijas sākumā nepietiek vietas kodam, kas visus parametrus saglabā failā. Man tas bija jāsadala daļās un jāizmanto atkritumu bloki no blakus esošajām funkcijām. Kopā bija četras daļas.

Pirmā daļa:

Meklē ievainojamības UC pārlūkprogrammā

ARM arhitektūrā pirmie četri funkciju parametri tiek nodoti caur reģistriem R0-R3, pārējie, ja tādi ir, tiek nodoti caur steku. LR reģistrā ir atgriešanas adrese. Tas viss ir jāsaglabā, lai funkcija varētu darboties pēc tās parametru izmešanas. Mums ir arī jāsaglabā visi reģistri, kurus izmantosim šajā procesā, tāpēc mēs veicam PUSH.W {R0-R10,LR}. R7 mēs iegūstam parametru saraksta adresi, kas funkcijai tiek nodota caur steku.

Izmantojot funkciju atvērts atvērsim failu /data/local/tmp/aes "ab" režīmā
i., par papildinājumu. R0 mēs ielādējam faila nosaukuma adresi, R1 - rindas adresi, kas norāda režīmu. Un šeit beidzas atkritumu kods, tāpēc mēs pārejam pie nākamās funkcijas. Lai tā turpinātu darboties, sākumā ievietojām pāreju uz funkcijas reālo kodu, apejot atkritumus, un atkritumu vietā pievienojam ielāpa turpinājumu.

Meklē ievainojamības UC pārlūkprogrammā

Zvana atvērts.

Pirmie trīs funkcijas parametri aES ir veids int. Tā kā sākumā mēs saglabājām reģistrus stekā, mēs varam vienkārši nodot funkciju fwrite viņu adreses uz kaudzes.

Meklē ievainojamības UC pārlūkprogrammā

Tālāk mums ir trīs struktūras, kas satur datu lielumu un rādītāju uz atslēgas datiem, inicializācijas vektoru un šifrētiem datiem.

Meklē ievainojamības UC pārlūkprogrammā

Beigās aizveriet failu, atjaunojiet reģistrus un pārsūtiet vadību uz reālo funkciju aES.

Mēs savācam APK ar ielāpu bibliotēku, parakstām to, augšupielādējam ierīcē/emulatorā un palaižam to. Mēs redzam, ka mūsu izgāztuve tiek veidota, un tur tiek rakstīts daudz datu. Pārlūkprogramma izmanto šifrēšanu ne tikai trafikam, un visa šifrēšana tiek veikta, izmantojot attiecīgo funkciju. Bet kādu iemeslu dēļ nepieciešamo datu nav, un nepieciešamais pieprasījums nav redzams satiksmē. Lai negaidītu, līdz UC Browser piekritīs veikt nepieciešamo pieprasījumu, ņemsim šifrēto atbildi no servera, kas saņemta agrāk, un vēlreiz ielāps lietojumprogrammu: pievienojiet atšifrēšanu galvenās darbības onCreate.

    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

Montējam, parakstām, uzstādām, palaižam. Mēs saņemam NullPointerException, jo metode atgrieza nulli.

Turpmākās koda analīzes laikā tika atklāta funkcija, kas atšifrē interesantas rindas: “META-INF/” un “.RSA”. Šķiet, ka lietojumprogramma pārbauda savu sertifikātu. Vai pat no tā ģenerē atslēgas. Es īsti nevēlos nodarboties ar to, kas notiek ar sertifikātu, tāpēc mēs vienkārši ieliksim to pareizo sertifikātu. Izlabosim šifrēto rindiņu, lai “META-INF/” vietā iegūtu “BLABLINF/”, izveidojiet APK mapi ar šādu nosaukumu un pievienojiet tur vāveres pārlūkprogrammas sertifikātu.

Montējam, parakstām, uzstādām, palaižam. Bingo! Mums ir atslēga!

MitM

Mēs saņēmām atslēgu un inicializācijas vektoru, kas vienāds ar atslēgu. Mēģināsim atšifrēt servera atbildi CBC režīmā.

Meklē ievainojamības UC pārlūkprogrammā

Mēs redzam arhīva URL, kaut ko līdzīgu MD5, “extract_unzipsize” un numuru. Mēs pārbaudām: arhīva MD5 ir vienāds, neizpakotās bibliotēkas izmērs ir vienāds. Mēs cenšamies izlabot šo bibliotēku un nodot to pārlūkprogrammai. Lai parādītu, ka mūsu ielāpu bibliotēka ir ielādēta, mēs palaidīsim nolūku izveidot SMS ar tekstu “PWNED!” Mēs aizstāsim divas atbildes no servera: puds.ucweb.com/upgrade/index.xhtml un lejupielādēt arhīvu. Pirmajā nomainām MD5 (izmērs pēc izpakošanas nemainās), otrajā dodam arhīvu ar patched bibliotēku.

Pārlūkprogramma vairākas reizes mēģina lejupielādēt arhīvu, pēc tam tiek parādīta kļūda. Acīmredzot kaut kas
viņam nepatīk. Analizējot šo neskaidro formātu, izrādījās, ka serveris pārraida arī arhīva lielumu:

Meklē ievainojamības UC pārlūkprogrammā

Tas ir kodēts LEB128. Pēc ielāpa arhīva izmērs ar bibliotēku nedaudz mainījās, tāpēc pārlūkprogramma uzskatīja, ka arhīvs ir lejupielādēts greizi, un pēc vairākiem mēģinājumiem iemeta kļūdu.

Pielāgojam arhīva lielumu... Un – uzvara! 🙂 Rezultāts ir redzams video.

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

Sekas un izstrādātāja reakcija

Tādā pašā veidā hakeri varētu izmantot UC Browser nedrošo funkciju, lai izplatītu un palaistu ļaunprātīgas bibliotēkas. Šīs bibliotēkas darbosies pārlūkprogrammas kontekstā, tāpēc tās saņems visas tās sistēmas atļaujas. Rezultātā tiek nodrošināta iespēja parādīt pikšķerēšanas logus, kā arī piekļūt oranžās ķīniešu vāveres darba failiem, ieskaitot pieteikšanās vārdus, paroles un datubāzē saglabātos sīkfailus.

Mēs sazinājāmies ar UC Browser izstrādātājiem un informējām viņus par atrasto problēmu, mēģinājām norādīt uz ievainojamību un tās bīstamību, taču viņi ar mums neko nerunāja. Tikmēr pārlūkprogramma turpināja atklāti demonstrēt savu bīstamo funkciju. Bet, tiklīdz mēs atklājām ievainojamības detaļas, vairs nebija iespējams to ignorēt kā iepriekš. 27. marts bija
tika izlaista jauna UC Browser 12.10.9.1193 versija, kas serverim piekļuva, izmantojot HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Turklāt pēc “labošanas” un līdz šī raksta rakstīšanas brīdim, mēģinot atvērt PDF failu pārlūkprogrammā, tika parādīts kļūdas ziņojums ar tekstu “Hmm, kaut kas nogāja greizi!” Pieprasījums serverim netika veikts, mēģinot atvērt PDF failu, bet pieprasījums tika veikts pārlūkprogrammas palaišanas laikā, kas liecina par nepārtrauktu iespēju lejupielādēt izpildāmo kodu, pārkāpjot Google Play noteikumus.

Avots: www.habr.com

Pievieno komentāru