مرحبا يا هبر!
في
HackQuest ، قبل مؤتمر ZeroNight 2019 ، كانت هناك مهمة مسلية واحدة. لم أتخذ القرار في الوقت المحدد ، لكنني تلقيت نصيبي من الإثارة. أعتقد أنك ستكون مهتمًا بمعرفة ما الذي أعده المنظمون وفريق الطاقم للمشاركين.
المهمة: الحصول على رمز التفعيل لنظام التشغيل
Micosoft 1998 السري.
في هذه المقالة سأخبرك بكيفية القيام بذلك.
محتوى
0. المهمة
1. الأدوات2. تفقد الصورة3. أجهزة الشخصيات والنواة4. بحث register_chrdev4.1. إعداد صورة جديدة طفيفة لينكس4.2. بعض الاستعدادات4.3. تعطيل KASLR على lunix4.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
لنحفر في الداخل:
لذلك لدينا:
- توزيع - الحد الأدنى لينكس 5.0.11.
- يتمثل نشاط جهاز
/dev/activate
في التحقق من البريد ، والمفتاح ، مما يعني أنه يجب البحث عن منطق التحقق في مكان ما في أحشاء النواة. - البريد ، يتم نقل
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. إعداد صورة جديدة طفيفة لينكس
- تثبيت الأدوات اللازمة:
sudo apt install wget make gawk gcc bc bison flex xorriso libelf-dev libssl-dev
- تنزيل البرامج النصية:
git clone https://github.com/ivandavidov/minimal cd minimal/src
- تصحيح
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
- ترجمة
./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 :) شكرا لاهتمامكم! كود
هنا