به دنبال آسیب پذیری در مرورگر UC

به دنبال آسیب پذیری در مرورگر UC

معرفی

در پایان ماه مارس ما گزارش شده است، که آنها یک توانایی پنهان برای بارگیری و اجرای کد تایید نشده در مرورگر UC کشف کردند. امروز به طور مفصل به نحوه انجام این دانلود و نحوه استفاده هکرها از آن برای اهداف خود خواهیم پرداخت.

مدتی پیش، مرورگر UC بسیار تهاجمی تبلیغ و توزیع شد: این مرورگر با استفاده از بدافزار بر روی دستگاه های کاربران نصب شد و از سایت های مختلف تحت پوشش فایل های ویدیویی توزیع شد (یعنی کاربران فکر می کردند که مثلاً یک فیلم پورنو دانلود می کنند، اما در عوض یک APK با این مرورگر دریافت کرد)، از بنرهای ترسناک با پیام هایی استفاده کرد که مرورگر قدیمی، آسیب پذیر و مواردی از این قبیل بود. در گروه رسمی مرورگر UC در VK وجود دارد موضوع، که در آن کاربران می توانند از تبلیغات ناعادلانه شکایت کنند، نمونه های زیادی در آنجا وجود دارد. در سال 2016 حتی وجود داشت تبلیغات ویدیویی به زبان روسی (بله، تبلیغات برای یک مرورگر مسدود کننده تبلیغات).

در زمان نگارش این مقاله، مرورگر UC بیش از 500،000،000 نصب در Google Play دارد. این قابل توجه است - فقط Google Chrome موارد بیشتری دارد. در میان بررسی ها می توانید شکایات بسیار زیادی در مورد تبلیغات و تغییر مسیر به برخی برنامه ها در Google Play مشاهده کنید. این دلیل تحقیقات ما بود: ما تصمیم گرفتیم ببینیم آیا مرورگر UC کار بدی انجام می دهد یا خیر. و معلوم شد که دارد!

در کد برنامه، قابلیت دانلود و اجرای کدهای اجرایی کشف شد، که مغایر با قوانین انتشار اپلیکیشن است در گوگل پلی علاوه بر دانلود کدهای اجرایی، مرورگر UC این کار را به شیوه ای ناامن انجام می دهد که می تواند برای راه اندازی یک حمله MitM استفاده شود. بیایید ببینیم آیا می توانیم چنین حمله ای را انجام دهیم.

هر آنچه در زیر نوشته شده است مربوط به نسخه مرورگر UC است که در زمان مطالعه در Google Play در دسترس بود:

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

بردار حمله

در مانیفست مرورگر UC می‌توانید سرویسی با نام خود توضیحی پیدا کنید com.uc.deployment.UpgradeDeployService.

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

هنگامی که این سرویس شروع به کار کرد، مرورگر یک درخواست POST به آن ارسال می کند puds.ucweb.com/upgrade/index.xhtml، که مدتی پس از شروع در ترافیک دیده می شود. در پاسخ، او ممکن است دستوری برای دانلود برخی به روز رسانی یا ماژول جدید دریافت کند. در حین تجزیه و تحلیل، سرور چنین دستوراتی را نداد، اما متوجه شدیم که وقتی می خواهیم یک PDF را در مرورگر باز کنیم، درخواست دوم را به آدرس مشخص شده در بالا می دهد و پس از آن کتابخانه اصلی را دانلود می کند. برای انجام این حمله، تصمیم گرفتیم از این ویژگی مرورگر UC استفاده کنیم: توانایی باز کردن PDF با استفاده از یک کتابخانه بومی، که در APK نیست و در صورت لزوم از اینترنت دانلود می‌کند. شایان ذکر است که از نظر تئوری، مرورگر UC می‌تواند مجبور شود چیزی را بدون تعامل کاربر بارگیری کند - اگر به درخواستی که پس از راه‌اندازی مرورگر اجرا می‌شود، پاسخ درستی ارائه دهید. اما برای انجام این کار، باید پروتکل تعامل با سرور را با جزئیات بیشتری مطالعه کنیم، بنابراین تصمیم گرفتیم که ویرایش پاسخ رهگیری شده و جایگزینی کتابخانه برای کار با 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، یکی از سه گزینه ممکن را نشان می دهد.

ما به تجزیه و تحلیل کد ادامه می دهیم. پس از چند پرش، خود را در روشی با نام خود توضیحی می یابیم رمزگشایی BytesByKey.

در اینجا دو بایت دیگر از پاسخ ما جدا می شود و یک رشته از آنها به دست می آید. واضح است که به این ترتیب کلید رمزگشایی پیام انتخاب می شود.

    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 به معنای رمزگشایی است، همانطور که در روش وجود دارد انجام نهایی کلاس سیستم javax.crypto.Cipher. و همه اینها به یک روتر خاص با شماره 10601 منتقل می شود - ظاهراً این شماره فرمان است.

بعد از زنجیره انتقال بعدی، کلاسی را پیدا می کنیم که اینترفیس را پیاده سازی می کند IRouterComponent و روش doCommand:

package com.alibaba.wireless.security.mainplugin;
import com.alibaba.wireless.security.framework.IRouterComponent;
import com.taobao.wireless.security.adapter.JNICLibrary;
public class a implements IRouterComponent {
public a() {
super();
}
public Object doCommand(int arg2, Object[] arg3) {
return JNICLibrary.doCommandNative(arg2, arg3);
}
}

و همچنین کلاس کتابخانه JNICL، که در آن متد بومی اعلان شده است doCommandNative:

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

این بدان معنی است که ما باید یک متد را در کد اصلی پیدا کنیم doCommandNative. و اینجاست که سرگرمی شروع می شود.

مبهم سازی کد ماشین

در پرونده libsgmain.so (که در واقع یک jar است و در آن پیاده‌سازی برخی از رابط‌های مرتبط با رمزگذاری را دقیقاً در بالا پیدا کردیم) یک کتابخانه بومی وجود دارد: libsgmainso-6.4.36.so. ما آن را در IDA باز می کنیم و یک دسته دیالوگ باکس با خطا دریافت می کنیم. مشکل این است که جدول هدر بخش نامعتبر است. این به عمد انجام می شود تا تجزیه و تحلیل را پیچیده کند.

به دنبال آسیب پذیری در مرورگر UC

اما نیازی به آن نیست: برای بارگیری صحیح یک فایل ELF و تجزیه و تحلیل آن، یک جدول هدر برنامه کافی است. بنابراین، ما به سادگی جدول بخش را حذف می کنیم و فیلدهای مربوطه را در هدر صفر می کنیم.

به دنبال آسیب پذیری در مرورگر UC

فایل را دوباره در IDA باز کنید.

دو راه وجود دارد که به ماشین مجازی جاوا بگوییم دقیقاً در کجای کتابخانه بومی پیاده سازی متدی که در کد جاوا به عنوان بومی اعلام شده است، قرار دارد. اولین مورد این است که به آن یک نام گونه بدهید 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، که حاوی آدرس برگشتی از تابع است، ذخیره می کند. . آخرین دستورالعمل رجیسترهای ذخیره شده را بازیابی می کند و آدرس برگشتی بلافاصله در ثبات PC قرار می گیرد - بنابراین از تابع باز می گردد. اما اگر دقت کنید متوجه خواهید شد که دستور ماقبل آخر آدرس بازگشت ذخیره شده در پشته را تغییر می دهد. بیایید محاسبه کنیم که بعد از آن چگونه خواهد بود
اجرای کد یک آدرس مشخص 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

و در واقع، در اینجا یک dword در آدرسی که در LR قرار دارد گرفته می شود، به این آدرس اضافه می شود، پس از آن مقدار آدرس حاصل گرفته شده و در پشته قرار می گیرد. همچنین 4 به LR اضافه می شود تا پس از بازگشت از تابع، همین افست حذف شود. پس از آن دستور POP {R1} مقدار حاصل را از پشته می گیرد. اگر به آنچه در آدرس 0xB4BA + 0xEA = 0xB5A4 قرار دارد نگاه کنید، چیزی شبیه به جدول آدرس خواهید دید:

به دنبال آسیب پذیری در مرورگر UC

برای پچ کردن این طرح، باید دو پارامتر از کد دریافت کنید: offset و شماره ثبت که می‌خواهید نتیجه را در آن قرار دهید. برای هر ثبت نام ممکن، باید یک کد از قبل آماده کنید.

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 را به شروع واقعی آن نشان دهید. تمام کدهای تابع را کنار هم می‌گذارد و می‌توان آن را با استفاده از HexRay دیکامپایل کرد.

رمزگشایی رشته ها

ما یاد گرفته ایم که با مبهم سازی کد ماشین در کتابخانه مقابله کنیم 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 علاقه مند هستیم.

از روی کد می توانید ببینید که شماره فرمان سه عدد تولید می کند: command/10000, فرمان % 10000/100 и فرمان % 10، یعنی در مورد ما، 1، 6 و 1. این سه عدد، و همچنین یک اشاره گر به JNIEnv و آرگومان های ارسال شده به تابع به یک ساختار اضافه می شوند و منتقل می شوند. با استفاده از سه عدد به دست آمده (بیایید آنها را N1، N2 و N3 نشان دهیم)، یک درخت فرمان ساخته می شود.

چیزی مثل این:

به دنبال آسیب پذیری در مرورگر UC

درخت به صورت پویا پر می شود JNI_OnLoad.
سه عدد مسیر را در درخت رمز می کنند. هر برگ درخت حاوی آدرس بسته شده تابع مربوطه است. کلید در گره والد است. یافتن مکانی در کد که در آن تابع مورد نیاز ما به درخت اضافه شده است دشوار نیست اگر تمام ساختارهای استفاده شده را درک کنید (ما آنها را توصیف نمی کنیم تا یک مقاله نسبتاً بزرگ را پر نکنید).

ابهام بیشتر

ما آدرس تابعی را دریافت کردیم که باید ترافیک را رمزگشایی کند: 0x5F1AC. اما هنوز برای خوشحالی زود است: توسعه دهندگان UC Browser یک سورپرایز دیگر را برای ما آماده کرده اند.

پس از دریافت پارامترها از آرایه ای که در کد جاوا تشکیل شده است، دریافت می کنیم
به تابع در آدرس 0x4D070. و در اینجا نوع دیگری از مبهم سازی کد در انتظار ما است.

ما دو شاخص را در R7 و R4 قرار می دهیم:

به دنبال آسیب پذیری در مرورگر UC

اولین شاخص را به R11 تغییر می دهیم:

به دنبال آسیب پذیری در مرورگر UC

برای دریافت آدرس از جدول، از یک شاخص استفاده کنید:

به دنبال آسیب پذیری در مرورگر UC

بعد از رفتن به آدرس اول از اندیس دوم که در R4 است استفاده می شود. 230 عنصر در جدول وجود دارد.

در مورد آن چه باید کرد؟ می توانید به IDA بگویید که این یک سوئیچ است: Edit -> Other -> Specify switch idiom.

به دنبال آسیب پذیری در مرورگر UC

کد حاصل وحشتناک است. اما با طی کردن مسیر خود در جنگل آن، می توانید متوجه فراخوانی یک تابع از قبل آشنا شوید sub_6115C:

به دنبال آسیب پذیری در مرورگر UC

یک سوئیچ وجود داشت که در مورد 3 رمزگشایی با استفاده از الگوریتم RC4 وجود داشت. و در این حالت ساختار ارسال شده به تابع از پارامترهای ارسال شده به آن پر می شود doCommandNative. بیایید به یاد بیاوریم که در آنجا چه داشتیم magicInt با مقدار 16. ما به مورد مربوطه نگاه می کنیم - و پس از چندین انتقال، کدی را پیدا می کنیم که توسط آن الگوریتم را می توان شناسایی کرد.

به دنبال آسیب پذیری در مرورگر UC

این AES است!

الگوریتم وجود دارد، تنها چیزی که باقی می ماند بدست آوردن پارامترهای آن است: حالت، کلید و احتمالاً بردار اولیه (حضور آن بستگی به حالت عملکرد الگوریتم AES دارد). ساختار با آنها باید جایی قبل از فراخوانی تابع تشکیل شود sub_6115C، اما این بخش از کد به خصوص به خوبی مبهم است، بنابراین این ایده ایجاد می شود که کد را اصلاح کنیم تا تمام پارامترهای تابع رمزگشایی در یک فایل ریخته شوند.

پچ

برای اینکه همه پچ کدها را به صورت دستی به زبان اسمبلی ننویسید، می‌توانید اندروید استودیو را راه‌اندازی کنید، تابعی را در آنجا بنویسید که پارامترهای ورودی مشابه تابع رمزگشایی ما را دریافت کند و در فایلی بنویسد، سپس کدی را که کامپایلر انجام می‌دهد، کپی و پیست کنید. تولید می کنند.

دوستان ما از تیم مرورگر UC نیز به راحتی اضافه کردن کد را انجام دادند. به یاد داشته باشید که در ابتدای هر تابع، کد زباله ای داریم که به راحتی می توان آن را با هر تابع دیگری جایگزین کرد. بسیار راحت 🙂 با این حال، در ابتدای تابع هدف فضای کافی برای کدی که تمام پارامترها را در یک فایل ذخیره می کند وجود ندارد. مجبور شدم آن را به قطعات تقسیم کنم و از بلوک های زباله از توابع همسایه استفاده کنم. کلا چهار قسمت بود

قسمت اول:

به دنبال آسیب پذیری در مرورگر UC

در معماری ARM، چهار پارامتر تابع اول از طریق رجیسترهای R0-R3، بقیه، در صورت وجود، از پشته عبور داده می‌شوند. رجیستر LR نشانی برگشت را دارد. همه اینها باید ذخیره شوند تا بعد از اینکه پارامترهایش را تخلیه کردیم، تابع بتواند کار کند. ما همچنین باید همه رجیسترهایی را که در فرآیند استفاده خواهیم کرد ذخیره کنیم، بنابراین PUSH.W {R0-R10,LR} را انجام می دهیم. در R7 آدرس لیست پارامترهایی که از طریق پشته به تابع ارسال شده است را دریافت می کنیم.

با استفاده از تابع باز کردن بیایید فایل را باز کنیم /data/local/tmp/aes در حالت "ab".
یعنی برای اضافه شدن در R0 آدرس نام فایل را بارگیری می کنیم، در R1 - آدرس خطی که حالت را نشان می دهد. و در اینجا کد زباله به پایان می رسد، بنابراین ما به تابع بعدی می رویم. برای اینکه به کار خود ادامه دهد، در ابتدا انتقال به کد واقعی تابع را با دور زدن زباله ها قرار می دهیم و به جای زباله، ادامه پچ را اضافه می کنیم.

به دنبال آسیب پذیری در مرورگر UC

زنگ میزنیم باز کردن.

سه پارامتر اول تابع aes نوع داشته باشد INT. از آنجایی که در ابتدا رجیسترها را در پشته ذخیره کردیم، می‌توانیم به سادگی تابع را منتقل کنیم fwrite آدرس آنها در پشته

به دنبال آسیب پذیری در مرورگر UC

بعد ما سه ساختار داریم که شامل اندازه داده و یک اشاره گر به داده های کلید، بردار اولیه و داده های رمزگذاری شده است.

به دنبال آسیب پذیری در مرورگر UC

در پایان فایل را ببندید، ثبات ها را بازیابی کنید و کنترل را به تابع واقعی انتقال دهید aes.

ما یک APK را با یک کتابخانه وصله‌شده جمع‌آوری می‌کنیم، آن را امضا می‌کنیم، آن را در دستگاه/شبیه‌ساز آپلود می‌کنیم و راه‌اندازی می‌کنیم. می بینیم که Dump ما در حال ایجاد است و داده های زیادی در آنجا نوشته می شود. مرورگر نه تنها برای ترافیک از رمزگذاری استفاده می کند، و تمام رمزگذاری از طریق عملکرد مورد نظر انجام می شود. اما به دلایلی داده های لازم وجود ندارد و درخواست مورد نیاز در ترافیک قابل مشاهده نیست. برای اینکه منتظر نمانیم تا مرورگر 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 دریافت می کنیم زیرا متد null را برگرداند.

در طول تجزیه و تحلیل بیشتر کد، تابعی کشف شد که خطوط جالبی را رمزگشایی می کند: "META-INF/" و ".RSA". به نظر می رسد برنامه در حال تأیید گواهی خود است. یا حتی از آن کلید تولید می کند. من واقعاً نمی‌خواهم با آنچه که در مورد گواهی اتفاق می‌افتد مقابله کنم، بنابراین ما فقط گواهی صحیح را به آن می‌دهیم. بیایید خط رمزگذاری شده را وصله کنیم تا به جای "META-INF/" "BLABLINF/" را دریافت کنیم، یک پوشه با آن نام در APK ایجاد کنیم و گواهی مرورگر squirrel را در آنجا اضافه کنیم.

ما جمع آوری می کنیم، امضا می کنیم، نصب می کنیم، راه اندازی می کنیم. بینگو! ما کلید داریم!

MitM

ما یک کلید و یک بردار اولیه برابر با کلید دریافت کردیم. بیایید سعی کنیم پاسخ سرور را در حالت CBC رمزگشایی کنیم.

به دنبال آسیب پذیری در مرورگر UC

ما URL آرشیو، چیزی شبیه به MD5، "extract_unzipsize" و یک عدد را می بینیم. بررسی می کنیم: MD5 آرشیو یکسان است، اندازه کتابخانه بدون بسته بندی یکسان است. ما سعی می کنیم این کتابخانه را وصله کنیم و به مرورگر بدهیم. برای اینکه نشان دهیم کتابخانه وصله‌شده ما بارگیری شده است، یک Intent برای ایجاد پیامک با متن «PWNED!» راه‌اندازی می‌کنیم. ما دو پاسخ از سرور را جایگزین خواهیم کرد: puds.ucweb.com/upgrade/index.xhtml و برای دانلود آرشیو در اولی MD5 را جایگزین می کنیم (اندازه پس از باز کردن بسته بندی تغییر نمی کند)، در دومی آرشیو را با کتابخانه وصله شده می دهیم.

مرورگر چندین بار سعی می کند آرشیو را دانلود کند، پس از آن خطا می دهد. ظاهرا یه چیزی
او دوست ندارد. در نتیجه تجزیه و تحلیل این قالب مبهم، مشخص شد که سرور نیز اندازه آرشیو را منتقل می کند:

به دنبال آسیب پذیری در مرورگر UC

در LEB128 کدگذاری شده است. پس از پچ، اندازه بایگانی با کتابخانه کمی تغییر کرد، بنابراین مرورگر در نظر گرفت که بایگانی به صورت کج دانلود شده است و پس از چندین بار تلاش با خطا مواجه شد.

ما اندازه آرشیو را تنظیم می کنیم ... و - پیروزی! 🙂 نتیجه در ویدیو هست.

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

پیامدها و واکنش توسعه دهنده

به همین ترتیب، هکرها می توانند از ویژگی ناامن UC Browser برای توزیع و اجرای کتابخانه های مخرب استفاده کنند. این کتابخانه ها در زمینه مرورگر کار خواهند کرد، بنابراین تمام مجوزهای سیستم آن را دریافت خواهند کرد. در نتیجه، امکان نمایش ویندوزهای فیشینگ و همچنین دسترسی به فایل های کاری سنجاب چینی نارنجی، از جمله لاگین، رمز عبور و کوکی های ذخیره شده در پایگاه داده.

ما با توسعه دهندگان UC Browser تماس گرفتیم و مشکلی را که پیدا کردیم به آنها اطلاع دادیم، سعی کردیم به آسیب پذیری و خطر آن اشاره کنیم، اما آنها چیزی با ما صحبت نکردند. در همین حال، مرورگر همچنان به نمایش ویژگی خطرناک خود در یک دید ساده ادامه داد. اما زمانی که جزئیات این آسیب پذیری را فاش کردیم، دیگر امکان نادیده گرفتن آن مانند قبل وجود نداشت. 27 مارس بود
نسخه جدیدی از UC Browser 12.10.9.1193 منتشر شد که از طریق HTTPS به سرور دسترسی داشت: puds.ucweb.com/upgrade/index.xhtml.

علاوه بر این، پس از "رفع" و تا زمان نوشتن این مقاله، تلاش برای باز کردن PDF در مرورگر منجر به پیام خطایی با متن "اوه، مشکلی پیش آمد!" هنگام تلاش برای باز کردن یک PDF، درخواستی از سرور انجام نشد، اما هنگام راه‌اندازی مرورگر درخواستی ارسال شد که به ادامه توانایی دانلود کدهای اجرایی در نقض قوانین Google Play اشاره دارد.

منبع: www.habr.com

اضافه کردن نظر