Барате пропусти во UC Browser

Барате пропусти во UC Browser

Вовед

На крајот на март ние пријавени, дека откриле скриена способност за вчитување и извршување на непроверен код во UC Browser. Денес ќе разгледаме детално како се случува ова преземање и како хакерите можат да го користат за свои цели.

Пред извесно време, UC Browser беше рекламиран и дистрибуиран многу агресивно: тој беше инсталиран на уредите на корисниците користејќи малициозен софтвер, дистрибуиран од различни сајтови под маската на видео датотеки (т.е., корисниците мислеа дека преземаат, на пример, порно видео, но наместо тоа, доби АПК со овој прелистувач), користеше застрашувачки банери со пораки дека прелистувачот е застарен, ранлив и слични работи. Во официјалната група на UC Browser на VK постои тема, во која корисниците можат да се жалат на нефер рекламирање, има многу примери таму. Во 2016 година имаше дури видео рекламирање на руски (да, рекламирање за прелистувач за блокирање реклами).

Во моментот на пишување, UC Browser има над 500 инсталации на Google Play. Ова е импресивно - само Google Chrome има повеќе. Меѓу прегледите можете да видите доста поплаки за рекламирање и пренасочувања кон некои апликации на Google Play. Ова беше причината за нашето истражување: решивме да видиме дали UC Browser прави нешто лошо. И испадна дека го прави тоа!

Во кодот на апликацијата, беше откриена можноста за преземање и извршување на извршниот код, што е спротивно на правилата за објавување пријави на Google Play. Покрај преземањето на извршниот код, UC Browser го прави тоа на небезбеден начин, што може да се користи за да започне напад на MitM. Да видиме дали можеме да извршиме таков напад.

Сè што е напишано подолу е релевантно за верзијата на UC Browser што беше достапна на Google Play во времето на студијата:

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

Вектор на напад

Во манифестот на прелистувачот UC можете да најдете услуга со самообјасниво име 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 Browser: можност за отворање PDF со помош на домашна библиотека, која не е во АПК и која ја презема од Интернет доколку е потребно. Вреди да се напомене дека, теоретски, UC Browser може да биде принуден да преземе нешто без интеракција со корисникот - ако обезбедите добро формиран одговор на барање што се извршува по стартувањето на прелистувачот. Но, за да го направите ова, треба подетално да го проучиме протоколот за интеракција со серверот, па решивме дека ќе биде полесно да се уреди пресретнатиот одговор и да се замени библиотеката за работа со PDF.

Значи, кога корисникот сака да отвори PDF директно во прелистувачот, следните барања може да се видат во сообраќајот:

Барате пропусти во UC Browser

Прво има барање POST да puds.ucweb.com/upgrade/index.xhtml, тогаш
Се презема архива со библиотека за прегледување на PDF и канцелариски формати. Логично е да се претпостави дека првото барање пренесува информации за системот (барем архитектурата за да ја обезбеди потребната библиотека), а како одговор на тоа прелистувачот добива некои информации за библиотеката што треба да се преземе: адресата и, можеби , нешто друго. Проблемот е што ова барање е шифрирано.

Побарајте фрагмент

Фрагмент од одговор

Барате пропусти во UC Browser

Барате пропусти во UC Browser

Самата библиотека е спакувана во ZIP и не е шифрирана.

Барате пропусти во UC Browser

Пребарајте код за дешифрирање на сообраќајот

Ајде да се обидеме да го дешифрираме одговорот на серверот. Ајде да го погледнеме кодот на класата 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. Звучи како тоа што ни треба. Судејќи според линиите („up_decrypt“, на пример), овде треба да се повика метод што ќе го дешифрира одговорот на серверот.
Ајде да преминеме на методот ѓ. Забележете дека првиот аргумент е бајтот на поместување 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, туку .тегла. Методот за кој сме заинтересирани е имплементиран на следниов начин:

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 значи дешифрирање, како во методот доКонечно системска класа 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.така (што всушност е .тегла и во која најдовме имплементација на некои интерфејси поврзани со шифрирање веднаш погоре) има една домашна библиотека: libsgmainso-6.4.36.so. Го отвораме во IDA и добиваме еден куп дијалог-кутии со грешки. Проблемот е што табелата со заглавие на секцијата е неважечка. Ова е направено намерно за да се комплицира анализата.

Барате пропусти во UC Browser

Но, тоа не е потребно: за правилно да се вчита датотеката ELF и да се анализира, доволна е табела за заглавие на програмата. Затоа, ние едноставно ја бришеме табелата со секции, нулајќи ги соодветните полиња во заглавието.

Барате пропусти во UC Browser

Повторно отворете ја датотеката во IDA.

Постојат два начини да се каже на Java виртуелната машина каде точно во матичната библиотека се наоѓа имплементацијата на методот деклариран во Java кодот како мајчин. Првата е да му се даде име на видот Java_package_name_ClassName_MethodName.

Втората е да ја регистрирате при вчитување на библиотеката (во функцијата JNI_OnLoad)
користејќи функциски повик Регистрирај се Домородци.

Во нашиот случај, ако го користиме првиот метод, името треба да биде вака: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

Не постои таква функција меѓу извезените функции, што значи дека треба да барате повик Регистрирај се Домородци.
Ајде да одиме на функцијата JNI_OnLoad и ја гледаме оваа слика:

Барате пропусти во UC Browser

Што се случува овде? На прв поглед, почетокот и крајот на функцијата се типични за архитектурата на ARM. Првата инструкција на оџакот ја складира содржината на регистрите што функцијата ќе ги користи во своето работење (во овој случај, R0, R1 и R2), како и содржината на регистарот LR, кој ја содржи повратната адреса од функцијата . Последната инструкција ги обновува зачуваните регистри, а адресата за враќање веднаш се става во регистарот на компјутер - со што се враќа од функцијата. Но, ако погледнете внимателно, ќе забележите дека претпоследната инструкција ја менува повратната адреса зачувана на оџакот. Ајде да пресметаме како ќе биде после
извршување на кодот. Одредена адреса 1xB0 се вчитува во R130, од неа се одзема 5, потоа се пренесува на R0 и на неа се додава 0x10. Излегува 0xB13B. Така, IDA смета дека последната инструкција е нормално враќање на функцијата, но всушност таа оди на пресметаната адреса 0xB13B.

Овде вреди да се потсетиме дека ARM процесорите имаат два режима и две групи инструкции: ARM и Thumb. Најмалку значајниот дел од адресата му кажува на процесорот кој сет на инструкции се користи. Односно, адресата е всушност 0xB13A, а еден во најмалку значајниот бит го означува режимот Thumb.

Сличен „адаптер“ е додаден на почетокот на секоја функција во оваа библиотека и
код за ѓубре. Ние нема да се задржиме на нив подетално - само се сеќаваме
дека вистинскиот почеток на речиси сите функции е малку подалеку.

Бидејќи кодот не прескокнува експлицитно на 0xB13A, самата IDA не препозна дека кодот се наоѓа на оваа локација. Од истата причина, тој не го препознава најголемиот дел од кодот во библиотеката како код, што ја отежнува анализата донекаде. Им кажуваме на IDA дека ова е кодот, и еве што се случува:

Барате пропусти во UC Browser

Табелата јасно започнува на 0xB144. Што има во sub_494C?

Барате пропусти во UC Browser

При повикување на оваа функција во регистарот LR, ја добиваме адресата на претходно споменатата табела (0xB144). Во R0 - индекс во оваа табела. Односно, вредноста се зема од табелата, се додава на LR и резултатот е
адресата на која треба да се оди. Ајде да се обидеме да го пресметаме: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Одиме на добиената адреса и гледаме буквално неколку корисни инструкции и повторно одиме на 0xB140:

Барате пропусти во UC Browser

Сега ќе има транзиција при поместување со индекс 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 Browser

ИДА повторно не ја препозна оваа област како шифра. Ние и помагаме и гледаме друг дизајн таму:

Барате пропусти во UC Browser

Инструкциите по BLX изгледа немаат многу смисла, тоа е повеќе како некакво поместување. Ајде да погледнеме во sub_4964:

Барате пропусти во UC Browser

И навистина, овде се зема dword на адресата што лежи во LR, додадена на оваа адреса, по што вредноста на добиената адреса се зема и се става на оџакот. Исто така, 4 се додава на LR, така што по враќањето од функцијата, истото поместување се прескокнува. По што командата POP {R1} ја зема добиената вредност од стекот. Ако погледнете што се наоѓа на адресата 0xB4BA + 0xEA = 0xB5A4, ќе видите нешто слично на табела со адреси:

Барате пропусти во UC Browser

За да го закрпите овој дизајн, ќе треба да добиете два параметри од кодот: поместувањето и бројот на регистарот во кој сакате да го ставите резултатот. За секој можен регистар, ќе треба однапред да подготвите парче код.

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 Browser

Покрај веќе споменатите структури, кодот го содржи и следново:

Барате пропусти во UC Browser

Како и во претходниот случај, по инструкцијата BLX има поместување:

Барате пропусти во UC Browser

Го земаме поместувањето на адресата од 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 Browser

Откако сè ќе се закрпи во функцијата, можете да го насочите IDA на неговиот вистински почеток. Ќе го собере целиот функционален код и може да се декомпајлира со помош на HexRays.

Низи за декодирање

Научивме да се справуваме со замаглување на машинскиот код во библиотеката libsgmainso-6.4.36.so од UC Browser и го доби функцискиот код 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;
}

Функција под_7AF78 создава примерок од контејнер за бајтни низи со одредената големина (нема да се задржиме на овие контејнери во детали). Тука се создаваат два такви контејнери: едниот ја содржи линијата „DcO/lcK+h?m3c*q@“ (лесно е да се погоди дека ова е клуч), другиот содржи шифрирани податоци. Следно, двата објекти се ставаат во одредена структура, која се пренесува на функцијата под_6115С. Ајде да означиме и поле со вредност 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: на функцијата под_6364С параметрите се пренесуваат од структурата што беа додадени таму во претходната функција, односно клучот и шифрираните податоци. Ако погледнете внимателно на под_6364С, можете да го препознаете алгоритмот RC4 во него.

Имаме алгоритам и клуч. Ајде да се обидеме да го дешифрираме името на класата. Еве што се случи: com / taobao / безжична / безбедност / адаптер / JNICL библиотека. Одлично! Ние сме на вистинскиот пат.

Дрво на команди

Сега треба да најдеме предизвик Регистрирај се Домородци, што ќе ни укаже на функцијата doCommandNative. Ајде да ги погледнеме функциите повикани од JNI_OnLoad, и го наоѓаме во под_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 Browser

Дрвото се пополнува динамично JNI_OnLoad.
Три броја ја кодираат патеката во дрвото. Секој лист од дрвото ја содржи ставената адреса на соодветната функција. Клучот е во родителскиот јазол. Пронаоѓањето на местото во кодот каде што функцијата што ни треба е додадена на дрвото не е тешко ако ги разбирате сите употребени структури (не ги опишуваме за да не се надуе веќе прилично голема статија).

Повеќе замаглување

Ја добивме адресата на функцијата што треба да го дешифрира сообраќајот: 0x5F1AC. Но, рано е да се радуваме: програмерите на UC Browser ни подготвија уште едно изненадување.

По добивањето на параметрите од низата што беше формирана во кодот Java, добиваме
на функцијата на адреса 0x4D070. И тука нè чека друг тип на замаглување на кодот.

Ставивме два индекси во R7 и R4:

Барате пропусти во UC Browser

Го префрламе првиот индекс на R11:

Барате пропусти во UC Browser

За да добиете адреса од табела, користете индекс:

Барате пропусти во UC Browser

По одењето на првата адреса се користи вториот индекс кој е во R4. Во табелата има 230 елементи.

Што да се прави во врска со тоа? Можете да му кажете на IDA дека ова е прекинувач: Уреди -> Друго -> Наведете идиом на прекинувачот.

Барате пропусти во UC Browser

Резултирачкиот код е застрашувачки. Но, пробивајќи се низ нејзината џунгла, можете да забележите повик до функција која веќе ни е позната под_6115С:

Барате пропусти во UC Browser

Имаше прекинувач во кој во случајот 3 имаше дешифрирање со помош на алгоритмот RC4. И во овој случај, структурата предадена на функцијата се пополнува од параметрите доставени до doCommandNative. Да се ​​потсетиме што имавме таму magicInt со вредност 16. Го разгледуваме соодветниот случај - и по неколку транзиции го наоѓаме кодот со кој може да се идентификува алгоритмот.

Барате пропусти во UC Browser

Ова е AES!

Алгоритмот постои, останува само да се добијат неговите параметри: режим, клуч и, можеби, векторот за иницијализација (неговото присуство зависи од режимот на работа на алгоритмот AES). Структурата со нив мора да се формира некаде пред повикот на функцијата под_6115С, но овој дел од кодот е особено добро заматен, па се наметнува идејата да се закрпи кодот така што сите параметри на функцијата за дешифрирање се фрлаат во датотека.

Лепенка

За да не се пишува рачно целиот код за закрпи на асемблерски јазик, можете да го стартувате Android Studio, да напишете функција таму што ги прима истите влезни параметри како нашата функција за дешифрирање и запишува во датотека, а потоа копирајте-залепете го кодот што компајлерот ќе го генерира.

Нашите пријатели од тимот на UC Browser се погрижија и за практичноста на додавање код. Да се ​​потсетиме дека на почетокот на секоја функција имаме ѓубре код кој лесно може да се замени со кој било друг. Многу погодно 🙂 Сепак, на почетокот на целната функција нема доволно простор за кодот што ги зачувува сите параметри во датотека. Морав да го поделам на делови и да користам блокови за ѓубре од соседните функции. Имаше вкупно четири дела.

Првиот дел:

Барате пропусти во UC Browser

Во архитектурата ARM, првите четири функционални параметри се пренесуваат низ регистрите R0-R3, а останатите, доколку ги има, се пренесуваат низ стекот. Регистарот LR ја носи повратната адреса. Сето ова треба да се зачува за да може функцијата да работи откако ќе ги исфрлиме нејзините параметри. Исто така, треба да ги зачуваме сите регистри што ќе ги користиме во процесот, па го правиме PUSH.W {R0-R10,LR}. Во R7 ја добиваме адресата на листата на параметри пренесени на функцијата преку стекот.

Користење на функцијата отворете ајде да ја отвориме датотеката /data/local/tmp/aes во режим „ab“.
односно за дополнување. Во R0 ја вчитуваме адресата на името на датотеката, во R1 - адресата на линијата што го означува режимот. И тука завршува кодот за ѓубре, па преминуваме на следната функција. За да продолжи да работи, на почетокот го ставаме преминот кон вистинскиот код на функцијата, заобиколувајќи го ѓубрето и наместо ѓубрето додаваме продолжение на закрпата.

Барате пропусти во UC Browser

Повикувајќи се отворете.

Првите три параметри на функцијата аес имаат тип int. Бидејќи на почетокот ги зачувавме регистрите во стекот, едноставно можеме да ја пренесеме функцијата fwrite нивните адреси на оџакот.

Барате пропусти во UC Browser

Следно, имаме три структури кои ја содржат големината на податоците и покажувач кон податоците за клучот, векторот за иницијализација и шифрирани податоци.

Барате пропусти во UC Browser

На крајот, затворете ја датотеката, вратете ги регистрите и префрлете ја контролата на вистинската функција аес.

Собираме АПК со закрпена библиотека, ја потпишуваме, ја поставуваме на уредот/емулаторот и ја стартуваме. Гледаме дека наша депонија се создава, а таму се пишуваат многу податоци. Прелистувачот користи шифрирање не само за сообраќај, и целото шифрирање поминува низ функцијата за која станува збор. Но, поради некоја причина потребните податоци ги нема, а бараното барање не е видливо во сообраќајот. За да не чекаме додека UC Browser не успее да го направи потребното барање, да го земеме шифрираниот одговор од серверот добиен порано и да ја закрпиме апликацијата повторно: додајте ја дешифрирањето во 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/“, да креираме папка со тоа име во АПК и таму да го додадеме сертификатот на прелистувачот squirrel.

Ние собираме, потпишуваме, инсталираме, стартуваме. Бинго! Ние го имаме клучот!

MitM

Добивме клуч и вектор за иницијализација еднаков на клучот. Ајде да се обидеме да го дешифрираме одговорот на серверот во CBC режим.

Барате пропусти во UC Browser

Го гледаме URL-то на архивата, нешто слично на MD5, „extract_unzipsize“ и број. Проверуваме: MD5 на архивата е иста, големината на неотпакуваната библиотека е иста. Се обидуваме да ја закрпиме оваа библиотека и да ја дадеме на прелистувачот. За да покажеме дека нашата закрпена библиотека е вчитана, ќе започнеме Intent да креираме SMS со текст „PWNED!“ Ќе замениме два одговори од серверот: puds.ucweb.com/upgrade/index.xhtml и да ја преземете архивата. Во првиот го заменуваме MD5 (големината не се менува по распакувањето), во вториот ја даваме архивата со закрпената библиотека.

Прелистувачот се обидува да ја преземе архивата неколку пати, по што дава грешка. Очигледно нешто
тој не сака. Како резултат на анализата на овој матен формат, се покажа дека серверот ја пренесува и големината на архивата:

Барате пропусти во UC Browser

Тој е кодиран во LEB128. По закрпата, големината на архивата со библиотеката малку се промени, па прелистувачот сметаше дека архивата е симната криво и по неколку обиди фрли грешка.

Ја прилагодуваме големината на архивата... И – победа! 🙂 Резултатот е во видеото.

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

Последици и реакција на развивачот

На ист начин, хакерите би можеле да ја користат небезбедната карактеристика на UC Browser за да дистрибуираат и стартуваат малициозни библиотеки. Овие библиотеки ќе работат во контекст на прелистувачот, така што ќе ги добијат сите негови системски дозволи. Како резултат на тоа, можноста за прикажување на фишинг прозорци, како и пристап до работните датотеки на портокаловата кинеска верверица, вклучувајќи најавувања, лозинки и колачиња зачувани во базата на податоци.

Ги контактиравме програмерите на UC Browser и ги информиравме за проблемот што го најдовме, се обидовме да укажеме на ранливоста и нејзината опасност, но тие не разговараа за ништо со нас. Во меѓувреме, прелистувачот продолжи да ја покажува својата опасна карактеристика на очигледен поглед. Но, откако ги откривме деталите за ранливоста, веќе не беше можно да се игнорира како порано. 27 март беше
беше објавена нова верзија на UC Browser 12.10.9.1193, која пристапи до серверот преку HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Дополнително, по „поправката“ и до моментот на пишување на овој напис, обидот да се отвори PDF во прелистувачот резултираше со порака за грешка со текстот „Упс, нешто тргна наопаку!“ Не беше поднесено барање до серверот при обидот да се отвори PDF, но беше поднесено барање кога беше стартуван прелистувачот, што укажува на континуираната можност за преземање на извршна шифра со кршење на правилата на Google Play.

Извор: www.habr.com

Додадете коментар