
أداة لتنفيذ هجوم الوسيط (Man-in-the-Middle) في الذاكرة
تم تطوير أداة MemITM (Mem In The Middle) لتسهيل اعتراض "الرسائل" في ذاكرة عمليات Windows. قمنا بتطوير العديد من أدوات اعتراض الذاكرة المخصصة لالتقاط رسائل الشبكة قبل التشفير، أو رسائل IPC، ولتتمكن من فحصها أو تعديلها لإجراء اختبارات التطفير (fuzzing). كانت كل أداة مخصصة حقًا، وليست عامة، ومطورة بلغة C/ASM، ولم تكن سهلة الاستخدام/الصيانة/التكيف.
تم تطوير أداة MemITM لمعالجة هذه المشكلات، وتتكون من:
يمكنك تنزيله (المصادر + الملفات المترجمة) من هنا: https://github.com/Amossys/MemITM
لنفترض أنك تريد اعتراض عمليات كتابة الملفات (هناك طرق أبسط للقيام بذلك، لكن هذا مثال) في عملية Windows محددة. تتم كتابة الملفات بواسطة الدالة WriteFile، التي يتم تصديرها بواسطة kernelbase.dll، وتتبع النمط BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...). WriteFile هي دالة من نوع __fastcall: يشير RDX إلى lpBuffer و R8 إلى NumberOfBytesToWrite. دعنا نعترض هذه المخازن المؤقتة.
افتح ملف "kernelbase.dll" الخاص بك في IDA Pro، وقم بتحميل النص البرمجي generate.idapython.py، وقم بتشغيل getHook(LocByName("WriteFile"), "rdx","r8") ثم قم بتشغيل updateConfig("config.bin") .
قم بتشغيل notepad ثم python memitm.py notepad.exe config.bin.
هذا كل شيء! تم وضع خطاف على الدالة WriteFile، وستستقبل دوال "logger" و "fuzzer" في memitm.py نسخة من المخزن المؤقت.
تتيح الدالة getHook تحديد السجلات للمخزن المؤقت وحجمه، ولكن في بعض الحالات قد لا يكون الأمر كذلك. لنفترض أنك تريد اعتراض استدعاءات النظام NtCreateFile والحصول على اسم الملف. يتبع NtCreateFile النمط NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...)، ويمكن الحصول على اسم الملف بواسطة ObjectAttributes->ObjectName->Buffer (الحجم هو ObjectAttributes->ObjectName->Length * sizeof(WCHAR)).
تسمح getHook أيضًا بتحديد شيل كود في معامل t1customOpcodes بدلاً من السجلات. يجب أن يقوم هذا الشيل كود بما يلي:
RCX؛RDX؛في حالة NtCreateFile، يمكننا ببساطة تشغيل كود ASM التالي للقيام بالمهمة:
mov rax, [r8+0x10] ; rax هو الآن ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx هو الآن ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx هو الآن ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length هو USHORT
shl rdx, 1 ; ويجب ضربه في 2
لنقم بتجميع هذا (على سبيل المثال باستخدام موقع التفكيك عبر الإنترنت): 498B4010488B4810488B104881E2FFFF000048D1E2. يجب أن يصبح استدعاء getHook الخاص بنا الآن getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2").
نعم! يمكن لملف الإعدادات تضمين تعريفات متعددة لنقاط الاعتراض، وتقوم الدالة getHook بإلحاق الملف. يمكنك تعيين نقاط اعتراض في وحدات متعددة. على سبيل المثال، يمكنك اعتراض رسائل ALPC و IOCTL في نفس الوقت.
ستتمكن من التمييز بينها من خلال "معرف الرسالة" الخاص بها في دوال رد النداء في Python.
لا تقلق، هناك أيضًا خادم HTTP بسيط (logserver.py) والدالة httpNetSend التي تسمح لك بإرسال حالات الاختبار الخاصة بك (والتعديلات) إلى مضيف بعيد في الوقت الحقيقي.
حسنًا، يمكنك البدء باستخدام الدالة bufferBitFlip من أجل... قلب عدة بتات. سيتيح لك تسجيل الرسائل الأولية كتابة محلل خاص بك، والبدء في تنفيذ مطفّر أفضل يدويًا :). تذكر: لا يمكنك تغيير حجم المخزن المؤقت!
على سبيل المثال، في مثال WriteFile الخاص بنا، ستقوم الدالة fuzzer التالية باستبدال "hello" بـ "world":
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
على سبيل المثال، بالنسبة لرسائل ALPC، قد يكون لديك الإعداد التالي في memitm.py:
def logger(data, msgID=None, pid=0):
totalLen = 0
dataLen = 0
typ = 0
dataInfoOffset = 0
cid = 0
tid = 0
messageID = 0
clientViewSize = 0
# تخطي رأس PORT_MESSAGE (0x18 بايت)
if len(data) > 0x18:
totalLen = struct.unpack("<H",data[:2])[0]
dataLen = struct.unpack("<H",data[2:4])[0]
typ = struct.unpack("<H",data[4:6])[0]
dataInfoOffset = struct.unpack("<H",data[6:8])[0]
zeroInit = struct.unpack("<L",data[4:8])[0]
cid = struct.unpack("<L",data[8:0xC])[0]
tid = struct.unpack("<L",data[0xC:0x10])[0]
messageID = struct.unpack("<L",data[0x10:0x14])[0]
clientViewSize = struct.unpack("<L",data[0x14:0x18])[0]
logMessage = "ALPC message : %x:%x - %x:%x - %d:%d - %x - %x" % (totalLen, dataLen, typ, dataInfoOffset, cid, tid, messageID, clientViewSize)
print logMessage
return
يحتوي النص البرمجي لـ IDA Pro على محرك "ربط" بسيط (hooking engine)، يسمح بنقل عدة تعليمات إلى منطقة محددة (مشتقة من أداة DIMCT الخاصة بنا). يجب أن يتعامل مع العديد من الحالات الحدية، مثل التعليمات النسبية للذاكرة، والمراجع المتقاطعة، إلخ، وسيعرض على الأرجح الكثير من "لا يمكن تثبيت الخطاف هنا" إذا حاولت اعتراض كتل أساسية صغيرة حقًا (<5 بايت لملفات x86 الثنائية، <12 بايت لملفات x64) والتي تحتوي على مراجع متقاطعة متعددة. لمزيد من المعلومات، ما عليك سوى قراءة الكود المصدري :).
يقوم بإنشاء المعلومات التالية في ملف الإعدادات:
RCX/RDX وتستدعي روتين تسجيل DLL؛أداة حقن DLL هي أداة حقن DLL بسيطة (أشياء CreateRemoteThread/LoadLibraryA).
يقوم DLL نفسه بتهيئة منطقة ذاكرة مشتركة وينتظر بيانات الإعدادات (تحتوي منطقة الذاكرة المشتركة على 3 حقول عامة: معرف رسالة الذاكرة المشتركة، وحجم الرسالة، ومخزن الرسالة المؤقت). بمجرد استلامها، يقوم بتوزيع الخطافات وتثبيتها في الذاكرة، ولن يُسمح بأي تحديث للإعدادات بعد ذلك (يجب عليك إنهاء العملية إذا كنت تريد إعداد خطافات جديدة). أي خطاف موضوع سينتهي في دالة DLL genericHookFunction. تقوم هذه الدالة فقط بملء منطقة الذاكرة (المحمية بأقسام حرجة) ببيانات المخزن المؤقت وحجمه ومعرف الرسالة (المعيّن في البتات العليا لحقل معرف رسالة الذاكرة المشتركة). من أجل إعلام عملية Python، يتم استخدام حدثين للإشارة إلى الرسائل الجديدة والانتظار حتى تقوم العملية بتصحيحها (المهلة هي ثانيتان). إذا تم تحديث المخزن المؤقت، يتم تعديل المخزن المؤقت الأصلي أيضًا.
يقوم النص البرمجي memitm.py بتشغيل عملية الحقن، وينتظر إنشاء الذاكرة المشتركة. ثم يرسل الإعدادات وينتظر حدث الرسالة. عند الاستلام، تتم قراءة الذاكرة المشتركة ويتم استدعاء دوال logger و fuzzer مع المخزن المؤقت، ومعرف العملية، ومعرف الرسالة. إذا أعادت دالة logger مخزنًا مؤقتًا مختلفًا عن الأصل وكان حجمه متساويًا، فسيتم كتابته في منطقة الذاكرة المشتركة. ثم يتم الإشارة إلى حدث "ACK"، وينتظر النص البرمجي رسالة جديدة.
نأمل أن تكون هذه الأداة مفيدة، وسنقدر أي مساهمة/مراجعة! أيضًا، إذا كنت فرنسيًا وتحب رين، فنحن نوظف :)