how to kernel ctf start ( basic setting )
커널 ctf를 시작한지 거의 2주가 지났다. 이제 슬슬 정리를 하고자 한다.
일단 기본적으로 QEMU로 에뮬레이션하는 케이스 위주로 설명한다.
다음사이트를 참고했다.
일단 기본적으로 주어진는 것들은
- vmlinuz: 압축된 리눅스 커널. bzImage라고도 부른다. 이걸 실제 커널 ELF 파일인 vmlinux로 추출할 수 있다.
- initramfs.cpio.gz: cpio와 gzip으로 압축된 리눅스 파일시스템. /bin, /etc 같은 디렉토리들이 들어있고, 보통 취약한 커널 모듈도 이 파일시스템 안에 포함되어 있다. 다른 챌린지에서는 다른 압축 포맷일 수도 있다.
- run.sh: QEMU 실행 명령이 들어있는 쉘 스크립트. 여기서 QEMU 옵션이나 리눅스 부팅 설정을 바꿀 수 있다.
다음과 같이 주어진다.
initramfs는 저번에도 설명을 했듯이 what is initramfs & buzybox
진짜 root 시스템으로 가기전에 사용하는 임시적인 파일시스템이다. 따라서 여기에 주로 취약한 모듈이 존재하게 되고 이를 올리는 것도 initramfs 단계에서 주로 한다.
일단 가장 먼저 일반적인 vmlinux가 아닌 vmlinuz 혹은 bzimage와 같은 압축파일이 있을 경우
$ ./extract-image.sh ./vmlinuz > vmlinux으로 vmlinux 를 생성해줘야 한다. 크게 여러가지 이유가 있는데 가장 큰 두가지 이유는 gdb할때 이걸로 attach하기 위함이고 두번째는 가젯을 미리 만들어 놓기 위함이다.
추가적으로 vmlinux-to-elf로 기본적인 심볼을 복구해줘야 한다.
$ ROPgadget --binary ./vmlinux > gadgets.txt가젯이 만들어지는데 오래걸리므로 미리 만들어놓는것을 추천한다.
그리고 이제 취약한 모듈을 분석해야하니깐 initramfs를 압축해제 한뒤 여기 안에서 취약한 .ko 파일을 주로 root 디렉토리 혹은 dev 에서 찾아서 미리 밖으로 복사를 해놓으면 된다. 이는 나중에 ida로 분석하기 위해 꼭 필요한 과정이다.
그리고 이제 더욱 추가적인 부분이 있는데 일단 현재는 익스를 위한 분석, 연구 단계이므로 굳이 문제를 실제로 풀어야 하는 낮은 단계로 분석을 할 이유가 없다. 따라서, /etc 디렉토리에 있는 init.d 디렉토리나 inittab 등등 초기화 설정파일을 찾아서
setuidgid 1000 /bin/sh와 같은 부분을
setuidgid 0 /bin/sh다음과 같이 수정해서 디버깅과 분석 중에는 root권한으로 진행되게 한다.
이러한 높은 권한으로
- /proc/kallsyms : 커널에 로드된 모든 심볼의 주소를 나열한다.
- /sys/module/core/sections/.text : 커널 .text 섹션의 주소(=커널 베이스 주소)를 보여준다. (이 챌린지에서는 /sys가 없지만, /proc/kallsyms에서 베이스를 얻을 수는 있다.)
다음과 같은 파일들에 접근해서 디버깅과 분석을 용이하게 한다.
커널 익스는 내가 c로 쓴 파일을 컴파일해서 서버에 원격으로 옮기고 그거를 실행시킴으로써 주로 진행이 된다.
근데 이는 서버에 있을때이지 내가 로컬에서 분석하고 있을때는 컴파일한뒤에 initramfs 시스템에 그냥 넣어놓으면 된다.
그리고 이제 run.sh 도 정말 자세하게 봐야 하는데
qemu-system-x86_64 \
-m 128M \
-cpu kvm64,+smep,+smap \
-kernel vmlinuz \
-initrd initramfs.cpio.gz \
-hdb flag.txt \
-snapshot \
-nographic \
-monitor /dev/null \
-no-reboot \
-append "console=ttyS0 kaslr kpti=1 quiet panic=1"주로 다음과 같은 형태를 띄는데 여기서 보면 어떤 mitigation이 있는지 바로 알 수 있는 정보들이 많다.
또한 -hdb로 flag.txt를 /dev/sda에 “디스크처럼” 붙여 넣는 방식임을 알 수 있으면 공격대상이 뭔지도 바로 알 수 있게 된다. 우리가 분석 단계에서 해야 할건 여기에 -s 옵션을 추가하는 것이다.
-S -gdb tcp::31235 아니면 다음과 같이 -S로 추가해도 된다.
file binding
기본적으로 커널 문제를 풀다보면, rootfs.cpio 파일을 계속 바꾸게 된다. 왜냐면 주로 /tmp 안에 파일을 넣어두고 ./run.sh을 돌려 바로 실행을 해야 빠르기 때문이다. 따라서 도커랑 바인딩하는게 기본이다.
docker run을 할때
-v "$PWD/deploy:/home/$DOCKER_USER/"다음과 같이 옵션을 붙이면, 서로 공유폴더의 느낌으로 되기 때문에 실시간으로 적용 할 수 있다.
근데 여기서 문제가 rootfs 파일안에 /tmp 가 최종적인 /tmp가 맞냐를 확인해야 한다.
이걸 확인하기 위해 주로 /etc/fstab 파일을 봐서 어떻게 마운팅 되는지 확인을 하면 되는데
# <file system> <mount pt> <type> <options> <dump> <pass>
/dev/root / ext2 rw,noauto 0 1
proc /proc proc defaults 0 0
devpts /dev/pts devpts defaults,gid=5,mode=620,ptmxmode=0666 0 0
tmpfs /dev/shm tmpfs mode=0777 0 0
tmpfs /tmp tmpfs mode=1777 0 0
tmpfs /run tmpfs mode=0755,nosuid,nodev 0 0
sysfs /sys sysfs defaults 0 0
이런식으로 되어 있으면
tmpfs /tmp tmpfs mode=1777 0 0
우리가 rootfs 안에 exploit 파일을 넣어놔도 여기서 다시 마운트 되면서 기존 /tmp 가 가려진다.
따라서 해당 부분을 주석처리 해야한다.
gdb debugging
run.sh
#!/bin/sh
timeout --foreground/ 300 qemu-system-x86_64 \
qemu-system-x86_64 \
-m 64M \
-nographic \
-kernel bzImage \
-initrd rootfs.cpio.gz \
-hda flag \
-append "console=ttyS0 quiet oops=panic loglevel=3 panic=-1 panic_on_warn=1 pti=on kaslr page_alloc.shuffle=1 root=/dev/sda" \
-no-reboot \
-cpu kvm64,+smap,+smep \
-monitor /dev/null \
-net nic,model=virtio \
-net user \
주로 run.sh 이 다음과 같이 주어지는데 디버깅 당시 일단 주로 해야할것은
- timeout --foreground/ 300 이 부분 없애기 ( 주석 처리 )
- kaslr → nokaslr
- 마지막 줄에
-S -gdb tcp:0.0.0.0:41234와 같다.