CVE-2017-9077
前言
实验环境:
相关信息:
漏洞成因:Linux内核4.11.11之前,net/ipv6/tcp_ipv6.c中的tcp_v6_syn_recv_sock函数错误处理继承,这与CVE-2017-8890类似
修补链接:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=83eaddab4378db256d00d295bda6ca997cd13a52
漏洞验证
执行POC:查看崩溃函数及调用堆栈
[ 226.374015] Unable to handle kernel paging request at virtual address 00200218
[ 226.374053] pgd = ffffffc03eea7000
[ 226.374069] [00200218] *pgd=0000000000000000
[ 226.374096] Internal error: Oops: 96000005 [#1] PREEMPT SMP
[ 226.374113] Modules linked in:
[ 226.374141] CPU: 3 PID: 3738 Comm: CVE-2017-9077 Not tainted 3.10.58-03546-gd85102c #1
[ 226.374160] task: ffffffc02d199600 ti: ffffffc0384dc000 task.ti: ffffffc0384dc000
[ 226.374194] PC is at ipv6_sock_mc_close+0xa0/0x104
[ 226.374215] LR is at ipv6_sock_mc_close+0x98/0x104
[ 226.374231] pc : [<ffffffc0008bda58>] lr : [<ffffffc0008bda50>] pstate: 80000145
[ 226.374245] sp : ffffffc0384dfd60
[ 226.374259] x29: ffffffc0384dfd60 x28: ffffffc0384dc000
[ 226.374285] x27: ffffffc000e2e000 x26: ffffffc00113b000
[ 226.374310] x25: ffffffc000e3ff40 x24: ffffffc0355aecc0
[ 226.374335] x23: ffffffc00113b1a0 x22: ffffffc0355af318
[ 226.374359] x21: ffffffc0555d06c0 x20: ffffffc0355aedb0
[ 226.374383] x19: 0000000000200200 x18: 00000000ffffffff
[ 226.374408] x17: 0000007ee813cb9c x16: ffffffc0001b3390
[ 226.374432] x15: 003b9aca00000000 x14: 001c983560000000
[ 226.374456] x13: ffffffffa58d9e94 x12: 0000000000000020
[ 226.374480] x11: 0000000000000000 x10: 00000000ffffffff
[ 226.374504] x9 : 000000000000000a x8 : 0000000000000039
[ 226.374528] x7 : 6620646e6f636573 x6 : 0000007ee780f014
[ 226.374552] x5 : 0000000000000000 x4 : 0000000000000000
[ 226.374576] x3 : 0000000000000003 x2 : 0000000000000000
[ 226.374599] x1 : 0000000000000000 x0 : 0000000000040004
[ 226.374621]
[ 226.374637] Process CVE-2017-9077 (pid: 3738, stack limit = 0xffffffc0384dc058)
[ 226.375167] Call trace:
[ 226.375190] [<ffffffc0008bda58>] ipv6_sock_mc_close+0xa0/0x104
[ 226.375212] [<ffffffc0008977d4>] inet6_release+0x24/0x4c
[ 226.375234] [<ffffffc00079ea80>] sock_release+0x2c/0xa8
[ 226.375252] [<ffffffc00079eb18>] sock_close+0x1c/0x30
[ 226.375275] [<ffffffc0001b593c>] __fput+0x9c/0x224
[ 226.375294] [<ffffffc0001b5b94>] ____fput+0x1c/0x2c
[ 226.375316] [<ffffffc0000c4acc>] task_work_run+0x9c/0xf4
[ 226.375339] [<ffffffc000088bd4>] do_notify_resume+0x5c/0x74
[ 226.375360] Code: aa1703e0 9402eea1 f9403ed3 b4000253 (f9400e61)
[ 226.375378] ---[ end trace 701d394ca5320577 ]---
[ 226.449802] Kernel panic - not syncing: Fatal exception
可以看到内核是在对ipv6的sock进行关闭的时候触发的崩溃,这和我们意料中的一致
漏洞分析
静态分析(熟悉触发流程,编写验证程序)
有了POC的崩溃信息后,通过崩溃时的PC在IDA中查看崩溃的具体信息
.text:FFFFFFC00046D42C LDR X1, [mc_lst,#0x18] //mc_lst == X19
结合vmlinux上下文和内核源码可知这条指令是获取mc_lst->next中的数据,但是因为此时的mc_lst == 0x200200,属于无效地址,所以导致取其偏移0x18中的内容时崩溃
我们知道是如何导致内核崩溃以后就需要分析他是怎么形成这种情景的,也就是漏洞模型(DoubuleFree)?,
感觉这漏洞像是属于释放后重引用漏洞(UAF),那么我们还是先找到它内存申请的位置和第一次释放的位置,第二次引用的位置
申请位置:
第一次释放位置
第二次引用位置
漏洞触发链:
内存申请:ipv6_setsockopt( ) –> do_ipv6_setsockopt( ) –> case MCAST_JOIN_GROUP: ipv6_sock_mc_join( ) –> sock_kmalloc( )
内存释放:sock_close( ) –> sock_release( ) –> inet6_release( ) –> ipv6_sock_mc_close( )
内存重引用:sock_close( ) –> sock_release( ) –> inet6_release( ) –> ipv6_sock_mc_close( )
像这种漏洞我一般使用堆喷射来控制释放后的结构体,如果在后续的操作中会调用结构体中的函数指针,那么我们的目的也就达成了
这一需要喷射的是mc_lst,它属于ipv6_mc_socklist结构体,
通过vmlinux计算该结构体的大小:sizeof(struct ipv6_mc_socklist) == 0x40
通过IDA中的反汇编也可以计算其大小(查看引用最后一个成员时使用的偏移,注意:该偏移属于成员偏移,而不是结构体大小,最后可能需要加上最后一个成员的大小),
:0x30+0x10(struct rcu_head size) == 0x40
.text:FFFFFFC00046D410 MOV X1, #0x30 ; func
.text:FFFFFFC00046D414 ADD X0, mc_lst, X1 ; head
.text:FFFFFFC00046D418 BL kfree_call_rcu
看了下该结构体中貌似就只有一个struct rcu_head rcu成员中存在函数指针,别的地方还真没有,还存在一个对struct ip6_sf_socklist *sflist成员进行kfree操作
动态调试(深入分析)
该漏洞属于ipv6相关漏洞:关键的下断函数
ipv6_setsockopt
ipv6_sock_mc_join
因为存在漏洞的64位的goldfish源码编译完毕后我模拟器启动不起来,所以测试环境是先找个模拟器能够启动的64位源码,然后手动修改去除CVE-2017-9077的补丁代码,修改时可以参考补丁链接上的补丁
漏洞利用
- 喷射思路:
- 首先获取需要喷射结构体的大小
- 触发漏洞
- 创建一个server线程
- 创建一个client线程
- 喷射线程
- 循环设置payload线程
堆喷射成功控制解引用地址
[ 148.998655] Unable to handle kernel paging request at virtual address 4343434343434347
[ 148.998691] pgd = ffffffc063872000
[ 148.998708] [4343434343434347] *pgd=0000000000000000
[ 148.998735] Internal error: Oops: 96000004 [#1] PREEMPT SMP
[ 148.998752] Modules linked in:
[ 148.998781] CPU: 3 PID: 4301 Comm: CVE-201 Not tainted 3.10.58-03546-gd85102c #1
[ 148.998800] task: ffffffc019bf3700 ti: ffffffc01942c000 task.ti: ffffffc01942c000
[ 148.998834] PC is at ip6_mc_leave_src+0x30/0xa4
[ 148.998856] LR is at ipv6_sock_mc_close+0xe0/0x104
[ 148.998873] pc : [<ffffffc0008bbb98>] lr : [<ffffffc0008bda98>] pstate: 80000145
[ 148.998887] sp : ffffffc01942fd30
[ 148.998901] x29: ffffffc01942fd30 x28: ffffffc01942c000
[ 148.998926] x27: ffffffc000e2e000 x26: ffffffc00113b000
[ 148.998951] x25: ffffffc000e3ff40 x24: ffffffc031daf400
[ 148.998975] x23: ffffffc00113b1a0 x22: ffffffc031dafa58
[ 148.999000] x21: ffffffc031daf400 x20: 0000000000000000
[ 148.999024] x19: ffffffc033818640 x18: 00000000ffffffff
[ 148.999048] x17: 00000079b3decb9c x16: ffffffc0001b3390
[ 148.999073] x15: 003b9aca00000000 x14: 00281cfcca000000
[ 148.999097] x13: ffffffffa58d1501 x12: 0000000000000020
[ 148.999121] x11: 0000000000000000 x10: 00000000ffffffff
[ 148.999145] x9 : 000000000000000a x8 : 0000000000000039
[ 148.999169] x7 : 6620646e6f636573 x6 : 00000079b2a0c014
[ 148.999193] x5 : 0000000000000000 x4 : 4343434343434343
[ 148.999217] x3 : 0000000000000001 x2 : 0000000043434343
[ 148.999241] x1 : ffffffc033818640 x0 : ffffffc0008bda98
[ 148.999263]
[ 148.999279] Process CVE-201 (pid: 4301, stack limit = 0xffffffc01942c058)
[ 148.999297] Stack: (0xffffffc01942fd30 to 0xffffffc019430000)
[ 148.999318] fd20: 1942fd60 ffffffc0 008bda98 ffffffc0
[ ... ]
[ 148.999838] ffe0: 00000004 00000000 00000039 00000000 0038002e 00370031 0020002c 00300032
[ 148.999851] Call trace:
[ 148.999874] [<ffffffc0008bbb98>] ip6_mc_leave_src+0x30/0xa4
[ 148.999897] [<ffffffc0008bda94>] ipv6_sock_mc_close+0xdc/0x104
[ 148.999918] [<ffffffc0008977d4>] inet6_release+0x24/0x4c
[ 148.999940] [<ffffffc00079ea80>] sock_release+0x2c/0xa8
[ 148.999957] [<ffffffc00079eb18>] sock_close+0x1c/0x30
[ 148.999980] [<ffffffc0001b593c>] __fput+0x9c/0x224
[ 148.999998] [<ffffffc0001b5b94>] ____fput+0x1c/0x2c
[ 149.000020] [<ffffffc0000c4acc>] task_work_run+0x9c/0xf4
[ 149.000043] [<ffffffc000088bd4>] do_notify_resume+0x5c/0x74
[ 149.000064] Code: d503201f f9401664 b9402662 b40002a4 (b9400483)
[ 149.000185] ---[ end trace 343ee1bba8b5cfe0 ]---
[ 149.074517] Kernel panic - not syncing: Fatal exception
struct rcu_head rcu;
利用时发现这个成员不能使用,因为内核会修改该成员的值,8890之前是通过一个结构体指针,指向用户空间虽然内核会修改里面的值,但是因为是在用户空间 我们只要开启一个线程进行保护修改就行,而这个不是一个结构体指针,喷射后我们就不能进行修改了
第二个思路:通过struct ipv6_mc_socklist __rcu *next;成员指向用户空间,然后构造数据
控制住了PC,后面的操作就简单多了
[ 116.545938] CPU: 0 PID: 3688 Comm: CVE-2017-9077 Not tainted 3.10.58-03546-gd85102c #1
[ 116.545948] task: ffffffc01d946e00 ti: ffffffc01d970000 task.ti: ffffffc01d970000
[ 116.545958] PC is at 0xaaaaaaaaaaaaaaaa
[ 116.545976] LR is at rcu_process_callbacks+0x368/0x5f0
[ 116.545984] pc : [<aaaaaaaaaaaaaaaa>] lr : [<ffffffc00012e1d0>] pstate: a0000145
[ 116.545990] sp : ffffffc01d973930
[ 116.545996] x29: ffffffc01d973930 x28: ffffffc0b3f40060
[ 116.546007] x27: 0000000000000001 x26: ffffffc025b9dd00
[ 116.546019] x25: ffffffc01cc40a48 x24: ffffffc000981000
[ 116.546031] x23: ffffffc01d970000 x22: 000000000000000a
[ 116.546042] x21: 0000000000000001 x20: ffffffc0b3f40088
[ 116.546053] x19: ffffffc000e33300 x18: 00000000ffffffff
[ 116.546065] x17: 00000074716cc25c x16: ffffffc0001da624
[ 116.546076] x15: 000000747170672c x14: 0000007471706a88
[ 116.546088] x13: ffffffc000985990 x12: 0000000000000001
[ 116.546100] x11: 00000000000003d8 x10: 00000000000001a0
[ 116.546111] x9 : ffffffc000985990 x8 : 0000000000000001
[ 116.546123] x7 : ffffffc0b3f43a58 x6 : 0000000000000007
[ 116.546134] x5 : aaaaaaaaaaaaaaaa x4 : 0000000000000001
[ 116.546145] x3 : 0000000000000000 x2 : 0000000000000081
[ 116.546157] x1 : 0000000002000030 x0 : 0000000002000030
[ 116.546168]
[ 116.546175] Process CVE-2017-9077 (pid: 3688, stack limit = 0xffffffc01d970058)
[ 116.546728] Call trace:
[ 116.546737] [<aaaaaaaaaaaaaaaa>] 0xaaaaaaaaaaaaaaaa
[ 116.546749] [<ffffffc0000a9a7c>] __do_softirq+0x108/0x288
[ 116.546757] [<ffffffc0000a9cc4>] do_softirq+0x64/0x70
[ 116.546766] [<ffffffc0000a9f48>] irq_exit+0x94/0xc8
[ 116.546776] [<ffffffc0000848b8>] handle_IRQ+0x58/0xd8
[ 116.546785] [<ffffffc0000813fc>] gic_handle_irq+0x44/0x88
提权
因为x0可控,使用总体来说提权还是蛮好提的,直接kernel_sock_ioctl即可
kernel_sock_ioctl == ffffffc00079beb4
1.调用rcu回调时在别的进程中,导致访问地址失败,因为别的进程没有映射我们设置的地址
2.稳定崩溃在kfree函数中
貌似因为内核竞争修改没有修改成功导致
本来以为可以复用8890那漏洞的思路,但是看了下漏洞结构体mc_lst,其中包含两个与rcu机制相关的结构体成员,
1.struct rcu_head rcu
2.struct ipv6_mc_socklist_rcu *next
最开始打算用mc_lst结构体中的rcu结构体成员,但是后来发现在kfree_rcu函数中会对我们控制的mc_lst->rcu.func函数指针做初始化,但是因为mc_lst成员不是指针,所以其成员在数据喷射的时候就已经固定在内核中了,这样就导致了我们直接喷射的数据不能利用,而需要通过间接利用,所以第一个结构体不能使用
还存在一个ipv6_mc_socklist_rcu next的函数指针,这个函数指针满足间接利用的条件(属于指针且可控),尝试在用户态构造一个ipv6_mc_socklist_rcu结构体,让ipv6_mc_socklist_rcu next指针指向它,这样当内核kfree_rcu第一个ipv6_mc_socklist_rcu结构体后就会kfree_rcu第二个(用户态)的ipv6_mc_socklist_rcu结构体,这样会再次添加一个rcu回调函数并且可控
为什么8890可以利用呢?
因为之前8890结构体中还存在一个结构体指针,该结构体中存在一个rcu_head结构体,因为该结构体指针是一个我们可控指针,所以可以把该指针指向用户空间可控的数据,然后再开启一个竞争修改数据的线程,与内核竞争修改rcu.func函数指针的值为我们想要控制的指针,就可以控制ruc回调函数的调用
可能在poc执行完毕后,测试机都没有崩溃
第一次调用ipv6_sock_mc_close时竟然没进入循环
总结
发生的情况
Author: Let_go
Link: http://github.com/2017/09/20/CVE-2017-9077/
Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.