UC шолғышында осалдықтарды іздеу

UC шолғышында осалдықтарды іздеу

Кіріспе

Наурыздың аяғында біз хабарлады, олар UC шолғышында тексерілмеген кодты жүктеу және іске қосудың жасырын мүмкіндігін анықтады. Бүгін біз бұл жүктеудің қалай орын алатынын және хакерлер оны өз мақсаттары үшін қалай пайдалана алатынын егжей-тегжейлі қарастырамыз.

Біраз уақыт бұрын UC Browser өте агрессивті түрде жарнамаланды және таратылды: ол әртүрлі сайттардан бейнефайлдардың желеуімен таратылатын зиянды бағдарламаны пайдаланып пайдаланушылардың құрылғыларына орнатылды (яғни, пайдаланушылар, мысалы, порно бейнені жүктеп жатыр деп ойлады, бірақ орнына осы шолғышпен APK алды), шолғыш ескірген, осал және сол сияқты нәрселер туралы хабарлары бар қорқынышты баннерлер пайдаланылды. ВК-дағы ресми UC Browser тобында бар тақырып, онда пайдаланушылар әділетсіз жарнамаға шағымдана алады, онда көптеген мысалдар бар. 2016 жылы тіпті болды бейне жарнама орыс тілінде (иә, жарнаманы блоктайтын браузердің жарнамасы).

Жазу кезінде UC браузерінде Google Play-де 500 000 000-нан астам орнату бар. Бұл әсерлі - тек Google Chrome-да көбірек. Пікірлер арасында сіз Google Play-дегі кейбір қолданбаларға жарнамаға және қайта бағыттауға қатысты көптеген шағымдарды көре аласыз. Бұл біздің зерттеуіміздің себебі болды: біз UC шолғышының жаман нәрсе істеп жатқанын көруді шештік. Және ол солай екені белгілі болды!

Қолданба кодында орындалатын кодты жүктеп алу және іске қосу мүмкіндігі ашылды, бұл өтініштерді жариялау ережелеріне қайшы келеді Google Play-де. UC шолғышы орындалатын кодты жүктеп алатындығына қоса, ол оны MitM шабуылын жүзеге асыру үшін пайдалануға болатын қауіпті түрде жасайды. Біз осындай шабуыл жасай аламыз ба, көрейік.

Төменде жазылғандардың барлығы зерттеу кезінде Google Play-де қолжетімді UC шолғышының нұсқасына қатысты:

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

Шабуыл векторы

UC Browser манифестінде сіз өзін-өзі түсіндіретін атауы бар қызметті таба аласыз com.uc.deployment.UpgradeDeployService.

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

Бұл қызмет іске қосылғанда, браузер POST сұрауын жасайды puds.ucweb.com/upgrade/index.xhtml, ол басталғаннан кейін біраз уақыттан кейін қозғалыста көрінеді. Жауап ретінде ол кейбір жаңартуды немесе жаңа модульді жүктеп алу пәрменін алуы мүмкін. Талдау кезінде сервер мұндай пәрмендерді бермеді, бірақ біз браузерде PDF файлын ашуға тырысқанда, ол жоғарыда көрсетілген мекенжайға екінші сұрау жіберетінін, содан кейін ол жергілікті кітапхананы жүктеп алатынын байқадық. Шабуыл жасау үшін біз UC шолғышының бұл мүмкіндігін пайдалануды ұйғардық: PDF файлын APK-де жоқ және қажет болған жағдайда Интернеттен жүктеп алатын жергілікті кітапхана арқылы ашу мүмкіндігі. Айта кету керек, теориялық тұрғыдан алғанда, UC шолғышын пайдаланушының араласуынсыз бірдеңені жүктеп алуға мәжбүрлеуге болады - егер сіз браузер іске қосылғаннан кейін орындалатын сұрауға дұрыс қалыптасқан жауап берсеңіз. Бірақ бұл үшін сервермен өзара әрекеттесу хаттамасын егжей-тегжейлі зерделеу керек, сондықтан біз ұсталған жауапты өңдеу және PDF-пен жұмыс істеу үшін кітапхананы ауыстыру оңайырақ болады деп шештік.

Сонымен, пайдаланушы PDF файлын тікелей браузерде ашқысы келгенде, трафиктен келесі сұрауларды көруге болады:

UC шолғышында осалдықтарды іздеу

Алдымен POST сұрауы бар puds.ucweb.com/upgrade/index.xhtml, содан кейін
PDF және кеңсе пішімдерін көруге арналған кітапханасы бар мұрағат жүктелді. Бірінші сұрау жүйе туралы ақпаратты (кем дегенде, қажетті кітапхананы қамтамасыз ету үшін архитектура) жібереді деп болжау қисынды, және оған жауап ретінде браузер жүктеп алу қажет кітапхана туралы кейбір ақпаратты алады: мекенжай және, мүмкін. , басқа нәрсе. Мәселе мынада, бұл сұрау шифрланған.

Сұраныс фрагменті

Жауап фрагменті

UC шолғышында осалдықтарды іздеу

UC шолғышында осалдықтарды іздеу

Кітапхананың өзі ZIP форматында жинақталған және шифрланбаған.

UC шолғышында осалдықтарды іздеу

Трафиктің шифрын шешу кодын іздеңіз

Сервер жауабын шешуге тырысайық. Класс кодын қарастырайық com.uc.deployment.UpgradeDeployService: әдістен onStartCommand бару com.uc.deployment.bx, және одан 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);
}

Біз мұнда POST сұранысының қалыптасуын көреміз. Біз 16 байт массивін құруға және оны толтыруға назар аударамыз: 0x5F, 0, 0x1F, -50 (=0xCE). Жоғарыдағы сұрауда көргенімізбен сәйкес келеді.

Сол сыныпта сіз басқа қызықты әдісі бар кірістірілген сыныпты көре аласыз:

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

Әдіс байт массивін кіріс ретінде қабылдайды және нөлдік байт 0x60 немесе үшінші байт 0xD0, ал екінші байт 1, 11 немесе 0x1F екенін тексереді. Біз серверден жауапты қараймыз: нөлдік байт - 0x60, екіншісі - 0x1F, үшінші - 0x60. Бізге керек сияқты. Жолдар бойынша («жоғары_дешифрлеу», мысалы), сервер жауабының шифрын ашатын әдісті осында шақыру керек.
Әдіске көшейік gj. Бірінші аргумент 2-офсеттегі байт (яғни біздің жағдайда 0x1F), ал екіншісі сервердің жауабы жоқ екенін ескеріңіз.
алғашқы 16 байт.

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

Әлбетте, бұл жерде біз шифрды шешу алгоритмін таңдаймыз және сол байт біздің
0x1F тең регистр үш ықтимал нұсқаның бірін білдіреді.

Біз кодты талдауды жалғастырамыз. Бір-екі секіруден кейін біз өзімізді түсінікті аты бар әдіске тап боламыз decryptBytesByKey.

Мұнда біздің жауаптан тағы екі байт бөлініп, олардан жол алынады. Осылайша хабарламаның шифрын шешуге арналған кілт таңдалатыны анық.

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

Болашаққа көз жүгіртсек, біз бұл кезеңде біз әлі кілтті алмаймыз, тек оның «идентификаторын» аламыз. Кілтті алу біршама күрделірек.

Келесі әдісте барларға тағы екі параметр қосылып, олардың төртеуін құрайды: сиқырлы 16 саны, кілт идентификаторы, шифрланған деректер және түсініксіз жол (біздің жағдайда бос).

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

Бірқатар ауысудан кейін біз әдіске келеміз staticBinarySafeDecryptNoB64 интерфейс com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Негізгі қолданба кодында осы интерфейсті жүзеге асыратын сыныптар жоқ. Файлда мұндай класс бар lib/armeabi-v7a/libsgmain.so, бұл шын мәнінде .so емес, .jar. Бізді қызықтыратын әдіс келесідей жүзеге асырылады:

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

Мұнда біздің параметрлер тізімі тағы екі бүтін санмен толықтырылған: 2 және 0.
барлығы, 2 әдістегідей шифрды шешуді білдіреді doFinal жүйелік класс javax.crypto.Cipher. Мұның бәрі 10601 нөмірі бар белгілі бір Маршрутизаторға беріледі - бұл пәрмен нөмірі сияқты.

Келесі ауысу тізбегінен кейін интерфейсті жүзеге асыратын классты табамыз IRouterComponent және әдіс 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);
}
}

Және де сынып JNICLкітапханасы, онда жергілікті әдіс жарияланған doCommandNative:

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

Бұл жергілікті кодта әдісті табу керек дегенді білдіреді doCommandNative. Міне, қызық осы жерден басталады.

Машиналық кодты жасыру

Файлда libsgmain.so (бұл шын мәнінде .jar және біз шифрлауға қатысты кейбір интерфейстердің орындалуын дәл жоғарыда таптық) бір жергілікті кітапхана бар: libsgmainso-6.4.36.so. Біз оны IDA-да ашамыз және қателері бар көптеген диалогтық терезелерді аламыз. Мәселе мынада, бөлім тақырыбының кестесі жарамсыз. Бұл талдауды қиындату үшін әдейі жасалады.

UC шолғышында осалдықтарды іздеу

Бірақ бұл қажет емес: ELF файлын дұрыс жүктеу және оны талдау үшін бағдарлама тақырыбының кестесі жеткілікті. Сондықтан тақырыптағы сәйкес өрістерді нөлге теңестіріп, бөлім кестесін жай ғана жоямыз.

UC шолғышында осалдықтарды іздеу

IDA файлын қайтадан ашыңыз.

Java виртуалды машинасына Java кодында жергілікті деп жарияланған әдісті іске асыру жергілікті кітапхананың қай жерде орналасқанын анықтаудың екі жолы бар. Біріншісі – түр атауын беру Java_package_name_ClassName_MethodName.

Екіншісі - кітапхананы жүктеген кезде оны тіркеу (функцияда JNI_OnLoad)
функция шақыруын пайдалану Туғандарды тіркеу.

Біздің жағдайда, егер бірінші әдісті қолдансақ, атау келесідей болуы керек: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Экспортталған функциялар арасында мұндай функция жоқ, яғни қоңырауды іздеу керек Туғандарды тіркеу.
Функцияға көшейік JNI_OnLoad және біз мына суретті көреміз:

UC шолғышында осалдықтарды іздеу

Мұнда не болып жатыр? Бір қарағанда, функцияның басталуы мен аяқталуы ARM архитектурасына тән. Стектің бірінші нұсқауы функция өз жұмысында пайдаланатын регистрлердің мазмұнын (бұл жағдайда R0, R1 және R2), сонымен қатар функциядан қайтарылатын адресті қамтитын LR регистрінің мазмұнын сақтайды. . Соңғы нұсқау сақталған регистрлерді қалпына келтіреді, ал қайтару адресі дереу ДК регистріне орналастырылады - осылайша функциядан оралады. Бірақ мұқият қарасаңыз, соңғы нұсқау стекте сақталған қайтару мекенжайын өзгертетінін байқайсыз. Содан кейін қандай болатынын есептеп көрейік
кодты орындау. Белгілі бір 1xB0 мекенжайы R130-ге жүктеледі, одан 5 алынады, содан кейін ол R0-ге ауыстырылады және оған 0x10 қосылады. 0xB13B болып шықты. Осылайша, IDA соңғы нұсқауды қалыпты функцияны қайтару деп санайды, бірақ іс жүзінде ол есептелген 0xB13B мекенжайына барады.

Бұл жерде ARM процессорларының екі режимі және екі нұсқаулар жинағы бар екенін еске салған жөн: ARM және Thumb. Мекенжайдың ең аз маңызды биті процессорға қандай командалар жинағы қолданылып жатқанын көрсетеді. Яғни, мекенжай шын мәнінде 0xB13A және ең аз маңызды биттің біреуі Бас бармақ режимін көрсетеді.

Осы кітапханадағы әрбір функцияның басына ұқсас «адаптер» қосылған және
қоқыс коды. Біз оларға бұдан әрі егжей-тегжейлі тоқталмаймыз - тек еске түсіреміз
барлық дерлік функциялардың нақты басталуы сәл алысырақ.

Код 0xB13A мәніне анық өтпегендіктен, IDA өзі кодтың осы жерде орналасқанын мойындамады. Дәл сол себепті ол кітапханадағы кодтың көпшілігін код ретінде танымайды, бұл талдауды біршама қиындатады. Біз IDA-ға бұл код екенін айтамыз және келесідей болады:

UC шолғышында осалдықтарды іздеу

Кесте анық 0xB144-тен басталады. Sub_494C ішінде не бар?

UC шолғышында осалдықтарды іздеу

LR регистрінде бұл функцияны шақырған кезде біз бұрын айтылған кестенің адресін аламыз (0xB144). R0 - осы кестедегі индекс. Яғни, мән кестеден алынады, LR-ге қосылады және нәтиже болады
баратын мекенжай. Оны есептеп көрейік: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Біз алынған мекен-жайға барамыз және бірнеше пайдалы нұсқауларды көреміз және қайтадан 0xB140-ге барамыз:

UC шолғышында осалдықтарды іздеу

Енді кестеден 0x20 индексімен офсеттік ауысу болады.

Кестенің өлшеміне қарағанда, кодта мұндай ауысулар көп болады. Мекенжайды қолмен есептемей-ақ, мұны автоматты түрде шешуге бола ма деген сұрақ туындайды. Сценарийлер мен 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"

Курсорды 0xB26A жолына қойыңыз, сценарийді іске қосыңыз және 0xB4B0 көшуін қараңыз:

UC шолғышында осалдықтарды іздеу

IDA бұл аймақты қайтадан код ретінде мойындамады. Біз оған көмектесеміз және сол жерде басқа дизайнды көреміз:

UC шолғышында осалдықтарды іздеу

BLX-тен кейінгі нұсқаулар көп мағыналы емес сияқты, бұл орын ауыстырудың қандай да бір түріне ұқсайды. sub_4964 қарастырайық:

UC шолғышында осалдықтарды іздеу

Шынында да, мұнда LR-де орналасқан адресте dword қабылданады, осы адреске қосылады, содан кейін алынған мекенжайдағы мән қабылданады және стекке қойылады. Сондай-ақ, функциядан оралғаннан кейін дәл сол ығысу өткізіп жіберу үшін LR-ге 4 қосылады. Осыдан кейін POP {R1} пәрмені стектен алынған мәнді алады. 0xB4BA + 0xEA = 0xB5A4 мекенжайында не орналасқанын қарасаңыз, мекенжай кестесіне ұқсас нәрсені көресіз:

UC шолғышында осалдықтарды іздеу

Бұл дизайнды түзету үшін кодтан екі параметрді алу керек: офсет және нәтижені енгізгіңіз келетін тіркеу нөмірі. Әрбір ықтимал тіркелім үшін алдын ала код бөлігін дайындау керек.

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"

Біз курсорды ауыстырғымыз келетін құрылымның басына орналастырамыз - 0xB4B2 - және сценарийді іске қосамыз:

UC шолғышында осалдықтарды іздеу

Жоғарыда аталған құрылымдардан басқа, кодта келесілер бар:

UC шолғышында осалдықтарды іздеу

Алдыңғы жағдайдағыдай, BLX нұсқауынан кейін офсет бар:

UC шолғышында осалдықтарды іздеу

Біз LR-ден мекен-жайға офсетті аламыз, оны LR-ге қосып, сол жерге барамыз. 0x72044 + 0xC = 0x72050. Бұл дизайнның сценарийі өте қарапайым:

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"

Сценарийдің орындалу нәтижесі:

UC шолғышында осалдықтарды іздеу

Функцияда барлығы түзетілгеннен кейін IDA-ны оның нақты басына көрсетуге болады. Ол барлық функция кодын біріктіреді және оны HexRays көмегімен декомпиляциялауға болады.

Жолдарды декодтау

Біз кітапханада машиналық кодтың бұрмалануымен күресуді үйрендік libsgmainso-6.4.36.so UC браузерінен және функция кодын алды 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;
}

Келесі жолдарды толығырақ қарастырайық:

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

Функцияда sub_73E24 сынып атауы анық шифрленеді. Бұл функцияның параметрлері ретінде шифрланған деректерге ұқсас деректерге көрсеткіш, белгілі бір буфер және сан жіберіледі. Функцияны шақырғаннан кейін буферде шифрланған жол болатыны анық, өйткені ол функцияға жіберіледі. FindClass, ол класс атын екінші параметр ретінде қабылдайды. Демек, сан буфер өлшемі немесе жолдың ұзындығы болып табылады. Класс атауын шешуге тырысайық, ол бізге дұрыс бағытта келе жатқанымызды айтуы керек. Онда не болып жатқанын егжей-тегжейлі қарастырайық 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;
}

функция sub_7AF78 көрсетілген өлшемдегі байт массивтері үшін контейнер данасын жасайды (біз бұл контейнерлерге егжей-тегжейлі тоқталмаймыз). Мұнда екі осындай контейнер жасалады: біреуі сызықты қамтиды "DcO/lcK+h?m3c*q@" (бұл кілт екенін болжау оңай), екіншісі шифрланған деректерді қамтиды. Әрі қарай, екі нысан да белгілі бір құрылымға орналастырылады, ол функцияға беріледі sub_6115C. Сондай-ақ осы құрылымдағы 3 мәні бар өрісті белгілейік.Келесі осы құрылыммен не болатынын көрейік.

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

Коммутатор параметрі бұрын 3 мәні тағайындалған құрылым өрісі болып табылады. 3 жағдайды қараңыз: функцияға sub_6364C параметрлер алдыңғы функцияда қосылған құрылымнан беріледі, яғни кілт және шифрланған деректер. Мұқият қарасаңыз sub_6364C, ондағы RC4 алгоритмін тануға болады.

Бізде алгоритм мен кілт бар. Класс атауын ашуға тырысайық. Міне, оқиға: com/taobao/wireless/security/adapter/JNICLlibrary. Тамаша! Біз дұрыс жолда келе жатырмыз.

Пәрмен ағашы

Енді бізге тапсырма табу керек Туғандарды тіркеу, ол бізді функцияға нұсқайды doCommandNative. -ден шақырылатын функцияларды қарастырайық JNI_OnLoad, және біз оны табамыз 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;
}

Шынында да, аты бар жергілікті әдіс осында тіркелген doCommandNative. Енді оның мекен-жайын білеміз. Оның не істейтінін көрейік.

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

Атау бойынша, әзірлеушілер жергілікті кітапханаға көшіруді шешкен барлық функциялардың кіру нүктесі осында екенін болжауға болады. Бізді 10601 нөмірі қызықтырады.

Кодтан команда нөмірі үш санды шығаратынын көруге болады: команда/10000, пәрмен % 10000 / 100 и пәрмен % 10, яғни, біздің жағдайда 1, 6 және 1. Бұл үш сан, сондай-ақ көрсеткіш JNIEnv және функцияға берілген аргументтер құрылымға қосылады және беріледі. Алынған үш санның көмегімен (оларды N1, N2 және N3 деп белгілейік) командалар ағашы құрастырылады.

Сол сияқты бірнәрсе:

UC шолғышында осалдықтарды іздеу

Ағаш динамикалық түрде толтырылады JNI_OnLoad.
Үш сан ағаштағы жолды кодтайды. Ағаштың әрбір жапырағында сәйкес функцияның қалталы мекенжайы бар. Кілт ата-аналық түйінде. Ағашқа қажетті функция қосылатын кодта орынды табу қиын емес, егер сіз барлық қолданылатын құрылымдарды түсінсеңіз (біз оларды қазірдің өзінде үлкен мақаланы толтырмау үшін сипаттамаймыз).

Көбірек түсініксіздік

Біз трафиктің шифрын шешуге болатын функцияның мекенжайын алдық: 0x5F1AC. Бірақ қуануға әлі ерте: UC Browser әзірлеушілері бізге тағы бір тосын сый дайындады.

Java кодында қалыптасқан массивтен параметрлерді алғаннан кейін біз аламыз
0x4D070 мекенжайындағы функцияға. Міне, бізді кодты бұрмалаудың тағы бір түрі күтіп тұр.

Біз R7 және R4 екі индексті қоямыз:

UC шолғышында осалдықтарды іздеу

Бірінші индексті R11-ге ауыстырамыз:

UC шолғышында осалдықтарды іздеу

Кестеден мекенжайды алу үшін индексті пайдаланыңыз:

UC шолғышында осалдықтарды іздеу

Бірінші мекенжайға өткеннен кейін R4-те болатын екінші индекс қолданылады. Кестеде 230 элемент бар.

Бұл туралы не істеу керек? IDA-ға бұл коммутатор екенін айта аласыз: Edit -> Other -> Specify switch идиома.

UC шолғышында осалдықтарды іздеу

Алынған код қорқынышты. Бірақ, Джунгли арқылы жүріп, бізге бұрыннан таныс функцияға шақыруды байқай аласыз sub_6115C:

UC шолғышында осалдықтарды іздеу

3-жағдайда RC4 алгоритмі арқылы шифрды шешу болған қосқыш болды. Ал бұл жағдайда функцияға берілген құрылым берілген параметрлерден толтырылады doCommandNative. Онда не болғанын еске түсірейік magicInt мәнімен 16. Біз сәйкес жағдайды қарастырамыз - және бірнеше ауысудан кейін алгоритмді анықтауға болатын кодты табамыз.

UC шолғышында осалдықтарды іздеу

Бұл AES!

Алгоритм бар, оның параметрлерін алу ғана қалады: режим, кілт және, мүмкін, инициализация векторы (оның болуы AES алгоритмінің жұмыс режиміне байланысты). Олармен құрылым функция шақырудан бұрын бір жерде құрылуы керек sub_6115C, бірақ кодтың бұл бөлігі әсіресе жақсы түсініксіз, сондықтан шифрды шешу функциясының барлық параметрлері файлға төгілетіндей кодты түзету идеясы туындайды.

Патч

Ассемблер тілінде барлық патч кодын қолмен жазбау үшін Android Studio бағдарламасын іске қосып, сол жерде шифрды шешу функциясы сияқты енгізу параметрлерін алатын және файлға жазатын функцияны жазып, содан кейін компилятор беретін кодты көшіріп қоюға болады. жасау.

UC Browser командасының достары да кодты қосудың ыңғайлылығы туралы қамқорлық жасады. Әрбір функцияның басында бізде кез келген басқасымен оңай ауыстырылатын қоқыс коды бар екенін еске түсірейік. Өте ыңғайлы 🙂 Дегенмен, мақсатты функцияның басында файлға барлық параметрлерді сақтайтын код үшін орын жеткіліксіз. Мен оны бөліктерге бөліп, көрші функциялардың қоқыс блоктарын қолдануға тура келді. Барлығы төрт бөлім болды.

Бірінші бөлім:

UC шолғышында осалдықтарды іздеу

ARM архитектурасында функцияның алғашқы төрт параметрі R0-R3 регистрлері арқылы, қалғандары, егер бар болса, стек арқылы өтеді. LR регистрінде қайтару адресі бар. Мұның барлығын функция параметрлерін тастағаннан кейін жұмыс істеуі үшін сақтау керек. Біз сонымен қатар процесте қолданатын барлық регистрлерді сақтауымыз керек, сондықтан PUSH.W {R0-R10,LR} орындаймыз. R7-де функцияға стек арқылы берілген параметрлер тізімінің адресін аламыз.

Функцияны пайдалану фопен файлды ашайық /data/local/tmp/aes «ab» режимінде
яғни қосу үшін. R0-де файл атауының адресін, R1-де режимді көрсететін жолдың адресін жүктейміз. Міне, қоқыс коды аяқталады, сондықтан біз келесі функцияға көшеміз. Оның жұмысын жалғастыру үшін біз бастапқыда қоқысты айналып өтіп, функцияның нақты кодына көшуді қоямыз және қоқыстың орнына патчтың жалғасын қосамыз.

UC шолғышында осалдықтарды іздеу

Қоңырау шалу фопен.

Функцияның алғашқы үш параметрі иә түрі бар INT. Регистрлерді стекке ең басында сақтағандықтан, функцияны жай ғана тапсыра аламыз қайта жазу стекке олардың мекенжайлары.

UC шолғышында осалдықтарды іздеу

Әрі қарай бізде деректер өлшемін және кілтке, инициализация векторына және шифрланған деректерге арналған деректерге көрсеткішті қамтитын үш құрылым бар.

UC шолғышында осалдықтарды іздеу

Соңында файлды жабыңыз, регистрлерді қалпына келтіріңіз және басқаруды нақты функцияға беріңіз иә.

Біз патчталған кітапханасы бар APK жинаймыз, оған қол қоямыз, құрылғыға/эмуляторға жүктеп, іске қосамыз. Біздің үйіндіміз жасалып жатқанын, онда көптеген деректердің жазылып жатқанын көріп отырмыз. Браузер шифрлауды тек трафик үшін ғана емес пайдаланады және барлық шифрлау қарастырылып отырған функция арқылы өтеді. Бірақ қандай да бір себептермен қажетті деректер жоқ, ал қажетті сұраныс трафикте көрінбейді. UC шолғышы қажетті сұрауды жасауды дайындағанша күтпеу үшін бұрын алынған серверден шифрланған жауапты алып, қолданбаны қайта түзетейік: негізгі әрекеттің 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

Біз құрастырамыз, қол қоямыз, орнатамыз, іске қосамыз. Біз NullPointerException аламыз, себебі әдіс нөлді қайтарды.

Кодты әрі қарай талдау барысында қызықты жолдарды ашатын функция табылды: «META-INF/» және «.RSA». Қолданба сертификатын тексеріп жатқан сияқты. Немесе одан кілттерді жасайды. Мен сертификатпен не болып жатқанымен шынымен айналысқым келмейді, сондықтан біз оған дұрыс сертификатты жібереміз. «META-INF/» орнына «BLABLINF/» алатындай етіп шифрланған жолды түзетейік, APK-де осындай атаумен қалта жасаңыз және сол жерге шолғыш куәлігін қосыңыз.

Біз құрастырамыз, қол қоямыз, орнатамыз, іске қосамыз. Бинго! Кілт бізде!

MitM

Біз кілт пен кілтке тең инициализация векторын алдық. CBC режимінде сервер жауабының шифрын шешуге тырысайық.

UC шолғышында осалдықтарды іздеу

Біз мұрағат URL мекенжайын көреміз, MD5-ке ұқсас нәрсе, «extract_unzipsize» және сан. Біз тексереміз: мұрағаттың MD5 бірдей, ашылған кітапхананың өлшемі бірдей. Біз бұл кітапхананы түзетуге және оны браузерге беруге тырысамыз. Түзетілген кітапханамыздың жүктелгенін көрсету үшін біз «PWNED!» мәтіні бар SMS жасау ниетін іске қосамыз. Біз серверден екі жауапты ауыстырамыз: puds.ucweb.com/upgrade/index.xhtml және мұрағатты жүктеп алу үшін. Біріншісінде біз MD5 ауыстырамыз (орамадан шыққаннан кейін өлшемі өзгермейді), екіншісінде мұрағатты патчталған кітапханамен береміз.

Браузер мұрағатты бірнеше рет жүктеп алуға тырысады, содан кейін ол қате береді. Бірдеңе сияқты
ол ұнатпайды. Бұл түсініксіз пішімді талдау нәтижесінде сервер мұрағат өлшемін де жіберетіні анықталды:

UC шолғышында осалдықтарды іздеу

Ол LEB128 кодталады. Патчтан кейін кітапханамен мұрағаттың өлшемі аздап өзгерді, сондықтан браузер мұрағат қисық жүктелді деп есептеп, бірнеше әрекеттен кейін қате жіберді.

Мұрағаттың көлемін реттейміз... Ал – жеңіс! 🙂 Нәтижесі видеода.

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

Салдары және әзірлеуші ​​реакциясы

Сол сияқты, хакерлер зиянды кітапханаларды тарату және іске қосу үшін UC шолғышының қауіпті мүмкіндігін пайдалана алады. Бұл кітапханалар браузер контекстінде жұмыс істейді, сондықтан олар оның барлық жүйелік рұқсаттарын алады. Нәтижесінде фишинг терезелерін көрсету мүмкіндігі, сондай-ақ қызғылт сары қытай тиінінің жұмыс файлдарына, соның ішінде деректер базасында сақталған логиндерге, парольдерге және cookie файлдарына қол жеткізу.

Біз UC Browser әзірлеушілерімен байланысып, оларды тапқан мәселе туралы хабардар еттік, осалдық пен оның қауіптілігін көрсетуге тырыстық, бірақ олар бізбен ештеңе талқыламады. Сонымен қатар, браузер өзінің қауіпті мүмкіндігін көзге көрінетін жерде көрсетуді жалғастырды. Бірақ біз осалдықтың егжей-тегжейлерін ашқаннан кейін, оны бұрынғыдай елемеу мүмкін болмады. 27 наурыз болды
HTTPS арқылы серверге кіретін UC Browser 12.10.9.1193 жаңа нұсқасы шығарылды: puds.ucweb.com/upgrade/index.xhtml.

Сонымен қатар, «түзетуден» кейін және осы мақаланы жазу уақытына дейін браузерде PDF файлын ашу әрекеті «Оп, бірдеңе дұрыс болмады!» мәтіні бар қате туралы хабарға әкелді. PDF файлын ашу әрекеті кезінде серверге сұрау жіберілмеді, бірақ браузер іске қосылғанда сұрау жасалды, бұл Google Play ережелерін бұза отырып, орындалатын кодты жүктеп алу мүмкіндігін көрсетеді.

Ақпарат көзі: www.habr.com

пікір қалдыру