اليوم لدينا برنامج غني (سيكون هناك الكثير من مجالات الأمن السيبراني في وقت واحد!): فكّر في فك ترميز تطبيق Android ، واعتراض حركة المرور للحصول على عناوين URL ، وإعادة بناء apk بدون شفرة المصدر ، والعمل مع محللي التشفير والمزيد :)وفقًا للأسطورة NeoQUEST-2020 ، عثر البطل على أجزاء روبوت قديمة يجب استخدامها للحصول على المفتاح. دعنا نبدأ!1. Reversim apk
لذا ، قبلنا قليلاً تمكنا من استخلاصه من روبوت شبه مفكك - تطبيق apk يجب أن يساعدنا بطريقة أو بأخرى في الحصول على المفتاح. لنقم بأكثر وضوح: تشغيل apk وإلقاء نظرة على وظائفه. لا تترك واجهة التطبيق أكثر من أضيق الحدود - هذا عميل ملف FileDroid مخصص يسمح لك بتنزيل ملف من خادم بعيد. حسنًا ، يبدو الأمر سهلاً. نقوم بتوصيل الهاتف بالإنترنت ، ونقوم بمحاولة تجريبية للتنزيل (على الفور key.txt - حسنًا ، ماذا لو؟) - غير ناجح ، الملف مفقود على الخادم.
ننتقل إلى الحدث التالي من حيث التعقيد - نقوم بفك ملف apk باستخدام JADXوتحليل شفرة المصدر للتطبيق ، والتي ، لحسن الحظ ، ليست غامضة على الإطلاق. مهمتنا الحالية هي فهم الملفات التي يوفرها الخادم البعيد للتنزيل ، وتحديد الملف الذي يحتوي على المفتاح منه.نبدأ بفئة com.ctf.filedroid.MainActivity ، التي تحتوي على طريقة onClick () الأكثر إثارة للاهتمام بالنسبة لنا ، والتي تتم فيها معالجة النقر على الزر "تنزيل". داخل هذه الطريقة ، يتم استدعاء فئة ConnectionHandler مرتين: أولاً ، يتم استدعاء أسلوب ConnectionHandler.getToken () ، وعندها فقط ، يتم استدعاء ConnectionHandler.getEncryptedFile () ، الذي يمرر اسم الملف الذي يطلبه المستخدم.
نعم ، هذا أولاً ، نحن بحاجة إلى رمز مميز! سوف ندرس أكثر بقليل مع عملية الحصول عليها.تأخذ طريقة ConnectionHandler.getToken () سطرين من المدخلات ، ثم ترسل طلب GET ، لتمرير هذه السطور كمعلمات "crc" و "sign". ورداً على ذلك ، يرسل الخادم البيانات بتنسيق JSON ، الذي يستخرج منه تطبيقنا رمز الدخول ويستخدمه لتنزيل الملف. هذا كله ، بالطبع ، جيد ، لكن ما هي "crc" و "sign"؟
لفهم هذا ، ننتقل أكثر نحو فئة الشيكات ، يرجى تقديم أساليب badHash () و badSign (). الأول يحسب المجموع الاختباري من classes.dex و resources.arsc ، يربط هاتين القيمتين ويلفها في Base64 (انتبه للعلم 10 = NO_WRAP | URL_SAFE ، سيكون مفيدًا). وماذا عن الطريقة الثانية؟ ويفعل الشيء نفسه مع بصمة SHA-256 لتوقيع التطبيق. إيه ، يبدو أن FileDroid ليس حريصًا حقًا على إعادة بنائه :(
حسنًا ، لنفترض أننا تلقينا الرمز المميز. ماذا بعد؟ نقوم بتمريره إلى إدخال أسلوب ConnectionHandler.getEncryptedFile () ، الذي يلحق اسم الملف المطلوب بالرمز ويولد طلب GET آخر ، هذه المرة مع معلمات "token" و "file". يرسل الخادم استجابة (بناءً على اسم الطريقة) ملفًا مشفرًا ، يتم تخزينه على / sdcard /.لذا ، لتلخيص المجموع الفرعي الصغير: لدينا خبران ، و ... كلاهما سيئان. أولاً ، لا يدعم FileDroid بالفعل حماستنا لتعديل apk (يتم التحقق من المجموع الاختباري والتوقيع) ، وثانيًا ، يعد الملف المستلم من الخادم بتشفيره.حسنًا ، سنقوم بحل المشكلات بمجرد توفرها ، والآن مشكلتنا الرئيسية هي أننا ما زلنا لا نعرف الملف الذي نحتاج لتنزيله. ومع ذلك ، في عملية دراسة فئة ConnectionHandler ، لا يسعنا إلا أن نلاحظ هذا الحق بين طرق getToken () و getEncryptedFile () ، نسي مطورو FileDroid طريقة أخرى جذابة جدًا تسمى تحدث getListing (). لذا ، يدعم الخادم مثل هذه الوظائف ... يبدو أن هذا ما تحتاجه!
للحصول على القائمة ، سنحتاج إلى "crc" و "sign" المعروفين - ليست مشكلة ، فنحن نعرف بالفعل من أين أتوا. نقرأ القيم ونرسل طلب GET و ... لذا توقف. إلى أين سنرسل طلب GET؟ سيكون من الجيد الحصول على عنوان URL للخادم البعيد أولاً. إيه ، نعود إلى MainActivity.onClick () ونرى كيف يتم إنشاء وسيطات netPath لاستدعاء طريقتين getToken () و getEncryptedFile ():Method getSecureMethod =
wat.class.getDeclaredMethod("getSecure", new Class[]{String.class});
// . . .
// netPath --> ConnectionHandler.getToken()
(String) getSecureMethod.invoke((Object) null, new Object[]{"fnks"})
// netPath --> ConnectionHandler. getEncryptedFile()
(String) getSecureMethod.invoke((Object) null, new Object[]{"qdkm"})
تجبرنا تركيبات الأحرف الغريبة "fnks" و "qdmk" على التحول إلى نتيجة فك طريقة wat.getSecure (). المفسد: JADX لها هذه النتيجة.
عند الفحص الدقيق ، يصبح من الواضح أن كل هذا المحتوى غير اللطيف جدًا للطريقة يمكن استبداله بحالة التبديل المعتادة من هذا النوع:// . . .
switch(CODE)
{
case «qdkm»:
r.2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(2);
break;
case «tkog»:
r2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(1);
break;
case «fnks»:
String r2 = com.ctf.filedroid.x37AtsW8g.rlieh786d(0);
break;
}
java.lang.StringBuilder r1 = new java.lang.StringBuilder
r1.<init>(r2)
java.lang.String r0 = r1.toString()
java.lang.String r1 = radon(r0)
return r1
نظرًا لأن "fnks" و "qdmk" يُستخدمان بالفعل للحصول على رمز مميز وتنزيل ملف ، يجب أن يعطي "tkog" عنوان URL المطلوب لطلب قائمة بالملفات المتوفرة على الخادم. يبدو أن هناك أمل في الحصول على المسار المطلوب بثمن بخس ... أولاً وقبل كل شيء ، دعنا نرى كيف يتم تخزين عناوين URL في التطبيق. نفتح وظيفة com.ctf.filedroid.x37AtsW8g.rlieh786d () ونرى أن كل عنوان URL يتم حفظه كصفيف بايت مشفر ، وأن الوظيفة نفسها تشكل سلسلة من هذه البايتات وتعيدها.
حسن. ولكن بعد ذلك يتم تمرير الخط إلى دالة com.ctf.filedroid.wat.radon () ، والتي يتم تقديم تنفيذها إلى المكتبة الأصلية libae3d8oe1.so. عكس الذراع 64؟ محاولة جيدة ، FileDroid ، ولكن يأتي في وقت آخر؟2. احصل على عناوين url للخادم
دعنا نحاول الاقتراب من الجانب الآخر: لاعتراض حركة المرور والحصول على عناوين URL بنص واضح (وكمكافأة أيضًا ، وقيم المجموع الاختباري والتوقيع!) ، ومطابقتها مع صفائف البايت من com.ctf.filedroid.x37AtsW8g.rlieh786d () - يمكن هل اتضح أن التشفير هو تشفير قيصر أو XOR المعتاد؟ .. فلن يكون من الصعب استعادة عنوان URL الثالث وتنفيذ القائمة.لإعادة توجيه حركة المرور ، يمكنك استخدام أي وكيل مناسب ( Charles ، عازف الكمان ، BURPإلخ.). نقوم بتكوين إعادة التوجيه على جهاز محمول ، وتثبيت الشهادة المناسبة ، والتحقق من نجاح الاعتراض ، وإطلاق FileFroid. نحن نحاول تنزيل ملف تعسفي و ... انظر "NetworkError". حدث هذا الخطأ بسبب وجود تثبيت الشهادة (راجع أسلوب com.ctf.filedroid.ConnectionHandler.sendRequest): يتحقق عميل الملف من أن الشهادة "سلكية" في التطبيق تتوافق مع الخادم الذي تتفاعل معه. أصبح من الواضح الآن سبب التحكم في سلامة موارد التطبيق!
ومع ذلك ، في حركة المرور التي تم اعتراضها ، يمكننا أن نرى على الأقل اسم نطاق الخادم الذي تم الوصول إليه من قبل عميل الملف ، مما يعني أن الأمل في فك تشفير عناوين URL لا يزال!
دعنا نعود إلى وظيفة com.ctf.filedroid.x37AtsW8g.rlieh786d () ونلاحظ أن العشرات القليلة الأولى من البايت تتزامن في جميع المصفوفات:cArr[0] = new char[]{'K', 'S', 'Y', '5', 'E', 'R', 'Q', 'J', 'S', '0', 't', 'W', 'B', '2', 'w', 'k', 'N', 'j', '8', 'O', 'D', 'l', 'd', 'K', 'C', 'l', 'U', 'B', 'c', 'T', 'Q', '3', 'P', 'h', 'V', 'J', 'Q', 'R', 'F', 'L', 'U', 'R', '5', 'p', 'b', 'i', . . .};
cArr[1] = new char[]{'K', 'S', 'Y', '5', 'E', 'R', 'Q', 'J', 'S', '0', 't', 'W', 'B', '2', 'w', 'k', 'N', 'j', '8', 'O', 'D', 'l', 'd', 'K', 'C', 'l', 'U', 'B', 'c', 'T', 'Q', '3', 'P', 'h', 'V', 'J', 'Q', 'R', 'F', 'L', 'U', 'R', '5', 'p', 'b', 'j', . . .};
cArr[2] = new char[]{'K', 'S', 'Y', '5', 'E', 'R', 'Q', 'J', 'S', '0', 't', 'W', 'B', '2', 'w', 'k', 'N', 'j', '8', 'O', 'D', 'l', 'd', 'K', 'C', 'l', 'U', 'B', 'c', 'T', 'Q', '3', 'P', 'h', 'V', 'J', 'Q', 'R', 'F', 'L', 'U', 'R', '5', 'p', 'b', 'j', . . ., '='};
بالإضافة إلى ذلك ، يشير البايت الأخير من المصفوفة الثالثة إلى أنه لم يكن خاليًا من base64. دعنا نحاول فك تشفير البايت الناتج مع جزء معروف من عنوان URL:
يبدو أنه لم يكن أي شخص سعيدًا على الإطلاق باستخدام ARMag3dd0n! الشيء صغير: فك تشفير عناوين URL base64 بالتسلسل و xorim باستخدام المفتاح الذي تم العثور عليه. ولكن ... وإذا لم يكن XOR ، ولكن تشفير تبادلي ذاتي ، لا يمكنك التقاطه حتى مع مائة محاولة؟3. إعادة بناء apk مع فريدا
كجزء من هذه الكتابة ، سننظر في طريقة حل أكثر إيلامًا (وفي رأينا ، أكثر جمالًا) - باستخدام إطار Frida ، والذي سيسمح لنا بتنفيذ طرق تطبيق apk تعسفية بالحجج التي نحتاجها في وقت التشغيل. للقيام بذلك ، تحتاج إلى هاتف مع حقوق الجذر أو محاكي. نفترض خطة العمل التالية:- تركيب مكونات فريدا على جهاز كمبيوتر وهاتف تجريبي.
- استعد عناوين URL المطابقة لرمز أو طلبات الإدراج وقم بتنزيل الملف (باستخدام Frida).
- استرجاع قيم المجموع الاختباري والتوقيع للتطبيق الأصلي.
- الحصول على قائمة بالملفات المخزنة على الخادم وتحديد الملف المطلوب.
- قم بتنزيل الملف وفك تشفيره.
أولاً ، سنوضح العلاقة بين الهاتف الجذر و apk. نقوم بتثبيت التطبيق وتشغيله ، ولكن عميل الملفات لا يريد التمهيد بالكامل ، فهو يومض فقط ويغلق. نتحقق من الرسائل من خلال logcat - نعم ، يشعر FileDroid بالفعل أن شيئًا ما خاطئ ويقاوم قدر الإمكان.
ننتقل مرة أخرى إلى فئة MainActivity ونجد أن طريقة doChecks () تسمى onCreate () ، وتعرض الأخطاء التالية في السجل:
بالإضافة إلى ذلك ، يتحقق onResume () أيضًا لمعرفة ما إذا كان المنفذ النموذجي لـ Frida مفتوحًا:
عميل الملفات الخاص بنا غير متسامح قليلاً إلى التصحيح والجذر وفريدا نفسها. لا يتم تضمين هذه المعارضة على الإطلاق في خططنا ، لذلك نحصل على رمز smali للتطبيق باستخدام أداة apktool، افتح ملف MainActivity.smali في أي محرر نصوص ، وابحث عن طريقة onCreate () وقم بتحويل استدعاء doChecks () إلى تعليق غير ضار:
ثم نحرم طريقة الانتحار () من الفرصة لإغلاق التطبيق حقًا:
بعد ذلك ، دعونا نبني تطبيقنا المحسن قليلاً مرة أخرى باستخدام apktool وتوقيع له عن طريق تنفيذ الأوامر التالية (قد تحتاج إلى حقوق المسؤول):cd "C:\Program Files\Java\jdk-14\bin"
.\keytool -genkey -v -keystore filedroid.keystore -alias filedroid_alias -keyalg RSA -keysize 2048 -validity 10000
.\jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore filedroid.keystore filedroid_patched.apk filedroid_alias
.\jarsigner -verify -verbose -certs filedroid_patched.apk
نحن نعيد تثبيت التطبيق على الهاتف ، ونشغله - على عجل ، التنزيل بدون حوادث ، السجل نظيف!
ننتقل إلى تثبيت إطار عمل Frida على جهاز كمبيوتر وجهاز محمول:$ sudo pip3 install frida-tools
$ wget https://github.com/frida/frida/releases/download/$(frida --version)/frida-server-$(frida --version)-android-arm.xz
$ unxz frida-server-$(frida --version)-android-arm.xz
$ adb push frida-server-$(frida --version)-android-arm /data/local/tmp/frida-server
قم بتشغيل خادم إطار فريدا على جهاز محمول:$ adb shell su - "chmod 755 /data/local/tmp/frida-server"
$ adb shell su - "/data/local/tmp/frida-server &"
نحن بصدد إعداد نص برمجي get-urls.js بسيط يستدعي wat.getSecure () لجميع خوادم الطلبات المدعومة:Java.perform(function ()
{
const wat = Java.use('com.ctf.filedroid.wat');
console.log(wat.getSecure("fnks"));
console.log(wat.getSecure("qdmk"));
console.log(wat.getSecure("tkog"));
});
أطلقنا FileDroid على الجهاز المحمول و "التشبث" مع نصنا للعملية المقابلة:
4. الحصول على قائمة الملفات الموجودة على الخادم
وأخيرًا ، أصبح الخادم البعيد أقرب إلينا قليلاً! نعلم الآن أن الخادم يدعم الطلبات بالطرق التالية:- مجتمعة في androidroid.neoquest.ru/api/verifyme؟crc= {crc} & sign = {sign}
- chedroid.neoquest.ru/api/list_post_apocalyptic_collection؟crc= {crc} & sign = {sign}
- لم يتم حفظه في ملف androidroid.neoquest.ru/api/file؟file= {file} & token = {token}
من أجل الحصول على قائمة بالملفات المتوفرة ، يبقى حساب قيم المجموع الاختباري والتوقيع للتطبيق الأصلي ، ثم ترميزها في base64.سيسمح لك مثل هذا البرنامج النصي في python3 بالقيام بذلك:المفسدimport hashlib
import binascii
import base64
from asn1crypto import cms, x509
from zipfile import ZipFile
def get_info(apk):
with ZipFile(apk, 'r') as zipObj:
classes = zipObj.read("classes.dex")
resources = zipObj.read("resources.arsc")
cert = zipObj.read("META-INF/CERT.RSA")
crc = "%s%s" % (get_crc(classes), get_crc(resources))
return get_full_crc(classes, resources).decode("utf-8"), get_sign(cert).decode("utf-8")
def get_crc(file):
crc = binascii.crc32(file) & 0xffffffff
return crc
def get_full_crc(classes, resources):
crc = "%s%s" % (get_crc(classes), get_crc(resources))
return base64.urlsafe_b64encode(bytes(crc, "utf-8"))
def get_sign(file):
pkcs7 = cms.ContentInfo.load(file)
data = pkcs7['content']['certificates'][0].chosen.dump()
sha256 = hashlib.sha256()
sha256.update(data)
return base64.urlsafe_b64encode(sha256.digest())
get_info('filedroid.apk')
يمكنك أيضًا يدويًا. نعتبر CRC32 من classes.dex و resources.arsc أي أداة مناسبة (على سبيل المثال ، لنظام Linux - أداة crc32 القياسية) ، نحصل على القيم 1276945813 و 2814166583 على التوالي ، وقم بربطها (12769458132814166583 ستخرج) وترميز في base64 ، على سبيل المثال ، هنا :
من أجل تنفيذ إجراء مماثل لتوقيع التطبيق ، في نافذة JADX ، انتقل إلى قسم "توقيع APK" ، انسخ قيمة "SHA-256 Fingerprint" وقم بترميزها في base64 كمجموعة بايت:
هام: هام:في ملف apk الأصلي ، يتم تنفيذ ترميز base64 بعلامة URL_SAFE ، أي بدلاً من استخدام الأحرف "+" و "/" و "-" و "_" على التوالي. من الضروري التأكد من أنه سيتم ملاحظة ذلك أيضًا مع الترميز الذاتي. للقيام بذلك ، عند الترميز عبر الإنترنت ، يمكنك استبدال الأبجدية المستخدمة بـ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcde fghijklmnopqrstuvwxyz0123456789 + /" بـ "ABCDEFGHIJKLMNOPQRSTUVWxvppc6464.cp.cp.cp64.cp.cp.cp64.cp.cp64.cpأخيرًا ، لدينا جميع المكونات لإدراج الملفات بنجاح:- chedroid.neoquest.ru/api/list_post_apocalyptic_collection؟crc= {crc} & sign = {sign}
- crc: MTI3Njk0NTgxMzI4MTQxNjY1ODM =
- تسجيل الدخول: HeiTSPWdCuhpbmVxqLxW-uhrozfG_QWpTv9ygn45eHY =
ننفذ طلب GET - ونهتف ، قائمتنا! علاوة على ذلك ، فإن اسم أحد الملفات يتحدث عن نفسه - "مفتوح إذا أردت أن تهرب" - يبدو أننا بحاجة إليه.
بعد ذلك ، نطلب رمز وصول لمرة واحدة ونقوم بتنزيل الملف:import requests
response = requests.get('https://filedroid.neoquest.ru/api/verifyme',
params={ 'crc': 'MTI3Njk0NTgxMzI4MTQxNjY1ODM=',
'sign': HeiTSPWdCuhpbmVxqLxW-uhrozfG_QWpTv9ygn45eHY=},
verify=False)
token = response.json()['token']
print(token)
response = requests.get('https://filedroid.neoquest.ru/api/file',
params={'token': token, 'file': '0p3n1fuw4nt2esk4p3.jpg'}, verify=False)
with open("0p3n1fuw4nt2esk4p3.jpg", 'wb') as fd:
fd.write(response.content)
نفتح الملف الذي تم تنزيله ونتذكر ظرفًا صغيرًا تركناه لوقت لاحق:
5. إضافة قليل من التشفير ...
إيه ، من المبكر جدًا تأجيل FileDroid. دعنا نعود إلى JADX ونرى ما إذا كان مطورو عملاء الملف قد تركوا أي شيء مفيد لنا. نعم ، هذا هو الحال عندما يكون تنظيف التعليمات البرمجية غير شائع بشكل واضح: تنتظر طريقة decryptFile () غير المستخدمة بهدوء انتباهنا في فئة ConnectionHandler. ما لدينا؟تشفير AES وضع CBC ، sinhroposylka تحتل أول 16 بايت ... الكسل - محرك التقدم ، فمن الأفضل مرة أخرى استخدام Frida وفك شفرة 0p3n1fuw4nt2esk4p3.jpg جهدنا. ولكن مجرد تمرير كمفتاح التشفير؟ لا يوجد العديد من الخيارات ، ولكن نظرًا لوجود طريقة أخرى "منسية" savePlainFile (ملف String ، String token) ، فإن الاختيار واضح.تحضير البرنامج النصي decrypt.js التالي ( كرمز أشر إلى القيمة الفعلية ، على سبيل المثال ، "HoHknc572mVpZESSQN1Xa7S9zOidxX1PMbykdoM1EXI = '):Java.perform(function () {
const JavaString = Java.use('java.lang.String');
const file_name = JavaString.$new('0p3n1fuw4nt2esk4p3.jpg');
const ConnectionHandler = Java.use('com.ctf.filedroid.ConnectionHandler');
const result = ConnectionHandler.savePlainFile(file_name, <token>);
console.log(result);
});
وضعنا الملف المشفر 0p3n1fuw4nt2esk4p3.jpg على / sdcard / ، وقم بتشغيل FileDroid وحقن البرنامج النصي decrypt.js باستخدام Frida. بعد تشغيل البرنامج النصي ، سيظهر ملف plainfile.jpg على / sdcard /. نفتحه و ... حل فقط!
تتطلب هذه المهمة الصعبة أن يكون لدى المشاركين المعرفة والمهارات في العديد من مجالات أمن المعلومات في وقت واحد ، ويسعدنا أن معظم المنافسين تعاملوا معها بنجاح!نأمل أن أولئك الذين لم يكن لديهم الوقت أو المعرفة الكافية قبل استلام المفتاح سوف يجتازون الآن مهام مماثلة بنجاح في أي CTF :)