Linux Kernel BINFMT_ELF Loader Local Privilege Escalation Vulnerabilities
bugtraq id 11646
http://www.securityfocus.com/bid/11646
针对isec paper中对问题点的描述,我先简单分析一下:
1. 第一个问题出在kernel_read上,如果我们使用一些手段使其返回值大于0而少于
调用所给定的长度,我们就可以构造一个虚假的elf映象。例如,通过控制
477,523行处的kernel_read调用的返回值,我们就可以为elf二进制文件指定一个虚假的elf_interpreter。
如果这个elf二进制文件是一个setuid程序的话,我们就可以通过弹出的interpreter获得root权限。
上面这个例子是一个纯理论上的分析,事实上要想做到这一点,还需要一些更深入
的分析和实践,但是由于isec的paper中提到了在x86下对于read调用的控制可能是不
可行的,因此我也暂时不在此投入更多的时间。
2. 如果说isec已经做到了权限提升,那在这一点的可能性最大,这个有
时间会进行重点分析,在这里就不多说了。
3. 与2的类似,但是至少表面看起来这个比2更容易看出问题,简单看一下代码就可以
发现301行的kernel_read返回值可以被当作elf_entry使用!如果是一个setuid程序的话会发生什么?!
4. 这是一个interpreter名字未作'\0'结尾检测问题。按照isec的说法,这个问题有
可能造成系统挂起。试验了一下,至少在我的环境下,系统没有受到太大的影响,也
许是内存中的0太多?这个效果不是很容易表现出来。
5. 这个比较明显,open_exec()函数调用中没有对读权限进行检查。isec在paper后面
给出了验证代码,这处利用价值有限,暂时略过。
---------------------------------------------------------------------------------------------------------
对(3)的进一步分析:
retval = kernel_read(interpreter,interp_elf_ex->e_phoff,(char *)elf_phdata,size);
error = retval;
if (retval < 0)
goto out_close;
前面提到过retval的值可以返回作为elf_entry被使用,这个是没有问题的。但是进一步阅读代码发现retval <= size,就是说retval的值不会超过size。向前查阅代码确认size的取值范围
size = sizeof(struct elf_phdr) * interp_elf_ex->e_phnum;
if (size > ELF_MIN_ALIGN)
goto out;
可以看出, size被限制为 <= ELF_MIN_ALIGN。再来看ELF_MIN_ALIGN的值
#if ELF_EXEC_PAGESIZE > PAGE_SIZE
# define ELF_MIN_ALIGN ELF_EXEC_PAGESIZE
#else
# define ELF_MIN_ALIGN PAGE_SIZE
#endif
......
#define ELF_EXEC_PAGESIZE 4096
在我查阅的内核代码中, ELF_MIN_ALIGN在两种情况下的值均为PAGE_SIZE。
到这里可以确定retval的范围- retval <= PAGE_SIZE,也就是说即使我们利用301和321行处的两个程序BUG,使得retval被返回作为elf_entry使用,最后也无法得到有价 值的结果。原因就是elf_entry只能指向0到PAGE_SIZE这个范围内的地址空间,而这会引起进程访问无效页面,最终导致进程被kill。
---------------------------------------------------------------------------------------------------------
对(2)的进一步分析:(事实上,经过上面的分析后(2)是唯一一个还有可能有利用价值的了)
...
...
for(i = 0, elf_ppnt = elf_phdata; i < elf_ex.e_phnum; i++, elf_ppnt++) {
int elf_prot = 0, elf_flags;
unsigned long vaddr;
if (elf_ppnt->p_type != PT_LOAD) <-- (a)
continue;
...
...
error = elf_map(bprm->file, load_bias + vaddr, elf_ppnt, elf_prot, elf_flags);
if (BAD_ADDR(error)) <-- (b)
continue;
...
...
}
...
...
if (elf_interpreter) {
...
elf_entry = load_elf_interp(&interp_elf_ex, <-- (c)
interpreter,
&interp_load_addr);
if (BAD_ADDR(elf_entry)) { <-- (d)
printk(KERN_ERR "Unable to load interpreter\n");
send_sig(SIGSEGV, current, 0);
retval = -ENOEXEC; /* Nobody gets to see this, but.. */
goto out_free_dentry;
}
...
}
a,b两处代码是(2)的问题所在,由于b处代码对elf_map返回错误时处理不当,导致在某些情况下会形成PT_LOAD类型的段(通常是text和data段)加载失败,但程序却仍然继续执行的安全问题。
目前想到的一个利用方法是:
当elf_map在映射text段时,由于内存原因,elf_map失败,这时当前要新进程空间text段缺失,但是由于b处的问题,进程继续被 创建。当进程创建完成后,程序跳到elf_entry处执行。由于text段映射的失败,其对应的虚拟地址空间内内容未知,但如果在我们的程序中使用 sys_execv调用的话,那此处空间内的VMA应该是我们的原始进程的(即调用sys_execv的进程),因此此虚拟地址空间的内容是我们可控制 的。这样当新进程跳到elf_entry处时,将执行的是我们安排好的代码,也就是说我们可以很容易得到root权限。
上面的方法只是一个理论上的方法,在转化成实践中还有一个很难解决的问题:
------ 如何有效的控制elf_map的失败?
isec的paper中提到了两个造成elf_map失败的情况:
- a temporary low memory condition, so that the allocation of a new VMA
descriptor fails
- memory limit (RLIMIT_AS) excedeed, which can be easily manpipulated
before calling execve()
对于第一个,目前的感觉是太难于控制了,还没想到一个有效的方法。
对于第二个,实现很简单,通过setrlimit(RLIMIT_AS, ...)就可以做到。通过setrlimit和execle的组合,我们可以很好的限制SETUID程序使用的内存,使其elf_map调用失败,但一个 随之而来的问题就是,造成elf_map失败的内存限制也会造成后续的内存分配失败。除非这个限制只对b处的elf_map有效,其中一个可能就是, setuid程序text段占用的内存比其它部分需要的大,这样后续调用还可以进行。好,试试看是否可行:
在c和d处代码interpreter将被加载,不幸(对kernel coder是好事)的是这里并不存在b处的问题,因此我们只能寄希望于interpreter需要的内存比setuid程序text段需要的内存少。用 readelf观察了一下我的系统内的几个setuid程序,并没有发现.text大于其需要加载的interpreter的内存空间的,因此这个思路暂 时走到尽头。
总结一下目前的情况:
1 由于无法灵活的控制内存分配失败,因此exploit趋向于更困难
2 在目前只能利用setrlimit简单的限制内存的方法下,我们下一步还可以考虑static编译的setuid程序或特殊的text段大于 interpreter所需内存的setuid程序(也许有这样的setuid程序,但那也许是非常不常见的程序,这样我们写出exploit代码也是没 有通用性的)
没有评论:
发表评论