Introduction

커널 익스플로잇을 처음 입문했을때 바이너리 파일만 분석하면 됐던 포너블과는 다르게 정체를 알 수 없는 파일들을 줘서 시작부터 멘탈이 나갔던 나와 비슷한 사람들이 있을까봐 글을 작성한다.
진행 환경 : Ubuntu 22.04

Linux kernel build

리눅스 커널 빌드 과정은 다음과 같다.

  1. Install Package
  2. Linux kernel source code download
  3. Setting config
  4. build

Install Package

아래 명령어를 사용해 필요한 패키지들을 설치해준다.

sudo apt install -y build-essential \
				libncurses5 \
                libncurses5-dev \
                kernel-package \
                bin86 \
                fakeroot \
                libssl-dev \
                bison \
                flex \
                libelf-dev \
                qemu-system

Linux 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 init

cttyhack : 컨트롤 터미널을 설정하고 셸을 실행하는 도구

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에서는 앞에서 만든 bzImagerootfs.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시까지 달려버렸다. 그래도 이렇게 한번 자세하게 글을 써놓으면 나처럼 삽질을 하는 사람들에게 많은 도움이 될 수 있지 않을까 싶다.. 커널 공부하는 사람들 모두 화이팅!

Reference