据说InCTF国际赛是印度的强网杯233333 。
官方WP: https://blog.bi0s.in/2021/08/17/Pwn/InCTFi21-Kqueue/
比赛的时候有点事情,没怎么看题,后面重新复现一下,感觉我的exp比官方的wp简单一些。
给出了源码。
在内核态实现了一个队列管理程序。
最多管理五个队列(其实是6个,他写的有问题,后面再说)。
每个队列由一个 (queue *)
查找,维护。
单个队列的管理结构是一个 queue
队列中的每一项是一个 queue_entry
我们主要关注:
首先,__builtin_umulll_overflow 是gcc 内置的用于检测乘法溢出的函数:
https://gcc.gnu.org/onlinedocs/gcc/Integer-Overflow-Builtins.html
他做的事情就是去检测 sizeof(queue_entry) * (request.max_entries+1)
是否乘法溢出(这个结果被放在space里)
问题在于:request.max_entries
本身并没有进行溢出检测。而它是一个32位无符号数,如果request.max_entries = 0xffffffff
那么 +1 后会造成整数溢出,通过检测。
而此时 request.max_entries
为一个极大值。
此时space变量计算错误(为0),导致 queue_size
为一个极小值。
queue_size = sizeof(queue)
进而queue_size也变成一个极小值
最后:queue->max_entries = 0xffffffff,导致循环被跳过,没有真正分配queue_entry.
值得一提的是,他这个函数后面写的也有问题,可以越界分配一个queue。。然后free掉就可以直接panic(好像是他自己写错了。。)
由于我们的构造,导致 queue_size
变成了一个极小值。进而此处 new_queue
分配过小 。而在for循环里又直接向new_queue的对应位置拷贝了数据。并且拷贝的数据是 kqueue_entry->data
,此时kqueue也是分配的有问题(具体可以回到create里,总之就是kqueue_entry没有正常分配空间)
运行后会panic掉,因为此时 kqueue_entry->data 不是一个合法的值。
根本原因是在:
中 kqueue_entry指针越界,访问了不合法位置的数据。
在堆上喷射大量的 seq_operations
通过堆溢出overwrite掉ops[0],即:void * (*start) (struct seq_file *m, loff_t *pos);
实现hijack rip。
一个poc如下:
https://paste.ubuntu.com/p/b3j29GhtQt/
经过尝试,当我们第二次分配queue时,<u>有很大概率被分配到第一次的data数据的上方</u>。
我们将第一次的data数据进行恶意的构造,然后在第二次完成堆溢出,覆盖函数指针劫持rip,然后在用户态执行shellcode即可。
由于没有开启smep、smap,我们ret2usr之后在用户态再swapgs,iretq一下重新着陆到shell函数即可。
我的exp如下,感觉比官网的简单不少。只需要一次堆溢出就可以pwn。
效果:
queue
*
kqueues[MAX_QUEUES]
=
{(queue
*
)NULL};
queue
*
kqueues[MAX_QUEUES]
=
{(queue
*
)NULL};
/
*
Sometimes , waiting
in
a queue
is
so boring, but wait , this isn't
any
ordinary queue
*
/
typedef struct{
uint16_t data_size;
/
/
队列每一项entry的大小
uint64_t queue_size;
/
/
队列整体的大小
uint32_t max_entries;
/
/
队列最多的项数
uint16_t idx;
char
*
data;
}queue;
/
*
Sometimes , waiting
in
a queue
is
so boring, but wait , this isn't
any
ordinary queue
*
/
typedef struct{
uint16_t data_size;
/
/
队列每一项entry的大小
uint64_t queue_size;
/
/
队列整体的大小
uint32_t max_entries;
/
/
队列最多的项数
uint16_t idx;
char
*
data;
}queue;
typedef struct queue_entry queue_entry;
struct queue_entry{
uint16_t idx;
/
/
当前entry的idx
char
*
data;
/
/
当前entry维护的数据
queue_entry
*
next
;
/
/
next
指针
};
typedef struct queue_entry queue_entry;
struct queue_entry{
uint16_t idx;
/
/
当前entry的idx
char
*
data;
/
/
当前entry维护的数据
queue_entry
*
next
;
/
/
next
指针
};
static noinline
long
create_kqueue(request_t request){
long
result
=
INVALID;
/
/
最多是五个队列
if
(queueCount > MAX_QUEUES)
err(
"[-] Max queue count reached"
);
/
/
创建队列时元素可以等于
1
,不能小于
1
if
(request.max_entries<
1
)
err(
"[-] kqueue entries should be greater than 0"
);
if
(request.data_size>MAX_DATA_SIZE)
err(
"[-] kqueue data size exceed"
);
queue_entry
*
kqueue_entry;
ull space
=
0
;
if
(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries
+
1
),&space)
=
=
true)
/
/
整数溢出
err(
"[-] Integer overflow"
);
/
*
Size
is
the size of queue structure
+
size of entry
*
request entries
*
/
ull queue_size
=
0
;
if
(__builtin_saddll_overflow(sizeof(queue),space,&queue_size)
=
=
true)
err(
"[-] Integer overflow"
);
if
(queue_size>sizeof(queue)
+
0x10000
)
err(
"[-] Max kqueue alloc limit reached"
);
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
queue
-
>data
=
validate((char
*
)kmalloc(request.data_size,GFP_KERNEL));
queue
-
>data_size
=
request.data_size;
queue
-
>max_entries
=
request.max_entries;
queue
-
>queue_size
=
queue_size;
kqueue_entry
=
(queue_entry
*
)((uint64_t)(queue
+
(sizeof(queue)
+
1
)
/
8
));
queue_entry
*
current_entry
=
kqueue_entry;
queue_entry
*
prev_entry
=
current_entry;
uint32_t i
=
1
;
/
/
[
1
,request.max_entries]
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
if
(i!
=
request.max_entries)
prev_entry
-
>
next
=
NULL;
current_entry
-
>idx
=
i;
current_entry
-
>data
=
(char
*
)(validate((char
*
)kmalloc(request.data_size,GFP_KERNEL)));
/
*
Increment current_entry by size of queue_entry
*
/
current_entry
+
=
sizeof(queue_entry)
/
16
;
/
*
Populate
next
pointer of the previous entry
*
/
prev_entry
-
>
next
=
current_entry;
prev_entry
=
prev_entry
-
>
next
;
}
/
/
这里尝试找到kqueue中一个不为NULL的项
uint32_t j
=
0
;
for
(j
=
0
;j<MAX_QUEUES;j
+
+
){
if
(kqueues[j]
=
=
NULL)
break
;
}
/
/
break
出
for
循环后 j
=
MAX_QUEUES,不会触发下面的
if
if
(j>MAX_QUEUES)
err(
"[-] No kqueue slot left"
);
/
/
导致我们越界分配了一个 queue?
/
*
Assign the newly created kqueue to the kqueues
*
/
/
/
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
kqueues[j]
=
queue;
queueCount
+
+
;
result
=
0
;
return
result;
}
static noinline
long
create_kqueue(request_t request){
long
result
=
INVALID;
/
/
最多是五个队列
if
(queueCount > MAX_QUEUES)
err(
"[-] Max queue count reached"
);
/
/
创建队列时元素可以等于
1
,不能小于
1
if
(request.max_entries<
1
)
err(
"[-] kqueue entries should be greater than 0"
);
if
(request.data_size>MAX_DATA_SIZE)
err(
"[-] kqueue data size exceed"
);
queue_entry
*
kqueue_entry;
ull space
=
0
;
if
(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries
+
1
),&space)
=
=
true)
/
/
整数溢出
err(
"[-] Integer overflow"
);
/
*
Size
is
the size of queue structure
+
size of entry
*
request entries
*
/
ull queue_size
=
0
;
if
(__builtin_saddll_overflow(sizeof(queue),space,&queue_size)
=
=
true)
err(
"[-] Integer overflow"
);
if
(queue_size>sizeof(queue)
+
0x10000
)
err(
"[-] Max kqueue alloc limit reached"
);
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
queue
-
>data
=
validate((char
*
)kmalloc(request.data_size,GFP_KERNEL));
queue
-
>data_size
=
request.data_size;
queue
-
>max_entries
=
request.max_entries;
queue
-
>queue_size
=
queue_size;
kqueue_entry
=
(queue_entry
*
)((uint64_t)(queue
+
(sizeof(queue)
+
1
)
/
8
));
queue_entry
*
current_entry
=
kqueue_entry;
queue_entry
*
prev_entry
=
current_entry;
uint32_t i
=
1
;
/
/
[
1
,request.max_entries]
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
if
(i!
=
request.max_entries)
prev_entry
-
>
next
=
NULL;
current_entry
-
>idx
=
i;
current_entry
-
>data
=
(char
*
)(validate((char
*
)kmalloc(request.data_size,GFP_KERNEL)));
/
*
Increment current_entry by size of queue_entry
*
/
current_entry
+
=
sizeof(queue_entry)
/
16
;
/
*
Populate
next
pointer of the previous entry
*
/
prev_entry
-
>
next
=
current_entry;
prev_entry
=
prev_entry
-
>
next
;
}
/
/
这里尝试找到kqueue中一个不为NULL的项
uint32_t j
=
0
;
for
(j
=
0
;j<MAX_QUEUES;j
+
+
){
if
(kqueues[j]
=
=
NULL)
break
;
}
/
/
break
出
for
循环后 j
=
MAX_QUEUES,不会触发下面的
if
if
(j>MAX_QUEUES)
err(
"[-] No kqueue slot left"
);
/
/
导致我们越界分配了一个 queue?
/
*
Assign the newly created kqueue to the kqueues
*
/
/
/
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
kqueues[j]
=
queue;
queueCount
+
+
;
result
=
0
;
return
result;
}
ull space
=
0
;
if
(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries
+
1
),&space)
=
=
true)
/
/
整数溢出
err(
"[-] Integer overflow"
);
ull space
=
0
;
if
(__builtin_umulll_overflow(sizeof(queue_entry),(request.max_entries
+
1
),&space)
=
=
true)
/
/
整数溢出
err(
"[-] Integer overflow"
);
queue
-
>max_entries
=
request.max_entries;
queue
-
>max_entries
=
request.max_entries;
ull queue_size
=
0
;
if
(__builtin_saddll_overflow(sizeof(queue),space,&queue_size)
=
=
true)
err(
"[-] Integer overflow"
);
ull queue_size
=
0
;
if
(__builtin_saddll_overflow(sizeof(queue),space,&queue_size)
=
=
true)
err(
"[-] Integer overflow"
);
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
queue
-
>queue_size
=
queue_size;
queue
*
queue
=
validate((char
*
)kmalloc(queue_size,GFP_KERNEL));
queue
-
>queue_size
=
queue_size;
/
/
request.max_entries
+
1
=
0
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
......
}
/
/
request.max_entries
+
1
=
0
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
......
}
static noinline
long
save_kqueue_entries(request_t request){
......
/
/
为此需要save的队列分配空间,size为queue
-
>queue
-
>size
char
*
new_queue
=
validate((char
*
)kzalloc(queue
-
>queue_size,GFP_KERNEL));
/
/
先拷贝queue头数据,这里没有问题
if
(queue
-
>data && request.data_size)
validate(memcpy(new_queue,queue
-
>data,request.data_size));
else
err(
"[-] Internal error"
);
/
/
再拷贝所有queue的entry数据,这里发生了溢出
uint32_t i
=
0
;
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
if
(!kqueue_entry || !kqueue_entry
-
>data)
break
;
if
(kqueue_entry
-
>data && request.data_size)
validate(memcpy(new_queue,kqueue_entry
-
>data,request.data_size));
else
err(
"[-] Internal error"
);
kqueue_entry
=
kqueue_entry
-
>
next
;
new_queue
+
=
queue
-
>data_size;
}
......
}
static noinline
long
save_kqueue_entries(request_t request){
......
/
/
为此需要save的队列分配空间,size为queue
-
>queue
-
>size
char
*
new_queue
=
validate((char
*
)kzalloc(queue
-
>queue_size,GFP_KERNEL));
/
/
先拷贝queue头数据,这里没有问题
if
(queue
-
>data && request.data_size)
validate(memcpy(new_queue,queue
-
>data,request.data_size));
else
err(
"[-] Internal error"
);
/
/
再拷贝所有queue的entry数据,这里发生了溢出
uint32_t i
=
0
;
for
(i
=
1
;i<request.max_entries
+
1
;i
+
+
){
if
(!kqueue_entry || !kqueue_entry
-
>data)
break
;
if
(kqueue_entry
-
>data && request.data_size)
validate(memcpy(new_queue,kqueue_entry
-
>data,request.data_size));
else
err(
"[-] Internal error"
);
kqueue_entry
=
kqueue_entry
-
>
next
;
new_queue
+
=
queue
-
>data_size;
}
......
}
►
0xffffffffc00004ce
<save_kqueue_entries
+
238
> call memcpy <memcpy>
dest:
0xffff88801e3b9fa0
◂— sbb al,
0x1d
/
*
0x232221201f1e1d1c
*
/
src:
0xdead000000000100
n:
0x20
►
0xffffffffc00004ce
<save_kqueue_entries
+
238
> call memcpy <memcpy>
dest:
0xffff88801e3b9fa0
◂— sbb al,
0x1d
/
*
0x232221201f1e1d1c
*
/
src:
0xdead000000000100
n:
0x20
validate(memcpy(new_queue,kqueue_entry
-
>data,request.data_size));
validate(memcpy(new_queue,kqueue_entry
-
>data,request.data_size));
►
0xffffffffc00004ce
<save_kqueue_entries
+
238
> call memcpy <memcpy>
dest:
0xffff88801dc10980
—▸
0xffffffff812005d0
(single_start) ◂— xor eax, eax
/
*
0x940f003e8348c031
*
/
src:
0xffffea0000683e30
◂— add byte ptr [rax], al
/
*
0x100000000000000
*
/
n:
0x20
►
0xffffffffc00004ce
<save_kqueue_entries
+
238
> call memcpy <memcpy>
dest:
0xffff88801dc10980
—▸
0xffffffff812005d0
(single_start) ◂— xor eax, eax
/
*
0x940f003e8348c031
*
/
src:
0xffffea0000683e30
◂— add byte ptr [rax], al
/
*
0x100000000000000
*
/
n:
0x20
[
8.977709
] RIP:
0010
:
0x100000000000000
[
8.978444
] Code: Bad RIP value.
[
8.987225
] Call Trace:
[
8.989460
] ? seq_read
+
0x89
/
0x3d0
[
8.989770
] ? vfs_read
+
0x9b
/
0x180
[
8.989895
] ? ksys_read
+
0x5a
/
0xd0
[
8.990136
] ? do_syscall_64
+
0x3e
/
0x70
[
8.990332
] ? entry_SYSCALL_64_after_hwframe
+
0x44
/
0xa9
[
8.990577
] Modules linked
in
: kqueue(O)
[
8.992286
]
-
-
-
[ end trace
8ca9e01e6f1c5a76
]
-
-
-
[
8.992629
] RIP:
0010
:
0x100000000000000
[
8.977709
] RIP:
0010
:
0x100000000000000
[
8.978444
] Code: Bad RIP value.
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2021-8-23 16:28
被Roland_编辑
,原因: