UC Браузеринде алсыздыктар изделүүдө

UC Браузеринде алсыздыктар изделүүдө

тааныштыруу

Марттын аягында биз билдирди, алар UC Браузеринде текшерилбеген кодду жүктөө жана иштетүү үчүн жашыруун жөндөмдү табышты. Бүгүн биз бул жүктөө кандайча пайда болоорун жана хакерлер аны өз максаттары үчүн кантип колдоно аларын майда-чүйдөсүнө чейин карап чыгабыз.

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

Жазуу учурунда UC Браузеринде Google Play'де 500 000 000ден ашык орнотуулар бар. Бул таасирдүү - Google Chrome'до гана көбүрөөк. Сын-пикирлердин ичинен сиз Google Playдеги айрым тиркемелерге жарнама жана багыттоо боюнча көптөгөн даттанууларды көрө аласыз. Бул биздин изилдөөбүздүн себеби болду: биз UC Браузер жаман нерсе кылып жатканын көрүүнү чечтик. Жана ал жасайт экен!

Колдонмо кодунда аткарылуучу кодду жүктөп алуу жана иштетүү мүмкүнчүлүгү табылган, бул арыздарды жарыялоонун эрежелерине карама-каршы келет Google Play'де. Аткарылуучу кодду жүктөп алуудан тышкары, UC Browser муну 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 Browser колдонуучунун өз ара аракети жок эле бир нерсени жүктөп алууга аргасыз болушу мүмкүн - эгер сиз браузер ишке киргизилгенден кийин аткарылуучу суроо-талапка туура түзүлгөн жооп берсеңиз. Бирок бул үчүн биз сервер менен өз ара аракеттенүү протоколун тереңирээк изилдеп чыгышыбыз керек, ошондуктан биз кармалган жоопту түзөтүү жана 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. Бизге эмне керек окшойт. Саптарга караганда («up_decrypt», мисалы), бул жерде сервердин жообун чечмелей турган ыкманы чакыруу керек.
Келгиле, ыкмага өтөбүз 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);
}
}

Жана ошондой эле класс JNICLlibrary, анда жергиликтүү ыкма жарыяланган 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)
Функция чакыруусун колдонуу RegisterNatives.

Биздин учурда, биз биринчи ыкманы колдонсок, аты төмөнкүдөй болушу керек: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

Экспорттолгон функциялардын арасында мындай функция жок, демек сиз чалууну издөө керек RegisterNatives.
Функцияга баралы JNI_OnLoad жана биз бул сүрөттү көрүп:

UC Браузеринде алсыздыктар изделүүдө

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

Бул жерде ARM процессорлорунун эки режими жана эки нускама топтому бар экенин эстен чыгарбоо керек: ARM жана Thumb. Даректин эң аз маанилүү биттери процессорго кайсы инструкциялар топтому колдонулуп жатканын айтат. Башкача айтканда, дарек чындыгында 0xB13A жана эң аз маанилүү биттин бири Thumb режимин көрсөтөт.

Окшош "адаптер" бул китепканадагы ар бир функциянын башына кошулган жана
таштанды коду. Биз аларга мындан ары майда-чүйдөсүнө чейин токтолбойбуз – жөн гана эстейбиз
дээрлик бардык функциялардын чыныгы башталышы бир аз алыс экенин.

Код ачык түрдө 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. Абдан жакшы! Биз туура жолдо баратабыз.

Буйрук дарагы

Эми биз кыйынчылык табышыбыз керек RegisterNatives, бул бизди функцияга көрсөтөт 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 Браузеринин иштеп чыгуучулары биз үчүн дагы бир сюрприз даярдашты.

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, бирок коддун бул бөлүгү өзгөчө жакшы бүдөмүк, ошондуктан кодду чечмелөө функциясынын бардык параметрлери файлга ташталгандай кылып түзөө идеясы пайда болот.

Patch

Бардык патч-кодду ассемблер тилинде кол менен жазбаш үчүн, сиз Android Studio программасын ишке киргизип, ал жерге биздин чечмелөө функциябыз сыяктуу эле киргизүү параметрлерин алган функцияны жазып, файлга жаза аласыз, андан кийин компилятор жасай турган кодду көчүрүп чаптасаңыз болот. түзүү.

UC Browser командасынын достору да кодду кошуунун ыңгайлуулугуна кам көрүштү. Ар бир функциянын башында бизде башкасы менен оңой алмаштырыла турган таштанды коду бар экенин унутпайлы. Абдан ыңгайлуу 🙂 Бирок, максаттуу функциянын башында файлга бардык параметрлерди сактаган код үчүн орун жетишсиз. Мен аны бөлүктөргө бөлүп, кошуна функциялардын таштанды блокторун колдонууга туура келди. Бардыгы болуп төрт бөлүктөн турган.

Биринчи бөлүк:

UC Браузеринде алсыздыктар изделүүдө

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

Функцияны колдонуу fopen файлды ачалы /data/local/tmp/aes "ab" режиминде
б.а. кошуу үчүн. R0до файлдын аталышынын дарегин, R1де - режимди көрсөткөн саптын дарегин жүктөйбүз. Жана бул жерде таштанды коду аяктайт, ошондуктан биз кийинки функцияга өтөбүз. Ал иштей бериши үчүн, биз башында таштандыны айланып өтүп, функциянын чыныгы кодуна өтүүнү койдук жана таштандынын ордуна патчтын уландысын кошобуз.

UC Браузеринде алсыздыктар изделүүдө

Чалуу fopen.

Функциянын алгачкы үч параметри Өнүктүрүлгөн түрү бар Int. Башында регистрлерди стекке сактагандыктан, функцияны жөн эле өткөрүп бере алабыз fwrite стекте алардын даректери.

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 Браузеринин кооптуу өзгөчөлүгүн колдонушу мүмкүн. Бул китепканалар браузердин контекстинде иштешет, ошондуктан алар анын бардык тутумдук уруксаттарын алышат. Натыйжада, фишингдик терезелерди көрсөтүү мүмкүнчүлүгү, ошондой эле кызгылт сары кытай тырмагынын жумушчу файлдарына, анын ичинде маалымат базасында сакталган логиндер, паролдор жана кукилер.

Биз UC Браузеринин иштеп чыгуучулары менен байланышып, аларга табылган көйгөй тууралуу маалымат бердик, аялуу жерин жана анын коркунучун көрсөтүүгө аракет кылдык, бирок алар биз менен эч нерсени талкуулашкан жок. Ошол эле учурда, браузер көз алдында өзүнүн коркунучтуу өзгөчөлүгүн көрсөтө берди. Бирок биз алсыздыктын чоо-жайын ачыкка чыгаргандан кийин, мурункудай көз жаздымда калтыруу мүмкүн болбой калды. 27-март болду
UC Browser 12.10.9.1193 жаңы версиясы чыкты, ал серверге HTTPS аркылуу кирди: puds.ucweb.com/upgrade/index.xhtml.

Мындан тышкары, "оңдоодон" кийин жана ушул макаланы жазганга чейин, браузерде PDFти ачууга аракет кылуу "Ой, бир жерден ката кетти!" деген текст менен ката билдирүүсүнө алып келди. PDFти ачууга аракет кылып жатканда серверге суроо-талап жасалган эмес, бирок браузер ишке киргизилгенде, Google Play эрежелерин бузуу менен аткарылуучу кодду жүктөп алуу мүмкүнчүлүгүн кыйытууда.

Source: www.habr.com

Комментарий кошуу