كسر Micosoft Lunix في HackQuest 2019


مرحبا يا هبر!

في HackQuest ، قبل مؤتمر ZeroNight 2019 ، كانت هناك مهمة مسلية واحدة. لم أتخذ القرار في الوقت المحدد ، لكنني تلقيت نصيبي من الإثارة. أعتقد أنك ستكون مهتمًا بمعرفة ما الذي أعده المنظمون وفريق الطاقم للمشاركين.

المهمة: الحصول على رمز التفعيل لنظام التشغيل Micosoft 1998 السري.

في هذه المقالة سأخبرك بكيفية القيام بذلك.

محتوى


0. المهمة
1. الأدوات
2. تفقد الصورة
3. أجهزة الشخصيات والنواة
4. بحث register_chrdev
4.1. إعداد صورة جديدة طفيفة لينكس
4.2. بعض الاستعدادات
4.3. تعطيل KASLR على lunix
4.4. نحن نبحث والعثور على التوقيع
5. البحث عن fops من / ديف / تفعيل وظيفة الكتابة
6. ندرس الكتابة
6.1. وظيفة التجزئة
6.2. خوارزمية توليد المفاتيح
6.3. كجن

مهمة


تتطلب الصورة التي يتم إطلاقها في QEMU البريد ومفتاح التنشيط. نحن نعرف بالفعل البريد ، دعنا نبحث عن الباقي!

1. الأدوات


  • GDB
  • كيمو
  • binwalk
  • المؤسسة الدولية للتنمية

في ~/.gdbinit تحتاج إلى كتابة وظيفة مفيدة:

 define xxd dump binary memory dump.bin $arg0 $arg0+$arg1 shell xxd dump.bin end 

2. تفقد الصورة


أولاً ، أعد تسمية jD74nd8_task2.iso إلى lunix.iso.

باستخدام binwalk ، نرى أن هناك برنامج نصي عند الإزاحة 0x413000 . يتحقق هذا البرنامج النصي من البريد والمفتاح:


نقوم بتفكيك الشيك مع محرر hex مباشرة في الصورة ونجعل البرنامج النصي ينفذ أوامرنا. كيف يبدو الآن:


لاحظ أنه كان عليك تقليم الخط activated بحيث يظل حجم الصورة كما هو. لحسن الحظ ، لا يوجد تحقق التجزئة. تسمى الصورة lunix_broken_activation.iso.

تشغيله من خلال QEMU:

 sudo qemu-system-x86_64 lunix_broken_activation.iso -enable-kvm 

لنحفر في الداخل:


لذلك لدينا:

  1. توزيع - الحد الأدنى لينكس 5.0.11.
  2. يتمثل نشاط جهاز /dev/activate في التحقق من البريد ، والمفتاح ، مما يعني أنه يجب البحث عن منطق التحقق في مكان ما في أحشاء النواة.
  3. البريد ، يتم نقل email|key شكل email|key

لن تكون الصورة target_broken_activation.iso مطلوبة بعد الآن.

3. أجهزة الشخصيات والنواة


أجهزة مثل /dev/mem ، /dev/vcs ، /dev/activate ، إلخ. التسجيل باستخدام وظيفة register_chrdev :

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

name هو الاسم ، وتحتوي بنية fops على مؤشرات إلى وظائف برنامج التشغيل:

 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 

نحن مهتمون فقط بهذه الوظيفة:

 ssize_t (*write) (struct file *, const char *, size_t, loff_t *); 

هنا ، الوسيطة الثانية هي المخزن المؤقت مع نقل البيانات ، التالي هو حجم المخزن المؤقت.

4. بحث register_chrdev


تبعًا للإعدادات الافتراضية ، يقوم برنامج Minimal Linux بترجمة معلومات تصحيح معطلة لتقليل حجم الصورة ، لكن مع الحد الأدنى. لذلك ، لا يمكنك فقط بدء تشغيل مصحح الأخطاء والعثور على الوظيفة بالاسم. ولكن من الممكن عن طريق التوقيع.

والتوقيع موجود في صورة الحد الأدنى لنظام Linux مع تضمين معلومات تصحيح الأخطاء. بشكل عام ، تحتاج إلى بناء الحد الأدنى الخاص بك.

وهذا هو ، المخطط كما يلي:

  Minimal Linux ->   register_chrdev ->  ->   register_chrdev  Lunix 

4.1. إعداد صورة جديدة طفيفة لينكس


  1. تثبيت الأدوات اللازمة:
     sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev 
  2. تنزيل البرامج النصية:

     git clone https://github.com/ivandavidov/minimal cd minimal/src 
  3. تصحيح 02_build_kernel.sh :
    احذفها

     # Disable debug symbols in kernel => smaller kernel binary. sed -i "s/^CONFIG_DEBUG_KERNEL.*/\\# CONFIG_DEBUG_KERNEL is not set/" .config 

    أضفه

     echo "CONFIG_GDB_SCRIPTS=y" >> .config 

  4. ترجمة

     ./build_minimal_linux_live.sh 

الصورة هي الأدنى / src / minimal_linux_live.iso.

4.2. بعض الاستعدادات


قم بفك minimal_linux_live.iso إلى المجلد الأدنى / src / iso.

يحتوي الملف الأدنى / src / iso / boot rootfs.xz kernel.xz rootfs.xz kernel kernel.xz FS rootfs.xz . قم بإعادة kernel.minimal.xz إلى kernel.minimal.xz ، rootfs.minimal.xz .

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

 extract-vmlinux kernel.minimal.xz > vmlinux.minimal 

الآن في المجلد الأدنى / src / iso / boot لدينا هذه المجموعة: kernel.minimal.xz ، rootfs.minimal.xz ، vmlinux.minimal .

ولكن من lunix.iso نحن بحاجة فقط إلى النواة. لذلك ، نحن vmlinux.lunix جميع العمليات نفسها ، نسمي vmlinux.lunix kernel.xz ، وننسى kernel.xz ، rootfs.xz ، والآن سأخبرك لماذا.

4.3. تعطيل KASLR على lunix


تمكنت من تعطيل KASLR في حالة Minimal Linux التي تم تجميعها حديثًا في QEMU.
لكنها لم تنجح مع لونيكس. لذلك ، عليك تحرير الصورة نفسها.

للقيام بذلك ، افتحه في محرر سداسي عشرية ، ابحث عن السطر "APPEND vga=normal" "APPEND nokaslr\x20\x20\x20" بـ "APPEND nokaslr\x20\x20\x20" .

وتسمى الصورة lunix_nokaslr.iso.

4.4. نحن نبحث والعثور على التوقيع


نطلق تطبيق Minimal Linux الجديد في محطة واحدة:

 sudo qemu-system-x86_64 -kernel kernel.minimal.xz -initrd rootfs.minimal.xz -append nokaslr -s 

في مصحح أخطاء آخر:

 sudo gdb vmlinux.minimal (gdb) target remote localhost:1234 

الآن ابحث عن register_chrdev في قائمة الوظائف:


من الواضح أن خيارنا هو __register_chrdev .
لسنا مرتبكين من أننا بحثنا عن register_chrdev ، ولكن وجدنا __register_chrdev

تفكيك:


ما التوقيع لاتخاذ؟ جربت عدة خيارات واستقرت على القطعة التالية:

  0xffffffff811c9785 <+101>: shl $0x14,%esi 0xffffffff811c9788 <+104>: or %r12d,%esi 


الحقيقة هي أنه في lunix هناك وظيفة واحدة فقط تحتوي على 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 .

سوف أعرض الآن ، لكن أولاً نكتشف في أي مقطع للبحث عنه.


__register_chrdev الدالة __register_chrdev العنوان 0xffffffff811c9720 ، هذا هو مقطع النص. هناك سوف ننظر.

قطع الاتصال من مرجع الحد الأدنى لينكس. الاتصال lunix الآن.

في محطة واحدة:

 sudo qemu-system-x86_64 lunix_nokaslr.iso -s -enable-kvm 

في مكان آخر:

 sudo gdb vmlinux.lunix (gdb) target remote localhost:1234 

ننظر إلى حدود مقطع النص.


الحدود 0xffffffff81000000 - 0xffffffff81600b91 ، ابحث عن 0xc1, 0xe6, 0x14, 0x44, 0x09, 0xe6 :


نجد القطعة على العنوان 0xffffffff810dc643 . ولكن هذا ليس سوى جزء من الوظيفة ، دعنا نرى ما سبق:


وهنا بداية الدالة 0xffffffff810dc5d0 (لأن retq هو الخروج من الوظيفة المجاورة).

5. البحث fops من / ديف / تفعيل


النموذج الأولي للدالة register_chrdev هو:

 int register_chrdev (unsigned int major, const char * name, const struct fops); 

نحن بحاجة إلى هيكل fops .

إعادة تشغيل المصحح و QEMU. 0xffffffff810dc5d0 استراحة على 0xffffffff810dc5d0 . ستعمل عدة مرات. هذه هي الأجهزة mem, vcs, cpu/msr, cpu/cpuid activate فورًا.


يتم تخزين المؤشر إلى الاسم في rcx . والمؤشر إلى fops في r8 :


أذكر هيكل fops
 struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char *, size_t, loff_t *); ssize_t (*write) (struct file *, const char *, size_t, loff_t *); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); }; 


لذلك ، فإن عنوان وظيفة write هو 0xffffffff811f068f .

6. ندرس الكتابة


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

6.1. وظيفة التجزئة


vmlinux.lunix نفتح المؤسسة الدولية للتنمية ، vmlinux.lunix بتحميل kernel vmlinux.lunix ونرى ما تحتوي عليه وظيفة الكتابة في الداخل.

أول شيء يجب ملاحظته هو هذه الدورة:


sub_FFFFFFFF811F0413 بعض وظائف sub_FFFFFFFF811F0413 ، والتي تبدأ مثل هذا:


وفي العنوان 0xffffffff81829ce0 ، تم الكشف عن جدول sha256:


وهذا هو ، sub_FFFFFFFF811F0413 = sha256. يتم إرسال وحدات البايت التي يجب الحصول على التجزئة لها عبر $sp+0x50+var49 ، ويتم تخزين النتيجة على $sp+0x50+var48 . بالمناسبة ، var49=-0x49 ، var48=-0x48 ، لذلك $sp+0x50+var49 = $sp+0x7 ، $sp+0x50+var48 = $sp+0x8 .

التحقق من ذلك.

نبدأ qemu ، gdb ، بتعيين استراحة على 0xffffffff811f0748 call sub_FFFFFFFF811F0413 وعلى التعليمات 0xffffffff811f074d xor ecx, ecx ، والتي تقع مباشرة وراء الوظيفة. test@mail.ru البريد الإلكتروني test@mail.ru ، كلمة المرور 1234-5678-0912-3456 .

يتم تمرير بايت البريد إلى الوظيفة والنتيجة هي:


 >>> import hashlib >>> hashlib.sha256(b"t").digest().hex() 'e3b98a4da31a127d4bde6e43033f66ba274cab0eb7eb1c70ec41402bf6273dd8' >>> 

هذا هو ، نعم ، إنه حقًا sha256 ، إلا أنه يحسب التجزئة لجميع وحدات بايت البريد ، وليس علامة تجزئة واحدة فقط من البريد.

ثم يتم تلخيص التجزئة بواسطة البايت. ولكن إذا كان المجموع أكبر من 0xEC ، فسيتم حفظ ما تبقى من القسمة على 0xEC :

 import hashlib def get_email_hash(email): h = [0]*32 for sym in email: sha256 = hashlib.sha256(sym.encode()).digest() for i in range(32): s = h[i] + sha256[i] if s <= 0xEC: h[i] = s else: h[i] = s % 0xEC return h 

يتم حفظ المبلغ في 0xffffffff81c82f80 . دعونا نرى ما سيكون التجزئة من test@mail.ru .

ffffffff811f0786 dec r13d استراحة على ffffffff811f0786 dec r13d (هذا هو الخروج من الحلقة):


وقارن مع:

 >>> get_email_hash('test@mail.ru') 2b902daf5cc483159b0a2f7ed6b593d1d56216a61eab53c8e4b9b9341fb14880 

ولكن من الواضح أن التجزئة نفسها طويلة جدًا بالنسبة للمفتاح.

6.2. خوارزمية توليد المفاتيح


المفتاح مسؤول عن هذا الرمز:


هنا هو الحساب النهائي لكل بايت:

 0xFFFFFFFF811F0943 imul eax, r12d 0xFFFFFFFF811F0947 cdq 0xFFFFFFFF811F0948 idiv r10d 

في r12d تجزئة eax و r12d ، يتم ضربها ، ثم يتم أخذ ما تبقى من القسمة على 9.

ل


وتؤخذ بايت في ترتيب غير متوقع. سأشير إليها في كجن.

6.3. كجن


 def keygen(email): email_hash = get_email_hash(email) pairs = [(0x00, 0x1c), (0x1f, 0x03), (0x01, 0x1d), (0x1e, 0x02), (0x04, 0x18), (0x1b, 0x07), (0x05, 0x19), (0x1a, 0x06), (0x08, 0x14), (0x17, 0x0b), (0x09, 0x15), (0x16, 0x0a), (0x0c, 0x10), (0x13, 0x0f), (0x0d, 0x11), (0x12, 0x0e)] key = [] for pair in pairs: i = pair[0] j = pair[1] key.append((email_hash[i] * email_hash[j])%9) return [''.join(map(str, key[i:i+4])) for i in range(0, 16, 4)] 

لذلك ، فلنولد مفتاحًا:

 >>> import lunix >>> lunix.keygen("m.gayanov@gmail.com") ['0456', '3530', '0401', '2703'] 


والآن يمكنك الاسترخاء ولعب اللعبة 2048 :) شكرا لاهتمامكم! كود هنا

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


All Articles