UC Brauzerində boşluqlar axtarılır

UC Brauzerində boşluqlar axtarılır

Giriş

Martın sonunda biz bildirildi, onlar UC Brauzerində təsdiqlənməmiş kodu yükləmək və işlətmək üçün gizli bir qabiliyyət aşkar etdilər. Bu gün biz bu yükləmənin necə baş verdiyini və hakerlərin ondan öz məqsədləri üçün necə istifadə edə biləcəyini ətraflı nəzərdən keçirəcəyik.

Bir müddət əvvəl UC Browser çox aqressiv şəkildə reklam edildi və yayıldı: o, zərərli proqramlardan istifadə edərək istifadəçilərin cihazlarında quraşdırılıb, müxtəlif saytlardan videofayllar adı altında yayılıb (yəni, istifadəçilər elə bilirdilər ki, məsələn, porno videonu yükləyirlər, lakin əvəzinə bu brauzerlə APK əldə etdi), brauzerin köhnəldiyi, həssas olduğu və bu kimi şeylər mesajları olan qorxulu bannerlərdən istifadə etdi. VK-da rəsmi UC Browser qrupunda var mövzu, istifadəçilərin ədalətsiz reklamdan şikayət edə bildiyi bir çox nümunə var. 2016-cı ildə hətta var idi video reklam rus dilində (bəli, reklamı bloklayan brauzer üçün reklam).

Yazı zamanı UC Brauzerində Google Play-də 500-dan çox quraşdırma var. Bu təsir edicidir - yalnız Google Chrome-da daha çox şey var. Rəylər arasında Google Play-də bəzi tətbiqlərə reklam və yönləndirmə ilə bağlı kifayət qədər çox şikayətlər görə bilərsiniz. Tədqiqatımızın səbəbi bu idi: UC Brauzerinin pis bir şey edib-etmədiyini görmək qərarına gəldik. Və məlum oldu ki, o edir!

Tətbiq kodunda icra edilə bilən kodu yükləmək və işlətmək imkanı aşkar edildi, ərizələrin dərci qaydalarına ziddir Google Play-də. İcra edilə bilən kodu yükləməkdən əlavə, UC Brauzer bunu etibarlı olmayan şəkildə edir, bu da MitM hücumunu başlatmaq üçün istifadə edilə bilər. Görək belə bir hücumu həyata keçirə bilərikmi?

Aşağıda yazılan hər şey tədqiqat zamanı Google Play-də mövcud olan UC Brauzer versiyası üçün uyğundur:

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

Hücum vektoru

UC Brauzer manifestində siz özünü izah edən adı olan bir xidmət tapa bilərsiniz com.uc.deployment.UpgradeDeployService.

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

Bu xidmət işə salındıqda, brauzer POST sorğusu göndərir puds.ucweb.com/upgrade/index.xhtml, başlanğıcdan bir müddət sonra tıxacda görünə bilər. Cavab olaraq o, hansısa yeniləməni və ya yeni modulu yükləmək üçün əmr ala bilər. Təhlil zamanı server belə əmrlər vermədi, lakin biz gördük ki, biz brauzerdə PDF-i açmağa cəhd edəndə yuxarıda göstərilən ünvana ikinci sorğu göndərir, bundan sonra doğma kitabxananı yükləyir. Hücumu həyata keçirmək üçün biz UC Brauzerinin bu xüsusiyyətindən istifadə etmək qərarına gəldik: APK-da olmayan və lazım olduqda İnternetdən endirdiyi doğma kitabxanadan istifadə edərək PDF-i açmaq imkanı. Qeyd etmək lazımdır ki, nəzəri olaraq, UC Brauzer istifadəçinin qarşılıqlı əlaqəsi olmadan nəyisə yükləməyə məcbur edilə bilər - əgər siz brauzer işə salındıqdan sonra yerinə yetirilən sorğuya düzgün formada cavab versəniz. Ancaq bunun üçün serverlə qarşılıqlı əlaqə protokolunu daha ətraflı öyrənməliyik, ona görə də biz qərara gəldik ki, tutulan cavabı redaktə etmək və PDF ilə işləmək üçün kitabxananı əvəz etmək daha asan olacaq.

Beləliklə, istifadəçi birbaşa brauzerdə PDF açmaq istədikdə, trafikdə aşağıdakı sorğular görünə bilər:

UC Brauzerində boşluqlar axtarılır

Əvvəlcə POST sorğusu var puds.ucweb.com/upgrade/index.xhtmlbundan sonra
PDF və ofis formatlarına baxmaq üçün kitabxanası olan arxiv endirilir. Güman etmək məntiqlidir ki, ilk sorğu sistem haqqında məlumat ötürür (ən azı tələb olunan kitabxananı təmin etmək üçün arxitektura) və buna cavab olaraq brauzer yükləmək lazım olan kitabxana haqqında bəzi məlumatları alır: ünvan və bəlkə də. , başqa bir şey. Problem ondadır ki, bu sorğu şifrələnmişdir.

Sorğu fraqmenti

Cavab fraqmenti

UC Brauzerində boşluqlar axtarılır

UC Brauzerində boşluqlar axtarılır

Kitabxananın özü ZIP-də qablaşdırılıb və şifrələnmir.

UC Brauzerində boşluqlar axtarılır

Trafik deşifrə kodunu axtarın

Gəlin server cavabını deşifrə etməyə çalışaq. Gəlin sinif koduna baxaq com.uc.deployment.UpgradeDeployService: metoddan onStartCommand getmək com.uc.deployment.bx, və ondan 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);
}

Biz burada POST sorğusunun formalaşdığını görürük. 16 baytlıq massivin yaradılmasına və onun doldurulmasına diqqət yetiririk: 0x5F, 0, 0x1F, -50 (=0xCE). Yuxarıdakı sorğuda gördüklərimizlə üst-üstə düşür.

Eyni sinifdə başqa maraqlı metodu olan iç-içə sinfi görə bilərsiniz:

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

Metod giriş kimi bayt massivi götürür və sıfır baytın 0x60 və ya üçüncü baytın 0xD0, ikinci baytın isə 1, 11 və ya 0x1F olduğunu yoxlayır. Serverdən gələn cavaba baxırıq: sıfır bayt 0x60, ikincisi 0x1F, üçüncüsü 0x60. Bizə lazım olan kimi səslənir. Sətirlərə əsasən (“yuxarı_decrypt”, məsələn), burada serverin cavabının şifrəsini açacaq bir üsul çağırılmalıdır.
Gəlin üsula keçək gj. Qeyd edək ki, birinci arqument ofset 2-dəki baytdır (yəni bizim vəziyyətimizdə 0x1F), ikincisi isə server cavabı olmadan.
ilk 16 bayt.

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

Aydındır ki, burada biz bir deşifrə alqoritmini seçirik və bizdə olan eyni baytı
0x1F-ə bərabər olan halda, üç mümkün variantdan birini bildirir.

Kodu təhlil etməyə davam edirik. Bir neçə sıçrayışdan sonra özümüzü izahlı adı olan bir üsulla tapırıq decryptBytesByKey.

Burada cavabımızdan daha iki bayt ayrılır və onlardan bir sətir əldə edilir. Aydındır ki, bu şəkildə mesajın şifrəsini açmaq üçün açar seçilir.

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

İrəliyə baxaraq qeyd edirik ki, bu mərhələdə biz hələ bir açar əldə etmirik, yalnız onun "identifikatorunu" alırıq. Açarı əldə etmək bir az daha mürəkkəbdir.

Növbəti üsulda mövcud olanlara daha iki parametr əlavə edilir, onlardan dördü düzəldilir: sehrli nömrə 16, açar identifikator, şifrələnmiş məlumatlar və anlaşılmaz bir sətir (bizim vəziyyətimizdə boş).

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

Bir sıra keçidlərdən sonra metoda çatırıq staticBinarySafeDecryptNoB64 interfeysi com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Əsas proqram kodunda bu interfeysi həyata keçirən siniflər yoxdur. Faylda belə bir sinif var lib/armeabi-v7a/libsgmain.so, bu əslində .so deyil, .jar. Bizi maraqlandıran üsul aşağıdakı kimi həyata keçirilir:

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

Burada bizim parametrlər siyahısı daha iki tam ədədlə tamamlanır: 2 və 0.
hər şey, 2 metodda olduğu kimi şifrənin açılması deməkdir doFinal sistem sinfi javax.crypto.Cipher. Və bütün bunlar 10601 nömrəsi ilə müəyyən bir Routerə köçürülür - bu, yəqin ki, komanda nömrəsidir.

Növbəti keçid zəncirindən sonra interfeysi həyata keçirən bir sinif tapırıq IRouterComponent və üsul 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);
}
}

Həm də sinif JNICL Kitabxanası, burada doğma metodun elan edildiyi doCommandNative:

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

Bu o deməkdir ki, biz yerli kodda bir üsul tapmalıyıq doCommandNative. Və burada əyləncə başlayır.

Maşın kodunun gizlədilməsi

Faylda libsgmain.so (bu, əslində .jardır və biz yuxarıda bəzi şifrələmə ilə əlaqəli interfeyslərin tətbiqini tapdıq) bir yerli kitabxana var: libsgmainso-6.4.36.so. Biz onu IDA-da açırıq və səhvləri olan bir dəstə dialoq qutusu alırıq. Problem bölmə başlığı cədvəlinin etibarsız olmasıdır. Bu, təhlili çətinləşdirmək üçün məqsədyönlü şəkildə edilir.

UC Brauzerində boşluqlar axtarılır

Ancaq buna ehtiyac yoxdur: bir ELF faylını düzgün yükləmək və təhlil etmək üçün proqramın başlıq cədvəli kifayətdir. Buna görə də, başlıqdakı müvafiq sahələri sıfırlayaraq bölmə cədvəlini sadəcə silirik.

UC Brauzerində boşluqlar axtarılır

Faylı yenidən IDA-da açın.

Java virtual maşınına Java kodunda yerli olaraq elan edilmiş metodun tətbiqinin yerli kitabxanada tam olaraq harada yerləşdiyini söyləməyin iki yolu var. Birincisi, ona bir növ adı verməkdir Java_paket_adı_ClassName_methodName.

İkincisi, kitabxananı yükləyərkən onu qeydiyyatdan keçirməkdir (funksiyada JNI_OnLoad)
funksiya çağırışından istifadə etməklə Yerliləri qeydiyyatdan keçirin.

Bizim vəziyyətimizdə birinci üsuldan istifadə etsək, ad belə olmalıdır: Java_com_taobao_wireless_security_adapter_JNICLlibrary_doCommandNative.

İxrac edilən funksiyalar arasında belə bir funksiya yoxdur, yəni zəng axtarmaq lazımdır Yerliləri qeydiyyatdan keçirin.
Gəlin funksiyaya keçək JNI_OnLoad və bu şəkli görürük:

UC Brauzerində boşluqlar axtarılır

Burda nə baş verir? İlk baxışda funksiyanın başlanğıcı və sonu ARM arxitekturası üçün xarakterikdir. Stəkdəki ilk göstəriş funksiyanın öz işində istifadə edəcəyi registrlərin məzmununu (bu halda R0, R1 və R2), həmçinin funksiyadan qayıtma ünvanını ehtiva edən LR registrinin məzmununu saxlayır. . Son göstəriş saxlanılan registrləri bərpa edir və qaytarma ünvanı dərhal PC reyestrinə yerləşdirilir - beləliklə funksiyadan qayıdır. Ancaq diqqətlə baxsanız, sondan əvvəlki təlimatın yığında saxlanan qaytarma ünvanını dəyişdirdiyini görəcəksiniz. Bundan sonra necə olacağını hesablayaq
kodun icrası. Müəyyən bir ünvan 1xB0 R130-ə yüklənir, ondan 5 çıxarılır, sonra R0-a köçürülür və ona 0x10 əlavə olunur. 0xB13B çıxır. Beləliklə, IDA son göstərişin normal bir funksiyanın qaytarılması olduğunu düşünür, lakin əslində hesablanmış 0xB13B ünvanına gedir.

Burada xatırlatmaq lazımdır ki, ARM prosessorlarının iki rejimi və iki təlimat dəsti var: ARM və Thumb. Ünvanın ən az əhəmiyyətli biti prosessora hansı təlimat dəstinin istifadə olunduğunu bildirir. Yəni, ünvan əslində 0xB13A-dır və ən az əhəmiyyətli bitdən biri Thumb rejimini göstərir.

Bu kitabxanada hər bir funksiyanın əvvəlinə oxşar “adapter” əlavə edilmişdir və
zibil kodu. Onları daha ətraflı nəzərdən keçirməyəcəyik - sadəcə xatırlayırıq
demək olar ki, bütün funksiyaların əsl başlanğıcının bir az uzaqda olduğunu.

Kod açıq şəkildə 0xB13A-a keçmədiyi üçün IDA özü kodun bu yerdə olduğunu tanımırdı. Eyni səbəbdən o, kitabxanadakı kodun çoxunu kod kimi tanımır, bu da təhlili bir qədər çətinləşdirir. BİA-ya deyirik ki, bu koddur və belə olur:

UC Brauzerində boşluqlar axtarılır

Cədvəl aydın şəkildə 0xB144-dən başlayır. Sub_494C-də nə var?

UC Brauzerində boşluqlar axtarılır

LR registrində bu funksiyanı çağırarkən əvvəllər qeyd olunan cədvəlin ünvanını alırıq (0xB144). R0-da - bu cədvəldəki indeks. Yəni qiymət cədvəldən götürülür, LR-ə əlavə edilir və nəticə belə olur
getmək üçün ünvan. Gəlin onu hesablamağa çalışaq: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Alınan ünvana gedirik və sözün əsl mənasında bir neçə faydalı təlimat görürük və yenidən 0xB140-a gedirik:

UC Brauzerində boşluqlar axtarılır

İndi cədvəldən 0x20 indeksi ilə ofsetdə keçid olacaq.

Cədvəlin ölçüsünə görə kodda belə keçidlər çox olacaq. Sual yaranır ki, ünvanları əl ilə hesablamadan bununla daha avtomatik məşğul olmaq mümkündürmü? Və skriptlər və IDA-da kodu yamaq qabiliyyəti bizə kömək edir:

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"

Kursoru 0xB26A xəttinə qoyun, skripti işə salın və 0xB4B0-a keçidə baxın:

UC Brauzerində boşluqlar axtarılır

BİA yenə də bu ərazini kod kimi tanımadı. Biz ona kömək edirik və orada başqa bir dizayn görürük:

UC Brauzerində boşluqlar axtarılır

BLX-dən sonra verilən təlimatlar çox mənalı görünmür, daha çox bir növ yerdəyişmə kimidir. sub_4964-ə baxaq:

UC Brauzerində boşluqlar axtarılır

Həqiqətən də, burada LR-də yerləşən ünvanda bir dword alınır, bu ünvana əlavə olunur, bundan sonra nəticədə yaranan ünvandakı dəyər götürülür və yığına qoyulur. Həmçinin, LR-ə 4 əlavə olunur ki, funksiyadan qayıtdıqdan sonra bu eyni ofset atlanır. Bundan sonra POP {R1} əmri yığından alınan dəyəri alır. 0xB4BA + 0xEA = 0xB5A4 ünvanında nə olduğuna baxsanız, ünvan cədvəlinə bənzər bir şey görəcəksiniz:

UC Brauzerində boşluqlar axtarılır

Bu dizaynı düzəltmək üçün koddan iki parametr əldə etməlisiniz: ofset və nəticəni qoymaq istədiyiniz registr nömrəsi. Hər bir mümkün qeydiyyat üçün əvvəlcədən bir kod parçası hazırlamalı olacaqsınız.

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"

Kursoru əvəz etmək istədiyimiz strukturun - 0xB4B2 - başlanğıcına qoyuruq və skripti işə salırıq:

UC Brauzerində boşluqlar axtarılır

Artıq qeyd olunan strukturlara əlavə olaraq, kodda aşağıdakılar da var:

UC Brauzerində boşluqlar axtarılır

Əvvəlki vəziyyətdə olduğu kimi, BLX təlimatından sonra bir ofset var:

UC Brauzerində boşluqlar axtarılır

LR-dən ünvana ofset götürürük, LR-ə əlavə edirik və ora gedirik. 0x72044 + 0xC = 0x72050. Bu dizayn üçün skript olduqca sadədir:

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"

Skriptin icrasının nəticəsi:

UC Brauzerində boşluqlar axtarılır

Funksiyada hər şey düzəldildikdən sonra IDA-nı onun əsl başlanğıcına yönəldə bilərsiniz. Bütün funksiya kodunu birləşdirəcək və HexRays istifadə edərək dekompilyasiya edilə bilər.

Deşifrə sətirləri

Biz kitabxanada maşın kodunun qarışması ilə məşğul olmağı öyrəndik libsgmainso-6.4.36.so UC Brauzerindən və funksiya kodunu aldı 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;
}

Aşağıdakı sətirlərə daha yaxından nəzər salaq:

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

Funksiyada sub_73E24 sinif adı aydın şəkildə deşifrə edilir. Bu funksiyanın parametrləri olaraq, şifrələnmiş məlumatlara bənzər məlumatlara bir göstərici, müəyyən bir bufer və bir sıra ötürülür. Aydındır ki, funksiya çağırıldıqdan sonra buferdə şifrəsi açılmış xətt olacaq, çünki o, funksiyaya ötürülür. FindClass, ikinci parametr kimi sinif adını qəbul edir. Buna görə də nömrə buferin ölçüsü və ya xəttin uzunluğudur. Gəlin sinif adını deşifrə etməyə çalışaq, o bizə düzgün istiqamətdə getdiyimizi bildirməlidir. Gəlin nə baş verdiyinə daha yaxından nəzər salaq 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;
}

Function sub_7AF78 müəyyən ölçülü bayt massivləri üçün konteyner nümunəsini yaradır (bu konteynerlər üzərində ətraflı dayanmayacağıq). Burada iki belə konteyner yaradılır: biri xətt ehtiva edir "DcO/lcK+h?m3c*q@" (bunun açar olduğunu təxmin etmək asandır), digərində şifrələnmiş məlumatlar var. Sonra, hər iki obyekt funksiyaya ötürülən müəyyən bir quruluşa yerləşdirilir sub_6115C. Gəlin bu strukturda dəyəri 3 olan sahəni də qeyd edək.Görək bu strukturun bundan sonra nə baş verdiyi.

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

Keçid parametri əvvəllər 3 dəyəri təyin edilmiş struktur sahəsidir. 3-cü vəziyyətə baxın: funksiyaya sub_6364C parametrlər əvvəlki funksiyaya əlavə edilmiş strukturdan, yəni açardan və şifrələnmiş məlumatdan ötürülür. Diqqətlə baxsanız sub_6364C, onda siz RC4 alqoritmini tanıya bilərsiniz.

Bizim alqoritmimiz və açarımız var. Gəlin sinif adını deşifrə etməyə çalışaq. Budur, baş verənlər: com/taobao/wireless/security/adapter/JNICLlibrary. Əla! Biz düzgün yoldayıq.

Komanda ağacı

İndi bir problem tapmalıyıq Yerliləri qeydiyyatdan keçirin, bu bizi funksiyaya işarə edəcək doCommandNative. -dən çağırılan funksiyalara baxaq JNI_OnLoad, və biz onu tapırıq 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;
}

Və həqiqətən, adı ilə yerli üsul burada qeydiyyatdan keçmişdir doCommandNative. İndi biz onun ünvanını bilirik. Görək nə edir.

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

Adı ilə təxmin edə bilərsiniz ki, burada tərtibatçıların doğma kitabxanaya köçürməyə qərar verdiyi bütün funksiyaların giriş nöqtəsi var. Bizi 10601 nömrəli funksiya maraqlandırır.

Koddan görə bilərsiniz ki, komanda nömrəsi üç rəqəm yaradır: komanda/10000, komanda % 10000 / 100 и komanda % 10, yəni bizim vəziyyətimizdə 1, 6 və 1. Bu üç ədəd, həmçinin göstərici JNIEnv və funksiyaya ötürülən arqumentlər struktura əlavə edilir və ötürülür. Alınan üç ədəddən (onları N1, N2 və N3 kimi qeyd edək) istifadə edərək komanda ağacı qurulur.

Bu kimi bir şey:

UC Brauzerində boşluqlar axtarılır

Ağac dinamik şəkildə doldurulur JNI_OnLoad.
Üç rəqəm ağacdakı yolu kodlayır. Ağacın hər yarpağında müvafiq funksiyanın cibli ünvanı var. Açar ana qovşaqdadır. İstifadə olunan bütün strukturları başa düşsəniz, kodda bizə lazım olan funksiyanın ağaca əlavə olunduğu yeri tapmaq çətin deyil (onsuz da kifayət qədər böyük bir məqaləni şişirtməmək üçün onları təsvir etmirik).

Daha çox çaşqınlıq

Trafikin şifrəsini açmalı olan funksiyanın ünvanını aldıq: 0x5F1AC. Ancaq sevinmək üçün hələ tezdir: UC Browser-in tərtibatçıları bizim üçün daha bir sürpriz hazırladılar.

Java kodunda formalaşmış massivdən parametrləri aldıqdan sonra alırıq
0x4D070 ünvanındakı funksiyaya. Və burada bizi başqa bir kod gizlətməsi gözləyir.

R7 və R4-ə iki indeks qoyuruq:

UC Brauzerində boşluqlar axtarılır

İlk indeksi R11-ə keçirik:

UC Brauzerində boşluqlar axtarılır

Cədvəldən ünvan əldə etmək üçün indeksdən istifadə edin:

UC Brauzerində boşluqlar axtarılır

Birinci ünvana getdikdən sonra R4-də olan ikinci indeks istifadə olunur. Cədvəldə 230 element var.

Bununla bağlı nə etməli? Siz IDA-ya bunun bir keçid olduğunu deyə bilərsiniz: Redaktə et -> Digər -> Keçid deyimini təyin et.

UC Brauzerində boşluqlar axtarılır

Nəticə kod dəhşətlidir. Lakin, onun cəngəlliyindən keçərək, artıq bizə tanış olan bir funksiyaya zəng görə bilərsiniz sub_6115C:

UC Brauzerində boşluqlar axtarılır

3-cü vəziyyətdə RC4 alqoritmindən istifadə edərək şifrənin açılması olan bir keçid var idi. Və bu zaman funksiyaya ötürülən struktur ötürülən parametrlərdən doldurulur doCommandNative. Gəlin orada nələrimiz olduğunu xatırlayaq magicInt dəyəri ilə 16. Biz müvafiq işə baxırıq - və bir neçə keçiddən sonra alqoritmin müəyyən edilə biləcəyi kodu tapırıq.

UC Brauzerində boşluqlar axtarılır

Bu AES!

Alqoritm mövcuddur, onun parametrlərini əldə etmək qalır: rejim, açar və ehtimal ki, başlatma vektoru (onun mövcudluğu AES alqoritminin iş rejimindən asılıdır). Onlarla struktur funksiya çağırışından əvvəl haradasa formalaşmalıdır sub_6115C, lakin kodun bu hissəsi xüsusilə yaxşı qarışıqdır, ona görə də şifrəni açmaq funksiyasının bütün parametrlərinin fayla atılması üçün kodu yamaq etmək ideyası yaranır.

Yamaq

Bütün yamaq kodunu montaj dilində əl ilə yazmamaq üçün siz Android Studio proqramını işə sala, şifrləmə funksiyamızla eyni giriş parametrlərini alan funksiyanı orada yaza və fayla yaza, sonra kompilyatorun göstərəcəyi kodu kopyalayıb yapışdıra bilərsiniz. yaratmaq.

UC Brauzer komandasındakı dostlarımız da kodun əlavə edilməsinin rahatlığının qayğısına qaldılar. Unutmayaq ki, hər bir funksiyanın əvvəlində asanlıqla hər hansı digəri ilə əvəz edilə bilən zibil kodumuz var. Çox rahatdır 🙂 Bununla belə, hədəf funksiyasının əvvəlində bütün parametrləri faylda saxlayan kod üçün kifayət qədər yer yoxdur. Mən onu hissələrə bölməli və qonşu funksiyaların zibil bloklarından istifadə etməli oldum. Ümumilikdə dörd hissədən ibarət idi.

Birinci hissə:

UC Brauzerində boşluqlar axtarılır

ARM arxitekturasında ilk dörd funksiya parametri R0-R3 registrlərindən, qalanları, əgər varsa, stekdən keçir. LR reyestri geri qayıtma ünvanını daşıyır. Bütün bunları yadda saxlamaq lazımdır ki, biz onun parametrlərini atdıqdan sonra funksiya işləyə bilsin. Biz həmçinin prosesdə istifadə edəcəyimiz bütün registrləri saxlamalıyıq, ona görə də PUSH.W {R0-R10,LR} edirik. R7-də stek vasitəsilə funksiyaya ötürülən parametrlər siyahısının ünvanını alırıq.

Funksiyadan istifadə etməklə fopen faylı açaq /data/local/tmp/aes "ab" rejimində
yəni əlavə üçün. R0-da fayl adının ünvanını, R1-də rejimi göstərən xəttin ünvanını yükləyirik. Və burada zibil kodu bitir, ona görə də növbəti funksiyaya keçirik. Onun işləməyə davam etməsi üçün biz başlanğıcda zibildən yan keçərək funksiyanın real koduna keçidi qoyuruq və zibil yerinə yamağın davamını əlavə edirik.

UC Brauzerində boşluqlar axtarılır

Zəng edirik fopen.

Funksiyanın ilk üç parametri aes növü var int. Başlanğıcda registrləri stekdə saxladığımız üçün funksiyanı sadəcə ötürə bilərik yaz onların ünvanları yığında.

UC Brauzerində boşluqlar axtarılır

Sonra, məlumat ölçüsünü və açar, başlatma vektoru və şifrələnmiş məlumat üçün məlumatlara işarə edən üç strukturumuz var.

UC Brauzerində boşluqlar axtarılır

Sonda faylı bağlayın, registrləri bərpa edin və idarəetməni real funksiyaya köçürün aes.

Yamaqlanmış kitabxanası olan APK toplayırıq, onu imzalayırıq, cihaza/emulatora yükləyirik və işə salırıq. Görürük ki, bizim zibilimiz yaradılır və orada çoxlu məlumatlar yazılır. Brauzer şifrələmədən təkcə trafik üçün istifadə etmir və bütün şifrələmə sözügedən funksiyadan keçir. Amma nədənsə lazımi məlumatlar yoxdur və tələb olunan sorğu trafikdə görünmür. UC Brauzerinin lazımi sorğu ilə çıxış etməsini gözləməmək üçün gəlin əvvəllər alınan serverdən şifrələnmiş cavabı götürək və tətbiqi yenidən yamaq edək: əsas fəaliyyətin onCreate-ə deşifrəni əlavə edin.

    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

Biz yığırıq, imzalayırıq, quraşdırırıq, işə salırıq. Metod null qaytardığına görə biz NullPointerException alırıq.

Kodun sonrakı təhlili zamanı maraqlı sətirləri deşifrə edən funksiya aşkar edilmişdir: “META-INF/” və “.RSA”. Deyəsən, tətbiq öz sertifikatını yoxlayır. Və ya hətta ondan açarlar yaradır. Sertifikatla baş verənlərlə həqiqətən məşğul olmaq istəmirəm, ona görə də ona düzgün sertifikat verəcəyik. Şifrələnmiş xətti düzəldək ki, “META-INF/” əvəzinə “BLABLINF/” əldə edək, APK-da bu adda bir qovluq yaradın və ora dələ brauzeri sertifikatını əlavə edin.

Biz yığırıq, imzalayırıq, quraşdırırıq, işə salırıq. Bingo! Açar bizdə!

MitM

Açar və açara bərabər başlanğıc vektoru aldıq. CBC rejimində server cavabının şifrəsini açmağa çalışaq.

UC Brauzerində boşluqlar axtarılır

Arxiv URL-ni, MD5-ə bənzər bir şey, “extract_unzipsize” və bir nömrəni görürük. Yoxlayırıq: arxivin MD5-i eynidir, açılmamış kitabxananın ölçüsü eynidir. Bu kitabxananı yamaq və brauzerə verməyə çalışırıq. Yamaqlanmış kitabxanamızın yükləndiyini göstərmək üçün biz “PWNED!” mətni ilə SMS yaratmaq niyyətini işə salacağıq. Serverdən iki cavabı əvəz edəcəyik: puds.ucweb.com/upgrade/index.xhtml və arxivi yükləmək üçün. Birincidə MD5-i əvəz edirik (paketdən çıxardıqdan sonra ölçüsü dəyişmir), ikincisində arxivi yamaqlı kitabxana ilə veririk.

Brauzer arxivi bir neçə dəfə yükləməyə çalışır, bundan sonra xəta verir. Görünür nəsə
bəyənmir. Bu qaranlıq formatın təhlili nəticəsində məlum oldu ki, server arxivin ölçüsünü də ötürür:

UC Brauzerində boşluqlar axtarılır

LEB128-də kodlaşdırılmışdır. Yamadan sonra kitabxana ilə arxivin ölçüsü bir az dəyişdi, buna görə də brauzer arxivin əyri şəkildə yükləndiyini hesab etdi və bir neçə cəhddən sonra xəta verdi.

Arxivin ölçüsünü tənzimləyirik... Və – qələbə! 🙂 Nəticə videodadır.

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

Nəticələr və inkişaf etdirici reaksiyası

Eyni şəkildə, hakerlər zərərli kitabxanaları yaymaq və işə salmaq üçün UC Brauzerinin etibarsız xüsusiyyətindən istifadə edə bilər. Bu kitabxanalar brauzer kontekstində işləyəcək, ona görə də onun bütün sistem icazələrini alacaqlar. Nəticədə, fişinq pəncərələrini göstərmək imkanı, həmçinin məlumat bazasında saxlanılan loginlər, parollar və kukilər daxil olmaqla, narıncı Çin dələsinin iş fayllarına daxil olmaq imkanı.

Biz UC Browser-in tərtibatçıları ilə əlaqə saxladıq və tapdığımız problem barədə onlara məlumat verdik, zəifliyi və onun təhlükəsini qeyd etməyə çalışdıq, lakin onlar bizimlə heç nə müzakirə etmədilər. Bu arada, brauzer təhlükəli xüsusiyyətini açıq şəkildə nümayiş etdirməyə davam etdi. Ancaq zəifliyin təfərrüatlarını açıqladıqdan sonra, əvvəlki kimi buna məhəl qoymamaq artıq mümkün deyildi. 27 mart idi
HTTPS vasitəsilə serverə daxil olan UC Browser 12.10.9.1193-ün yeni versiyası buraxıldı: puds.ucweb.com/upgrade/index.xhtml.

Bundan əlavə, "düzeltmədən" sonra və bu məqalənin yazıldığı vaxta qədər brauzerdə PDF-i açmağa çalışmaq "Oops, bir şey səhv oldu!" Mətni ilə səhv mesajı ilə nəticələndi. PDF-i açmağa çalışarkən serverə sorğu göndərilmədi, lakin brauzer işə salındıqda sorğu verildi ki, bu da Google Play qaydalarını pozaraq icra edilə bilən kodu yükləmək imkanının davam etdiyinə işarə edir.

Mənbə: www.habr.com

Добавить комментарий