Тражење рањивости у УЦ претраживачу

Тражење рањивости у УЦ претраживачу

Увод

Крајем марта ми пријавио, да су открили скривену могућност учитавања и покретања непровереног кода у УЦ претраживачу. Данас ћемо детаљно погледати како долази до овог преузимања и како га хакери могу користити у своје сврхе.

Пре неког времена, УЦ Бровсер се рекламирао и дистрибуирао веома агресивно: инсталиран је на уређаје корисника користећи малвер, дистрибуиран са разних сајтова под маском видео датотека (тј. корисници су мислили да преузимају, на пример, порно видео, али уместо тога добио АПК са овим прегледачем), користио је застрашујуће банере са порукама да је прегледач застарео, рањив и слично. У званичној групи УЦ Бровсер на ВК постоји тема, у којој корисници могу да се жале на непоштено оглашавање, има много примера. 2016. било је чак видео оглашавање на руском (да, оглашавање претраживача за блокирање огласа).

У време писања овог текста, УЦ претраживач је имао преко 500 инсталација на Гоогле Плаи-у. Ово је импресивно - само Гоогле Цхроме има више. Међу рецензијама можете видети доста притужби на оглашавање и преусмеравања на неке апликације на Гоогле Плаи-у. Ово је био разлог нашег истраживања: одлучили смо да видимо да ли УЦ Бровсер ради нешто лоше. И испоставило се да има!

У коду апликације откривена је могућност преузимања и покретања извршног кода, што је супротно правилима за објављивање пријава на Гоогле Плаи-у. Поред преузимања извршног кода, УЦ Бровсер то ради на несигуран начин, који се може користити за покретање МитМ напада. Хајде да видимо да ли можемо да изведемо такав напад.

Све написано у наставку је релевантно за верзију УЦ претраживача која је била доступна на Гоогле Плаи-у у време студије:

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

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

У манифесту УЦ претраживача можете пронаћи услугу са именом које је само по себи разумљиво цом.уц.деплоимент.УпградеДеплоиСервице.

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

Када се ова услуга покрене, претраживач шаље ПОСТ захтев за пудс.уцвеб.цом/упграде/индек.кхтмл, што се може видети у саобраћају неко време након старта. Као одговор, он може добити команду за преузимање неког ажурирања или новог модула. Током анализе, сервер није давао такве команде, али смо приметили да када покушамо да отворимо ПДФ у претраживачу, он упућује други захтев на горе наведену адресу, након чега преузима изворну библиотеку. Да бисмо извршили напад, одлучили смо да користимо ову функцију УЦ претраживача: могућност отварања ПДФ-а користећи изворну библиотеку, која није у АПК-у и коју по потреби преузима са Интернета. Вреди напоменути да, теоретски, УЦ Бровсер може бити приморан да преузме нешто без интеракције корисника - ако пружите добро формиран одговор на захтев који се извршава након покретања претраживача. Али да бисмо то урадили, морамо детаљније проучити протокол интеракције са сервером, па смо одлучили да ће бити лакше уредити пресретнути одговор и заменити библиотеку за рад са ПДФ-ом.

Дакле, када корисник жели да отвори ПДФ директно у претраживачу, у саобраћају се могу видети следећи захтеви:

Тражење рањивости у УЦ претраживачу

Прво постоји ПОСТ захтев за пудс.уцвеб.цом/упграде/индек.кхтмл, онда
Преузима се архива са библиотеком за преглед ПДФ и канцеларијских формата. Логично је претпоставити да први захтев преноси информације о систему (барем архитектуру која обезбеђује потребну библиотеку), а као одговор на њега претраживач добија неке информације о библиотеци коју треба преузети: адресу и, евентуално, , нешто друго. Проблем је што је овај захтев шифрован.

Фрагмент захтева

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

Тражење рањивости у УЦ претраживачу

Тражење рањивости у УЦ претраживачу

Сама библиотека је упакована у ЗИП и није шифрована.

Тражење рањивости у УЦ претраживачу

Потражите код за дешифровање саобраћаја

Хајде да покушамо да дешифрујемо одговор сервера. Погледајмо шифру класе цом.уц.деплоимент.УпградеДеплоиСервице: из методе онСтартЦомманд Иди на цом.уц.деплоимент.бк, а од њега до цом.уц.бровсер.цоре.дцфе:

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

Овде видимо формирање ПОСТ захтева. Обраћамо пажњу на креирање низа од 16 бајтова и његово попуњавање: 0к5Ф, 0, 0к1Ф, -50 (=0кЦЕ). Поклапа се са оним што смо видели у захтеву изнад.

У истој класи можете видети угнежђену класу која има још један интересантан метод:

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

Метод узима низ бајтова као улаз и проверава да ли је нулти бајт 0к60 или је трећи бајт 0кД0, а други бајт 1, 11 или 0к1Ф. Гледамо одговор са сервера: нулти бајт је 0к60, други је 0к1Ф, трећи је 0к60. Звучи као оно што нам треба. Судећи по редовима („уп_децрипт“, на пример), овде треба позвати метод који ће дешифровати одговор сервера.
Пређимо на методу гј. Имајте на уму да је први аргумент бајт на офсету 2 (тј. 0к1Ф у нашем случају), а други је одговор сервера без
првих 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;
}

Очигледно, овде бирамо алгоритам за дешифровање и исти бајт који је у нашем
случај једнак 0к1Ф, означава једну од три могуће опције.

Настављамо да анализирамо код. После пар скокова налазимо се у методи са саморазумљивим именом децриптБитесБиКеи.

Овде се од нашег одговора одвајају још два бајта и од њих се добија низ. Јасно је да се на овај начин бира кључ за дешифровање поруке.

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

Након низа прелаза долазимо до методе статицБинариСафеДецриптНоБ64 приступ цом.алибаба.вирелесс.сецурити.опен.статицдатаенцрипт.ИСтатицДатаЕнцриптЦомпонент. У главном коду апликације нема класа које имплементирају овај интерфејс. Постоји таква класа у датотеци либ/армеаби-в7а/либсгмаин.со, што заправо није .со, већ .јар. Метода која нас интересује имплементирана је на следећи начин:

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 значи дешифровање, као у методу доФинал класа система јавак.црипто.Ципхер. И све ово се преноси на одређени рутер са бројем 10601 - ово је очигледно број команде.

Након следећег ланца прелаза налазимо класу која имплементира интерфејс ИРоутерЦомпонент и метод доЦомманд:

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

И такође класа ЈНИЦЛибрари, у којем је декларисана нативна метода доЦоммандНативе:

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

То значи да морамо да пронађемо метод у изворном коду доЦоммандНативе. И ту почиње забава.

Замагљивање машинског кода

У фајлу либсгмаин.со (који је заправо .јар и у којем смо пронашли имплементацију неких интерфејса везаних за шифровање непосредно изнад) постоји једна изворна библиотека: либсгмаинсо-6.4.36.со. Отварамо га у ИДА и добијамо гомилу дијалошких оквира са грешкама. Проблем је што је табела заглавља одељка неважећа. Ово се ради намерно да би се компликовала анализа.

Тражење рањивости у УЦ претраживачу

Али то није потребно: да бисте исправно учитали ЕЛФ датотеку и анализирали је, довољна је табела заглавља програма. Због тога једноставно бришемо табелу секција, нулирајући одговарајућа поља у заглављу.

Тражење рањивости у УЦ претраживачу

Поново отворите датотеку у ИДА.

Постоје два начина да се Јава виртуелној машини каже где се тачно у матичној библиотеци налази имплементација методе која је у Јава коду декларисана као матична. Први је да му дате име врсте Јава_пацкаге_наме_ЦлассНаме_МетходНаме.

Други је да га региструјете приликом учитавања библиотеке (у функцији ЈНИ_ОнЛоад)
користећи позив функције РегистерНативес.

У нашем случају, ако користимо први метод, име би требало да буде овако: Јава_цом_таобао_вирелесс_сецурити_адаптер_ЈНИЦЛибрари_доЦоммандНативе.

Не постоји таква функција међу извезеним функцијама, што значи да морате тражити позив РегистерНативес.
Идемо на функцију ЈНИ_ОнЛоад и видимо ову слику:

Тражење рањивости у УЦ претраживачу

Шта се дешава овде? На први поглед, почетак и крај функције су типични за АРМ архитектуру. Прва инструкција на стеку чува садржај регистара које ће функција користити у свом раду (у овом случају Р0, Р1 и Р2), као и садржај ЛР регистра који садржи повратну адресу из функције . Последња инструкција враћа сачуване регистре, а повратна адреса се одмах смешта у ПЦ регистар - тако се враћа из функције. Али ако пажљиво погледате, приметићете да претпоследња инструкција мења повратну адресу сачувану на стеку. Хајде да израчунамо како ће бити после
извршавање кода. Одређена адреса 1кБ0 се учитава у Р130, од ње се одузима 5, затим се преноси у Р0 и додаје јој се 0к10. Испоставило се да је 0кБ13Б. Дакле, ИДА мисли да је последња инструкција нормалан повратак функције, али у ствари иде на израчунату адресу 0кБ13Б.

Овде је вредно подсетити да АРМ процесори имају два режима и два сета инструкција: АРМ и Тхумб. Најмањи значајни бит адресе говори процесору који скуп инструкција се користи. То јест, адреса је заправо 0кБ13А, а један у најмањем значајном биту указује на режим Тхумб.

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

Пошто код експлицитно не скаче на 0кБ13А, ИДА сама није препознала да се код налази на овој локацији. Из истог разлога, већину кода у библиотеци не препознаје као код, што отежава анализу. Кажемо ИДА-и да је ово код и да се дешава следеће:

Тражење рањивости у УЦ претраживачу

Табела јасно почиње од 0кБ144. Шта је у суб_494Ц?

Тражење рањивости у УЦ претраживачу

Приликом позивања ове функције у ЛР регистру добијамо адресу претходно поменуте табеле (0кБ144). У Р0 - индекс у овој табели. То јест, вредност се узима из табеле, додаје се ЛР и резултат је
адресу на коју треба отићи. Покушајмо да израчунамо: 0кБ144 + [0кБ144 + 8* 4] = 0кБ144 + 0к120 = 0кБ264. Идемо на примљену адресу и видимо буквално пар корисних упутстава и поново идемо на 0кБ140:

Тражење рањивости у УЦ претраживачу

Сада ће доћи до прелаза у офсету са индексом 0к20 из табеле.

Судећи по величини табеле, у коду ће бити много таквих прелаза. Поставља се питање да ли је то могуће некако аутоматски, без ручног израчунавања адреса. У помоћ нам долазе скрипте и могућност закрпе кода у ИДА-и:

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"

Поставите курсор на линију 0кБ26А, покрените скрипту и погледајте прелазак на 0кБ4Б0:

Тражење рањивости у УЦ претраживачу

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

Тражење рањивости у УЦ претраживачу

Упутства после БЛКС-а изгледа немају много смисла, то је више као нека врста померања. Погледајмо суб_4964:

Тражење рањивости у УЦ претраживачу

И заиста, овде се узима дворд на адреси која лежи у ЛР, додаје се овој адреси, након чега се узима вредност на резултујућој адреси и ставља на стек. Такође, 4 се додаје у ЛР тако да се по повратку из функције овај исти помак прескаче. Након чега команда ПОП {Р1} узима резултујућу вредност из стека. Ако погледате шта се налази на адреси 0кБ4БА + 0кЕА = 0кБ5А4, видећете нешто слично адресној табели:

Тражење рањивости у УЦ претраживачу

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

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"

Постављамо курсор на почетак структуре коју желимо да заменимо - 0кБ4Б2 - и покрећемо скрипту:

Тражење рањивости у УЦ претраживачу

Поред већ поменутих структура, код садржи и следеће:

Тражење рањивости у УЦ претраживачу

Као иу претходном случају, након БЛКС инструкције постоји помак:

Тражење рањивости у УЦ претраживачу

Узимамо офсет на адресу са ЛР, додајемо га у ЛР и идемо тамо. 0к72044 + 0кЦ = 0к72050. Скрипта за овај дизајн је прилично једноставна:

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"

Резултат извршавања скрипте:

Тражење рањивости у УЦ претраживачу

Када се све закрпи у функцији, можете указати ИДА на њен прави почетак. Он ће саставити сав код функције и може се декомпилирати помоћу ХекРаис-а.

Декодирање низова

Научили смо да се носимо са замагљивањем машинског кода у библиотеци либсгмаинсо-6.4.36.со из УЦ претраживача и примио код функције ЈНИ_ОнЛоад.

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

У функцији суб_73Е24 назив класе се очигледно дешифрује. Као параметри овој функцији, прослеђује се показивач на податке сличне шифрованим подацима, одређени бафер и број. Очигледно, након позива функције, у баферу ће бити дешифрована линија, пошто се она прослеђује функцији ФиндЦласс, који узима име класе као други параметар. Дакле, број је величина бафера или дужина линије. Хајде да покушамо да дешифрујемо назив класе, требало би да нам каже да ли идемо у правом смеру. Хајде да ближе погледамо шта се дешава у суб_73Е24.

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

Функција суб_7АФ78 креира инстанцу контејнера за низове бајтова одређене величине (нећемо се детаљно задржавати на овим контејнерима). Овде се креирају два таква контејнера: један садржи линију "ДцО/лцК+х?м3ц*к@" (лако је погодити да је ово кључ), други садржи шифроване податке. Затим се оба објекта постављају у одређену структуру, која се прослеђује функцији суб_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Ц, у њему можете препознати РЦ4 алгоритам.

Имамо алгоритам и кључ. Хајде да покушамо да дешифрујемо име класе. Ево шта се десило: цом/таобао/вирелесс/сецурити/адаптер/ЈНИЦЛибрари. Велики! На добром смо путу.

Командно дрво

Сада треба да нађемо изазов РегистерНативес, што ће нас упутити на функцију доЦоммандНативе. Погледајмо функције позване из ЈНИ_ОнЛоад, и налазимо га у суб_Б7Б0:

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

И заиста, овде је регистрован изворни метод са именом доЦоммандНативе. Сада знамо његову адресу. Хајде да видимо шта ради.

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. Ова три броја, као и показивач на ЈНИЕнв а аргументи прослеђени функцији се додају структури и прослеђују даље. Користећи три добијена броја (означимо их Н1, Н2 и Н3) гради се командно стабло.

Овако нешто:

Тражење рањивости у УЦ претраживачу

Стабло се динамички попуњава ЈНИ_ОнЛоад.
Три броја кодирају путању у стаблу. Сваки лист дрвета садржи урезану адресу одговарајуће функције. Кључ је у родитељском чвору. Проналажење места у коду где је функција која нам треба додата стаблу није тешко ако разумете све структуре које се користе (не описујемо их да не бисмо надували ионако прилично велики чланак).

Више замагљивања

Добили смо адресу функције која треба да дешифрује саобраћај: 0к5Ф1АЦ. Али прерано је за радовање: програмери УЦ Бровсер-а су нам припремили још једно изненађење.

Након што добијемо параметре из низа који је формиран у Јава коду, добијамо
на функцију на адреси 0к4Д070. А овде нас чека још једна врста замагљивања кода.

Ставили смо два индекса у Р7 и Р4:

Тражење рањивости у УЦ претраживачу

Пребацујемо први индекс на Р11:

Тражење рањивости у УЦ претраживачу

Да бисте добили адресу из табеле, користите индекс:

Тражење рањивости у УЦ претраживачу

Након одласка на прву адресу, користи се други индекс, који је у Р4. У табели се налази 230 елемената.

Шта учинити поводом тога? Можете рећи ИДА-и да је ово прекидач: Уреди -> Остало -> Одреди идиом прекидача.

Тражење рањивости у УЦ претраживачу

Добијени код је застрашујући. Али, пролазећи кроз његову џунглу, можете приметити позив функције која нам је већ позната суб_6115Ц:

Тражење рањивости у УЦ претраживачу

Постојао је прекидач у којем је у случају 3 дошло до дешифровања помоћу РЦ4 алгоритма. И у овом случају, структура која је прослеђена функцији се попуњава из параметара којима се прослеђује доЦоммандНативе. Да се ​​подсетимо шта смо тамо имали магицИнт са вредношћу 16. Гледамо одговарајући случај – и након неколико прелаза налазимо код по коме се алгоритам може идентификовати.

Тражење рањивости у УЦ претраживачу

Ово је АЕС!

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

Закрпа

Да не бисте ручно писали сав код закрпе на асемблерском језику, можете покренути Андроид Студио, тамо написати функцију која прима исте улазне параметре као и наша функција дешифровања и уписује у датотеку, а затим копирати и залепити код који ће компајлер Генериши.

Наши пријатељи из тима УЦ Бровсер такође су се побринули за погодност додавања кода. Подсетимо се да на почетку сваке функције имамо код за смеће који се лако може заменити било којим другим. Веома згодно 🙂 Међутим, на почетку циљне функције нема довољно простора за код који чува све параметре у датотеку. Морао сам да га поделим на делове и користим блокове смећа из суседних функција. Било је укупно четири дела.

Први део:

Тражење рањивости у УЦ претраживачу

У АРМ архитектури, прва четири параметра функције се прослеђују кроз регистре Р0-Р3, а остали, ако их има, пролазе кроз стек. ЛР регистар носи повратну адресу. Све ово треба сачувати да би функција могла да ради након што избацимо њене параметре. Такође морамо да сачувамо све регистре које ћемо користити у процесу, па радимо ПУСХ.В {Р0-Р10,ЛР}. У Р7 добијамо адресу листе параметара прослеђених функцији преко стека.

Коришћење функције фопен хајде да отворимо датотеку /дата/лоцал/тмп/аес у режиму "аб".
односно за додавање. У Р0 учитавамо адресу имена датотеке, у Р1 - адресу линије која означава режим. И овде се код за смеће завршава, па прелазимо на следећу функцију. Да би наставио да ради, на почетак стављамо прелазак на прави код функције, заобилазећи смеће, а уместо смећа додајемо наставак закрпе.

Тражење рањивости у УЦ претраживачу

Зове фопен.

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

Тражење рањивости у УЦ претраживачу

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

Тражење рањивости у УЦ претраживачу

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

Сакупљамо АПК са закрпљеном библиотеком, потписујемо га, отпремамо на уређај/емулатор и покрећемо га. Видимо да се прави наш думп, и ту се уписује много података. Прегледач користи шифровање не само за саобраћај, и сва енкрипција иде кроз дотичну функцију. Али из неког разлога потребни подаци нису ту, а тражени захтев није видљив у саобраћају. Да не бисмо чекали док се УЦ претраживач удостоји да упути неопходан захтев, узмимо шифровани одговор са сервера који је раније примљен и поново закрпимо апликацију: додајте дешифровање у онЦреате главне активности.

    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

Састављамо, потписујемо, инсталирамо, покрећемо. Добијамо НуллПоинтерЕкцептион јер је метод вратио нулл.

Током даље анализе кода откривена је функција која дешифрује занимљиве редове: „МЕТА-ИНФ/” и „.РСА”. Изгледа да апликација проверава свој сертификат. Или чак генерише кључеве од њега. Не желим да се бавим оним што се дешава са сертификатом, па ћемо му само дати исправан сертификат. Хајде да закрпимо шифровану линију тако да уместо „МЕТА-ИНФ/“ добијемо „БЛАБЛИНФ/“, креирамо фасциклу са тим именом у АПК-у и тамо додамо сертификат претраживача веверице.

Састављамо, потписујемо, инсталирамо, покрећемо. Бинго! Имамо кључ!

МитМ

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

Тражење рањивости у УЦ претраживачу

Видимо УРЛ архиве, нешто слично МД5, „ектрацт_унзипсизе“ и број. Проверавамо: МД5 архиве је исти, величина распаковане библиотеке је иста. Покушавамо да закрпимо ову библиотеку и да је дамо претраживачу. Да бисмо показали да се наша закрпљена библиотека учитала, покренућемо намеру да креирамо СМС са текстом „ПВНЕД!“ Заменићемо два одговора са сервера: пудс.уцвеб.цом/упграде/индек.кхтмл и да преузмете архиву. У првом замењујемо МД5 (величина се не мења након распакивања), у другом дајемо архиву са закрпљеном библиотеком.

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

Тражење рањивости у УЦ претраживачу

Кодиран је у ЛЕБ128. Након закрпе, величина архиве са библиотеком се мало променила, па је претраживач сматрао да је архива криво преузета и после неколико покушаја је исписао грешку.

Подешавамо величину архиве... И – победа! 🙂 Резултат је у видеу.

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

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

На исти начин, хакери би могли да користе несигурну функцију УЦ претраживача за дистрибуцију и покретање злонамерних библиотека. Ове библиотеке ће радити у контексту претраживача, тако да ће добити све његове системске дозволе. Као резултат, могућност приказивања пхисхинг прозора, као и приступ радним датотекама наранџасте кинеске веверице, укључујући пријаве, лозинке и колачиће ускладиштене у бази података.

Контактирали смо програмере УЦ Бровсер-а и обавестили их о проблему који смо пронашли, покушали да укажемо на рањивост и њену опасност, али са нама нису разговарали ни о чему. У међувремену, претраживач је наставио да се размеће својом опасном карактеристиком наочиглед. Али када смо открили детаље рањивости, више је није било могуће игнорисати као раније. 27. март је био
објављена је нова верзија УЦ претраживача 12.10.9.1193, која је приступила серверу преко ХТТПС-а: пудс.уцвеб.цом/упграде/индек.кхтмл.

Поред тога, након „поправке“ и до тренутка писања овог чланка, покушај отварања ПДФ-а у претраживачу резултирао је поруком о грешци са текстом „Упс, нешто је пошло наопако!“ Захтев серверу није постављен приликом покушаја отварања ПДФ-а, али је захтев направљен када је прегледач покренут, што наговештава континуирану могућност преузимања извршног кода кршећи правила Гоогле Плаи-а.

Извор: ввв.хабр.цом

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