البحث عن نقاط الضعف في متصفح UC


مقدمة


في نهاية شهر مارس ، أبلغنا أننا اكتشفنا قدرة خفية على تنزيل وتشغيل شفرة لم يتم التحقق منها في متصفح UC. اليوم سنحلل بالتفصيل كيفية حدوث هذا التنزيل وكيف يمكن للمتسللين استخدامه لأغراضهم الخاصة.

منذ بعض الوقت ، تم الإعلان عن UC Browser وتوزيعه بقوة: تم تثبيته على أجهزة المستخدمين التي تستخدم برامج ضارة ، تم توزيعها من مواقع مختلفة تحت ستار ملفات الفيديو (على سبيل المثال ، اعتقد المستخدمون أنهم قاموا بتنزيلها ، على سبيل المثال ، مقطع إباحي ، ولكن بدلاً من ذلك حصلوا على ملف APK باستخدام هذا المتصفح) ، تستخدم لافتات مخيفة مع رسائل تقول أن المتصفح عفا عليها الزمن ، وعرضة للخطر وأشياء من هذا القبيل. تتضمن مجموعة UC Browser الرسمية في VK موضوعًا يمكن للمستخدمين من خلاله الشكوى من الإعلانات غير العادلة ، وهناك العديد من الأمثلة. في عام 2016 ، كان هناك حتى إعلانات فيديو باللغة الروسية (نعم ، إعلانات من متصفح يمنع الإعلانات).

في وقت كتابة هذا التقرير ، يوجد لدى UC Browser أكثر من 500،000،000 عملية تثبيت على Google Play. هذا مثير للإعجاب - فقط Google Chrome لديه المزيد. من بين المراجعات ، يمكنك رؤية الكثير من الشكاوى حول الإعلان وإعادة التوجيه إلى بعض التطبيقات على Google Play. كان هذا هو سبب الدراسة: قررنا معرفة ما إذا كان متصفح UC يقوم بعمل سيء. واتضح أنه كان يفعل ذلك!

كشف رمز التطبيق عن القدرة على تنزيل وتشغيل التعليمات البرمجية القابلة للتنفيذ ، مما يتعارض مع قواعد نشر التطبيقات على Google Play. بالإضافة إلى حقيقة أن UC Browser يقوم بتنزيل التعليمات البرمجية القابلة للتنفيذ ، فإنه يجعلها غير آمنة ، والتي يمكن استخدامها لتنفيذ هجوم MitM. دعونا نرى ما اذا كنا نجح في تنفيذ مثل هذا الهجوم.

كل ما هو مكتوب أدناه مناسب لإصدار UC Browser الذي كان موجودًا على Google Play وقت الدراسة:

package: com.UCMobile.intl versionName: 12.10.8.1172 versionCode: 10598 sha1 APK-: f5edb2243413c777172f6362876041eb0c3a928c 

ناقل الهجوم


في ملف UC Browser ، يمكنك العثور على خدمة تسمى com.uc.deployment.UpgradeDeployService .

  <service android:exported="false" android:name="com.uc.deployment.UpgradeDeployService" android:process=":deploy" /> 

عند بدء تشغيل هذه الخدمة ، يقوم المستعرض بتنفيذ طلب POST على puds.ucweb.com/upgrade/index.xhtml ، والذي يمكن ملاحظته في حركة المرور في وقت ما بعد البداية. استجابة لذلك ، قد يتلقى أمرًا لتنزيل تحديث أو وحدة نمطية جديدة. في عملية التحليل ، لم يقدم الخادم مثل هذه الأوامر ، لكننا لاحظنا أنه عند محاولة فتح PDF في المتصفح ، فإنه يقدم طلبًا ثانيًا على العنوان أعلاه ، وبعد ذلك يقوم بتنزيل المكتبة الأصلية. لتنفيذ الهجوم ، قررنا استخدام هذه الميزة في UC Browser: القدرة على فتح PDF باستخدام مكتبة أصلية ، ليست في APK والتي يتم تنزيلها ، إذا لزم الأمر ، من الإنترنت. تجدر الإشارة إلى أنه من الناحية النظرية ، يمكن إجبار UC Browser على تنزيل شيء دون تدخل من المستخدم - إذا قدمت استجابة صحيحة بشكل صحيح لطلب تم تنفيذه بعد بدء تشغيل المتصفح. لكن لهذا نحتاج إلى دراسة بروتوكول التفاعل مع الخادم بمزيد من التفصيل ، لذلك قررنا أنه كان من الأسهل تحرير الاستجابة المعترضة واستبدال المكتبة للعمل مع PDF.

لذلك ، عندما يريد المستخدم فتح PDF مباشرة في متصفح ، يمكن رؤية الطلبات التالية في حركة المرور:



أولاً ، يأتي طلب POST على puds.ucweb.com/upgrade/index.xhtml ، وبعد ذلك
قم بتنزيل الأرشيف مع مكتبة لعرض تنسيقات PDF والمكاتب. من المنطقي أن نفترض أنه في أول طلب يتم إرسال معلومات حول النظام (على الأقل البنية لإعطاء المكتبة اللازمة) ، واستجابة لذلك ، يتلقى المستعرض بعض المعلومات حول المكتبة التي يجب تنزيلها: العنوان ، وربما شيء آخر. المشكلة هي أن هذا الطلب مشفر.

طلب مقتطف

مقتطف الاستجابة







يتم حزم المكتبة نفسها في ZIP وليس مشفرة.



البحث رمز فك التشفير المرور



دعنا نحاول فك تشفير استجابة الخادم. ننظر إلى شفرة 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(); mb(v3_4); h v4_2 = new h(); mb(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.baacgetCpuArch())); v3_2.add(g.fs("cpu_arch", com.uc.baacPb())); String v4_3 = com.uc.baacPd(); 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.baacPc())); 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.baacJl()))); v3_2.add(g.fs("ram_1", String.valueOf(com.uc.baahPo()))); v3_2.add(g.fs("totalram", String.valueOf(com.uc.baahOL()))); 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 = gi(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.dci 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.dci$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); mb(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 = gj(arg11[2], v7); } if(v7 == null) { goto label_57; } if(v5 != 0) { v1_1 = gP(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 = gb(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" ، على سبيل المثال) ، ينبغي استدعاء طريقة هنا لفك تشفير استجابة الخادم.
نمر إلى طريقة جي جي . لاحظ أن البايت عند الإزاحة 2 (على سبيل المثال ، 0x1F في حالتنا) يتم نقلها إليه كوسيطة أولى ، واستجابة الخادم دون
أول 16 بايت.

  public static byte[] j(int arg1, byte[] arg2) { if(arg1 == 1) { arg2 = cc(arg2, c.adu); } else if(arg1 == 11) { arg2 = m.aF(arg2); } else if(arg1 != 0x1F) { } else { arg2 = EncryptHelper.decrypt(arg2); } return arg2; } 

من الواضح ، هناك خيار من خوارزمية فك التشفير ، والبايت نفسه ، والذي في منطقتنا
الحالة هي 0x1F ، تشير إلى واحد من ثلاثة خيارات ممكنة.

نواصل تحليل الرمز. بعد بضع القفزات ، ندخل في طريقة مع اسم الحديث decryptBytesByKey .

هنا ، يتم فصل وحدتي بايت آخرين عن إجابتنا ، ويتم الحصول على سلسلة منها. من الواضح أنه بهذه الطريقة يتم اختيار المفتاح لفك تشفير الرسالة.

  private static byte[] decryptBytesByKey(byte[] bytes) { byte[] v0 = null; if(bytes != null) { try { if(bytes.length < EncryptHelper.PREFIX_BYTES_SIZE) { } else if(bytes.length == EncryptHelper.PREFIX_BYTES_SIZE) { return v0; } else { byte[] prefix = new byte[EncryptHelper.PREFIX_BYTES_SIZE]; // 2  System.arraycopy(bytes, 0, prefix, 0, prefix.length); String keyId = c.ayR().d(ByteBuffer.wrap(prefix).getShort()); //   if(keyId == null) { return v0; } else { a v2 = EncryptHelper.ayL(); if(v2 == null) { return v0; } else { byte[] enrypted = new byte[bytes.length - EncryptHelper.PREFIX_BYTES_SIZE]; System.arraycopy(bytes, EncryptHelper.PREFIX_BYTES_SIZE, enrypted, 0, enrypted.length); return v2.l(keyId, enrypted); } } } } catch(SecException v7_1) { EncryptHelper.handleDecryptException(((Throwable)v7_1), v7_1.getErrorCode()); return v0; } catch(Throwable v7) { EncryptHelper.handleDecryptException(v7, 2); return v0; } } return v0; } 

بالنظر إلى المستقبل ، نلاحظ أنه في هذه المرحلة لم يتم الحصول على المفتاح بعد ، ولكن فقط "معرفه". الحصول على المفتاح هو أكثر تعقيدا قليلا.

في الطريقة التالية ، يتم إضافة اثنين آخرين إلى المعلمات الحالية ، وهناك أربع منها: الرقم السحري 16 ، معرف المفتاح ، البيانات المشفرة وسلسلة غير مفهومة (في حالتنا ، فارغة).

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

بعد سلسلة من التحولات ، وصلنا إلى طريقة staticBinarySafeDecryptNoB64 الخاصة بالواجهة com.alibaba.wireless.security.open.staticdataencrypt.IStaticDataEncryptComponent . لا توجد فئات في رمز التطبيق الرئيسي الذي يقوم بتنفيذ هذه الواجهة. هذه الفئة موجودة في الملف lib / armeabi-v7a / libsgmain.so ، وهو في الواقع ليس كذلك. لكن .jar. يتم تطبيق طريقة الاهتمام بالنسبة لنا على النحو التالي:

 package com.alibaba.wireless.security.ai; // ... public class a implements IStaticDataEncryptComponent { private ISecurityGuardPlugin a; // ... private byte[] a(int mode, int magicInt, int xzInt, String keyId, byte[] encrypted, String magicString) { return this.a.getRouter().doCommand(10601, new Object[]{Integer.valueOf(mode), Integer.valueOf(magicInt), Integer.valueOf(xzInt), keyId, encrypted, magicString}); } // ... private byte[] b(int magicInt, String keyId, byte[] encrypted, String magicString) { return this.a(2, magicInt, 0, keyId, encrypted, magicString); } // ... public byte[] staticBinarySafeDecryptNoB64(int magicInt, String keyId, byte[] encrypted, String magicString) throws SecException { if(keyId != null && keyId.length() > 0 && magicInt >= 0 && magicInt < 19 && encrypted != null && encrypted.length > 0) { return this.b(magicInt, keyId, encrypted, magicString); } throw new SecException("", 301); } //... } 

هنا ، يتم استكمال قائمة المعلمات لدينا من قبل اثنين من الأعداد الصحيحة: 2 و 0
للجميع ، 2 يعني فك التشفير ، كما هو الحال في طريقة doFinal لفئة نظام javax.crypto.Cipher . ويتم نقل كل هذا إلى جهاز توجيه معين برقم 10601 - وهذا ، على ما يبدو ، هو رقم الأمر.

بعد سلسلة الانتقال التالية ، نجد فئة تنفذ واجهة IRouterComponent وطريقة doCommand :

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

وأيضًا فئة JNICLibrary ، التي تعلن عن الأسلوب الأصلي لـ 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 . فتحه في المؤسسة الدولية للتنمية والحصول على مجموعة من مربعات الحوار مع الأخطاء. المشكلة هي أن جدول رأس القسم غير صالح. يتم ذلك على وجه التحديد لتعقيد التحليل.



ولكن ليس هناك حاجة أيضًا: من أجل تحميل ملف ELF وتحليله بشكل صحيح ، يكون جدول رأس البرنامج كافياً. لذلك ، نقوم ببساطة بحذف جدول القسم عن طريق وضع علامة على الحقول المقابلة في الرأس.



مرة أخرى افتح الملف في المؤسسة الدولية للتنمية.

هناك طريقتان لإخبار جهاز Java الظاهري بالتحديد عن مكان تنفيذ الطريقة المعلنة في كود Java في المكتبة الأصلية. الأول هو إعطائها اسم النموذج Java_package_name_ClassName_Method_name .

والثاني هو تسجيله عند تحميل المكتبة (في وظيفة JNI_OnLoad )
عن طريق استدعاء وظيفة RegisterNatives .

في حالتنا ، إذا استخدمت الطريقة الأولى ، يجب أن يكون الاسم كما يلي: Java_com_taobao_wireless_security_adapter_JNICLibrary_doCommandNative .

من بين الوظائف المصدرة ، لا يوجد شيء من هذا القبيل ، لذلك تحتاج إلى البحث عن استدعاء RegisterNatives .
نذهب إلى وظيفة JNI_OnLoad ونرى الصورة التالية:



ما الذي يحدث هنا؟ للوهلة الأولى ، تعد بداية ونهاية الوظيفة نموذجية لهندسة ARM. التعليمة الأولى على المكدس تخزن محتويات السجلات التي ستستخدمها الوظيفة في عملها (في هذه الحالة R0 ، R1 و R2) ، وكذلك محتويات السجل LR ، حيث يوجد عنوان المرسل من الوظيفة. باستخدام التعليمة الأخيرة ، تتم استعادة السجلات المحفوظة ، ويوضع عنوان المرسل على الفور في سجل الكمبيوتر الشخصي - وبالتالي يعود من الوظيفة. ولكن إذا نظرت عن كثب ، ستلاحظ أن التعليمات قبل الأخيرة تغير عنوان المرسل المخزن على الرصة. نحسب ما سوف يكون بعد
تنفيذ التعليمات البرمجية. يتم تحميل عنوان معين 0xB130 في R1 ، يتم طرح 5 منه ، ثم يتم نقله إلى R0 وتتم إضافة 0x10 إليه. اتضح 0xB13B. وبالتالي ، تعتقد المؤسسة الدولية للتنمية أن هناك عائدًا طبيعيًا من الوظيفة في التعليمة الأخيرة ، ولكن في الحقيقة هناك انتقال إلى العنوان المحسوب 0xB13B.

تجدر الإشارة إلى أن معالجات ARM لها وضعان ومجموعتان من التعليمات: ARM و Thumb. يخبر الجزء الأقل أهمية من العنوان المعالج بأنه يتم استخدام مجموعة التعليمات. أي أن العنوان هو في الواقع 0xB13A ، وتشير الوحدة الموجودة في البت المنخفض إلى وضع الإبهام.

في بداية كل وظيفة في هذه المكتبة يضاف "محول" مماثل و
رمز القمامة. علاوة على ذلك ، لن نتناولها بالتفصيل - نحن نتذكر فقط
أن البداية الحقيقية لجميع الوظائف تقريبًا هي أبعد قليلاً.

نظرًا لعدم وجود انتقال صريح إلى 0xB13A في التعليمات البرمجية ، لم تتعرف المؤسسة IDA نفسها على وجود الكود في هذا المكان. للسبب نفسه ، لا يتعرف على معظم الكود الموجود في المكتبة كرمز ، مما يجعل التحليل صعبًا إلى حد ما. نقول للمؤسسة الدولية للتنمية أن الرمز موجود هنا ، والنتيجة هي:



في 0xB144 ، يبدأ الجدول بوضوح. ماذا عن sub_494C؟



عندما يتم استدعاء هذه الوظيفة في سجل LR ، نحصل على عنوان الجدول المذكور أعلاه (0xB144). في R0 ، الفهرس في هذا الجدول. وهذا هو ، يتم أخذ قيمة من الجدول ، تضاف إلى LR والحصول عليها
عنوان للذهاب إلى. دعنا نحاول حسابه: 0xB144 + [0xB144 + 8 * 4] = 0xB144 + 0x120 = 0xB264. نذهب إلى العنوان المستلم ونرى بضعة تعليمات مفيدة ومرة ​​أخرى الانتقال إلى 0xB140:



الآن سيكون هناك تحول عن طريق تعويض مع مؤشر 0x20 من الجدول.

استنادا إلى حجم الجدول ، هناك العديد من هذه التحولات في التعليمات البرمجية. السؤال الذي يطرح نفسه هو ما إذا كان من الممكن التعامل بطريقة أو بأخرى مع هذا بشكل تلقائي ، دون حساب العناوين يدويًا. تقدم البرامج النصية والقدرة على تصحيح الكود في المؤسسة الدولية للتنمية مساعدتنا:

 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:



المؤسسة الدولية للتنمية مرة أخرى لم تتعرف على هذا الموقع كرمز. نساعدها ونرى بناء آخر هناك:



لا تبدو التعليمات بعد BLX ذات مغزى كبير ، إنها أشبه بنوع من التحيز. ننظر في sub_4964:



في الواقع ، هنا يتم أخذ الكلمة في العنوان الموجود في LR ، المضافة إلى هذا العنوان ، وبعد ذلك يتم أخذ القيمة في العنوان المستلم ودفعها إلى الحزمة. أيضا ، يتم إضافة 4 إلى LR ، بحيث بعد العودة من وظيفة للقفز هذه الإزاحة نفسها. ثم يسحب الأمر POP {R1} القيمة المستلمة من المكدس. إذا نظرت إلى العنوان 0xB4BA + 0xEA = 0xB5A4 ، يمكنك رؤية شيء مشابه لجدول العنوان:



لتصحيح هذا التصميم ، تحتاج إلى الحصول على معلمتين من التعليمات البرمجية: الإزاحة ورقم التسجيل الذي تريد وضع النتيجة فيه. لكل سجل ممكن ، سوف تضطر إلى إعداد قطعة من التعليمات البرمجية مقدما.

 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] == '\x5d\xf8\x04': 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 - وتشغيل البرنامج النصي:



بالإضافة إلى الإنشاءات المذكورة بالفعل في الكود ، إليك أيضًا هذه:



كما في الحالة السابقة ، بعد تعليمات BLX ، هناك إزاحة:



نأخذ الإزاحة إلى العنوان من 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" 

نتيجة البرنامج النصي:



بعد تصحيح كل شيء في الوظيفة ، يمكنك توجيه المؤسسة إلى بدايتها الحقيقية. سوف يجمع كل رمز الوظيفة في قطع ، ويمكن فك تشفيره باستخدام HexRays.

سلاسل فك


لقد تعلمنا كيفية التعامل مع تشفير رمز الجهاز في مكتبة libsgmainso-6.4.36.so من متصفح UC وحصلنا على رمز وظيفة JNI_OnLoad .

 int __fastcall real_JNI_OnLoad(JavaVM *vm) { int result; // r0 jclass clazz; // r0 MAPDST int v4; // r0 JNIEnv *env; // r4 int v6; // [sp-40h] [bp-5Ch] int v7; // [sp+Ch] [bp-10h] v7 = *(_DWORD *)off_8AC00; if ( !vm ) goto LABEL_39; sub_7C4F4(); env = (JNIEnv *)sub_7C5B0(0); if ( !env ) goto LABEL_39; v4 = sub_72CCC(); sub_73634(v4); sub_73E24(&unk_83EA6, &v6, 49); clazz = (jclass)((int (__fastcall *)(JNIEnv *, int *))(*env)->FindClass)(env, &v6); if ( clazz && (sub_9EE4(), sub_71D68(env), sub_E7DC(env) >= 0 && sub_69D68(env) >= 0 && sub_197B4(env, clazz) >= 0 && sub_E240(env, clazz) >= 0 && sub_B8B0(env, clazz) >= 0 && sub_5F0F4(env, clazz) >= 0 && sub_70640(env, clazz) >= 0 && sub_11F3C(env) >= 0 && sub_21C3C(env, clazz) >= 0 && sub_2148C(env, clazz) >= 0 && sub_210E0(env, clazz) >= 0 && sub_41B58(env, clazz) >= 0 && sub_27920(env, clazz) >= 0 && sub_293E8(env, clazz) >= 0 && sub_208F4(env, clazz) >= 0) ) { result = (sub_B7B0(env, clazz) >> 31) | 0x10004; } else { LABEL_39: result = -1; } return result; } 

النظر بعناية في السطور التالية:

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

تقوم الدالة sub_73E24 بفك تشفير اسم الفئة بوضوح. كمعلمات لهذه الوظيفة ، يتم تمرير مؤشر إلى بيانات مشابهة للبيانات المشفرة ومخزن مؤقت ورقم. , , . . FindClass , . , — . , , . , sub_73E24.

 int __fastcall sub_73E56(unsigned __int8 *in, unsigned __int8 *out, size_t size) { int v4; // r6 int v7; // r11 int v8; // r9 int v9; // r4 size_t v10; // r5 int v11; // r0 struc_1 v13; // [sp+0h] [bp-30h] int v14; // [sp+1Ch] [bp-14h] int v15; // [sp+20h] [bp-10h] v4 = 0; v15 = *(_DWORD *)off_8AC00; v14 = 0; v7 = sub_7AF78(17); v8 = sub_7AF78(size); if ( !v7 ) { v9 = 0; goto LABEL_12; } (*(void (__fastcall **)(int, const char *, int))(v7 + 12))(v7, "DcO/lcK+h?m3c*q@", 16); if ( !v8 ) { LABEL_9: v4 = 0; goto LABEL_10; } v4 = 0; if ( !in ) { LABEL_10: v9 = 0; goto LABEL_11; } v9 = 0; if ( out ) { memset(out, 0, size); v10 = size - 1; (*(void (__fastcall **)(int, unsigned __int8 *, size_t))(v8 + 12))(v8, in, v10); memset(&v13, 0, 0x14u); v13.field_4 = 3; v13.field_10 = v7; v13.field_14 = v8; v11 = sub_6115C(&v13, &v14); v9 = v11; if ( v11 ) { if ( *(_DWORD *)(v11 + 4) == v10 ) { qmemcpy(out, *(const void **)v11, v10); v4 = *(_DWORD *)(v9 + 4); } else { v4 = 0; } goto LABEL_11; } goto LABEL_9; } LABEL_11: sub_7B148(v7); LABEL_12: if ( v8 ) sub_7B148(v8); if ( v9 ) sub_7B148(v9); return v4; } 

sub_7AF78 ( ). : «DcO/lcK+h?m3c*q@» ( , ), — . , sub_6115C . 3. , .

 int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2) { int v3; // lr unsigned int v4; // r1 int v5; // r0 int v6; // r1 int result; // r0 int v8; // r0 *a2 = 820000; if ( a1 ) { v3 = a1->field_14; if ( v3 ) { v4 = a1->field_4; if ( v4 < 0x19 ) { switch ( v4 ) { case 0u: v8 = sub_6419C(a1->field_0, a1->field_10, v3); goto LABEL_17; case 3u: v8 = sub_6364C(a1->field_0, a1->field_10, v3); goto LABEL_17; case 0x10u: case 0x11u: case 0x12u: v8 = sub_612F4( a1->field_0, v4, *(_QWORD *)&a1->field_8, *(_QWORD *)&a1->field_8 >> 32, a1->field_10, v3, a2); goto LABEL_17; case 0x14u: v8 = sub_63A28(a1->field_0, v3); goto LABEL_17; case 0x15u: sub_61A60(a1->field_0, v3, a2); return result; case 0x16u: v8 = sub_62440(a1->field_14); goto LABEL_17; case 0x17u: v8 = sub_6226C(a1->field_10, v3); goto LABEL_17; case 0x18u: v8 = sub_63530(a1->field_14); LABEL_17: v6 = 0; if ( v8 ) { *a2 = 0; v6 = v8; } return v6; default: LOWORD(v5) = 28032; goto LABEL_5; } } } } LOWORD(v5) = -27504; LABEL_5: HIWORD(v5) = 13; v6 = 0; *a2 = v5; return v6; } 

switch , 3. case 3: sub_6364C , , . . . sub_6364C , RC4.

. . : com/taobao/wireless/security/adapter/JNICLibrary . ! ممتاز .


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 , command % 10000 / 100 command % 10 , . ., , 1, 6 1. , JNIEnv , , . ( N1, N2 N3) .

:



JNI_OnLoad .
. . — . , , , ( , ).


, : 0x5F1AC. : UC Browser .

, Java-,
0x4D070. .

R7 R4 :



R11:



, :



, R4. 230 .

ماذا تفعل حيال ذلك؟ IDA, switch: Edit -> Other -> Specify switch idiom.



. , , sub_6115C :



switch, case 3 RC4. , doCommandNative . , magicInt 16. case – , .



AES!

, : , , , ( AES). - sub_6115C , , , .


, Android Studio, , , , , , .

UC Browser «». , , . :) , , , . . .

:



ARM R0-R3, , — . LR . , , . , , PUSH.W {R0-R10,LR}. R7 , .

fopen /data/local/tmp/aes «ab»,
. . . R0 , R1 — . , . , , .



fopen .

aes int . , fwrite .



, , .



, aes .

APK , , /, . , , . , . - , . , UC Browser , , , : 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 .

, , , . البنغو! !

MitM


, . CBC.



URL , - MD5, «extract_unzipsize» . : MD5 , . . , , Intent «PWNED!». : puds.ucweb.com/upgrade/index.xhtml . MD5 ( ), .

, . , -
. , :



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.

Source: https://habr.com/ru/post/ar451936/


All Articles