Naghahanap ng mga kahinaan sa UC Browser

Naghahanap ng mga kahinaan sa UC Browser

Pagpapakilala

Sa katapusan ng Marso namin iniulat, na natuklasan nila ang isang nakatagong kakayahang mag-load at magpatakbo ng hindi na-verify na code sa UC Browser. Ngayon ay titingnan natin nang detalyado kung paano nangyayari ang pag-download na ito at kung paano ito magagamit ng mga hacker para sa kanilang sariling mga layunin.

Noong nakaraan, ang UC Browser ay na-advertise at ipinamahagi nang napaka-agresibo: na-install ito sa mga device ng mga user gamit ang malware, na ipinamahagi mula sa iba't ibang mga site sa ilalim ng pagkukunwari ng mga video file (ibig sabihin, ang mga user ay nag-iisip na sila ay nagda-download, halimbawa, isang porn video, ngunit sa halip ay nakatanggap ng APK gamit ang browser na ito), gumamit ng mga nakakatakot na banner na may mga mensahe na ang browser ay lipas na, mahina, at mga bagay na katulad nito. Sa opisyal na grupo ng UC Browser sa VK mayroong tema, kung saan maaaring magreklamo ang mga user tungkol sa hindi patas na advertising, maraming mga halimbawa doon. Noong 2016 nagkaroon ng kahit na video advertising sa Russian (oo, advertising para sa isang ad-blocking browser).

Sa oras ng pagsulat, ang UC Browser ay may higit sa 500 mga pag-install sa Google Play. Ito ay kahanga-hanga - ang Google Chrome lamang ang may higit pa. Sa mga review, marami kang makikitang reklamo tungkol sa advertising at pag-redirect sa ilang application sa Google Play. Ito ang dahilan ng aming pananaliksik: nagpasya kaming tingnan kung may ginagawang masama ang UC Browser. At ito ay naging siya!

Sa application code, natuklasan ang kakayahang mag-download at magpatakbo ng executable code, na salungat sa mga patakaran para sa pag-publish ng mga aplikasyon sa Google Play. Bilang karagdagan sa pag-download ng executable code, ginagawa ito ng UC Browser sa isang hindi secure na paraan, na maaaring magamit upang maglunsad ng pag-atake ng MitM. Tingnan natin kung magagawa natin ang gayong pag-atake.

Ang lahat ng nakasulat sa ibaba ay may kaugnayan para sa bersyon ng UC Browser na available sa Google Play sa oras ng pag-aaral:

package: com.UCMobile.intl
versionName: 12.10.8.1172
versionCode: 10598
sha1 APK-Ρ„Π°ΠΉΠ»Π°: f5edb2243413c777172f6362876041eb0c3a928c

Vektor ng pag-atake

Sa manifest ng UC Browser maaari kang makahanap ng isang serbisyo na may sariling paliwanag na pangalan com.uc.deployment.UpgradeDeployService.

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

Kapag nagsimula ang serbisyong ito, ang browser ay gumagawa ng isang POST na kahilingan sa puds.ucweb.com/upgrade/index.xhtml, na makikita sa trapiko ilang oras pagkatapos ng simula. Bilang tugon, maaari siyang makatanggap ng utos na mag-download ng ilang update o bagong module. Sa panahon ng pagsusuri, ang server ay hindi nagbigay ng gayong mga utos, ngunit napansin namin na kapag sinubukan naming buksan ang isang PDF sa browser, gumawa ito ng pangalawang kahilingan sa address na tinukoy sa itaas, pagkatapos nito i-download ang katutubong library. Upang maisagawa ang pag-atake, napagpasyahan naming gamitin ang tampok na ito ng UC Browser: ang kakayahang magbukas ng PDF gamit ang isang katutubong library, na wala sa APK at kung saan dina-download ito mula sa Internet kung kinakailangan. Kapansin-pansin na, ayon sa teorya, ang UC Browser ay maaaring pilitin na mag-download ng isang bagay nang walang pakikipag-ugnayan ng gumagamit - kung magbibigay ka ng isang mahusay na nabuong tugon sa isang kahilingan na naisakatuparan pagkatapos na mailunsad ang browser. Ngunit upang gawin ito, kailangan naming pag-aralan ang protocol ng pakikipag-ugnayan sa server nang mas detalyado, kaya napagpasyahan namin na mas madaling i-edit ang na-intercept na tugon at palitan ang library para sa pagtatrabaho sa PDF.

Kaya, kapag ang isang user ay gustong magbukas ng PDF nang direkta sa browser, ang mga sumusunod na kahilingan ay makikita sa trapiko:

Naghahanap ng mga kahinaan sa UC Browser

Una mayroong isang kahilingan sa POST sa puds.ucweb.com/upgrade/index.xhtml, pagkatapos
Ang isang archive na may library para sa pagtingin sa PDF at mga format ng opisina ay dina-download. Makatuwirang ipagpalagay na ang unang kahilingan ay nagpapadala ng impormasyon tungkol sa system (hindi bababa sa arkitektura upang magbigay ng kinakailangang aklatan), at bilang tugon dito ay tumatanggap ang browser ng ilang impormasyon tungkol sa aklatan na kailangang i-download: ang address at, posibleng , iba pa. Ang problema ay naka-encrypt ang kahilingang ito.

Humiling ng fragment

Sagutin ang fragment

Naghahanap ng mga kahinaan sa UC Browser

Naghahanap ng mga kahinaan sa UC Browser

Ang library mismo ay nakabalot sa ZIP at hindi naka-encrypt.

Naghahanap ng mga kahinaan sa UC Browser

Maghanap ng traffic decryption code

Subukan nating tukuyin ang tugon ng server. Tingnan natin ang code ng klase com.uc.deployment.UpgradeDeployService: mula sa pamamaraan onStartCommand pumunta sa com.uc.deployment.bx, at mula dito hanggang 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);
}

Nakikita namin ang pagbuo ng isang kahilingan sa POST dito. Binibigyang-pansin namin ang paglikha ng isang array ng 16 bytes at ang pagpuno nito: 0x5F, 0, 0x1F, -50 (=0xCE). Kasabay ng nakita natin sa kahilingan sa itaas.

Sa parehong klase maaari mong makita ang isang nested na klase na may isa pang kawili-wiling pamamaraan:

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

Ang pamamaraan ay kumukuha ng hanay ng mga byte bilang input at sinusuri kung ang zero byte ay 0x60 o ang ikatlong byte ay 0xD0, at ang pangalawang byte ay 1, 11 o 0x1F. Tinitingnan namin ang tugon mula sa server: ang zero byte ay 0x60, ang pangalawa ay 0x1F, ang pangatlo ay 0x60. Parang kailangan natin. Sa paghusga sa mga linya ("up_decrypt", halimbawa), isang paraan ang dapat tawagan dito na magde-decrypt ng tugon ng server.
Lumipat tayo sa pamamaraan gj. Tandaan na ang unang argumento ay ang byte sa offset 2 (i.e. 0x1F sa aming kaso), at ang pangalawa ay ang tugon ng server nang walang
unang 16 byte.

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

Malinaw, dito pumili kami ng isang decryption algorithm, at ang parehong byte na nasa aming
case na katumbas ng 0x1F, ay nagsasaad ng isa sa tatlong posibleng opsyon.

Patuloy naming sinusuri ang code. Pagkatapos ng ilang paglukso nakita natin ang ating sarili sa isang pamamaraan na may sariling paliwanag na pangalan decryptBytesByKey.

Narito ang dalawang higit pang mga byte ay pinaghihiwalay mula sa aming tugon, at isang string ay nakuha mula sa kanila. Malinaw na sa ganitong paraan ang susi para sa pag-decrypting ng mensahe ay napili.

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

Sa hinaharap, tandaan namin na sa yugtong ito ay hindi pa kami nakakakuha ng isang susi, ngunit ang "identifier" lamang nito. Ang pagkuha ng susi ay medyo mas kumplikado.

Sa susunod na paraan, dalawa pang parameter ang idinagdag sa mga umiiral na, na ginagawang apat sa mga ito: ang magic number 16, ang key identifier, ang naka-encrypt na data, at isang hindi maintindihan na string (sa aming kaso, walang laman).

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

Pagkatapos ng isang serye ng mga transition ay nakarating kami sa pamamaraan staticBinarySafeDecryptNoB64 interface com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent. Walang mga klase sa pangunahing application code na nagpapatupad ng interface na ito. Mayroong ganoong klase sa file lib/armeabi-v7a/libsgmain.so, na hindi talaga isang .so, ngunit isang .jar. Ang pamamaraan na interesado kami ay ipinatupad tulad ng sumusunod:

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

Narito ang aming listahan ng mga parameter ay pupunan ng dalawa pang integer: 2 at 0. Sa paghusga sa pamamagitan ng
lahat, 2 ay nangangahulugan ng decryption, tulad ng sa pamamaraan doFinal klase ng sistema javax.crypto.Cipher. At ang lahat ng ito ay inilipat sa isang tiyak na Router na may numerong 10601 - ito ay tila ang command number.

Pagkatapos ng susunod na kadena ng mga transition makakahanap kami ng isang klase na nagpapatupad ng interface IRouterComponent at pamamaraan 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);
}
}

At pati klase JNICLlibrary, kung saan idineklara ang katutubong pamamaraan doCommandNative:

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

Nangangahulugan ito na kailangan nating maghanap ng paraan sa native code doCommandNative. At dito na magsisimula ang saya.

Obfuscation ng machine code

Nasa file libsgmain.so (na talagang isang .jar at kung saan natagpuan namin ang pagpapatupad ng ilang mga interface na nauugnay sa pag-encrypt sa itaas) mayroong isang katutubong library: libsgmainso-6.4.36.so. Binuksan namin ito sa IDA at nakakuha ng isang grupo ng mga dialog box na may mga error. Ang problema ay ang talahanayan ng header ng seksyon ay hindi wasto. Ginagawa ito sa layunin upang gawing kumplikado ang pagsusuri.

Naghahanap ng mga kahinaan sa UC Browser

Ngunit hindi ito kinakailangan: upang mai-load nang tama ang isang ELF file at pag-aralan ito, sapat na ang isang talahanayan ng header ng programa. Samakatuwid, tatanggalin lang namin ang talahanayan ng seksyon, i-zero ang kaukulang mga patlang sa header.

Naghahanap ng mga kahinaan sa UC Browser

Buksan muli ang file sa IDA.

Mayroong dalawang paraan upang sabihin sa Java virtual machine kung saan eksaktong matatagpuan sa native library ang pagpapatupad ng isang paraan na idineklara sa Java code bilang native. Ang una ay bigyan ito ng pangalan ng species Java_package_name_ClassName_MethodName.

Ang pangalawa ay irehistro ito kapag naglo-load ng library (sa function JNI_OnLoad)
gamit ang isang function na tawag RegisterNatives.

Sa aming kaso, kung gagamitin namin ang unang paraan, ang pangalan ay dapat na ganito: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative.

Walang ganoong function sa mga na-export na function, na nangangahulugang kailangan mong maghanap ng tawag RegisterNatives.
Pumunta tayo sa function JNI_OnLoad at nakita natin ang larawang ito:

Naghahanap ng mga kahinaan sa UC Browser

Anong nangyayari dito? Sa unang tingin, ang simula at pagtatapos ng function ay tipikal para sa ARM architecture. Ang unang pagtuturo sa stack ay nag-iimbak ng mga nilalaman ng mga rehistro na gagamitin ng function sa operasyon nito (sa kasong ito, R0, R1 at R2), pati na rin ang mga nilalaman ng LR register, na naglalaman ng return address mula sa function. . Ang huling pagtuturo ay nagpapanumbalik ng mga nai-save na rehistro, at ang return address ay agad na inilagay sa rehistro ng PC - kaya bumabalik mula sa function. Ngunit kung titingnan mong mabuti, mapapansin mo na ang penultimate na pagtuturo ay nagbabago sa return address na nakaimbak sa stack. Kalkulahin natin kung ano ang magiging hitsura nito pagkatapos
pagpapatupad ng code. Ang isang tiyak na address 1xB0 ay na-load sa R130, 5 ay ibabawas mula dito, pagkatapos ay ililipat ito sa R0 at 0x10 ay idinagdag dito. Ito ay lumalabas na 0xB13B. Kaya, iniisip ng IDA na ang huling pagtuturo ay isang normal na function return, ngunit sa katunayan ito ay papunta sa kinakalkulang address na 0xB13B.

Ito ay nagkakahalaga ng pag-alala dito na ang mga processor ng ARM ay may dalawang mode at dalawang set ng mga tagubilin: ARM at Thumb. Ang pinakamaliit na bahagi ng address ay nagsasabi sa processor kung aling set ng pagtuturo ang ginagamit. Iyon ay, ang address ay talagang 0xB13A, at ang isa sa hindi bababa sa makabuluhang bit ay nagpapahiwatig ng Thumb mode.

Ang isang katulad na "adapter" ay idinagdag sa simula ng bawat function sa library na ito at
code ng basura. Hindi na natin sila tatalakayin nang mas detalyado - naaalala lang natin
na ang tunay na simula ng halos lahat ng mga function ay medyo malayo.

Dahil ang code ay hindi tahasang tumalon sa 0xB13A, ang IDA mismo ay hindi nakilala na ang code ay matatagpuan sa lokasyong ito. Para sa parehong dahilan, hindi nito kinikilala ang karamihan sa mga code sa library bilang code, na ginagawang medyo mahirap ang pagsusuri. Sinasabi namin sa IDA na ito ang code, at ito ang mangyayari:

Naghahanap ng mga kahinaan sa UC Browser

Ang talahanayan ay malinaw na nagsisimula sa 0xB144. Ano ang nasa sub_494C?

Naghahanap ng mga kahinaan sa UC Browser

Kapag tinawag ang function na ito sa LR register, nakukuha namin ang address ng naunang nabanggit na talahanayan (0xB144). Sa R0 - index sa talahanayang ito. Iyon ay, ang halaga ay kinuha mula sa talahanayan, idinagdag sa LR at ang resulta ay
ang address na pupuntahan. Subukan nating kalkulahin ito: 0xB144 + [0xB144 + 8* 4] = 0xB144 + 0x120 = 0xB264. Pumunta kami sa natanggap na address at literal na tumingin ng ilang kapaki-pakinabang na tagubilin at pumunta muli sa 0xB140:

Naghahanap ng mga kahinaan sa UC Browser

Ngayon ay magkakaroon ng paglipat sa offset na may index na 0x20 mula sa talahanayan.

Sa paghusga sa laki ng talahanayan, magkakaroon ng maraming gayong mga paglipat sa code. Ang tanong ay lumalabas kung posible na kahit papaano ay harapin ito nang mas awtomatiko, nang walang manu-manong pagkalkula ng mga address. At ang mga script at ang kakayahang mag-patch ng code sa IDA ay tumulong sa amin:

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"

Ilagay ang cursor sa linya 0xB26A, patakbuhin ang script at tingnan ang paglipat sa 0xB4B0:

Naghahanap ng mga kahinaan sa UC Browser

Hindi nakilala muli ng IDA ang lugar na ito bilang isang code. Tinutulungan namin siya at nakakita ng isa pang disenyo doon:

Naghahanap ng mga kahinaan sa UC Browser

Ang mga tagubilin pagkatapos ng BLX ay mukhang hindi gaanong makatwiran, ito ay mas katulad ng isang uri ng displacement. Tingnan natin ang sub_4964:

Naghahanap ng mga kahinaan sa UC Browser

At sa katunayan, narito ang isang dword ay kinuha sa address na nakahiga sa LR, idinagdag sa address na ito, pagkatapos kung saan ang halaga sa nagresultang address ay kinuha at ilagay sa stack. Gayundin, ang 4 ay idinagdag sa LR upang pagkatapos bumalik mula sa function, ang parehong offset na ito ay nilaktawan. Pagkatapos nito, kinukuha ng POP {R1} na utos ang nagresultang halaga mula sa stack. Kung titingnan mo kung ano ang matatagpuan sa address 0xB4BA + 0xEA = 0xB5A4, makakakita ka ng katulad ng isang talahanayan ng address:

Naghahanap ng mga kahinaan sa UC Browser

Upang i-patch ang disenyong ito, kakailanganin mong kumuha ng dalawang parameter mula sa code: ang offset at ang numero ng rehistro kung saan mo gustong ilagay ang resulta. Para sa bawat posibleng pagpaparehistro, kailangan mong maghanda ng isang piraso ng code nang maaga.

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"

Inilalagay namin ang cursor sa simula ng istraktura na gusto naming palitan - 0xB4B2 - at patakbuhin ang script:

Naghahanap ng mga kahinaan sa UC Browser

Bilang karagdagan sa mga nabanggit na istruktura, naglalaman din ang code ng mga sumusunod:

Naghahanap ng mga kahinaan sa UC Browser

Tulad ng sa nakaraang kaso, pagkatapos ng pagtuturo ng BLX mayroong isang offset:

Naghahanap ng mga kahinaan sa UC Browser

Kinukuha namin ang offset sa address mula sa LR, idagdag ito sa LR at pumunta doon. 0x72044 + 0xC = 0x72050. Ang script para sa disenyo na ito ay medyo simple:

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"

Resulta ng pagpapatupad ng script:

Naghahanap ng mga kahinaan sa UC Browser

Kapag na-patch na ang lahat sa function, maaari mong ituro ang IDA sa totoong simula nito. Pagsasama-samahin nito ang lahat ng function code, at maaari itong i-decompiled gamit ang HexRays.

Pag-decode ng mga string

Natutunan naming harapin ang obfuscation ng machine code sa library libsgmainso-6.4.36.so mula sa UC Browser at natanggap ang function code 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;
}

Tingnan natin ang mga sumusunod na linya:

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

Sa pag-andar sub_73E24 malinaw na nade-decrypt ang pangalan ng klase. Bilang mga parameter sa function na ito, isang pointer sa data na katulad ng naka-encrypt na data, isang tiyak na buffer at isang numero ay ipinasa. Malinaw, pagkatapos tawagan ang function, magkakaroon ng decrypted na linya sa buffer, dahil ipinasa ito sa function. FindClass, na kumukuha ng pangalan ng klase bilang pangalawang parameter. Samakatuwid, ang numero ay ang laki ng buffer o ang haba ng linya. Subukan nating tukuyin ang pangalan ng klase, dapat itong sabihin sa atin kung tayo ay pupunta sa tamang direksyon. Tingnan natin kung ano ang nangyayari sa 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;
}

Tungkulin sub_7AF78 lumilikha ng isang halimbawa ng isang lalagyan para sa mga byte array ng tinukoy na laki (hindi namin tatalakayin ang mga lalagyan na ito nang detalyado). Dito nilikha ang dalawang ganoong lalagyan: ang isa ay naglalaman ng linya "DcO/lcK+h?m3c*q@" (madaling hulaan na ito ay isang susi), ang iba ay naglalaman ng naka-encrypt na data. Susunod, ang parehong mga bagay ay inilalagay sa isang tiyak na istraktura, na ipinasa sa function sub_6115C. Markahan din natin ang isang patlang na may halaga 3 sa istrukturang ito. Tingnan natin kung ano ang susunod na mangyayari sa istrukturang ito.

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

Ang switch parameter ay isang structure field na dating itinalaga ang value 3. Tingnan ang case 3: sa function sub_6364C Ang mga parameter ay ipinasa mula sa istraktura na idinagdag doon sa nakaraang function, i.e. ang susi at naka-encrypt na data. Kung titingnan mong mabuti sub_6364C, makikilala mo ang RC4 algorithm sa loob nito.

Mayroon kaming isang algorithm at isang susi. Subukan nating tukuyin ang pangalan ng klase. Narito ang nangyari: com/taobao/wireless/security/adapter/JNICLibrary. Malaki! Nasa tamang landas tayo.

Command tree

Ngayon kailangan nating maghanap ng hamon RegisterNatives, na magtuturo sa amin sa function doCommandNative. Tingnan natin ang mga function na tinawag mula sa JNI_OnLoad, at nakita namin ito sa 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;
}

At sa katunayan, isang katutubong pamamaraan na may pangalan ay nakarehistro dito doCommandNative. Ngayon alam na natin ang address niya. Tingnan natin kung ano ang ginagawa niya.

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

Sa pamamagitan ng pangalan maaari mong hulaan na narito ang entry point ng lahat ng mga function na napagpasyahan ng mga developer na ilipat sa katutubong library. Interesado kami sa function number 10601.

Makikita mo mula sa code na ang command number ay gumagawa ng tatlong numero: utos/10000, command % 10000 / 100 ΠΈ utos % 10, ibig sabihin, sa aming kaso, 1, 6 at 1. Ang tatlong numerong ito, pati na rin ang isang pointer sa JNIEnv at ang mga argumentong ipinasa sa function ay idinaragdag sa isang istraktura at ipinasa. Gamit ang tatlong numerong nakuha (ipahiwatig natin ang mga ito na N1, N2 at N3), isang command tree ang binuo.

Isang bagay na tulad nito:

Naghahanap ng mga kahinaan sa UC Browser

Ang puno ay dynamic na napuno JNI_OnLoad.
Tatlong numero ang naka-encode sa landas sa puno. Ang bawat dahon ng puno ay naglalaman ng pocked address ng kaukulang function. Ang susi ay nasa parent node. Ang paghahanap ng lugar sa code kung saan ang pag-andar na kailangan namin ay idinagdag sa puno ay hindi mahirap kung naiintindihan mo ang lahat ng mga istrukturang ginamit (hindi namin inilalarawan ang mga ito upang hindi mabulok ang isang medyo malaking artikulo).

Higit pang obfuscation

Natanggap namin ang address ng function na dapat mag-decrypt ng trapiko: 0x5F1AC. Ngunit masyadong maaga para magsaya: ang mga developer ng UC Browser ay naghanda ng isa pang sorpresa para sa amin.

Matapos matanggap ang mga parameter mula sa array na nabuo sa Java code, nakukuha namin
sa function sa address na 0x4D070. At dito naghihintay sa amin ang isa pang uri ng code obfuscation.

Naglagay kami ng dalawang indeks sa R7 at R4:

Naghahanap ng mga kahinaan sa UC Browser

Inilipat namin ang unang index sa R11:

Naghahanap ng mga kahinaan sa UC Browser

Upang makakuha ng isang address mula sa isang talahanayan, gumamit ng isang index:

Naghahanap ng mga kahinaan sa UC Browser

Pagkatapos pumunta sa unang address, ang pangalawang index ay ginagamit, na nasa R4. Mayroong 230 elemento sa talahanayan.

Ano ang gagawin tungkol dito? Maaari mong sabihin sa IDA na ito ay isang switch: I-edit -> Iba pa -> Tukuyin ang switch idiom.

Naghahanap ng mga kahinaan sa UC Browser

Nakakatakot ang resultang code. Ngunit, habang naglalakad sa kagubatan nito, mapapansin mo ang isang tawag sa isang function na pamilyar na sa amin sub_6115C:

Naghahanap ng mga kahinaan sa UC Browser

Nagkaroon ng switch kung saan kung sakaling 3 ay mayroong decryption gamit ang RC4 algorithm. At sa kasong ito, ang istraktura na ipinasa sa function ay napunan mula sa mga parameter na ipinasa sa doCommandNative. Alalahanin natin kung ano ang mayroon tayo doon magicInt na may halagang 16. Tinitingnan namin ang kaukulang kaso - at pagkatapos ng ilang mga transition nakita namin ang code kung saan maaaring makilala ang algorithm.

Naghahanap ng mga kahinaan sa UC Browser

Ito ay AES!

Ang algorithm ay umiiral, ang natitira lamang ay upang makuha ang mga parameter nito: mode, key at, posibleng, ang initialization vector (ang presensya nito ay depende sa operating mode ng AES algorithm). Ang istraktura kasama ang mga ito ay dapat na nabuo sa isang lugar bago ang function na tawag sub_6115C, ngunit ang bahaging ito ng code ay partikular na mahusay na na-obfuscated, kaya ang ideya ay lumitaw na i-patch ang code upang ang lahat ng mga parameter ng decryption function ay itinapon sa isang file.

Patch

Upang hindi manu-manong isulat ang lahat ng patch code sa wika ng pagpupulong, maaari mong ilunsad ang Android Studio, magsulat ng isang function doon na tumatanggap ng parehong mga parameter ng input gaya ng aming function ng pag-decryption at nagsusulat sa isang file, pagkatapos ay kopyahin-i-paste ang code na gagawin ng compiler. bumuo.

Ang aming mga kaibigan mula sa koponan ng UC Browser ay nag-ingat din sa kaginhawaan ng pagdaragdag ng code. Tandaan natin na sa simula ng bawat function ay mayroon tayong garbage code na madaling mapalitan ng iba. Tunay na maginhawa πŸ™‚ Gayunpaman, sa simula ng target na function ay walang sapat na espasyo para sa code na nagse-save ng lahat ng mga parameter sa isang file. Kinailangan kong hatiin ito sa mga bahagi at gumamit ng mga bloke ng basura mula sa mga kalapit na function. May apat na bahagi sa kabuuan.

Ang unang bahagi:

Naghahanap ng mga kahinaan sa UC Browser

Sa arkitektura ng ARM, ang unang apat na mga parameter ng pag-andar ay ipinapasa sa mga rehistro ng R0-R3, ang natitira, kung mayroon man, ay ipinapasa sa stack. Ang LR register ay nagdadala ng return address. Ang lahat ng ito ay kailangang i-save upang gumana ang function pagkatapos naming itapon ang mga parameter nito. Kailangan din naming i-save ang lahat ng mga rehistro na gagamitin namin sa proseso, kaya ginagawa namin ang PUSH.W {R0-R10,LR}. Sa R7 nakuha namin ang address ng listahan ng mga parameter na ipinasa sa function sa pamamagitan ng stack.

Gamit ang function fopen buksan natin ang file /data/local/tmp/aes sa "ab" mode
i.e. para sa karagdagan. Sa R0 nilo-load namin ang address ng pangalan ng file, sa R1 - ang address ng linya na nagpapahiwatig ng mode. At dito nagtatapos ang code ng basura, kaya lumipat kami sa susunod na function. Upang magpatuloy itong gumana, inilalagay namin sa simula ang paglipat sa totoong code ng pag-andar, pag-bypass sa basura, at sa halip na basura ay nagdaragdag kami ng pagpapatuloy ng patch.

Naghahanap ng mga kahinaan sa UC Browser

Tumatawag fopen.

Ang unang tatlong mga parameter ng function aes may type int. Dahil nai-save namin ang mga rehistro sa stack sa simula, maaari lang naming ipasa ang function fwrite ang kanilang mga address sa stack.

Naghahanap ng mga kahinaan sa UC Browser

Susunod, mayroon kaming tatlong istruktura na naglalaman ng laki ng data at isang pointer sa data para sa key, initialization vector at naka-encrypt na data.

Naghahanap ng mga kahinaan sa UC Browser

Sa dulo, isara ang file, ibalik ang mga rehistro at ilipat ang kontrol sa totoong function aes.

Kinokolekta namin ang isang APK na may naka-patch na library, nilagdaan ito, ina-upload ito sa device/emulator, at inilunsad ito. Nakikita namin na ang aming dump ay ginagawa, at maraming data ang isinusulat doon. Gumagamit ang browser ng encryption hindi lamang para sa trapiko, at lahat ng encryption ay dumadaan sa function na pinag-uusapan. Ngunit sa ilang kadahilanan ang kinakailangang data ay wala doon, at ang kinakailangang kahilingan ay hindi nakikita sa trapiko. Upang hindi maghintay hanggang ang UC Browser ay nagdedesisyon na gumawa ng kinakailangang kahilingan, kunin natin ang naka-encrypt na tugon mula sa server na natanggap nang mas maaga at i-patch muli ang application: idagdag ang decryption sa onCreate ng pangunahing aktibidad.

    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

Nagtipon kami, pumirma, nag-install, naglulunsad. Nakatanggap kami ng NullPointerException dahil ang pamamaraan ay bumalik na null.

Sa karagdagang pagsusuri ng code, natuklasan ang isang function na nagde-decipher ng mga interesanteng linya: "META-INF/" at ".RSA". Mukhang bini-verify ng application ang certificate nito. O kahit na bumubuo ng mga susi mula dito. I don't really want to deal with what's happening with the certificate, kaya ipapalusot na lang natin ang tamang certificate. I-patch natin ang naka-encrypt na linya para sa halip na "META-INF/" makuha natin ang "BLABLINF/", gumawa ng folder na may ganoong pangalan sa APK at idagdag ang squirrel browser certificate doon.

Nagtipon kami, pumirma, nag-install, naglulunsad. Bingo! Nasa amin ang susi!

MitM

Nakatanggap kami ng key at isang initialization vector na katumbas ng key. Subukan nating i-decrypt ang tugon ng server sa CBC mode.

Naghahanap ng mga kahinaan sa UC Browser

Nakikita namin ang archive URL, isang bagay na katulad ng MD5, "extract_unzipsize" at isang numero. Sinusuri namin: ang MD5 ng archive ay pareho, ang laki ng hindi naka-pack na library ay pareho. Sinusubukan naming i-patch ang library na ito at ibigay ito sa browser. Para ipakita na nag-load ang aming naka-patch na library, maglulunsad kami ng Intent na gumawa ng SMS na may text na β€œPWNED!” Papalitan namin ang dalawang tugon mula sa server: puds.ucweb.com/upgrade/index.xhtml at upang i-download ang archive. Sa una ay pinapalitan namin ang MD5 (ang laki ay hindi nagbabago pagkatapos i-unpack), sa pangalawa ay binibigyan namin ang archive na may naka-patch na library.

Sinusubukan ng browser na i-download ang archive nang maraming beses, pagkatapos ay nagbibigay ito ng isang error. Parang bagay
Hindi niya gusto. Bilang resulta ng pagsusuri sa madilim na format na ito, lumabas na ang server ay nagpapadala din ng laki ng archive:

Naghahanap ng mga kahinaan sa UC Browser

Ito ay naka-encode sa LEB128. Pagkatapos ng patch, ang laki ng archive na may library ay nagbago ng kaunti, kaya isinasaalang-alang ng browser na ang archive ay na-download nang baluktot, at pagkatapos ng ilang mga pagtatangka ay nagdulot ito ng isang error.

Inaayos namin ang laki ng archive... At – tagumpay! πŸ™‚ Nasa video ang resulta.

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

Mga kahihinatnan at reaksyon ng developer

Sa parehong paraan, maaaring gamitin ng mga hacker ang hindi secure na feature ng UC Browser upang ipamahagi at patakbuhin ang mga nakakahamak na aklatan. Ang mga aklatang ito ay gagana sa konteksto ng browser, kaya matatanggap nila ang lahat ng mga pahintulot ng system nito. Bilang resulta, ang kakayahang magpakita ng mga window ng phishing, pati na rin ang pag-access sa mga gumaganang file ng orange na Chinese squirrel, kabilang ang mga login, password at cookies na nakaimbak sa database.

Nakipag-ugnayan kami sa mga developer ng UC Browser at ipinaalam sa kanila ang tungkol sa problemang nakita namin, sinubukan naming ituro ang kahinaan at ang panganib nito, ngunit wala silang pinag-usapan sa amin. Samantala, patuloy na ipinamalas ng browser ang mapanganib na tampok nito sa simpleng paningin. Ngunit sa sandaling ibunyag namin ang mga detalye ng kahinaan, hindi na posible na balewalain ito tulad ng dati. Marso 27 noon
isang bagong bersyon ng UC Browser 12.10.9.1193 ang inilabas, na nag-access sa server sa pamamagitan ng HTTPS: puds.ucweb.com/upgrade/index.xhtml.

Bilang karagdagan, pagkatapos ng "pag-aayos" at hanggang sa oras ng pagsulat ng artikulong ito, ang pagsubok na magbukas ng PDF sa isang browser ay nagresulta sa isang mensahe ng error na may tekstong "Oops, may nangyaring mali!" Ang isang kahilingan sa server ay hindi ginawa noong sinusubukang magbukas ng PDF, ngunit isang kahilingan ang ginawa noong inilunsad ang browser, na nagpapahiwatig ng patuloy na kakayahang mag-download ng executable code na lumalabag sa mga panuntunan ng Google Play.

Pinagmulan: www.habr.com

Magdagdag ng komento