Ahultasunen bila UC arakatzailean

Ahultasunen bila UC arakatzailean

Sarrera

Martxoaren amaieran guk jakinarazi, UC arakatzailean egiaztatu gabeko kodea kargatzeko eta exekutatzeko gaitasun ezkutua aurkitu zutela. Gaur deskarga hau nola gertatzen den eta hacker-ek beren helburuetarako nola erabil dezaketen aztertuko dugu.

Duela denbora pixka bat, UC Browser oso modu oldarkorrean iragartzen eta banatzen zen: malwarea erabiliz erabiltzaileen gailuetan instalatzen zen, hainbat gunetatik banatuta bideo-fitxategien itxurapean (hau da, erabiltzaileek bideo porno bat deskargatzen ari zirela uste zuten, adibidez, baina horren ordez, APK bat jaso du arakatzaile honekin), pankarta beldurgarriak erabili ditu arakatzailea zaharkituta, zaurgarria eta horrelakoak zirela esaten zuten mezuekin. VK-ko UC Browser talde ofizialean dago Gaia, erabiltzaileek publizitate bidegabeaz kexatu ahal izateko, adibide asko daude hor. 2016an ere egon zen bideo publizitatea errusieraz (bai, iragarkiak blokeatzeko arakatzaile baten iragarkia).

Idazteko unean, UC arakatzaileak 500 instalazio baino gehiago ditu Google Play-n. Hau ikusgarria da - Google Chrome-k baino gehiago ditu. Berrikuspenen artean iragarkiei buruzko kexa dezente ikus ditzakezu eta Google Play-ko aplikazio batzuetara birbideratzeko. Hau izan zen gure ikerketaren arrazoia: UC Browser-ek zerbait txarra egiten ote zuen ikustea erabaki genuen. Eta hori bai!

Aplikazioaren kodean, kode exekutagarria deskargatzeko eta exekutatzeko gaitasuna aurkitu zen, aplikazioak argitaratzeko arauen aurkakoa dena Google Play-n. Kode exekutagarria deskargatzeaz gain, UC Browser-ek modu seguruan egiten du, MitM erasoa abiarazteko erabil daitekeena. Ea horrelako erasorik egin dezakegun.

Behean idatzitako guztia garrantzitsua da ikerketaren garaian Google Play-n eskuragarri zegoen UC arakatzailearen bertsiorako:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-Ρ„Π°ΠΉΠ»Π°: f5edb2243413c777172f6362876041eb0c3a928c

Eraso bektorea

UC arakatzailearen manifestuan, berez azaltzen den izena duen zerbitzu bat aurki dezakezu com.uc.deployment.UpgradeDeployService.

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

Zerbitzu hau hasten denean, arakatzaileak POST eskaera bat egiten dio puds.ucweb.com/upgrade/index.xhtml, trafikoan ikusi daitekeena hasi eta denbora gutxira. Horren harira, eguneraketa edo modulu berriren bat deskargatzeko komando bat jaso dezake. Azterketan zehar, zerbitzariak ez ditu halako komandoak eman, baina nabigatzailean PDF bat irekitzen saiatzen garenean, goian zehaztutako helbidean bigarren eskaera bat egiten duela ohartu gara, ondoren jatorrizko liburutegia deskargatzen duela. Erasoa burutzeko, UC Browser-en ezaugarri hau erabiltzea erabaki dugu: jatorrizko liburutegi bat erabiliz PDFa irekitzeko aukera, APK-n ez dagoena eta Internetetik deskargatzen duena behar izanez gero. Aipatzekoa da, teorikoki, UC arakatzailea zerbait deskargatzera behartu daitekeela erabiltzailearen interakziorik gabe, nabigatzailea abiarazi ondoren exekutatzen den eskaera bati erantzun ongi osatua ematen badiozu. Baina horretarako, zerbitzariarekiko interakzio-protokoloa zehatzago aztertu behar dugu, beraz, atzemandako erantzuna editatzea eta liburutegia PDFarekin lan egiteko errazagoa izango zela erabaki genuen.

Beraz, erabiltzaile batek PDF bat zuzenean arakatzailean ireki nahi duenean, honako eskaera hauek ikus daitezke trafikoan:

Ahultasunen bila UC arakatzailean

Lehenik eta behin, POST eskaera bat dago puds.ucweb.com/upgrade/index.xhtml, orduan
PDF eta ofimatika formatuak ikusteko liburutegia duen artxiboa deskargatzen da. Logikoa da lehenengo eskaerak sistemari buruzko informazioa transmititzen duela (gutxienez behar den liburutegia eskaintzeko arkitektura), eta horri erantzuteko arakatzaileak deskargatu behar den liburutegiari buruzko zenbait informazio jasotzen duela: helbidea eta, beharbada. , beste zerbait. Arazoa da eskaera hau zifratuta dagoela.

Eskatu zatia

Erantzun zatia

Ahultasunen bila UC arakatzailean

Ahultasunen bila UC arakatzailean

Liburutegia bera ZIP-en paketatuta dago eta ez dago zifratuta.

Ahultasunen bila UC arakatzailean

Bilatu trafikoa deszifratzeko kodea

Saia gaitezen zerbitzariaren erantzuna deszifratzen. Ikus dezagun klasearen kodea com.uc.deployment.UpgradeDeployService: metodotik onStartCommand joan com.uc.deployment.bx, eta bertatik 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);
}

Hemen POST eskaera baten eraketa ikusten dugu. Arreta jartzen dugu 16 byteko array bat sortzeari eta bere betetzeari: 0x5F, 0, 0x1F, -50 (=0xCE). Goiko eskaeran ikusi genuenarekin bat dator.

Klase berean beste metodo interesgarri bat duen klase habiaratu bat ikus dezakezu:

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

Metodoak byte-matrize bat hartzen du sarrera gisa eta zero bytea 0x60 dela edo hirugarren byta 0xD0 dela egiaztatzen du, eta bigarren bytea 1, 11 edo 0x1F dela. Zerbitzariaren erantzuna ikusten dugu: zero byte 0x60 da, bigarrena 0x1F, hirugarrena 0x60. Behar duguna dirudi. Lerroen arabera (Β«up_decryptΒ», adibidez), hemen zerbitzariaren erantzuna deszifratuko duen metodo bat deitu behar da.
Goazen metodora gj. Kontuan izan lehenengo argumentua 2 desplazamenduko bytea dela (hau da, 0x1F gure kasuan), eta bigarrena zerbitzariaren erantzuna dela.
lehenengo 16 byte.

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

Jakina, hemen deszifratze algoritmo bat hautatzen dugu, eta gurean dagoen byte bera
0x1F-ren kasua, hiru aukera posibleetako bat adierazten du.

Kodea aztertzen jarraitzen dugu. Pare bat jauzi egin ondoren, berez azaltzen den izena duen metodo batean aurkitzen gara decryptBytesByKey.

Hemen beste bi byte bereizten dira gure erantzunetik, eta haietatik kate bat lortzen da. Argi dago modu honetan mezua deszifratzeko gakoa hautatzen dela.

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

Aurrera begira, ohartzen gara fase honetan oraindik ez dugula gakorik lortzen, bere β€œidentifikatzailea” baizik. Gakoa lortzea pixka bat zailagoa da.

Hurrengo metodoan, daudenei beste bi parametro gehitzen zaizkie, horietako lau eginez: 16 zenbaki magikoa, gako-identifikatzailea, enkriptatutako datuak eta kate ulertezina (gure kasuan, hutsa).

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

Trantsizio batzuen ondoren metodora iristen gara staticBinarySafeDecryptNoB64 interfazea com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Aplikazio-kode nagusian ez dago interfaze hau inplementatzen duen klaserik. Fitxategian halako klase bat dago lib/armeabi-v7a/libsgmain.so, izatez ez da .so bat, .jar bat baizik. Interesatzen zaigun metodoa honela inplementatzen da:

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

Hemen gure parametroen zerrenda beste bi zenbaki osorekin osatzen da: 2 eta 0. arabera
dena, 2 deszifratzea esan nahi du, metodoan bezala doFinal sistema klasea javax.crypto.Cipher. Eta hori guztia 10601 zenbakia duen Router jakin batera transferitzen da - itxuraz hau da komando-zenbakia.

Hurrengo trantsizio-katearen ondoren interfazea inplementatzen duen klase bat aurkituko dugu IRouterComponent eta metodoa egin Agindua:

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

Baita klasea ere JNICLiburutegia, zeinetan jatorrizko metodoa deklaratzen den doCommandNative:

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

Horrek esan nahi du jatorrizko kodean metodo bat aurkitu behar dugula doCommandNative. Eta hemen hasten da dibertsioa.

Makinaren kodea lausotzea

Fitxategian libsgmain.so (Egia esan .jar bat dena eta enkriptazioarekin erlazionatutako interfaze batzuen ezarpena aurkitu genuen gainean) jatorrizko liburutegi bat dago: libsgmainso-6.4.36.so. IDAn irekitzen dugu eta erroreak dituzten elkarrizketa-koadro mordoa lortzen dugu. Arazoa da atalaren goiburuko taula baliogabea dela. Hau nahita egiten da analisia zailtzeko.

Ahultasunen bila UC arakatzailean

Baina ez da beharrezkoa: ELF fitxategi bat behar bezala kargatzeko eta hura aztertzeko, nahikoa da programaren goiburuko taula. Hori dela eta, atalen taula ezabatzen dugu, goiburuko dagozkien eremuak zeroz kenduz.

Ahultasunen bila UC arakatzailean

Ireki fitxategia berriro IDAn.

Javako makina birtualari esateko bi modu daude jatorrizko liburutegian zehazki non dagoen Java kodean jatorrizko gisa deklaratutako metodo baten ezarpena. Lehenengoa espeziearen izena ematea da Java_package_name_ClassName_MethodName.

Bigarrena liburutegia kargatzean erregistratzea da (funtzioan JNI_OnLoad)
funtzio-dei bat erabiliz ErregistratuBertakoak.

Gure kasuan, lehenengo metodoa erabiltzen badugu, izenak honela izan behar du: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Esportatutako funtzioen artean ez dago horrelako funtziorik, hau da, dei bat bilatu behar duzu ErregistratuBertakoak.
Goazen funtziora JNI_OnLoad eta argazki hau ikusten dugu:

Ahultasunen bila UC arakatzailean

Zer gertatzen da hemen? Lehen begiratuan, funtzioaren hasiera eta amaiera ohikoak dira ARM arkitekturarako. Pilako lehen instrukzioak funtzioak bere funtzionamenduan erabiliko dituen erregistroen edukia gordetzen du (kasu honetan, R0, R1 eta R2), baita LR erregistroaren edukia ere, funtzioaren itzulera helbidea duen. . Azken instrukzioak gordetako erregistroak berrezartzen ditu eta itzulera helbidea berehala jartzen da ordenagailuko erregistroan - horrela funtziotik itzultzen da. Baina ondo begiratuz gero, azkenaurreko instrukzioak pilan gordetako itzulera helbidea aldatzen duela ohartuko zara. Kalkula dezagun nolakoa izango den ondoren
kodearen exekuzioa. 1xB0 helbide jakin bat R130-en kargatzen da, 5 kentzen zaio, gero R0-ra transferitzen da eta 0x10 gehitzen zaio. 0xB13B bihurtzen da. Horrela, IDAk uste du azken instrukzioa funtzio-itzulera normala dela, baina hain zuzen ere kalkulatutako 0xB13B helbidera doa.

Hemen gogoratzea komeni da ARM prozesadoreek bi modu eta bi argibide multzo dituztela: ARM eta Thumb. Helbideko bit esanguratsuenak prozesadoreari zein instrukzio-multzo erabiltzen ari den esaten dio. Hau da, helbidea benetan 0xB13A da, eta bit esanguratsuenean dagoen batek Thumb modua adierazten du.

Liburutegi honetako funtzio bakoitzaren hasieran antzeko β€œegokitzaile” bat gehitu da eta
zabor kodea. Ez gara zehatz-mehatz gehiago luzatuko, gogoratzen gara
ia funtzio guztien benetako hasiera pixka bat urrunago dagoela.

Kodeak 0xB13A-ra esplizituki jauzi egiten ez duenez, IDAk berak ez zuen ezagutzen kodea kokaleku honetan kokatu zenik. Arrazoi beragatik, ez du liburutegiko kode gehiena kode gisa ezagutzen, eta horrek azterketa zaila egiten du. IDAri esaten diogu hau kodea dela, eta hauxe gertatzen da:

Ahultasunen bila UC arakatzailean

Taula argi eta garbi 0xB144-n hasten da. Zer dago sub_494C-n?

Ahultasunen bila UC arakatzailean

Funtzio honi LR erregistroan deitzean, lehen aipatutako taularen helbidea (0xB144) lortuko dugu. R0-n - taula honetako indizea. Hau da, balioa taulatik hartzen da, LRri gehitu eta emaitza da
helbidera joan. Saia gaitezen kalkulatzen: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Jasotako helbidera joaten gara eta literalki argibide erabilgarria pare bat ikusiko ditugu eta berriro 0xB140-ra joango gara:

Ahultasunen bila UC arakatzailean

Orain taulako 0x20 indizearekin desplazamenduan trantsizio bat egongo da.

Taularen tamainaren arabera, horrelako trantsizio asko egongo dira kodean. Galdera sortzen da ea posible den horri nolabait automatikoki aurre egitea, helbideak eskuz kalkulatu gabe. Eta script-ak eta IDAn kodea adabakitzeko gaitasuna gure laguntzan datoz:

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"

Jarri kurtsorea 0xB26A lerroan, exekutatu scripta eta ikusi 0xB4B0rako trantsizioa:

Ahultasunen bila UC arakatzailean

IDAk ez zuen eremu hau kode gisa aitortu. Berari laguntzen diogu eta hor beste diseinu bat ikusten dugu:

Ahultasunen bila UC arakatzailean

BLX ondorengo argibideek ez omen dute zentzu handirik, desplazamendu moduko bat baino gehiago da. Ikus dezagun sub_4964:

Ahultasunen bila UC arakatzailean

Eta, hain zuzen ere, hemen LR-n dagoen helbidean dword bat hartzen da, helbide honi gehitzen zaio, eta ondoren, ondoriozko helbidean balioa hartzen da eta pilan jartzen da. Gainera, 4 gehitzen zaio LRri, funtziotik itzuli ondoren, desplazamendu hori bera saltatu dadin. Horren ondoren, POP {R1} komandoak pilatik sortutako balioa hartzen du. 0xB4BA + 0xEA = 0xB5A4 helbidean dagoenari begiratzen badiozu, helbide taula baten antzeko zerbait ikusiko duzu:

Ahultasunen bila UC arakatzailean

Diseinu hau adabakitzeko, kodearen bi parametro lortu beharko dituzu: desplazamendua eta emaitza jarri nahi duzun erregistro-zenbakia. Erregistro posible bakoitzeko, aldez aurretik kode zati bat prestatu beharko duzu.

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"

Kurtsorea ordezkatu nahi dugun egituraren hasieran kokatuko dugu - 0xB4B2 - eta scripta exekutatuko dugu:

Ahultasunen bila UC arakatzailean

Lehen aipaturiko egiturez gain, kodeak honako hauek ere baditu:

Ahultasunen bila UC arakatzailean

Aurreko kasuan bezala, BLX instrukzioaren ondoren desplazamendu bat dago:

Ahultasunen bila UC arakatzailean

LR-tik helbidera desplazamendua hartzen dugu, LRra gehitu eta hara joaten gara. 0x72044 + 0xC = 0x72050. Diseinu honen gidoia nahiko erraza da:

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"

Script exekuzioaren emaitza:

Ahultasunen bila UC arakatzailean

Funtzioan dena adabakita dagoenean, IDA bere benetako hasierara bidera dezakezu. Funtzio-kode guztia bilduko du eta HexRays erabiliz deskonpilatu daiteke.

Kateak deskodetzea

Liburutegian makina-kodearen nahasteari aurre egiten ikasi dugu libsgmainso-6.4.36.so UC Browser-etik eta funtzio-kodea jaso zuen 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;
}

Ikus ditzagun ondoko lerro hauek:

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

Funtzioan azpi_73E24 klasearen izena argi eta garbi deszifratzen ari da. Funtzio honen parametro gisa, enkriptatutako datuen antzeko datuen erakuslea, buffer jakin bat eta zenbaki bat pasatzen dira. Jakina, funtzioari deitu ondoren, deszifratutako lerro bat egongo da bufferean, funtziora pasatzen baita. Aurkitu Klasea, klasearen izena bigarren parametro gisa hartzen duena. Beraz, zenbakia buffer-aren tamaina edo lerroaren luzera da. Saia gaitezen klasearen izena deszifratzen, bide onetik goazen esan behar digu. Ikus dezagun gertutik zer gertatzen den azpi_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;
}

Funtzioa azpi_7AF78 zehaztutako tamainako byte-matrizeentzako edukiontzi baten instantzia bat sortzen du (ez gara edukiontzi horiei buruz xehetasunez luzatuko). Hona hemen halako bi edukiontzi sortzen dira: batek lerroa dauka "DcO/lcK+h?m3c*q@" (erraza da hau gako bat dela asmatzea), besteak enkriptatutako datuak ditu. Ondoren, bi objektuak egitura jakin batean jartzen dira, eta hori funtziora pasatzen da azpi_6115C. Egitura honetan 3 balioa duen eremu bat ere markatu dezagun. Ikus dezagun zer gertatzen den egitura honekin.

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 parametroa aldez aurretik 3 balioa esleitu zitzaion egitura-eremu bat da. Begira 3. kasua: funtzioari azpi_6364C parametroak aurreko funtzioan bertan gehitu ziren egituratik pasatzen dira, hau da, gakoa eta datu zifratuak. Arretaz begiratuz gero azpi_6364C, RC4 algoritmoa ezagutu dezakezu bertan.

Algoritmo bat eta gako bat ditugu. Saia gaitezen klasearen izena deszifratzen. Hona zer gertatu den: com/taobao/wireless/security/adapter/JNICLibrary. Bikaina! Bide onetik goaz.

Aginduen zuhaitza

Orain erronka bat aurkitu behar dugu ErregistratuBertakoak, funtzioa adieraziko diguna doCommandNative. Ikus ditzagun deitutako funtzioak JNI_OnLoad, eta bertan aurkitzen dugu azpi_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;
}

Eta hain zuzen ere, izena duen jatorrizko metodo bat erregistratu da hemen doCommandNative. Orain badakigu bere helbidea. Ea zer egiten duen.

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

Izenarekin asma dezakezu hemen garatzaileek jatorrizko liburutegira transferitzea erabaki zuten funtzio guztien sarrera puntua dela. 10601 funtzio zenbakia interesatzen zaigu.

Kodetik ikus dezakezu komando-zenbakiak hiru zenbaki sortzen dituela: komandoa/10000, komandoa % 10000 / 100 ΠΈ komandoa % 10, hau da, gure kasuan, 1, 6 eta 1. Hiru zenbaki hauek, baita erakuslea ere JNIEnv eta funtzioari pasatzen zaizkion argumentuak egitura bati gehitzen zaizkio eta pasatzen dira. Lortutako hiru zenbakiak erabiliz (adierazi ditzagun N1, N2 eta N3), komandoen zuhaitza eraikitzen da.

Honelako zerbait:

Ahultasunen bila UC arakatzailean

Zuhaitza dinamikoki betetzen da JNI_OnLoad.
Hiru zenbakik kodetzen dute bidea zuhaitzean. Zuhaitz-hosto bakoitzak dagokion funtzioaren helbidea pocked dauka. Gakoa nodo nagusian dago. Behar dugun funtzioa zuhaitzari gehitzen zaion kodean tokia aurkitzea ez da zaila erabiltzen diren egitura guztiak ulertzen badituzu (ez ditugu deskribatzen dagoeneko artikulu handi samarra ez puzteko).

Lauso gehiago

Trafikoa deszifratu behar duen funtzioaren helbidea jaso dugu: 0x5F1AC. Baina goizegi da pozteko: UC Browser-en garatzaileek beste sorpresa bat prestatu digute.

Java kodean eratutako arraytik parametroak jaso ondoren, lortzen dugu
0x4D070 helbidean dagoen funtziora. Eta hemen beste kode lausotze mota bat itxaroten zaigu.

R7 eta R4-n bi indize jarri ditugu:

Ahultasunen bila UC arakatzailean

Lehenengo indizea R11ra aldatuko dugu:

Ahultasunen bila UC arakatzailean

Taula batetik helbide bat lortzeko, erabili indize bat:

Ahultasunen bila UC arakatzailean

Lehen helbidera joan ondoren, bigarren indizea erabiltzen da, hau da, R4n. Taulan 230 elementu daude.

Zer egin horren aurrean? IDAri esan diezaiokezu hau etengailu bat dela: Editatu -> Bestelakoak -> Zehaztu switch-idioma.

Ahultasunen bila UC arakatzailean

Ondorioz kodea beldurgarria da. Baina, bere oihanean barrena bidea eginez, dagoeneko ezaguna zaigun funtzio bati dei bat nabari dezakezu azpi_6115C:

Ahultasunen bila UC arakatzailean

Etengailu bat zegoen, 3. kasuan, RC4 algoritmoa erabiliz deszifratze bat zegoen. Eta kasu honetan, funtziora pasatu den egitura pasatutako parametroetatik betetzen da doCommandNative. Gogora dezagun han genuena magiaInt 16 balioarekin. Dagokion kasuari erreparatuko diogu eta hainbat trantsizioren ondoren algoritmoa identifikatu daitekeen kodea aurkituko dugu.

Ahultasunen bila UC arakatzailean

Hau da AES!

Algoritmoa existitzen da, bere parametroak lortzea baino ez da geratzen: modua, gakoa eta, beharbada, hasierako bektorea (bere presentzia AES algoritmoaren funtzionamendu-moduaren araberakoa da). Haiekin egitura nonbait eratu behar da funtzio-deia baino lehen azpi_6115C, baina kodearen zati hau bereziki ondo lausotuta dago, beraz, kodea adabakitzea sortzen da, deszifratze funtzioaren parametro guztiak fitxategi batera bota daitezen.

Adabakia

Adabaki kode guztia muntaia-lengoaian eskuz ez idazteko, Android Studio abiarazi dezakezu, bertan gure deszifratze-funtzioaren sarrera-parametro berdinak jasotzen dituen funtzio bat idatz dezakezu eta fitxategi batean idazten du, ondoren konpilatzaileak egingo duen kodea kopiatu-itsatsi. sortu.

UC Browser taldeko gure lagunak ere arduratu ziren kodea gehitzearen erosotasunaz. Gogora dezagun funtzio bakoitzaren hasieran beste edozeinekin erraz ordezka daitekeen zabor-kodea dugula. Oso erosoa πŸ™‚ Hala ere, xede-funtzioaren hasieran ez dago parametro guztiak fitxategi batean gordetzen dituen kodearako leku nahikorik. Zatitan banatu eta aldameneko funtzioetako zabor blokeak erabili behar izan ditut. Lau zati izan ziren guztira.

Lehenengo zatia:

Ahultasunen bila UC arakatzailean

ARM arkitekturan, lehen lau funtzio-parametroak R0-R3 erregistroetatik pasatzen dira, gainerakoak, egonez gero, pilatik pasatzen dira. LR erregistroak itzulera helbidea darama. Hori guztia gorde behar da, funtzioak bere parametroak bota ondoren funtziona dezan. Prozesuan erabiliko ditugun erregistro guztiak ere gorde behar ditugu, beraz PUSH.W {R0-R10,LR} egiten dugu. R7-n pilaren bidez funtzioari pasatutako parametroen zerrendaren helbidea lortzen dugu.

Funtzioa erabiliz fopen ireki dezagun fitxategia /data/local/tmp/aes "ab" moduan
hau da, gehitzeko. R0-n fitxategi-izenaren helbidea kargatzen dugu, R1-n - modua adierazten duen lerroaren helbidea. Eta hemen zabor kodea amaitzen da, beraz, hurrengo funtziora pasako gara. Funtzionatzen jarrai dezan, hasieran funtzioaren benetako kodearen trantsizioa jartzen dugu, zaborra saihestuz, eta zaborraren ordez adabakiaren jarraipena gehitzen dugu.

Ahultasunen bila UC arakatzailean

Deitzen fopen.

Funtzioaren lehen hiru parametroak aes mota dute int. Hasieran erregistroak pila batean gorde genituenez, funtzioa pasa dezakegu idatzi beren helbideak pilan.

Ahultasunen bila UC arakatzailean

Ondoren, hiru egitura dauzkagu datuen tamaina eta gakoaren datuen erakuslea, hasierako bektorea eta datu enkriptatutakoak.

Ahultasunen bila UC arakatzailean

Amaieran, itxi fitxategia, leheneratu erregistroak eta transferitu kontrola funtzio errealera aes.

Liburutegi adabaki batekin APK bat biltzen dugu, sinatzen dugu, gailu/emuladorera kargatzen dugu eta abiarazten dugu. Gure zabortegia sortzen ari dela ikusten dugu, eta hor datu asko idazten ari direla. Arakatzaileak enkriptatzea erabiltzen du trafikorako ez ezik, eta zifratze guztiak kasuan kasuko funtziotik pasatzen dira. Baina arrazoiren batengatik beharrezkoak diren datuak ez daude hor, eta eskatutako eskaera ez da trafikoan ikusten. UC Browser-ek beharrezko eskaera egin arte itxaron ez dezan, har dezagun lehenago jasotako zerbitzariaren erantzun zifratua eta aplikazioa adabaki berriro: gehitu deszifratzea jarduera nagusiaren onCreate-ra.

    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

Muntatu, sinatu, instalatu, martxan jartzen dugu. NullPointerException bat jasotzen dugu metodoak nulua itzuli duelako.

Kodea gehiago aztertzean, lerro interesgarriak deszifratzen dituen funtzio bat aurkitu zen: β€œMETA-INF/” eta β€œ.RSA”. Aplikazioa bere ziurtagiria egiaztatzen ari dela dirudi. Edo bertatik gakoak ere sortzen ditu. Ez dut ziurtagiriarekin gertatzen ari denari aurre egin nahi, beraz, ziurtagiri zuzena sartuko dugu. Adabaki dezagun enkriptatutako lerroa, "META-INF/"-ren ordez "BLABLINF/" lortuko dugu, sortu izen horrekin APK-n karpeta bat eta gehitu urtxintxa arakatzailearen ziurtagiria bertan.

Muntatu, sinatu, instalatu, martxan jartzen dugu. Bingoa! Badaukagu ​​giltza!

MitM

Gakoaren berdina eta hasierako bektore bat jaso ditugu. Saia gaitezen zerbitzariaren erantzuna CBC moduan deszifratzen.

Ahultasunen bila UC arakatzailean

Artxiboaren URLa, MD5-ren antzeko zerbait, "extract_unzipsize" eta zenbaki bat ikusiko ditugu. Egiaztatzen dugu: artxiboaren MD5 berdina da, paketatu gabeko liburutegiaren tamaina berdina da. Liburutegi hau adabakitzen eta arakatzaileari ematen saiatzen ari gara. Gure adabakidun liburutegia kargatu dela erakusteko, SMS bat sortzeko Asmoa jarriko dugu martxan "PWNED!" testuarekin. Zerbitzariaren bi erantzun ordezkatuko ditugu: puds.ucweb.com/upgrade/index.xhtml eta artxiboa deskargatzeko. Lehenengoan MD5 ordezkatzen dugu (tamaina ez da deskonprimitu ondoren aldatzen), bigarrenean artxiboa adabakitako liburutegiarekin ematen dugu.

Arakatzailea hainbat aldiz saiatzen da artxiboa deskargatzen, eta ondoren errore bat ematen du. Itxuraz zerbait
ez zaio gustatzen. Formatu ilun hau aztertzearen ondorioz, zerbitzariak artxiboaren tamaina ere transmititzen duela ikusi zen:

Ahultasunen bila UC arakatzailean

LEB128n kodetuta dago. Adabakiaren ondoren, liburutegiarekin artxiboaren tamaina pixka bat aldatu zen, beraz, arakatzaileak artxiboa gaizki deskargatu zela uste zuen eta hainbat saiakera egin ondoren errore bat bota zuen.

Artxiboaren tamaina egokitzen dugu... Eta – garaipena! πŸ™‚ Emaitza bideoan dago.

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

Ondorioak eta garatzailearen erreakzioa

Modu berean, hackerrek UC arakatzailearen funtzio segurua erabil dezakete liburutegi gaiztoak banatzeko eta exekutatzeko. Liburutegi hauek nabigatzailearen testuinguruan funtzionatuko dute, beraz, bere sistemaren baimen guztiak jasoko dituzte. Ondorioz, phishing-leihoak bistaratzeko gaitasuna, baita txinatar urtxintxa laranjaren lan-fitxategietarako sarbidea ere, datu-basean gordetako saioak, pasahitzak eta cookieak barne.

UC Browser-en garatzaileekin jarri ginen harremanetan eta aurkitu genuen arazoaren berri eman genien, ahultasuna eta arriskua adierazten saiatu ginen, baina ez zuten ezer eztabaidatu gurekin. Bien bitartean, arakatzaileak bere ezaugarri arriskutsua bistaratzen jarraitu zuen. Baina behin ahultasunaren xehetasunak agerian utzi genituenean, ezin zen lehen bezala alde batera utzi. Martxoaren 27a izan zen
UC Browser 12.10.9.1193 bertsio berri bat kaleratu zen, HTTPS bidez zerbitzarira sartzen zena: puds.ucweb.com/upgrade/index.xhtml.

Horrez gain, "konponketaren" ondoren eta artikulu hau idazteko unera arte, nabigatzaile batean PDF bat irekitzen saiatzeak errore-mezu bat agertu zuen "Oops, something was wrong!" PDF bat irekitzen saiatzean ez zen zerbitzariari eskaerarik egin, baina arakatzailea abiarazi zenean eskaera bat egin zen, eta horrek iradokitzen du Google Play-ren arauak urratuz kode exekutagarria deskargatzeko gaitasuna izaten jarraitzen duela.

Iturria: www.habr.com

Gehitu iruzkin berria