
Sissejuhatus
MÀrtsi lÔpus teatasime, MÔni aeg tagasi reklaamiti ja levitati UC Browserit vÀga agressiivselt: seda installeeriti kasutajate seadmetesse pahavara abil, levitati erinevatelt saitidelt videofailide nÀol (st kasutajad arvasid, et laadivad nÀiteks pornofilmi, aga said selle asemel APK selle brauseriga), kasutati hirmutavaid bannereid teadetega, et brauser on aegunud, haavatav ja nii edasi. UC Browseri ametlikus VK grupis on
, kus kasutajad saavad kahtlase reklaami kohta kaebusi esitada, seal on palju nĂ€iteid. 2016. aastal oli isegi videoreklaam Artikli kirjutamise hetkeks oli UC Browseri Google Play's rohkem kui 500 000 000 allalaadimist. See on muljetavaldav â ainult Google Chrome'il on rohkem. Arvustuste seas on piisavalt palju kaebusi reklaamide ja suunamiste kohta mingitesse rakendustesse Google Play's. Just see andis meile inspiratsiooni uurimiseks: otsustasime vaadata, kas UC Browser teeb midagi kahtlast. Ja selgus, et teebki!
Rakenduse koodis avastati vÔimalus laadida ja kÀivitada kÀivaid koode,
mis on vastuolus Google Play rakenduste avaldamise reeglitega. Lisaks sellele, et UC Browser laadib kĂ€ituvat koodi, teeb ta seda ebaturvaliselt, mida saab kasutada Man-in-the-Middle (MitM) rĂŒnnaku lĂ€biviimiseks. Vaatame, kas suudame sellist rĂŒnnakut teostada. package: com.UCMobile.intl versionName: 12.10.8.1172 versionCode: 10598 sha1 APK-faili: f5edb2243413c777172f6362876041eb0c3a928c
RĂŒnnakute vektor
UC Browseri manifestis vÔib leida teenuse, mille nimetus oncom.uc.deployment.UpgradeDeployService
<service android_exported="false" android_name="com.uc.deployment.UpgradeDeployService" android_process=":deploy" /> Selle teenuse kÀivitamise ajal teeb brauser POST-pÀringu aadressile.
puds.ucweb.com/upgrade/index.xhtml
KĂ€ivitades seda teenust, saadab brauser POST-pĂ€ringu aadressile , mida saab liikluses mĂ€rgata mĂ”ne aja pĂ€rast pĂ€rast kĂ€ivitamist. Vastuseks vĂ”ib ta saada kĂ€su laadida alla mĂ”ni uuendus vĂ”i uus moodul. Server ei andnud selliseid kĂ€ske analĂŒĂŒsiprotsessi kĂ€igus, kuid tĂ€helepanekud nĂ€itasid, et PDF-i avamisel brauseris tegi ta korduva pĂ€ringu ĂŒlaltoodud aadressile, pĂ€rast mida laadis alla originaalkirjastuse teegi. RĂŒnnaku lĂ€biviimiseks otsustasime kasutada UC Brauseri spetsiifikat: vĂ”imet avada PDF-i lĂ€bi originaalkirjastuse, mida APK-s ei ole ja mille ta vajadusel laadib Internetist alla. Tuleb mĂ€rkida, et teoreetiliselt on vĂ”imalik UC Brauserit sundida midagi alla laadima ka ilma kasutaja kaasamiseta â kui esitada Ă”igesti vormindatud vastus pĂ€ringule, mis toimub pĂ€rast brauseri kĂ€ivitamist. Kuid selleks tuleb detailsemalt uurida serveriga suhtlemise protokolli. SeetĂ”ttu otsustasime, et on lihtsam redigeerida pĂŒĂŒtud vastust ja asendada PDF-i avamiseks mĂ”eldud teek.
Nii et kui kasutaja tahab PDF-i brauseris otse avada, siis liikluses vÔib nÀha jÀrgmisi pÀringuid:

Esmalt tuleb POST-pÀring aadressile , pÀrast seda
laaditakse alla teek PDF-ide ja kontoriformaatide vaatamiseks. On loogiline eeldada, et esimeses pĂ€ringus edastatakse teave sĂŒsteemi kohta (vĂ€hemalt arhitektuur, et anda vajalik teek), ja sellele vastuseks saab brauser teavet teegi kohta, mida tuleb alla laadida: aadress ja vĂ”ib-olla veel midagi. Probleem on selles, et see pĂ€ring on krĂŒptitud.
PĂ€ringu fragment
Vastuse fragment


Ise teek on pakitud ZIP-faili ja ei ole krĂŒptitud.

Liikluse dekrĂŒpteerimise koodi otsimine
Proovime dekrĂŒpteerida serveri vastust. Vaadake klassi koodi Selle teenuse kĂ€ivitamise ajal teeb brauser POST-pĂ€ringu aadressile: meetodist onStartCommand liigume com.uc.deployment.b.x, sealt edasi com.uc.browser.core.d.c.f.e:
public final void e(l arg9) {
int v4_5;
String v3_1;
byte[] v3;
byte[] v1 = null;
if(arg9 == null) {
v3 = v1;
}
else {
v3_1 = arg9.iGX.ipR;
StringBuilder v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]toode:");
v4.append(arg9.iGX.ipR);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]versioon:");
v4.append(arg9.iGX.iEn);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]uuendustĂŒĂŒp:");
v4.append(arg9.iGX.mMode);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]sundimine:");
v4.append(arg9.iGX.iEo);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]vaikereĆŸiim:");
v4.append(arg9.iGX.iDQ);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]vaikereĆŸiimi tĂŒĂŒp:");
v4.append(arg9.iGX.iEr);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]vaikereĆŸiimi olek:");
v4.append(arg9.iGX.iEp);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]vaikereĆŸiimi fail:");
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("]laadimistĂŒĂŒp:");
v4.append(arg9.mDownloadType);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]laadimiste grupp:");
v4.append(arg9.mDownloadGroup);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]laadimise tee:");
v4.append(arg9.iGH);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_lapse_versioon:");
v4.append(arg9.iGX.iEx);
v4 = new StringBuilder("[");
v4.append(v3_1);
v4.append("]apollo_seeria:");
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("]komponendi_nimi:");
v5.append(((au)v4_1).getName());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]komponendi_versiooni_nimi:");
v5.append(((au)v4_1).aDA());
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]komponendi_versiooni_kood:");
v5.append(((au)v4_1).gBl);
v5 = new StringBuilder("[");
v5.append(((au)v4_1).getName());
v5.append("]komponendi_nĂ”udlustĂŒĂŒp:");
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_versioon", c.getRomInfo()));
v3_2.add(g.fs("protsessori_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_tĂŒĂŒp", String.valueOf(com.uc.base.system.a.Jo())));
v3_2.add(g.fs("fromhost", arg9.iGX.iEm));
v3_2.add(g.fs("plugin_versioon", arg9.iGX.iEn));
v3_2.add(g.fs("sihtkeel", 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_lapse_versioon", arg9.iGX.iEx));
v3_2.add(g.fs("versioon_seeria", arg9.iGX.iEw));
v3_2.add(g.fs("lapse_versioon", r.aVw()));
v3_2.add(g.fs("praegune_versioon_md5", arg9.iGX.iEl));
v3_2.add(g.fs("praegune_versiooni_allkiri", SystemHelper.getUCMSignature()));
v3_2.add(g.fs("uuendamise_logi", i.bjt()));
v3_2.add(g.fs("vaikime_install", String.valueOf(arg9.iGX.iDQ)));
v3_2.add(g.fs("vaikereĆŸiimi_olek", String.valueOf(arg9.iGX.iEp)));
v3_2.add(g.fs("vaikereĆŸiimi_fail", arg9.iGX.iEq));
v3_2.add(g.fs("vaikereĆŸiimi_tĂŒĂŒp", 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("kokku_ram", 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_tase", String.valueOf(Build$VERSION.SDK_INT)));
v3_2.add(g.fs("uc_apk_loend", 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, "uuenda_encode", "jah", "ebaÔnnestus");
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, "uuenda_encrypt", "jah", "ebaÔnnestus");
return;
}
if(TextUtils.isEmpty(this.iGY.mUpgradeUrl)) {
this.iGY.iGI.a(arg9, "uuenda_url", "jah", "ebaÔnnestus");
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, "uuenda_tĂŒhi", "jah", "edukalt");
this.iGY.iGI.b(arg9);
}Siin nÀeme POST-pÀringu loomist. Pöörake tÀhelepanu 16-baidise massiivi loomisele ja selle tÀitmisele: 0x5F, 0, 0x1F, -50 (=0xCE). See sobib kokku sellega, mida nÀgime eelnevas pÀringus.
Samas klassis on nĂ€ha ka sisemine klass, kus on veel ĂŒks huvitav meetod:
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");
}
} Meetod vĂ”tab sisendiks byte massiivi ja kontrollib, et nulls byte oleks 0x60 vĂ”i kolmas byte 0xD0 ning teine byte oleks 1, 11 vĂ”i 0x1F. Vaatame serveri vastust: nulls byte â 0x60, teine â 0x1F, kolmas â 0x60. Tundub, et see on see, mida vajame. Arvestades rea (âup_decryptâ, nĂ€iteks), peaks siin olema meetod, mis dekodeerib serveri vastuse.
Liigume meetodi juurde g.j. TĂ€helepanu, et esimese argumendina antakse edasi byte positsioonil 2 (st 0x1F meie puhul), ja teise argumendina â serveri vastus ilma
esimeste 16 baitideta.
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;
} Ilmselt toimub siin dekrĂŒpteerimise algoritmi valik, ja just see byte, mis meie kontekstis on.
juhtum on 0x1F, mis tĂ€histab ĂŒhte kolmest vĂ”imalikust variandist.
JĂ€tkame koodi analĂŒĂŒsi. PĂ€rast paarit hĂŒpet jĂ”uame meetodi juurde, millel on kĂ”nekujuline nimi decryptBytesByKey.
Siin lahutatakse meie vastusest veel kaks bait, ja nende pĂ”hjal saadakse string. Selge on, et selle meetodiga valitakse vĂ”tme jaoks krĂŒpteeritud sĂ”numi dekrĂŒpteerimise vĂ”tme.
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 baiti
System.arraycopy(bytes, 0, prefix, 0, prefix.length);
String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); // VÔtme valik
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;
}Eelnevalt mÀrgime, et antud etapis ei saadud veel vÔtme, vaid ainult selle "identifikaator". VÔtme saamine on veidi keerulisem.
JĂ€rgmises meetodis lisatakse olemasolevatele parameetritele veel kaks, muutes nende arvu neljaks: maagiline number 16, vĂ”tme identifikaator, krĂŒpteeritud andmed ja arusaamatu string (meie juhul tĂŒhi).
public final byte[] l(String keyId, byte[] encrypted) throws SecException {
return this.ayJ().staticBinarySafeDecryptNoB64(16, keyId, encrypted, "");
}PĂ€rast seeriat ĂŒleminekuid jĂ”uame meetodi juurde staticBinarySafeDecryptNoB64 liidese com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Rakenduse pĂ”hikoodis pole klassid, mis rakendavad seda liidest. Selline klass on failis lib/armeabi-v7a/libsgmain.so, mis tegelikult ei ole .so, vaid .jar. Meid huvitav meetod on rakendatud jĂ€rgmiselt:
pakett com.alibaba.wireless.security.a.i;
// ...
publiku klass a rakendab IStaticDataEncryptComponent {
privaatne ISecurityGuardPlugin a;
// ...
privaatne 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});
}
// ...
privaatne byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) {
return this.a(2, magicInt, 0, keyId, encrypted, magicString);
}
// ...
publik byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) viskab SecException {
if(keyId != null && keyId.length() > 0 && magicInt >= 0 && magicInt 0) {
return this.b(magicInt, keyId, encrypted, magicString);
}
viska new SecException("", 301);
}
//...
} Siin meie parameetrite loetelu tÀiendatakse veel kahe tervete arvuga: 2 ja 0. Oletades, et
2 tĂ€hendab dekrĂŒpteerimist, nagu meetodis doFinal sĂŒsteemne klass javax.crypto.Cipher. Ja kĂ”ik see edastatakse mingisse Routerisse numbriga 10601 â see on ilmselt kĂ€skluse number.
PĂ€rast jĂ€rgmise ĂŒlemineku ahelat leiame klassi, mis rakendab liidest IRouterComponent ja meetod doCommand:
pakett com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
publiku klass a rakendab IRouterComponent {
publiku a() {
super();
}
publik Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}Ja ka klass JNICLibrary, kus on deklareeritud pÔhimÔtteline meetod doCommandNative:
pakett com.taobao.wireless.security.adapter;
publiku klass JNICLibrary {
public static native Object doCommandNative(int arg0, Object[] arg1);
}See tÀhendab, et peame leidma pÔhimÔttelise meetodi doCommandNative. Ja siin algab tÔeline lÔbu.
Masinkoodi obfuskatsioon
Failis libsgmain.so (mis on tegelikult .jar ja milles me leidsime mĂ”ned varem rakendatud liidesed, mis on seotud krĂŒpteerimisega) on ĂŒks pĂ”himĂ”tteline teek: libsgmainso-6.4.36.so. Avame selle IDA-s ja saame hulga dialoogikaste vigadega. Probleem seisneb selles, et sektsioonide tabel (section header table) â ei ole kehtiv. See on tehtud spetsiaalselt analĂŒĂŒsi raskendamiseks.

Aga see pole vajalik: et Ă”igesti laadida ELF-faili ja analĂŒĂŒsida seda, piisab sektsioonide tabelist (program header table). SeetĂ”ttu lihtsalt kustutame sektsioonide tabeli, nullides vastavad vĂ€ljad pĂ€isesse.

Avame faili uuesti IDA-s.
On kaks viisi, kuidas teavitada virtuaalset Java-masinat, kus tĂ€pselt pĂ”himĂ”ttelise teegi rakendus asub, mis on Java-koodis deklareeritud kui native. Esimene â anda sellele nimi vormis Java_paketi_nime_klass_nimi_metood.
Teine â registreerida see, kui laaditakse teeki (funktsioonis JNI_OnLoad)
funktsiooni kutsumisega RegisterNatives.
Meie puhul, kui kasutada esimest meetodit, peab nimi olema jÀrgmine: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.
Eksporditud funktsioonide seas sellist pole, seega tuleb otsida kutset RegisterNatives.
LÀheme funktsiooni JNI_OnLoad ja nÀeme sellist pilti:

Mis siin toimub? Esmapilgul on funktsiooni algus ja lĂ”pp tĂŒĂŒpilised ARM-arhitektuurile. Esimese kĂ€su korral salvestatakse stacki register, mida funktsioon oma töös kasutab (antud juhul R0, R1 ja R2), samuti LR registreeritud vÀÀrtus, milles asub funktsioonist tagasipöördumise aadress. Viimane kĂ€sk taastab salvestatud registrid, tuues tagasipöördumise aadressi kohe registreeritud PC-sse â nii toimub funktsioonist tagasipöördumine. Kuid kui tĂ€helepanelikult vaadata, vĂ”ib mĂ€rgata, et eelviimane kĂ€sk muudab stackis hoitavat tagasipöördumise aadressi. Arvutame, mis see aadress pĂ€rast
koodi tĂ€itmist olema saab. R1-sse laaditakse aadress 0xB130, millest lahutatakse 5, seejĂ€rel paigutatakse see R0-sse ja sellele lisatakse 0x10. NĂ€htavasti saame 0xB13B. Nii arvab IDA, et viimases kĂ€sus toimub tavaline tagasipöördumine funktsioonist, kuid tegelikult toimub ĂŒleminek arvutatud aadressile 0xB13B.
Siinkohal tasub meenutada, et ARM-protsessoritel on kaks reĆŸiimi ja kaks kĂ€sukogumit: ARM ja Thumb. Aadressi madalam bit ĂŒtleb protsessorile, millist kĂ€sukogumit kasutatakse. Ehk aadress on tegelikult 0xB13A, ja madalama biti ĂŒks nĂ€itab Thumb reĆŸiimi.
Iga funktsiooni alguses selles teegis on sarnane "adapter" ja
prĂŒgikood. Edasi ei peatume neile ĂŒksikasjalikult â lihtsalt meenutame,
et peaaegu kÔikide funktsioonide tÔeline algus on veidi edasi.
Kuna koodis pole selget ĂŒleminekut aadressile 0xB13A, ei tuvastanud IDA ise, et sellesse kohta asub kood. Samuti ei tuvastanud ta suures osas teekis olevat koodi, mis mĂ”nevĂ”rra raskendab analĂŒĂŒsi. Ătleme IDA-le, et siin on kood, ja siin on tulemuseks:

Aadressil 0xB144 algab selgelt tabel. Aga mis on sub_494C-s?

Selle funktsiooni kutsumisel LR registris saame mainitud tabeli aadressi (0xB144). R0-s on indeksi vÀÀrtus selles tabelis. Ehk tabelist vÔetakse vÀÀrtus, lisatakse LR-ile ja saadakse
aadress, kuhu minna. Proovime selle vĂ€lja arvutada: 0xB144 + [0xB144 + 8 * 4] = 0xB144 + 0x120 = 0xB264. Liigume saadud aadressile ja nĂ€eme sĂ”na otseses mĂ”ttes mĂ”nda kasulikku juhendit ja jĂ€lle ĂŒleminekut 0xB140:

NĂŒĂŒd toimub ĂŒleminek nihke jĂ€rgi, mille indeks on 0x20 tabelis.
Tabeli suuruse pĂ”hjal esineb selliseid ĂŒleminekuid koodis palju. TĂ”useb kĂŒsimus, kas seda kuidagi automaatsemalt lahendada, ilma aadresside kĂ€sitsi arvutamiseta. Ja siin tulevad meile appi skriptid ja vĂ”imalus koodi IDA-s patĆĄida:
def put_unconditional_branch(source, destination):
offset = (destination - source - 4) >> 1
if offset > 2097151 or offset 1023 or offset > 11) & 0x7ff)
instruction2 = 0xb800 | (offset & 0x7ff)
patch_word(source, instruction1)
patch_word(source + 2, instruction2)
else:
instruction = 0xe000 | (offset & 0x7ff)
patch_word(source, instruction)
ea = here()
if get_wide_word(ea) == 0xb503: #PUSH {R0,R1,LR}
ea1 = ea + 2
if get_wide_word(ea1) == 0xbf00: #NOP
ea1 += 2
if get_operand_type(ea1, 0) == 1 and get_operand_value(ea1, 0) == 0 and get_operand_type(ea1, 1) == 2:
index = get_wide_dword(get_operand_value(ea1, 1))
print "indeks =", 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 "Vale operanditĂŒĂŒp", hex(ea1), "-", get_operand_type(ea1, 0), get_operand_type(ea1, 1)
table = None
if table is None:
print "Tabelit ei leitud"
else:
print "tabel =", hex(table)
offset = get_wide_dword(table + (index << 2))
put_unconditional_branch(ea, table + offset)
else:
print "Tundmatu kood", get_operand_type(ea1, 0), get_operand_value(ea1, 0), get_operand_type(ea1, 1) == 2
else:
print "Esimese kĂ€su tuvastamine ebaĂ”nnestus"Paneme kursori reale 0xB26A, kĂ€ivitame skripti ja nĂ€eme ĂŒleminekut 0xB4B0:

IDA ei tuvastanud jÀlle seda osa koodina. Aitame tal mÔista ja nÀeme seal muud konstruktsiooni:

BLX-i jÀrel olevad kÀsud ei tundu kuigi mÔistlikud, need rohkem meenutavad mingit nihket. Vaatame sub_4964:

Ja tÔesti, siin vÔetakse dword aadressilt, mis asub LR-is, sellele aadressile lisatakse ning pÀrast seda vÔetakse saadud aadressilt vÀÀrtus ja pannakse see kuhugi. Samuti lisatakse LR-ile 4, et pÀrast funktsioonist tagasiminekut vahele jÀtta see nihke. PÀrast seda vÔtab kÀsk POP {R1} saadud vÀÀrtuse kuhjast. Kui vaadata, mis asub aadressil 0xB4BA + 0xEA = 0xB5A4, siis vÔib nÀha midagi sarnast aadresside tabeliga:

Ette konstruktsiooni patcheerimiseks tuleb koodist hankida kaks parameetrit: nihke ja registri numbri, kuhu tulemus paigutada. Iga vÔimaliku registri jaoks tuleb eelnevalt ette valmistada koodilÔik.
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 kÀsku ei leitud"
else:
print "Vale operanditĂŒĂŒp +4-l:", get_operand_type(ea + 4, 0)
else:
print "Esimese kĂ€sku ei suutnud tuvastada"Seame kursori konstruktsiooni algusesse, mida soovime asendada â 0xB4B2 â ja kĂ€ivitame skripti:

Lisaks juba mainitud konstruktsioonidele leidub koodis ka jÀrgmisi:

Nagu eelmisel juhul, jÀrgneb kÀsklusele BLX nihke:

Hankime nihke LR aadressilt, lisame selle LR-ile ja liigume sinna. 0x72044 + 0xC = 0x72050. Skript selle konstruktsiooni jaoks on ĂŒsna lihtne:
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 "Esimese kÀsu tuvastamine ebaÔnnestus"Skripti tÀitmise tulemus:

PĂ€rast seda, kui funktsioon on kĂ”ik patĆĄitakse, saab IDA nĂ€idata selle tĂ”elist algust. See kogub kogu funktsiooni koodi tĂŒkkhaaval, ja seda saab dekompileerida HexRays'i abil.
SÔnumite dekodeerimine
Oleme Ôppinud vÔitlema masinkoodi obfuskaatorite vastu raamatukogus. libsgmainso-6.4.36.so UC Browserist ja saime funktsiooni koodi. 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;
}Vaadakem jÀrgmisi ridu lÀhemalt:
sub_73E24(&unk_83EA6, &v6, 49);
clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6);Funktsioonis sub_73E24 toimub ilmselt klassinime dekrĂŒpteerimine. Selle funktsiooni parameetritena edastatakse viide andmetele, mis nĂ€ivad olema krĂŒpteeritud, mingi puhvers ja number. On selge, et funktsiooni kutsumise jĂ€rel on puhvris dekrĂŒpteeritud string, kuna see edastatakse funktsioonile FindClass, mis vĂ”tab teise parameetrina klassi nime. Seega on number puhvri suurus vĂ”i stringi pikkus. Proovime klassi nime dekrĂŒpteerida, see peaks meile nĂ€itama, kas me liigume Ă”iges suunas. Vaatame lĂ€hemalt, mis toimub 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;
}Funktsioon sub_7AF78 loob kaks sĂ€ilituskonteinerit, mis on mÀÀratud antud suurusega (Ă€ra sĂŒvene nende konteinerite funktsiooni). Siin luuakse kaks sellist konteinerit: ĂŒhte paigutatakse string âDcO/lcK+h?m3c*q@â (ei ole raske arvata, et see on vĂ”ti), teise â krĂŒpteeritud andmed. SeejĂ€rel lisatakse mĂ”lemad objektid mingisse struktuuri, mis edastatakse funktsioonile sub_6115C. Samuti tasub mĂ€rkida, et selle struktuuri vĂ€li on vÀÀrtusega 3. Vaatame, mis sellega edasi juhtub.
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;
}Switchi parameetrina edastatakse struktuuri vĂ€li, millele on eelnevalt omistatud vÀÀrtus 3. Vaadake case 3: funktsiooni sub_6364C edastatakse struktuurist parameetrid, mis olid sinna eelnevas funktsioonis salvestatud, s.t. vĂ”ti ja krĂŒpteeritud andmed. Kui vaadata tĂ€helepanelikult sub_6364C, vĂ”ib seal leida algoritmi RC4.
Meil on algoritm ja vĂ”ti. Proovime dekrĂŒpteerida klassi nime. Siin on, mis me saime: com/taobao/wireless/security/adapter/JNICLibrary. SuurepĂ€rane! Me oleme Ă”igel teel.
KĂ€skude puu
NĂŒĂŒd tuleb leida kutsung RegisterNatives, mis viib meid funktsiooni doCommandNative. LĂ€heme lĂ€bi funktsioonid, mida kutsutakse JNI_OnLoad, ja leiame selle 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 meetod; // [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;
meetod.name = name;
meetod.signature = signature;
meetod.fnPtr = sub_B69C;
return ((int (__fastcall *)(JNIEnv *, jclass, JNINativeMethod *, int))(*env)->RegisterNatives)(env, clazz, &meetod, 1) >> 31;
}Ja tĂ”esti, siin registreeritakse natiivne meetod nimega doCommandNative. NĂŒĂŒd teame selle aadressi. Vaadake, mida see teeb.
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;
}Nime jÀrgi vÔib arvata, et siin on kÔigi funktsioonide sisenemiskoht, mille arendajad otsustasid viia natiivsetesse raamatukogudesse. Meid huvitab funktsioon numbriga 10601.
Koodist vÔib nÀha, et kÀskude numbrist saadakse kolm numbrit: command / 10000, command % 10000 / 100 ja command % 10, s.t., meie puhul 1, 6 ja 1. Need kolm numbrit, samuti viitamine JNIEnv ja funktsiooni edastatud argumendid, koondatakse struktuuri ja edastatakse edasi. Saadud kolme numbri (nimetame need N1, N2 ja N3) abil ehitatakse kÀskude puu.
Umbes selline:

Puu tĂ€idetakse dĂŒnaamiliselt JNI_OnLoad.
Kolm numbrit kodeerivad teed puus. Iga puu leht sisaldab vastava funktsiooni pangas aadressi. VÔti on vanemas sÔlmes. Leiame koha koodis, kus puusse lisatakse meie soovitud funktsioon, ei ole suurt vaeva, kui mÔista kÔiki kasutatavaid struktuure (nende kirjeldust ei tooda, et mitte paisutada juba niigi suurt artiklit).
Veel obfuskatsioonist
Oleme saanud funktsiooni aadressi, mis peaks dekrĂŒpteerima liiklust: 0x5F1AC. Kuid rÔÔmustamine on veel vara: UC Browseri arendajad on meile valmistunud veel ĂŒheks ĂŒllatuseks.
PÀrast parameetrite saamist massiivist, mis oli loodud Java-koodis, jÔuame
funktsiooni aadressile 0x4D070. Ja siin ootab meid ees veel ĂŒks obfuskatsiooni vorm.
Paneme R7 ja R4 kaks indeksit:

Asetame esimese indeksi R11:se.

Aadressi saamiseks tabelist kasutame indeksit:

PĂ€rast esimest aadressile ĂŒleminekut kasutatakse teist indeksit, mis on R4. Tabelis on 230 elementi.
Mida sellega peale hakata? VÔime IDA-le öelda, et see on selline switch: Muuda -> Muu -> TÀpsusta switch idiomit.

Saatud kood on hirmutav. Kuid selle lĂ€bi labĂŒrindi liikudes vĂ”ib mĂ€rkida, et kutsume juba tuttavat funktsiooni. sub_6115C:

Seal oli switch, kus case 3-s oli dekrĂŒpteerimine, kasutades algoritmi RC4. Ja selles juhusis tĂ€idetakse funktsioonile edastatav struktuur parameetritest, mis edastati doCommandNative. Tuletame meelde, et meil oli seal magicInt vÀÀrtusega 16. Vaatame vastavat case'i â ja pĂ€rast mitmeid ĂŒleminekuid leiame koodi, mille kaudu saab algoritmi tuvastada.

See on AES!
Algoritm on olemas, jÀÀb vaid saada selle parameetrid: reĆŸiim, vĂ”ti ja vĂ”ib-olla algvÀÀrtuse vektor (selle olemasolu sĂ”ltub AES-i algoritmi tööreĆŸiimist). Struktuur, mis neid sisaldab, peaks suutma vormida kuskil enne funktsiooni sub_6115C, kuid selle koodi osa on eriti hĂ€sti obfutseeritud, seega tekib idee koodi patĆĄida, et kĂ”ik dekrĂŒpteerimisfunktsiooni parameetrid salvestataks faili.
PatĆĄ
Kuna ei soovi kogu patĆĄi koodi kĂ€sitsi assembleri keeles kirjutada, saab avada Android Studio, kirjutada seal funktsiooni, mis vĂ”tab sisendiks samad parameetrid nagu meie dekrĂŒpteerimisfunktsioon, ja kirjutab faili, seejĂ€rel kopeerida-kleebida kood, mille genereerib kompilaator.
Meie sĂ”brad UC Browseri meeskonnast on ka kodu koodi lisamise mugavuse ĂŒle hoolitsenud. Tuletame meelde, et iga funktsiooni alguses on meil 'prĂŒgikood', mille saab lihtsalt asendada mĂ”ne teisega. See on vĂ€ga mugav đ TĂ”si, sihtfunktsiooni alguses on koodi jaoks, mis salvestab kĂ”ik parameetrid faili, ruumi liiga vĂ€he. Pidin selle kolmeks osaks jagama ja kasutama kĂ”rval asuvate funktsioonide prĂŒgiblokke. Kokku sai neli osa.
Esimene osa:

ARM arhitektuuris edastatakse funktsiooni esimesed neli parameetrit registerite R0-R3 kaudu ning ĂŒlejÀÀnud, kui need on olemas, via virna. Register LR edastab tagasipöördumise aadressi. KĂ”ik see tuleb salvestada, et funktsioon saaks töötada pĂ€rast seda, kui oleme selle parameetrid 'dumpinud'. Samuti tuleb salvestada kĂ”ik registerid, mida me protsessis kasutame, seega teeme PUSH.W {R0-R10,LR}. R7-s saame parameetrite nimekirja aadressi, mis edastati funktsioonile via virna.
Funktsiooni kaudu fopen avafaili /data/local/tmp/aes âabâ reĆŸiimis,
st. lisamiseks. R0-s laadime faili nime aadressi, R1-s reĆŸiimi mÀÀratleva stringi aadressi. Ja siin lĂ”peb prĂŒgikood, seega liigume jĂ€rgmisse funktsiooni. Selle jĂ€tkamiseks paneme algusesse ĂŒlemineku tegelikku funktsiooni koodi, mööda prĂŒgist, ning prĂŒgile lisame patĆĄi jĂ€tku.

Kutse fopen.
esimesele kolmele parameetrile funktsioonis aes on tĂŒĂŒp int. Kuna me alguses salvestasime registerid virna, vĂ”ime lihtsalt edastada funktsioonile fwrite nende aadresse virnas.

Edasi on meil kolm struktuuri, mis sisaldavad andmete suurust ja andmeedastuse osutajat vĂ”tme, alguse vektori ja krĂŒpteeritud andmete jaoks.

LÔpus sulgeme faili, taastame registerid ja edastame juhtimise tÔelisele funktsioonile. aes.
Kogume APK koos patĆĄitud teegiga, signeerime selle, laadime seadmesse/emulaatorisse, kĂ€ivitame. NĂ€eme, et meie dump luuakse ja sinna kirjutatakse palju andmeid. Brauser kasutab krĂŒpteerimist mitte ainult liikluse jaoks, ja kogu krĂŒpteerimine kĂ€ib lĂ€bi arutatava funktsiooni. Kuid vajalikud andmed on mingil pĂ”hjusel puuduvad ja liikluses ei ole vajalikku pĂ€ringut nĂ€ha. Et mitte oodata, kuni UC Browser korraldab vajaliku pĂ€ringu, vĂ”tame varasema serverilt saadud krĂŒpteeritud vastuse ja patĆĄime rakenduse uuesti: lisame dekrĂŒpteerimise peamise tegevuse onCreate funktsiooni.
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;)IKogume, allkirjastame, installime, kÀivitame. Saame NullPointerException, kuna meetod tagastas null.
Koodi edasisel analĂŒĂŒsimisel leiti funktsioon, mis dekrĂŒpteerib huvitavad read: «META-INF/» ja «.RSA». Tundub, et rakendus kontrollib oma sertifikaati. VĂ”i isegi genereerib vĂ”tmeid sellest. Tegeleda sertifikaadiga on tĂŒlikas, nii et lihtsalt anname sellele Ă”ige sertifikaadi. Patchime krĂŒpteeritud rea nii, et «META-INF/» asendatakse «BLABLINF/», loome APK-s kausta sellise nimega ja paneme sinna beæ”è§ćš sertifikaadi.
Kogume, allkirjastame, installime, kÀivitame. Bingo! Meil on vÔtmed!
MitM
Me saime vĂ”tme ja initsialiseerimisvektori, mis on vĂ”rdne vĂ”tmega. Proovime serveri vastust CBC reĆŸiimis dekrĂŒpteerida.

NÀeme arhivi URL-i, mis meenutab midagi MD5-d, «extract_unzipsize» ja numbrit. Kontrollime: arhiiv MD5 vastab, lahtipakitud teegi suurus vastab. Proovime seda teeki patchida ja anda selle brauserile. NÀiteks, et nÀidata, et meie patchitud teek on laaditud, kÀivitame Intent SMSi loomiseks tekstiga «PWNED!». Asendame kaks serveri vastust: ja arhiivi allalaadimisega. Esimeses asendame MD5 (lahtipakitud suurus ei muutu), teises anname arhiivi patchitud teegiga.
Brauser proovib korduvalt arhiivi alla laadida, pÀrast mida annab vea. Tundub, et midagi
ei meeldi talle. Selle kahtlase formaadi analĂŒĂŒsimisel selgus, et server edastab ka arhiivi suuruse:

See on kodeeritud LEB128 formaadis. PÀrast patchimist muutus arhiivi suurus teegiga veidi, seetÔttu pidas brauser, et arhiiv laaditi valesti, ja pÀrast mitut katset andis vea.
Parandame arhiivi suurust... Ja â vĂ”it! đ Tulemused videos.
TagajÀrjed ja arendaja reaktsioon
Sarnasena kas hakkere oleks vĂ”inud kasutada UC Browser'i turvaaukot, et levitada ja kĂ€ivitada pahavara teeke. Need teekid töötavad brauseri kontekstis, mistĂ”ttu nad saavad kĂ”ik selle sĂŒsteemi Ă”igused. Selle tulemusena on vĂ”imalus kuvada libahĂ€kkimisaken ja pÀÀseda ligi tööfailidele, sealhulgas oranzhi Hiina orava andmebaasis salvestatud sisselogimistele, paroolidele ja kĂŒpsistele.
Me vĂ”tsime ĂŒhendust UC Browser'i arendajatega ja teavitasi neid leitud probleemist, pĂŒĂŒdes osutada haavatavusele ja selle ohtudele, kuid nad ei soovinud meiega millestki arutada. Vahepeal jĂ€tkas brauser ohtliku funktsiooniga uhkustamist. Kuid niipea, kui avaldasime haavatavuse detailid, ei olnud vĂ”imalik seda enam nagu varem ignoreerida. 27. mĂ€rtsil
vÀlja lastud uus versioon UC Browser 12.10.9.1193, mis kutsus serverit HTTPS kaudu: .
Lisaks sellele, pÀrast "parandamist" ja kuni artikli kirjutamise hetkeni pÔhjustas brauseris PDF avamise katse veaaken, mille tekst oli "Ups, midagi lÀks valesti!". Serveri pÀring PDF avamisest ei toimunud, kuid brauseri kÀivitamisel toimus pÀring, mis vihjab jÀtkuvale vÔimalusele laadida tÀitmisfaili, rikkuvalt Google Play reegleid.
Allikas: habr.com
