Introduction
커널 익스플로잇을 처음 입문했을때 바이너리 파일만 분석하면 됐던 포너블과는 다르게 정체를 알 수 없는 파일들을 줘서 시작부터 멘탈이 나갔던 나와 비슷한 사람들이 있을까봐 글을 작성한다.
진행 환경 : Ubuntu 22.04
Linux kernel build
리눅스 커널 빌드 과정은 다음과 같다.
- Install Package
- Linux kernel source code download
- Setting config
- build
Install Package
아래 명령어를 사용해 필요한 패키지들을 설치해준다.
sudo apt install -y build-essential \
libncurses5 \
libncurses5-dev \
kernel-package \
bin86 \
fakeroot \
libssl-dev \
bison \
flex \
libelf-dev \
qemu-systemLinux kernel source code download
패키지들을 다 설치한 후 원하는 버전의 리눅스 커널 소스를 받아야한다. 대표적인 방법은 두가지 정도가 있다. 첫번째 방법은 https://github.com/torvalds/linux 에서 clone 받는 방법이다. 두번째 방법은 Linux Kernel Archives에서 특정 버전의 커널 소스를 다운 받는 방법이다. 이번 글에서는 두번째 방법으로 진행했다. 아래 명령어로 커널 소스 파일을 받을 수 있다.
wget https://www.kernel.org/pub/linux/kernel/v6.x/linux-6.4.8.tar.gz소스 파일을 받은 후 압축을 해제하면 아래와 같은 파일들이 나온다. 파일을 보면 Makefile이 존재하는데 이 파일을 이용해 커널을 빌드할 수 있다.

Setting config
make defconfig 명령어를 사용해서 커널 옵션을 현재 아키텍처의 default 옵션으로 설정해준다. 아래 사진은 x86_64 아키텍처의 default 옵션으로 설정했다. 설정한 옵션은 .config 파일로 저장된다.

make menuconfig명령어로 원하는 옵션들을 체크할 수 있다.
build
make -j N 명령어로 커널 이미지 빌드를 할 수 있다. -j 옵션은 CPU의 코어 개수를 설정해준다. 처음에 make -j 4로 빌드했다가 에러 출력도 없이 갑자기 빌드가 자꾸 끊기길래 코어 개수를 2로 설정해줬더니 빌드가 됐다..(커널 크기가 커서 빌드 시간이 오래 걸렸다)

커널 빌드가 끝나면 vmlinux, bzImage 두 개의 커널 이미지가 생긴다. bzImage는 arch/{아키텍쳐}/boot/ 위치에 생기고, vmlinux는 빌드를 수행한 디렉터리에 생성된다.
BUILD arch/x86/boot/bzImage
Kernel: arch/x86/boot/bzImage is ready (#1)
bzImage:vmlinux에서Instruction set만 추출한 커널 이미지
vmlinux: 디버깅 할때 유용하게 사용할 수 있는 정보들을 담고 있는 이미지
지금까지는 커널만 이미지로 만들어 준 것이다. 우리가 쓰는 리눅스의 root directory를 보면 bin, dev, home 등 많은 폴더들이 있는데, 이를 file system이라고 하며, 따로 만들어줘야 한다.
File system build
파일 시스템 빌드 과정은 다음과 같다.
- Install busybox
- Target Directory build
- Make cpio file
Install busybox
아래 명령어로 busybox를 설치해주자.
wget https://www.busybox.net/downloads/busybox-1.37.0.tar.bz2압축을 해제하면 여러가지 파일이 있는데, Makefile을 이용해 간단하게 빌드할 수 있다.

이번에는 make menuconfig 명령어를 사용해 몇가지 옵션을 설정해줘야 한다.
settings -> build options -> build static binary체크
networking utilities -> inetd체크 해제
Target Directory build
빌드할 파일 시스템의 파일들을 담을 target 디렉토리를 하나 생성해주고, 아래 명령어로 파일 시스템 빌드를 진행하면 된다.
make CONFIG_PREFIX=[Directory PATH] install
빌드가 완료되고 해당 폴더에 들어가면 bin, sbin, usr와 같이 기본 디렉토리만 생성된걸 볼 수 있는데, mkdir 명령어를 통해 dev, etc, lib, proc, tmp, sys 디렉토리를 직접 추가해주어야 한다.
mkdir dev etc lib proc tmp sys
init script
커널이 부팅되고 가장 먼저 실행되는 스크립트이며, 이 파일로 부팅 옵션을 지정할 수 있다. 해당 파일은 755 권한을 부여하여 실행 가능하도록 만들어야한다.
d0razi@server ~/kernel/build/target
❯ cat init
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1</dev/console
exec 2</dev/console
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f
d0razi@server ~/kernel/build/target
❯ chmod 755 initcttyhack : 컨트롤 터미널을 설정하고 셸을 실행하는 도구
Make cpio file
파일 시스템을 다 만들었으니 아래 명령어를 통해 target 디렉토리의 파일들을 cpio 파일로 압축하면 rootfs.cpio 파일이 생성된다.
find . | cpio -o --format=newc > rootfs.cpio
위 과정을 모두 거치면 bzImage, vmlinux, rootfs.cpio 파일을 얻을 수 있다. 이 중 필수적인 파일은 bzImage, rootfs.cpio 파일이고, vmlinux 파일은 있어도 되고 없어도 되는 부수적인 파일이다.
qemu & gdb
qemu
vmware와 같은 가상 머신 에뮬레이터다. 보통 ctf에서는 앞에서 만든 bzImage와 rootfs.cpio (vmlinux는 줄수도 있고 안줄수도 있음) 파일을 제공해줘서 qemu를 통해 가상 머신을 실행할 수 있다.
아래는 qemu 실행 스크립트다.
#!/bin/sh
qemu-system-x86_64 \
-m 256M \
-nographic \
-kernel bzImage \
-append "console=ttyS0 loglevel=3 oops=panic panic=-1 nopti nokaslr" \
-no-reboot \
-smp 1 \
-monitor /dev/null \
-initrd rootfs.cpio \
-net nic,model=virtio \
-net user \
-cpu qemu64 \
-s중요한 옵션들만 알아보면 아래와 같다.
-kernel ./bzImage : 커널 이미지 지정
-initrd ./rootfs.cpio : 파일 시스템 지정
-s : gdb attach를 위해 1234 port open
-append “nokaslr” : Kernel ASLR 비활성화
-echo 1 > /proc/sys/kernel/dmesg_restrict : 커널 메시지 출력 설정
커널 이미지와 파일 시스템 파일이 있는 디렉토리에 run.sh로 스크립트를 만들고, chmod 755 run.sh 명령어로 실행권한을 주고 실행하면 잘 작동하는 것을 볼 수 있다.


gdb
qemu 실행 스크립트에 -s 옵션이 지정되어 있으면 gdb에서 target remote localhost:1234 명령어로 커널에 붙을 수가 있다.


Conclusion
작성할 정보가 별로 없을 줄 알고 12시에 글 쓰기 시작했다가 쓰기 시작하니 생각보다 꼼꼼하게 작성해버려서 새벽 4시까지 달려버렸다. 그래도 이렇게 한번 자세하게 글을 써놓으면 나처럼 삽질을 하는 사람들에게 많은 도움이 될 수 있지 않을까 싶다.. 커널 공부하는 사람들 모두 화이팅!