Linux Kernel Exploitation: Setting Up Debugging Environment

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

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.