مقدمة
في نهاية شهر مارس ،
أبلغنا أننا اكتشفنا قدرة خفية على تنزيل وتشغيل شفرة لم يتم التحقق منها في متصفح 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];
بالنظر إلى المستقبل ، نلاحظ أنه في هذه المرحلة لم يتم الحصول على المفتاح بعد ، ولكن فقط "معرفه". الحصول على المفتاح هو أكثر تعقيدا قليلا.
في الطريقة التالية ، يتم إضافة اثنين آخرين إلى المعلمات الحالية ، وهناك أربع منها: الرقم السحري 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;
هنا ، يتم استكمال قائمة المعلمات لدينا من قبل اثنين من الأعداد الصحيحة: 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:
نضع المؤشر على الخط 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
نضع المؤشر في بداية الإنشاء الذي نريد استبداله - 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:
نتيجة البرنامج النصي:

بعد تصحيح كل شيء في الوظيفة ، يمكنك توجيه المؤسسة إلى بدايتها الحقيقية. سوف يجمع كل رمز الوظيفة في قطع ، ويمكن فك تشفيره باستخدام HexRays.
سلاسل فك
لقد تعلمنا كيفية التعامل مع
تشفير رمز الجهاز في مكتبة
libsgmainso-6.4.36.so من متصفح UC وحصلنا على رمز وظيفة
JNI_OnLoad .
int __fastcall real_JNI_OnLoad(JavaVM *vm) { int 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;
sub_7AF78 ( ). :
«DcO/lcK+h?m3c*q@» ( , ), — . ,
sub_6115C . 3. , .
int __fastcall sub_611B4(struc_1 *a1, _DWORD *a2) { int v3;
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];
في الواقع ، يتم تسجيل طريقة أصلية تسمى doCommandNative هنا . الآن نحن نعرف عنوانه. دعونا نرى ما يفعله. int __fastcall doCommandNative(JNIEnv *env, jobject obj, int command, jarray args) { int 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=Nfns7uH03J8UC Browser, . , . — , , , .
UC Browser , , - . . , , , . 27
UC Browser 12.10.9.1193, HTTPS:
puds.ucweb.com/upgrade/index.xhtml .
, «» PDF «, - !». PDF , , Google Play.