
إثبات المفهوم CVE-2017-5123 - تصعيد الامتيازات المحلية - تجاوز SMEP/SMAP. بدون KASLR
PoC CVE-2017-5123 - LPE - تجاوز SMAP/SMEP. بدون KASLR
في هذا الشرح الصغير، سأقوم بتحليل ثغرة في النواة تسمح لنا بالحصول على امتياز الجذر.
ينقسم هذا الملف إلى أربعة أجزاء:
أود أن أشير إلى أن هناك العديد من الطرق الأفضل لاستغلال هذا CVE (في الواقع، هذا مجرد PoC لتعلم النواة، ولا يمكن استخدامه في الواقع) لكنني أعتقد أن هذه المنهجية يمكن أن تكون مفيدة كمقدمة لاستغلال النواة.
تم تقديم هذه الثغرة في 4c48abe91be0 لذا نحتاج إلى بناء هذا الإصدار من النواة.
قد يكون هذا صعبًا بعض الشيء لأنه إصدار قديم ويجب تصحيح الكود.
لقد قمت بإنشاء مستودع يحتوي على كود نواة مصحح بالفعل وملف .config حتى تتمكن من الاستنساخ والبناء.
git clone https://github.com/c3r34lk1ll3r/kernel_mirror.git
cd kernel_mirror
git checkout origin/modified_v4.14
wget https://gist.githubusercontent.com/c3r34lk1ll3r/c9c34ae86140cc7a24d0d90141686ee8/raw/52431b577a71e3fe8f89d6ce355ce9c1c54c53b6/.config
make -j 8 --output-sync=recurse
لاحظ أن هذه النواة سيتم بناؤها مع برامج تشغيل virtio حتى تتمكن من استخدام قرص virtio لمشاركة الملفات من/إلى الجهاز الظاهري.
الآن، سنقوم بإنشاء rootfs الأولي:
qemu-img create -f raw hda.raw 10G
# تنسيق القرص إلى ext4
mkfs.ext4 ./hda.raw
# إنشاء نقطة تحميل للصورة
mkdir /tmp/mount1
# تحميل القرص
sudo mount -o loop ./hda.raw /tmp/mount1
بعد ذلك، يجب علينا تثبيت توزيعة لينكس أساسية، على سبيل المثال باستخدام pacstrap أو debootstrap.
sudo pacstrap /tmp/mount1 base base-devel vim
أخيرًا، يمكننا تعديل النظام:
# إضافة مستخدم 'test'
echo 'test:x:1000:1000::/home/test:/bin/bash' | sudo tee -a /tmp/mount1/etc/passwd
# بدون كلمة مرور
echo 'test::14871::::::' | sudo tee -a /tmp/mount1/etc/shadow
# يمكننا تحميل قرص virtio لمشاركة الملفات بين المضيف والضيف
echo '/transient /home/test/shared 9p trans=virtio,version=9p2000.L,rw,user,exec 0 0' | sudo tee -a /tmp/mount1/etc/fstab
sudo mkdir -p /tmp/mount1/home/test/shared
# من المفيد الحصول على صلاحية sudo
echo '%wheel ALL=(ALL) NOPASSWD: ALL' | sudo tee -a /tmp/mount1/etc/sudoers
echo 'wheel:x:998:test' | sudo tee -a /tmp/mount1/etc/group
sudo chown -R 1000:1000 /tmp/mount1/home/test
sudo umount /tmp/mount1
إذا كان كل شيء على ما يرام، يمكننا الآن تجربة نظام الاختبار الخاص بنا باستخدام qemu:
qemu-system-x86_64 \
-kernel ./kernel_mirror/arch/x86_64/boot/bzImage \
-hda ./hda.raw \
-m 4G \
-cpu "Skylake-Client-IBRS,ss=on,vmx=on,hypervisor=on,tsc-adjust=on,clflushopt=on,umip=on,md-clear=on,stibp=on,arch-capabilities=on,ssbd=on,xsaves=on,pdpe1gb=on,ibpb=on,amd-ssbd=on,skip-l1dfl-vmentry=on,hle=off,rtm=off" \
-smp 4 \
-vga virtio \
-enable-kvm \
-nographic \
-machine type=q35,accel=kvm \
-virtfs "fsdriver=local,id=fs.1,path=./trans_fs,security_model=mapped,writeout=immediate,mount_tag=/transient" \
-append "root=/dev/sda rw noquiet nokaslr console=ttyS0 loglevel=5" \
-chardev "vc,id=vc.0,cols=1920,rows=1080" \
-net "user,hostfwd=tcp::10022-:22" \
-net "nic" \
-s
يصف وصف الـ CVE وجود عملية كتابة غير مقيدة أثناء استدعاء النظام waitid.
لنفتح kernel/exit.c وننظر إلى الكود:
SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,
infop, int, options, struct rusage __user *, ru)
{
struct rusage r;
struct waitid_info info = {.status = 0};
long err = kernel_waitid(which, upid, &info, options, ru ? &r : NULL);
int signo = 0;
if (err > 0) {
signo = SIGCHLD;
err = 0;
if (ru && copy_to_user(ru, &r, sizeof(struct rusage)))
return -EFAULT;
}
if (!infop)
return err;
user_access_begin();
unsafe_put_user(signo, &infop->si_signo, Efault);
unsafe_put_user(0, &infop->si_errno, Efault);
unsafe_put_user(info.cause, &infop->si_code, Efault);
unsafe_put_user(info.pid, &infop->si_pid, Efault);
unsafe_put_user(info.uid, &infop->si_uid, Efault);
unsafe_put_user(info.status, &infop->si_status, Efault);
user_access_end();
return err;
Efault:
user_access_end();
return -EFAULT;
}
هذه الدالة مباشرة إلى حد ما: بعد بضع عمليات فحص، هناك استدعاءات متعددة لـ unsafe_put_user(...) ثم تعود الدالة.
الجزء الرئيسي من هذه الدالة يتكون من دالة unsafe_put_user(...) لذا دعنا ننتقل إلى هناك (arch/x86/include/asm/uaccess.h):
/*
* The "unsafe" user accesses aren't really "unsafe", but the naming
* is a big fat warning: you have to not only do the access_ok()
* checking before using them, but you have to surround them with the
* user_access_begin/end() pair.
*/
#define user_access_begin() __uaccess_begin()
#define user_access_end() __uaccess_end()
#define unsafe_put_user(x, ptr, err_label) \
do { \
int __pu_err; \
__typeof__(*(ptr)) __pu_val = (x); \
__put_user_size(__pu_val, (ptr), sizeof(*(ptr)), __pu_err, -EFAULT); \
if (unlikely(__pu_err)) goto err_label; \
} while (0)
#define unsafe_get_user(x, ptr, err_label) \
do { \
int __gu_err; \
__inttype(*(ptr)) __gu_val; \
__get_user_size(__gu_val, (ptr), sizeof(*(ptr)), __gu_err, -EFAULT); \
(x) = (__force __typeof__(*(ptr)))__gu_val; \
if (unlikely(__gu_err)) goto err_label; \
} while (0)
هناك تحذير كبير جدًا في التعليق: إذا كنت تريد استخدام unsafe_put/get_user، يجب عليك أولاً استدعاء access_ok() وإحاطتها بـ user_access_begin/end().
إذا ألقينا نظرة على الكود السابق (waitid)، يمكننا أن نرى أن access_ok() لم يتم استدعاؤها أبدًا، لذا فإن استدعاء النظام ينتهك هذا التحذير.
ولكن ما هي هذه الماكرو?
SMAP و SMEP هما ميزتان أمنيتان تم تقديمهما في النواة من أجل جعل كتابة الاستغلالات أكثر صعوبة. وتجدر الإشارة إلى أن هذه الميزات يتم فرضها بواسطة وحدة المعالجة المركزية.
SMEP تمنع تنفيذ كود مساحة المستخدم بينما تكون وحدة المعالجة المركزية في وضع المشرف؛ SMAP، بدلاً من ذلك، تمنع الوصول للقراءة/الكتابة إلى ذاكرة المستخدم.
تحتاج النواة إلى كتابة/قراءة البيانات من/إلى ذاكرة المستخدم ويمكن تحقيق ذلك بطريقتين:
copy_from_user) تسمح بنسخ الذاكرة في مساحة النواة؛كما نرى في تعريف unsafe_put_user، ستقوم هذه الدالة فقط بنسخ قيمة x في الذاكرة المشار إليها بواسطة ptr (وتنتقل إلى err_label إذا حدث خطأ). لقد قلنا للتو أن النواة لا يمكنها الوصول إلى مساحة المستخدم بسبب SMAP ولهذا السبب يجب أن تكون هذه الدوال محاطة بين user_access_begin/end().
#define __uaccess_begin() stac()
#define __uaccess_end() clac()
كما نرى، user_access_begin/end هما ببساطة تعليمة ASM stac و clac.
stac: "يضبط بت AC في سجل EFLAGS. قد يتيح ذلك فحص المحاذاة لوصول بيانات وضع المستخدم. يسمح هذا بوصول صريح لوضع المشرف إلى صفحات وضع المستخدم حتى إذا تم تعيين بت SMAP في سجل CR4."clac: "يمسح بت AC في سجل EFLAGS. يعطل هذا أي فحص محاذاة لوصول بيانات وضع المستخدم. إذا تم تعيين بت SMAP في سجل CR4، فإن هذا يمنع الوصول الصريح لوضع المشرف إلى صفحات وضع المستخدم."بشكل أساسي، تقوم هاتان الماكرو بتمكين/تعطيل SMAP.