Introduction
These days I think there is still necessity to write a tutorial series on Linux Kernel exploitation and hope to summarize the kernel exploitation techniques as following:
(1) Kernel Debugging
(2) Return-oriented-Programming in Kernel
(3) Kernel Mitigation: KASLR, SMEP, SMAP
(4) Kernel Space Memory Allocator: SLAB Allocator
In this post, I will introduce how to debug a Linux Kernel via CVE-2017-7308 [1], including setting up debugging environment, root cause analysis and exploit primitive analysis.
Kernel Debugging
First of all, download the vulnerable kernel image from the archive[2]. In this post, I choose linux-4.10.6 as testing environment.
Usually, build two different kernels: one with address sanitizer and one without address sanitizer. To build a Linux kernel image, use the following steps to generate a kernel image
make defconfig make kvmconfig #open .config file #set CONFIG_DEBUG_INFO=y #set CONFIG_KASAN=y for address sanitizer make -j 8 #for prompted compiling configuration, choose the option by default
Then we use the following script to launch the testing machine. Option “-s” denotes to enable kernel debugging. The process to generate image file follows the same process in my previous Post
qemu-system-x86_64 -s \ -kernel /home/dango/Kernel/linux-4.10.6-asan/arch/x86/boot/bzImage \ -append "console=ttyS0 root=/dev/sda rw oops=panic panic=1 quiet kaslr" \ -hda /home/dango/Kernel/Image01.img \ -enable-kvm -m 2G -nographic
In gdb, we can use the following command to attach to the debuggee.
target remote:1234
The poc file for the crash has been uploaded in my github repo[3]. And we will get the final crash info as following:
[ 25.026630] BUG: KASAN: use-after-free in prb_retire_current_block+0x1ba/0x350 at addr ffff8800655f0010 [ 25.028255] Write of size 4 by task exp/1775 [ 25.029047] CPU: 0 PID: 1775 Comm: exp Tainted: G B 4.10.6 #2 [ 25.030403] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014 [ 25.032542] Call Trace: [ 25.033171] <IRQ> [ 25.033697] dump_stack+0xb0/0x125 [ 25.034297] ? _atomic_dec_and_lock+0x16b/0x16b [ 25.034846] ? load_image_and_restore+0xd8/0xd8 [ 25.035396] kasan_object_err+0x1c/0x70 [ 25.035864] kasan_report.part.1+0x20e/0x4e0 [ 25.036385] ? prb_retire_current_block+0x1ba/0x350 [ 25.036974] ? perf_event_task_tick+0xfe/0x6a0 [ 25.037524] ? raise_softirq_irqoff+0x160/0x160 [ 25.038066] kasan_report+0x21/0x30 [ 25.038488] __asan_store4+0x64/0x80 [ 25.038922] prb_retire_current_block+0x1ba/0x350 [ 25.039488] ? prb_open_block+0x240/0x240 [ 25.039974] ? cpu_load_update_active+0x1e3/0x1f0 [ 25.040539] ? cpu_load_update_nohz_stop+0x1d0/0x1d0 [ 25.041142] ? calc_global_load_tick+0x8a/0x160 [ 25.041693] ? calc_global_load+0x3a0/0x3a0 [ 25.042203] ? sched_clock+0x9/0x10 [ 25.042630] tpacket_rcv+0x5f4/0x18b0 [ 25.043077] ? packet_rcv_spkt+0x2b0/0x2b0 [ 25.043834] ? pvclock_read_flags+0x60/0x60 [ 25.044576] ? __remove_hrtimer+0xb0/0xb0 [ 25.045079] ? check_preempt_curr+0xb4/0x110 [ 25.045670] ? ttwu_do_wakeup+0xad/0x200 [ 25.046149] ? sched_clock_cpu+0xc5/0xe0 [ 25.046645] ? __lock_text_start+0x8/0x8 [ 25.047370] ? free_kthread_struct+0x30/0x50 [ 25.047887] ? try_to_wake_up+0x113/0x7e0 [ 25.048378] ? packet_rcv_spkt+0x2b0/0x2b0 [ 25.048919] __netif_receive_skb_core+0x86c/0x17b0 [ 25.049511] ? nf_ingress+0x3c0/0x3c0 [ 25.049960] ? tick_program_event+0x48/0x80 [ 25.050465] ? hrtimer_interrupt+0x3b2/0x3e0 [ 25.050983] ? hrtimer_get_next_event+0xf0/0xf0 [ 25.051528] ? vprintk_emit+0x398/0x590 [ 25.051994] ? detach_if_pending+0x87/0x260 [ 25.052499] ? wake_up_process+0x10/0x20 [ 25.052973] ? insert_work+0x288/0x300 [ 25.053442] ? trace_event_raw_event_workqueue_execute_start+0x140/0x140 [ 25.054243] ? apic_timer_interrupt+0x89/0x90 [ 25.054774] ? get_work_pool+0x197/0x1a0 [ 25.055256] ? pool_mayday_timeout+0x450/0x450 [ 25.055802] ? __internal_add_timer+0x11a/0x160 [ 25.056355] ? __lock_text_start+0x8/0x8 [ 25.056836] ? _raw_spin_unlock_irqrestore+0x9b/0xd0 [ 25.057447] ? mod_timer_pending+0x720/0x720 [ 25.057970] ? detach_if_pending+0x87/0x260 [ 25.058483] ? get_nohz_timer_target+0xb2/0x2a0 [ 25.059072] ? __internal_add_timer+0x11a/0x160 [ 25.059930] ? __lock_text_start+0x8/0x8 [ 25.060646] ? insert_work+0x300/0x300 [ 25.061417] ? mod_timer+0x3fe/0x760 [ 25.062135] ? _find_next_bit.part.0+0x2c/0xb0 [ 25.063029] ? mod_timer_pending+0x720/0x720 [ 25.063874] __netif_receive_skb+0x21/0xb0 [ 25.064870] process_backlog+0x15e/0x350 [ 25.065870] ? napi_gro_receive+0x220/0x220 [ 25.066902] ? _find_next_bit.part.0+0x2c/0xb0 [ 25.067720] net_rx_action+0x4fe/0xbd0 [ 25.068173] ? napi_complete_done+0x1a0/0x1a0 [ 25.068702] ? __writepage+0x60/0x60 [ 25.069146] ? process_timeout+0x10/0x10 [ 25.069622] ? __perf_event_task_sched_in+0x430/0x430 [ 25.070230] ? _find_next_bit.part.0+0x2c/0xb0 [ 25.070768] ? find_next_bit+0x18/0x20 [ 25.071225] ? __next_timer_interrupt+0xb7/0xe0 [ 25.071774] ? __run_timers+0x73d/0x790 [ 25.072242] ? msleep+0x50/0x50 [ 25.072627] ? timerqueue_add+0x40/0x140 [ 25.073189] ? __remove_hrtimer+0xa0/0xb0 [ 25.073816] ? __remove_hrtimer+0xb0/0xb0 [ 25.074441] ? __asan_loadN+0xf/0x20 [ 25.075002] ? pvclock_clocksource_read+0x157/0x250 [ 25.075758] ? pvclock_read_flags+0x60/0x60 [ 25.076408] ? hrtimer_cancel+0x20/0x20 [ 25.077126] ? pvclock_read_flags+0x60/0x60 [ 25.077894] ? kvm_clock_get_cycles+0x1e/0x20 [ 25.078457] ? ktime_get+0xd2/0x170 [ 25.078916] ? ktime_get_raw+0x130/0x130 [ 25.079397] ? lapic_next_event+0x37/0x40 [ 25.079930] ? clockevents_program_event+0x109/0x150 [ 25.080784] ? tick_program_event+0x48/0x80 [ 25.081303] __do_softirq+0x1af/0x4d3 [ 25.081748] ? __irqentry_text_end+0x1/0x1 [ 25.082317] ? handle_irq+0x105/0x1b0 [ 25.082767] ? rcu_irq_exit.part.75+0x53/0x80 [ 25.083288] ? rcu_irq_exit+0x1c/0x30 [ 25.083732] ? irq_exit+0x6b/0xf0 [ 25.084135] do_softirq_own_stack+0x1c/0x30 [ 25.084636] </IRQ> [ 25.084897] do_softirq.part.18+0x58/0x60 [ 25.085387] __local_bh_enable_ip+0x78/0x80 [ 25.085887] __dev_queue_xmit+0x6bc/0xe60 [ 25.086372] ? __skb_flow_dissect+0x3a6/0x18d0 [ 25.086906] ? netdev_pick_tx+0x160/0x160 [ 25.087389] ? __skb_flow_get_ports+0x1f0/0x1f0 [ 25.087934] ? update_stack_state+0xba/0xf0 [ 25.088435] ? alloc_skb_with_frags+0xf2/0x3a0 [ 25.088968] ? skb_queue_purge+0x30/0x30 [ 25.089448] ? depot_save_stack+0x12c/0x4a0 [ 25.089950] ? skb_set_owner_w+0xa2/0x120 [ 25.090434] ? ____fput+0x9/0x10 [ 25.090825] ? entry_SYSCALL_64_fastpath+0xa7/0xa9 [ 25.091400] ? kasan_check_write+0x14/0x20 [ 25.091892] ? copy_from_iter+0x226/0x750 [ 25.092420] ? copy_page_to_iter+0x5e0/0x5e0 [ 25.093081] ? __dev_get_by_index+0xa0/0xa0 [ 25.093897] ? skb_copy_datagram_from_iter+0x7c/0x2a0 [ 25.094881] ? lockref_mark_dead+0x9c/0xd0 [ 25.095690] dev_queue_xmit+0xb/0x10 [ 25.096418] packet_sendmsg+0x23b2/0x28e0 [ 25.097220] ? __dentry_kill+0x3f7/0x490 [ 25.098134] ? check_and_drop+0x40/0x40 [ 25.099083] ? tpacket_rcv+0x18b0/0x18b0 [ 25.100042] ? security_file_free+0x29/0x50 [ 25.101013] ? _raw_spin_trylock+0x75/0xd0 [ 25.101524] ? _raw_read_lock_irqsave+0x40/0x40 [ 25.102070] ? rcu_process_callbacks+0xc20/0xc20 [ 25.102625] ? strncmp+0x71/0xc0 [ 25.103018] ? mntput_no_expire+0xb9/0x480 [ 25.103512] ? dput.part.23+0x44c/0x520 [ 25.103975] ? dev_get_by_name_rcu+0x147/0x190 [ 25.104509] ? mnt_get_count+0xa0/0xa0 [ 25.104962] ? __dentry_kill+0x490/0x490 [ 25.105443] ? locks_remove_file+0xcf/0x310 [ 25.105947] ? do_lock_file_wait+0x1b0/0x1b0 [ 25.106460] ? __asan_storeN+0x12/0x20 [ 25.106913] ? sock_has_perm+0x111/0x1e0 [ 25.107387] ? selinux_secmark_relabel_packet+0x60/0x60 [ 25.108013] ? __mod_tree_remove+0x40/0x40 [ 25.108507] ? __fget_light+0x19e/0x210 [ 25.108972] ? expand_files+0x5d0/0x5d0 [ 25.109442] ? __fput+0x317/0x3d0 [ 25.109845] ? selinux_socket_sendmsg+0x28/0x30 [ 25.110600] ? tpacket_rcv+0x18b0/0x18b0 [ 25.111338] sock_sendmsg+0x6f/0x80 [ 25.111787] SYSC_sendto+0x2aa/0x330 [ 25.112251] ? SYSC_connect+0x260/0x260 [ 25.112742] ? task_work_cancel+0x160/0x160 [ 25.113269] ? __close_fd+0x2d2/0x380 [ 25.113979] ? prepare_exit_to_usermode+0xf0/0xf0 [ 25.114545] SyS_sendto+0x9/0x10 [ 25.114943] entry_SYSCALL_64_fastpath+0x1a/0xa9 [ 25.115856] RIP: 0033:0x7f7b7a54dc23 [ 25.116560] RSP: 002b:00007ffd19be47a8 EFLAGS: 00000246 ORIG_RAX: 000000000000002c [ 25.118038] RAX: ffffffffffffffda RBX: 0000000000000005 RCX: 00007f7b7a54dc23 [ 25.119419] RDX: 0000000000000012 RSI: 00007ffd19be47f2 RDI: 0000000000000004 [ 25.120802] RBP: 00007ffd19be47e0 R08: 00007ffd19be47c0 R09: 0000000000000014 [ 25.122190] R10: 0000000000000000 R11: 0000000000000246 R12: 00005596576a9df7 [ 25.123571] R13: 00007ffd19be5120 R14: 0000000000000000 R15: 0000000000000000 [ 25.124927] Object at ffff8800655f0000, in cache names_cache size: 4096 [ 25.125871] Allocated: [ 25.126171] PID = 1772 [ 25.126724] save_stack_trace+0x16/0x20 [ 25.127451] save_stack+0x43/0xd0 [ 25.128132] kasan_kmalloc+0xad/0xe0 [ 25.128828] kasan_slab_alloc+0x12/0x20 [ 25.129623] kmem_cache_alloc+0xa8/0x160 [ 25.130402] getname_flags+0x6a/0x2b0 [ 25.131223] user_path_at_empty+0x1e/0x40 [ 25.132249] SyS_access+0x148/0x340 [ 25.133161] entry_SYSCALL_64_fastpath+0x1a/0xa9 [ 25.134298] Freed: [ 25.134604] PID = 1772 [ 25.134896] save_stack_trace+0x16/0x20 [ 25.135358] save_stack+0x43/0xd0 [ 25.135762] kasan_slab_free+0x73/0xc0 [ 25.136214] kmem_cache_free+0x72/0x190 [ 25.136678] putname+0x6d/0x80 [ 25.137050] filename_lookup+0x1b3/0x290 [ 25.137532] user_path_at_empty+0x31/0x40 [ 25.138014] SyS_access+0x148/0x340 [ 25.138440] entry_SYSCALL_64_fastpath+0x1a/0xa9 [ 25.138996] Memory state around the buggy address: [ 25.139575] ffff8800655eff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 25.140435] ffff8800655eff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 [ 25.141302] >ffff8800655f0000: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 25.142156] ^ [ 25.142608] ffff8800655f0080: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 25.143462] ffff8800655f0100: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb [ 25.144803] ==================================================================
This time, address sanitizer serves as an oracle to check if the vulnerability exists or not. However, we can find that KASAN reports use-after-free vulnerability in the error report. But the error info given in [1] is an out-of-bound write vulnerability in the code.
Next, we are going to discuss the root cause analysis. Since this part has been discussed in [1], I will try to provide more debugging information to support the analysis. Then I will analyze the exploit primitives in this exploit.
Root Cause Analysis
The vulnerability exists in the sanity check in packet_set_ring as below:
if (po->tp_version >= TPACKET_V3 && (int)(req->tp_block_size - BLK_PLUS_PRIV(req_u->req3.tp_sizeof_priv)) <= 0) goto out;
From the source code, we can clearly tell that tp_block_size and tp_block_size are both copied from user space and controlled by user.
To better understand the code above, I dump the corresponding binary code as following:
0xffffffff8185cfc9 <packet_set_ring+761>: cmp $0x1,%edx # po->tp_version >= TPACKET_V3 0xffffffff8185cfcc <packet_set_ring+764>: jbe 0xffffffff8185cfe5 <packet_set_ring+789> 0xffffffff8185cfce <packet_set_ring+766>: mov 0x14(%r13),%esi => 0xffffffff8185cfd2 <packet_set_ring+770>: lea -0x30(%rax),%ecx 0xffffffff8185cfd5 <packet_set_ring+773>: lea 0x7(%rsi),%edx 0xffffffff8185cfd8 <packet_set_ring+776>: and $0xfffffff8,%edx 0xffffffff8185cfdb <packet_set_ring+779>: sub %edx,%ecx 0xffffffff8185cfdd <packet_set_ring+781>: test %ecx,%ecx 0xffffffff8185cfdf <packet_set_ring+783>: jle 0xffffffff8185d146 <packet_set_ring+1142> (gdb) p/x $rax $1 = 0x10000 (gdb) p/x $rsi $2 = 0xffffffe0
From the code above we can find that $rax represents tp_block_size and $rsi represents tp_sizeof_priv.
The data calculation starting from is given below:
$ecx = 0x10000 - 0x30 = 0xffd0 $edx = 0xffffffe0 + 0x7 = 0xffffffe7 $edx = 0xffffffe7 & 0xfffffff8 = 0xffffffe0 $ecx = 0xffd0 - 0xffffffe0 = 0xfff0;
At this time, value in $ecx is larger than 0 (0xfff0), bypassing the sanity check.
Exploit Primitive
For now, we just understand a negative value (or a large unsigned integer) passes the sanity check. But what we observe is a use-after-free, now we want to demonstrate that the vulnerability results in out-of-bound write.
After passing the sanity check in packet_set_ring, we come across the following code:
switch (po->tp_version) { case TPACKET_V3: /* Transmit path is not supported. We checked * it above but just being paranoid */ if (!tx_ring) init_prb_bdqc(po, rb, pg_vec, req_u); break; static void init_prb_bdqc(struct packet_sock *po, struct packet_ring_buffer *rb, struct pgv *pg_vec, union tpacket_req_u *req_u) { struct tpacket_kbdq_core *p1 = GET_PBDQC_FROM_RB(rb); struct tpacket_block_desc *pbd; memset(p1, 0x0, sizeof(*p1)); /* some code */ p1->kblk_size = req_u->req3.tp_block_size; p1->blk_sizeof_priv = req_u->req3.tp_sizeof_priv; p1->max_frame_len = p1->kblk_size - BLK_PLUS_PRIV(p1->blk_sizeof_priv); prb_init_ft_ops(p1, req_u); prb_setup_retire_blk_timer(po); prb_open_block(p1, pbd); }
Based on the previous calculation we can see that p1->max_frame_len is set to 0xffffffe0.
With some reverse engineering work, we get the following result:
Breakpoint 1 at 0xffffffff8185ec44: file /home/dango/Kernel/linux-4.10.6/net/packet/af_packet.c, line 1076. Breakpoint 1, __packet_lookup_frame_in_block (status=<optimized out>, len=<optimized out>, skb=<optimized out>, po=<optimized out>) at /home/dango/Kernel/linux-4.10.6/net/packet/af_packet.c:1076 1076 end = (char *)pbd + pkc->kblk_size; (gdb) p/x *pkc $1 = {pkbdq = 0xffff88007a5f6540, feature_req_word = 0x1, hdrlen = 0x44, reset_pending_on_curr_blk = 0x0, delete_blk_timer = 0x0, kactive_blk_num = 0x0, blk_sizeof_priv = 0xffe0, last_kactive_blk_num = 0x0, pkblk_start = 0xffff88007a730000, pkblk_end = 0xffff88007a740000, kblk_size = 0x10000, max_frame_len = 0xfffffff0, knum_blocks = 0x3, knxt_seq_num = 0x2, prev = 0xffff88007a740010, nxt_offset = 0xffff88007a740010, skb = 0x0, blk_fill_in_prog = {counter = 0x0}, retire_blk_tov = 0x4, version = 0x2, tov_in_jiffies = 0x4, retire_blk_timer = {entry = {next = 0x0, pprev = 0xffffc9000034fe10}, expires = 0x100042ef8, function = 0xffffffff8185b4d0, data = 0xffff88007c9eb000, flags = 0xe400000, start_pid = 0xffffffff, start_site = 0x0, start_comm = {0x0 <repeats 16 times>}}}
We can observe that max_frame_len is already set to 0xfffffff0.
Next, we reach skb_copy_bits in function tpacket_rcv as below:
Breakpoint 1 at 0xffffffff8185edf2: file /home/dango/Kernel/linux-4.10.6/net/packet/af_packet.c, line 2259. Breakpoint 1, 0xffffffff8185edf2 in tpacket_rcv (skb=0xffff88007a40a600, dev=0xffff88007c71c000, pt=<optimized out>, orig_dev=0xffff88007c71c000) at /home/dango/Kernel/linux-4.10.6/net/packet/af_packet.c:2259 2259 skb_copy_bits(skb, 0, h.raw + macoff, snaplen); (gdb) x/i $rip => 0xffffffff8185edf2 <tpacket_rcv+1026>: callq 0xffffffff81742010 <skb_copy_bits> (gdb) p/x $rdi $1 = 0xffff88007a40a600 (gdb) p/x $rsi $2 = 0x0 (gdb) p/x $rdx $3 = 0xffff88007a740062 (gdb) p/x $rcx $4 = 0x12 (gdb) p/x ((struct packet_sock*)($r12))->rx_ring->prb_bdqc $5 = {pkbdq = 0xffff88007a5f6600, feature_req_word = 0x1, hdrlen = 0x44, reset_pending_on_curr_blk = 0x0, delete_blk_timer = 0x0, kactive_blk_num = 0x1, blk_sizeof_priv = 0xffe0, last_kactive_blk_num = 0x1, pkblk_start = 0xffff88007a730000, pkblk_end = 0xffff88007a740000, kblk_size = 0x10000, max_frame_len = 0xfffffff0, knum_blocks = 0x3, knxt_seq_num = 0x3, prev = 0xffff88007a740010, nxt_offset = 0xffff88007a740078, skb = 0xffff88007a40a600, blk_fill_in_prog = {counter = 0x1}, retire_blk_tov = 0x4, version = 0x2, tov_in_jiffies = 0x4, retire_blk_timer = {entry = {next = 0x0, pprev = 0xffff88007fc0edb8}, expires = 0x1000477c0, function = 0xffffffff8185b4d0, data = 0xffff88007c9eb000, flags = 0x400000, start_pid = 0xffffffff, start_site = 0x0, start_comm = {0x0 <repeats 16 times>}}}
At this point, we can observe that pkblk_startis 0xffff88007a730000 and pkblk_end is 0xffff88007a740000. However, the destination address of function skb_copy_bits is 0xffff88007a740062.
With some further debugging step we can find that:
Breakpoint 1 at 0xffffffff8174206c: file /home/dango/Kernel/linux-4.10.6/include/linux/skbuff.h, line 3172. Breakpoint 1, 0xffffffff8174206c in skb_copy_from_linear_data_offset (skb=<optimized out>, len=<optimized out>, to=<optimized out>, offset=<optimized out>) at /home/dango/Kernel/linux-4.10.6/include/linux/skbuff.h:3172 3172 memcpy(to, skb->data + offset, len); (gdb) x/i $rip => 0xffffffff8174206c <skb_copy_bits+92>: callq 0xffffffff8134c330 <memcpy> (gdb) p/x $rdi $1 = 0xffff88007a740062 (gdb) p/x $rsi $2 = 0xffff88007a661a10 (gdb) x/2gx $rsi 0xffff88007a661a10: 0x4242424242424242 0x4242424242424242 (gdb) p/x $rdx $3 = 0x12
By now, we can confirm that the vulnerability results in an out-of-bound write primitive in the end. ┐( ̄ヮ ̄)┌
Conclusion
In this post, we give a basic analysis of a vulnerability in Linux Kernel and demonstrate the out-of-bound write primitive in the end. Next I plan to give a tutorial on SLUB allocators in linux kernel. Hope that I can finish that by end of October.
Reference
[1]https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html
[2]https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/
[3]https://github.com/dangokyo/LinuxKernelExploit/blob/master/CVE-2017-7308/CrashPOC/exp.c
[…] (struct packet_sock in this post) next adjacent to the vulnerable buffer (packet rv_ring buffer in previous post). In [3], a general abstraction of Linux Kernel memory management is given as following picture. […]
LikeLike
i was used this version
Linux ubuntu 4.10.6 #1 SMP Wed Mar 6 07:53:49 CET 2019 x86_64 GNU/Linux
but I can’t see the error message;
BUG: KASAN: use-after-free in prb_retire_current_block+0x1ba/0x350 at addr ffff8800655f0010
How to solve that ? Thanks for ur understanding -_-
LikeLike
Hi, did you build the kernel with ASAN enabled? Sorry for the late reply. I am busy with other stuff recently.
LikeLike