PWN Adventure 3
PWN Adventure 3
السلام عليكم ورحمة الله وبركاته
على الهامش :{هذه المقالة يُفترض أن تكون تكملة لسابقتها، لكن لكون الموضوع يستدعي الكثير من البحث الذي لا يتّسع له وقتي الحالي أتت هذه المقالة عوضًا عنها! }
هي لعبة تم نشرها في إحدى مسابقات الـ CTF لغرض توعية مُطوري الألعاب بالثغرات المُمكن حدوثها نتيجة لبعض الأخطاء التي قد يقوم بها هؤلاء المبرمجين، اللعبة أيضًا موجّهه للمُهتمين بتحليل الـ Binaries ، كما أنها ليست محصورة في هذا المجال فقط! توجد ثغرات في مجالات مختلفة أيضًا ( إذا أردت الاستزادة اطلع على هذه الصفحة).
في هذه المقالة نحن بصدد :
أولًا : تحليل بسيط جدًا للعبة
ثانيًا : إلقاء "القليل" من الضوء على بروتوكول الشبكة المُستخدم في اللعبة،
أخيرًا : سنقوم ببناء Proxy Server يكون حَلقة الوصل بين الـ Client ( برنامج اللعبة الأساسي ) و الـخادم الخاص باللعبة Game Server
لكن قبل البدء أود الإشارة بأن كل ما سيتم شرحه لاحقًا تم على بيئة إفتراضية ( Local Machines ) ولم يتم تطبيقه على الخادم الحقيقي الخاص باللعبة.
إذا أردت تثبيت اللعبة على بيئتك الإفتراضية اتبع الخطوات التي تم شرحها في هذه الصفحة
بسم الله لنبدأ
تحليل اللعبة
بعد الإنتهاء من تثبيت اللعبة سنجد البرنامج الخاص باللعبة في هذا المسار ( في حال لينكس ) :
PwnAdventure3/PwnAdventure3/Binaries/Linux
قبل تشغيل اللعبة لنقوم بجمع بعض المعلومات عن ملف اللعبة نفسه عن طريق الأداة : file
نلاحظ هنا أن الملف التنفيذي ELF يحمل الخاصيّة stripped ، أي أنه ليس بإمكاننا إستخراج بعض المعلومات عن هذا الـ binary من خلال عملية الـ Debugging بالتالي قد تكون عملية التحليل هنا صعبة بعض الشيء!
لكن ماذا عن المكتبات أو ما يسمى بالـ Dynamic Libraries التي تستخدمها اللعبة؟
نستطيع معرفة المكتبات المستخدمة في اللعبة عن طريق التعليمة : ldd
هذه قائمة بالمكتبات التي تقوم اللعبة بإستخدامها فور تشغيلها، لعل أهمها هنا هذا الملف :
libGameLogic.so
لنقوم بإستخدام نفس تعليمة الـ file السابقة حتى نتعرّف على هذا الملف أكثر!
على عكس ملف اللعبة الأساسي، ملف المكتبة لا يملك الخاصية stripped أي بإمكاننا إستخراج بعض المعلومات عن اللعبة من خلال هذا الملف .. الآن أترك لك متعة الإبحار والإستكشاف هُنا :[
حتى الآن قمنا بجمع بعض المعلومات بدون تشغيل اللعبة، لنبدأ بتشغيل اللعبة من لوحة الأوامر كالآتي :
ومن ثم ننظر قليلًا إلى العملية Process الخاصة باللعبة عن طريقة التعليمة : pstree -aslp
ماذا عن المساحة التي تقوم بإستخدامها اللعبة في الذاكرة وقت تشغيلها ؟ كيف تبدو ؟ وماذا يُكتب فيها ؟
نستطيع معرفة مثل هذه المعلومات عن طريق الدخول لمجلد /proc ومن ثم الدخول للمجلد الخاص بالـ process id ، هنا رقم العملية 3893
أي ستكون التعليمة كالآتي :
cd /proc/[pid]
ومن ثم سنجد في داخل هذا المجلد العديد من المجلدات والملفات التي تحمل معلومات عن العملية الخاصة باللعبة، حتى نستطيع معرفة كيف تبدو الذاكرة نقوم بقراءة الملف maps كالآتي :
cat /proc/[pid]/maps
يحتوي هذا الملف على العديد من المعلومات التي لسنا بصدد تحليلها هنا لكن لعل أهمها هو عناوين الذاكرة و الصلاحيات ، على سبيل المثال هذا عنوان الذاكرة للـ Stack
قبل البدء في الجزء الثاني من المقالة، أود الإشارة بأنه توجد العديد من الأدوات التي تسهّل عملية التحليل وقراءة المخرجات بطريقة أكثر سهولة من ما تم ذكره سابقًا، مثلًا هذه الأداة ، لم أقم بإستخدامها حقيقة لكن أعتقد أنها جيدة، فالملخّص لا تعتقد أن الأمر جدًا مُعقّد ومستحيل!، الأمر فقط يحتاج القليل من الصّبر والعديد من أكواب القهوة :[
نظرة على بروتوكول الشبكة المُستخدم في اللعبة
خلال تحليل البيانات التي تعبر بين الـ Client والـ Game Server عبر الشبكة قمت بإستخدام هذه الإضافة في برنامج الـ Wireshark حتى تسهّل قراءة الـ Packet ومحتوياتها
الفكرة من هذه الإضافة هو أن مطوّرها قام بتحليل بُنية الـ Packet المُتبادلة بين الـ Client والـ Game Server وكتب هذا الـ script الذي يُترجم البيانات المضمّنة في داخل الـ Packet إلى معنى مقرُوء و مفهوم بدلًا من أن تظهر في برنامج الـ Wireshark كـ Hex أو ASCII
على سبيل المثال هذه إحدى الـ Packet بدون إستخدام الإضافة :
وهذه بعد إضافة الـ script :
* على الهامش: { الألوان في الصورتين ليس لها علاقة بالإضافة، فقط قمت بعمل Screenshot من أجهزة مختلفة : [ }
بعد الإستعانة بالأداة السابقة وقراءة بعض المقالات في هذه السلسلة
تعرفت على أن كل Packet يقوم بإرسالها الـ Client تحمل ID ، وهذا الـ ID يتم تخزينه في أول 2 Byte ، الهدف من هذا الـ ID هو أن يتعرّف الـ Game Server على الـ Packet التي إستقبلها بالتالي يستطيع معالجتها والرد عليها بطريقة مُناسبة .. كما أن هذا الـ ID يُعرِّف الـ Server بالمحتويات او البيانات التي تحملها هذه الـ Packet بداخلها، لن أطيل الشرح هنا سنلقي نظره على هذه التفاصيل في الجزء الثالث من هذه المقالة
لننتقل الآن لبناء الـ Proxy Server
بناء Proxy Server :
في الوضع الطبيعي ستكون عملية التواصل بين الـ Client والـ Game Server كما هو مُمثّل بالرسم الآتي:
لأجل إتمام عملية تسجيل الدخول او الـ authenticate مع خادم اللعبة سيتم إستخدام المنفذ : 3333
ولبدء اللعبة سيتم إستخدام أحد هذه المنافذ 3000 إلى 3010
بعد بناء الـ Proxy Server ستكون عملية الإتصال بين الـ Client والـ Game Server كالآتي :
حتى نستطيع تطبيق السيناريو في الرسم أعلاه نحتاج:
1 - أن يكون الـ Proxy للـ Game Client بمثابة الـ Game Server
2 - أن يكون الـ Proxy للـ Game Server بمثابة الـ Game Client
3 - أن يعمل هذا الـ Proxy على جميع المنافذ المستخدمه في اللعبة
لماذا الخطوة رقم 1 و 2؟
بما أن الـ Proxy سيكون في المنتصف بين الـ Client والـ Game Server فيجب أن يؤدي دور كِلا الطرفين في كل حالة، فإذا أردنا التخاطب مع الـ Client يجب أن يقوم الـ Proxy الخاص بنا
بأداء دور الـ Game Server لهذا الـ Client
ونفس الشيء في الطرف المقابل، لو أردنا التخاطب مع الـ Game Server سيكون الـ Proxy الخاص بنا هنا عبارة عن Client لهذا الخادم
سنرى الآن كيف نقوم ببناء كل خطوة
1 - أن يكون الـ Proxy للـ Game Client بمثابة الـ Game Server
قمت بإستخدام لغة جافا ببناء هذا الـ Proxy
لماذا جافا تحديدًا ؟
لا يوجد سبب بالإمكان استخدام أي لغة تتقنها بشكل كافي، وفي حالتي كانت الجافا الخيار الأمثل.
حتى أحقق هذه الخطوة استخدمت مكتبة SocketServer
وهذه المتغيرات التي تم تعريفها في الكود لكلا الطرفين : Proxy و Game Client
بالنسبة للمتغيرين الآتيين :
- fromClient : هو متغير يحمل قيم البيانات ( Packets ) التي يقوم برنامج اللعبة بإرسالها للخادم ( في حالتنا هنا الخادم هو الـ Proxy )
- toClient : هذا المتغيّر يحمل البيانات ( Packets ) التي استقبلها الـ Proxy من الـ Game Server ومن ثم سيقوم بتمريرها للـ Game Client كما سنرى في الأكواد القادمة.
وهذا الجزء في الكود يُمثّل عملية التواصل بين الـ Proxy والـ Game Client
2 - أن يكون الـ Proxy للـ Game Server بمثابة الـ Game Client
حتى أحقق هذه الخطوة استخدمت مكتبة Socket
هذه المتغيرات الخاصة بالـ Game Server في الكود :
بالنسبة للمتغيرين الآتيين :
- fromGameServer : هو متغير يحمل قيم البيانات ( Packets ) التي يقوم خادم اللعبة بإرسالها للـ Proxy ، ومن ثم سيقوم الـ Proxy بإعادة توجيهها للـ Game Client
- toGameServer : هي البيانات التي قام الـ Proxy بقراءتها من الـ Game Client ويقوم بإعادة إرسالها للخادم الأساسي للعبة عبر هذا المتغيّر .
هذا الجزء من الكود يُمثّل عملية التواصل بين الـ Proxy والـ Game Server
لو حاولنا الجمع بين المتغيرات في الكود و السيناريو السابق ، سيبدو الرسم كالآتي:
3 - أن يعمل هذا الـ Proxy على جميع المنافذ المستخدمه في اللعبة
لتحقيق هذه الخطوة قمت بتشغيل البرنامج ( الـ Proxy ) من لوحة الأوامر مع تمرير المنفذ الذي سيعمل عليه ، أي بإختصار قمت بتشغيل البرنامج عدة مرّات وكل مرّة أمرر منفذ مختلف، بالطبع هذه الطريقة ليست جيدة ويوجد طُرق أخرى لتحقيق هذه المهمّة ، لكن لضيق الوقت "كالعادة :[" لجئت لهذا الحل السريع.
بعد أن انتهينا من بناء الـ Proxy ، لنبدأ بمقاطعة سير البيانات بين الطرفين
لكن قبل هذا يجب عليك عمل خطوتين مهمتين جدًا
أولًا : التعديل على ملف الـ /etc/hosts ( في حالة لينكس ) كالآتي :
<ProxyIP> <GameServerHostname>
الغرض من هذه الخطوة هو إيهام الـ Game Client بأن الـ Proxy هو خادم اللعبة
ثانيًا: إضافة الـ Hostname الخاص بالـ GameServer في الكود في المتغير الآتي :
الآن لنبدأ بتشغيل هذا الـ Proxy ونرى كيف سيتم تبادل البيانات بين الطرفين !
بداية ، البرنامج يعمل بهذه الطريقة :
java Proxy <portNumber>
ولأن الـ Game Client في البداية سيقوم بعملية تسجيل الدخول ، قمت أولًا بتشغيل البرنامج على المنفذ : 3333
بعد ذلك نبدأ بتشغيل اللعبة ومن ثم سنلاحظ أن الـ Proxy استقبل الـ Packet التي قام الـ Client بإرسالها
في الطرف المقابل أيضًا نرى أن الـ Game Server قام بإرسال بعض البيانات للـ Proxy ( إتمام عملية تسجيل الدخول )
بعد إكتمال عملية تسجيل الدخول ، يجب أن نبدأ تشغيل الـ Proxy على بقية المنافذ حتى نستطيع بدء اللعب!
الآن لننتقل لمرحلة مختلفة وهي تحليل بسيط جدًا للـ Packet التي يتم تبادلها بين برنامج اللعبة والخادم!
عملية تبادل الـبيانات بين الـ Client والـ Server يجب أن تتم بطريقة مفهومة لكلا الطرفين
وفي حالة اللعبة هذه، قام مطوروها ببناء بروتوكول التواصل بين كلا الطرفين بحيث يكون لكل Packet بُنية (Structure ) معينة ورقم خاص (ID ) بهذه الـ Packet
على سبيل المثال :
عندما يقوم اللاعب في اللعبة بالتحرك في الخريطة الخاصة باللعبة يقوم البرنامج الخاص باللعبة بإرسال هذه المعلومات ( إحداثيات اللاعب في الخريطة ) إلى خادم اللعبة ، وهذه البيانات يتم إرسالها في Packet معينة تملك ID خاص بها
أيضًا لو قام اللاعب بالقفز سيتم إرسال Packet أخرى تحمل ID مختلف تخبر خادم اللعبة بأن اللاعب قام بالقفز
وكل ما يحدث في طرف الـ Client يجب أن يتم إعلام خادم اللعبة به بطريقة معينة متفق عليها بين الطرفين ( لو أردت معرفة المزيد أرشح لك الإطلاع على هذه السلسلة)
الآن لنختم هذه المقالة بإلقاء نظرة على إحدى هذه الـ Packets
Location Packet
هذه الـ Packet تحمل الـ ID الآتي : 6d76
قمت بإضافة دالة بسيطة للـ Proxy تتعرّف على هذه الـ Packet في حال قام الـ Client بإرسالها
الدالة تقوم بمقارنة أول 2 Byte في الـ Array التي تحمل البيانات التي قام الـ Client بإرسالها ، وتعيد القيمة true في حال كانت القيم مساوية للـ ID الخاص بهذه الـ Packet
نستطيع أن نرى النتائج في لوحة الأوامر كالآتي :
Jump Packet
نفس الفكرة السابقة تم تطبيقها هنا مع إختلاف الـ ID الخاص بهذه الـ Packet ، فالـ ID الخاص بها هو : 6a70
وهذه هي الدالة التي تقوم بالتعرّف على هذه الـ Packet
وفي لوحة الأوامر نرى هذه النتائج :
قبل أن أختم أود ذكر بعض الملاحظات التي رُبما قد تكون مُفيدة لمن أراد الخوض في تحليل بروتوكول الشبكة الخاص بهذه اللعبة:
· جرّب أن تقوم بعمل Packet Injection :
- قمت بكتابة بعض الدوال هنا التي تقوم بهذه المهمة، بعضها هذه الدوال يقوم فعلًا بتعديل محتوى الـ Packet لكن في المقابل لا يوجد تغيير يحدث بعد إرسال هذه الـ Packet للخادم ، على سبيل المثال : في الـ Location Packet إستطعت الكتابة فوق قيم الإحداثيات ( x,y,z ) لكن مكان اللاعب لا يتغيّر ، أتصوّر أنه يوجد في طرف الخادم آلية تقوم بالتحقّق بأن الإحداثيات التي يقوم اللاعب بإرسالها يجب أن تكون في نطاق معيّن بحسب المكان الذي يتواجد به اللاعب؟ ( لأن اللعبة عبارة عن خرائط) هذه مجرّد فرضية لم أقم بإختبارها فلا تستند عليها بشكل كامل!
- إذا أردت التأكد بأنك قمت بالفعل بالكتابة فوق القيم التي تحملها الـ Packet إستخدم برنامج الـ Wireshark وراقب القيم هناك
- خذ بعين الإعتبار بأن البيانات التي تعبر عبر الشبكة قد تكون معكوسة، وأقصد هنا هذه المفاهيم: Network Byte order والـ Host Byte order والـ Little-endian والـ Big-endian ، فإذا أردت حقن قيم في الـ Packet يجب أن تعرف بأي ترتيب ستقوم بحقن هذه الـ Bytes
ختامًا ، إن أصبت فمن توفيق الله وحده ..
وإن أخطأت فمن نفسي والشيطان.
المراجع :
- https://github.com/LiveOverflow/PwnAdventure3
- https://github.com/Foxmole/PwnAdventure3
- https://github.com/beaujeant/PwnAdventure3
الكود المصدري الخاص بالـ Proxy :