얼마 전에 Black Hat USA 2021 학회에서 가상 머신 탈출 탐지 및 방어 관련 연구(Alcatraz)를 발표하게 되었다고 말씀을 드렸는데요, 해당 연구가 QEMU와 아마존(Amazon)에서 만든 MicroVM 기술(Firecracker)을 대상으로 하고 있습니다. MicroVM 기술은 전체 시스템을 가상화하여 가상 머신(Virtual Machine, VM)을 생성하는 QEMU와 달리 컨테이너 구동을 위한 최소한의 기능만 구현한 경량(micro) 가상 머신입니다. 그래서 이름도 MicroVM이죠. QEMU보다는 최근에 나온 기술이라 많이 생소하실 텐데요, QEMU보다 공격면이 작고 좀 더 안전하기 때문에 앞으로 주목받을 것 같습니다. ^^;;;

Firecracker는 현재 https://github.com/firecracker-microvm/firecracker에서 프로젝트가 진행 중입니다. 문서가 비교적 잘 정리되어 있어서 따라 하는 것이 크게 어렵지 않은데요, 좀 더 쉽게 사용할 수 있는 방법이 있어서 정리하는 셈 치고 남겨봅니다. ^^;;

1. 우분투 환경에서 도커(Docker) 설치하기

Firecracker는 MicroVM 내부에 도커(Docker)를 구동하기 때문에 먼저 설치해 줍니다. 우분투 환경에서 도커는 아래와 같이 설치할 수 있습니다.

# 도커 패키지 설치
$> sudo apt install docker.io

# 현재 유저 계정을 도커 그룹에 추가, 그룹 추가가 완료되면 로그아웃 및 로그인 수행 필요
$> sudo useradd -aG docker ${USER}

2. 아마존(Amazon) Firecracker 다운로드 및 빌드

Firecracker의 소스코드는 이미 공개되어 있으므로, 아래와 같이 입력하여 소스코드를 받아 빌드합니다.

# 소스코드 다운로드
$> git clone https://github.com/firecracker-microvm/firecracker

# 빌드 및 링크 생성
$> cd firecracker
$> tools/devtool build
$> ln -s build/cargo_target/x86_64-unknown-linux-musl/debug/firecracker firecracker

3. Firecracker 구동용 스크립트 생성하기

Firecracker는 웹 API를 제공합니다. 그래서 curl을 이용해서 직접 커맨드를 실행해도 되지만, 너무 불필요한 작업이 많아서 아래처럼 스크립트를 만들었습니다. 이 글을 쓰게 된 이유이기도 하죠. ^-^)/

#!/bin/bash
# 00_get_kernel_and_rootfs.sh로 저장 후 chmod +x로 실행권한 부여

arch=`uname -m`
dest_kernel="hello-vmlinux.bin"
dest_rootfs="hello-rootfs.ext4"
image_bucket_url="https://s3.amazonaws.com/spec.ccfc.min/img"

if [ ${arch} = "x86_64" ]; then
    kernel="${image_bucket_url}/quickstart_guide/x86_64/kernels/vmlinux.bin"
    rootfs="${image_bucket_url}/hello/fsfiles/hello-rootfs.ext4"
elif [ ${arch} = "aarch64" ]; then
    kernel="${image_bucket_url}/quickstart_guide/aarch64/kernels/vmlinux.bin"
    rootfs="${image_bucket_url}/aarch64/ubuntu_with_ssh/fsfiles/xenial.rootfs.ext4"
else
    echo "Cannot run firecracker on $arch architecture!"
    exit 1
fi

echo "Downloading $kernel..."
curl -fsSL -o $dest_kernel $kernel

echo "Downloading $rootfs..."
curl -fsSL -o $dest_rootfs $rootfs

echo "Saved kernel file to $dest_kernel and root block device to $dest_rootfs."
#!/bin/bash
# 01_start_firecracker.sh로 저장 후 chmod +x로 실행권한 부여

echo "Remove existing socker."
rm -f /tmp/firecracker.socket

echo ""
echo "Start firecracker."
./firecracker --api-sock /tmp/firecracker.socket
#!/bin/bash
# 02_set_guest_kernel.sh로 저장 후 chmod +x로 실행권한 부여

arch=`uname -m`
kernel_path=$(pwd)"/hello-vmlinux.bin"

if [ ${arch} = "x86_64" ]; then
    curl --unix-socket /tmp/firecracker.socket -i \
      -X PUT 'http://localhost/boot-source'   \
      -H 'Accept: application/json'           \
      -H 'Content-Type: application/json'     \
      -d "{
            \"kernel_image_path\": \"${kernel_path}\",
            \"boot_args\": \"console=ttyS0 reboot=k panic=1 pci=off\"
       }"
elif [ ${arch} = "aarch64" ]; then
    curl --unix-socket /tmp/firecracker.socket -i \
      -X PUT 'http://localhost/boot-source'   \
      -H 'Accept: application/json'           \
      -H 'Content-Type: application/json'     \
      -d "{
            \"kernel_image_path\": \"${kernel_path}\",
            \"boot_args\": \"keep_bootcon console=ttyS0 reboot=k panic=1 pci=off\"
       }"
else
    echo "Cannot run firecracker on $arch architecture!"
    exit 1
fi
#!/bin/bash
# 03_set_rootfs.sh로 저장 후 chmod +x로 실행권한 부여

rootfs_path=$(pwd)"/hello-rootfs.ext4"
curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/drives/rootfs' \
  -H 'Accept: application/json'           \
  -H 'Content-Type: application/json'     \
  -d "{
        \"drive_id\": \"rootfs\",
        \"path_on_host\": \"${rootfs_path}\",
        \"is_root_device\": true,
        \"is_read_only\": false
   }"
#!/bin/bash
# 04_start_microvm.sh로 저장 후 chmod +x로 실행권한 부여

curl --unix-socket /tmp/firecracker.socket -i \
  -X PUT 'http://localhost/actions'       \
  -H  'Accept: application/json'          \
  -H  'Content-Type: application/json'    \
  -d '{
      "action_type": "InstanceStart"
   }'

4. Firecracker 실행

최초 실행 시에는 커널(Kernel) 파일과 루트파일시스템(rootfs)가 없으므로, 00_get_kernel_and_rootfs.sh를 실행해서 이미지를 다운로드할 필요가 있습니다. 한 번 다운로드한 뒤에는 이미지를 계속 활용하면 되니 01_start_firecracker.sh부터 실행하면 됩니다. 여기서 주의할 점이 있는데요, 01_start_firecracker.sh 스크립트를 실행하면 터미널이 계속 대기상태로 있다는 겁니다. MicroVM이 시작되기를 기다리는 건데요, 이후에 다른 터미널을 열어서 스크립트를 실행하면 대기상태가 풀리고 로그인 쉘이 표시되기 때문에 걱정 안 하셔도 됩니다. 스크립트를 통해 Firecracker를 실행하는 과정은 아래와 같습니다.

# 커널 및 루트파일시스템 이미지를 다운로드하는 단계이므로, 최초 1회만 실행
$> ./00_get_kernel_and_rootfs.sh

# Firecracker 실행, 이때 터미널이 대기상태로 빠지므로 다른 터미널을 열어서 다음 스크립트를 실행
$> ./01_start_firecracker.sh

# MicroVM용 커널 설정
$> ./02_set_guest_kernel.sh

# MicroVM용 루트파일시스템 설정
$> ./03_set_rootfs.sh

# MicroVM 실행
$> ./04_start_microvm.sh

위의 스크립트를 차례로 실행하면, 01_start_firecracker.sh를 실행하여 대기중이던 터미널에서 아래와 같이 부팅이 진행되고 로그인 쉘이 표시됩니다. 기본 로그인 정보는 ID/PWD가 root/root로 설정되어 있습니다. 로그를 보니 Alpine 리눅스를 기반으로 MicroVM 이미지를 생성한 것 같네요.

[    0.000000] Linux version 4.14.225 (ubuntu@ip-172-31-11-16) (gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)) #1 SMP Fri Mar 12 16:47:14 UTC 2021
[    0.000000] Command line: console=ttyS0 reboot=k panic=1 pci=off root=/dev/vda rw virtio_mmio.device=4K@0xd0000000:5
[    0.000000] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'
[    0.000000] x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'
[    0.000000] x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256
[    0.000000] x86/fpu: xstate_offset[3]:  832, xstate_sizes[3]:   64
[    0.000000] x86/fpu: xstate_offset[4]:  896, xstate_sizes[4]:   64
[    0.000000] x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
[    0.000000] e820: BIOS-provided physical RAM map:

... omitted ...

Starting default runlevel

Welcome to Alpine Linux 3.8
Kernel 4.14.225 on an x86_64 (ttyS0)

localhost login:

그럼 즐거운 저녁 되세요 >ㅁ<)-b

가상화 기술의 핵심인 하이퍼바이저(hypervisor)를 샌드박스에 가두어 가상 머신 탈출(virtual machine escape)을 방어하는 연구가 Black Hat USA 2021에 채택되었습니다. Black Hat은 본 학회인 Black Hat USA와 지역 학회인 Black Hat Asia, Black Hat Europe으로 나누어지는데, 이제야 겨우 본 학회에 발을 들였네요. 그동안 계속 떨어지는 바람에 Asia와 Europe만 다녔거든요. 쿨럭...;; 네트워킹하기 좋은 기회인데 코로나 때문에 온라인으로 발표할 예정이라 많이 아쉽긴 합니다. ^^;;;

Black Hat USA 2021 - 발표 내용
Black Hat USA 2021 - 발표자 소개

가상 머신 탈출(virtual machine escape)은 가상 머신에서 동작하는 악성코드(malware)가 가상 머신을 빠져나와 시스템 전체, 즉 호스트(host)와 게스트(guest)에 영향을 미치고 악의적인 행위를 하는 것을 뜻합니다. 가상 머신 탈출은 특히나 Amazon과 같은 클라우드 사업자(cloud vendor)에게 치명적인데, 서비스의 특성상 가상 머신이 사용자에게 제공되고 임의의 코드가 내부에서 실행되기 때문입니다. 악의적인 사용자라면 가상 머신을 임대한 후, 가상 머신의 취약점을 이용해서 탈출할 수 있는 것이죠.

가상 머신 탈출은 이처럼 파급효과가 크기 때문에 #BlackHat 컨퍼런스에 단골로 등장하고 있습니다. 올해 5월에 열린 Black Hat Asia 2021에도 가상 머신 탈출 관련 발표가 있었구요. 앞으로도 문제가 있을 것 같아서 실용적으로 문제를 해결하고자 여러 가지 시도를 했는데, 많은 분들이 도와주셔서 좋은 결과가 나온 것 같습니다. 필드 적용성을 높이려고 현재 동작 중인 시스템은 최대한 수정하지 않았는데, 클라우드 사업자들에게 도움이 되면 좋겠네요. ^^)/

연구 내용이 조금 복잡해서 주요 내용만 간단히 소개해 드리면, 하이퍼바이저는 가상 머신과 직접적으로 연결되어 있습니다. 다수의 가상 머신을 통제하고 서비스를 제공해야 하기 때문이죠. 그런데, 하이퍼바이저는 꽤나 높은 권한에서 동작하므로, 하이퍼바이저에 취약점을 이용해서 가상 머신을 탈출할 수 있습니다. 이를 막고자 제가 만든 경량 하이퍼바이저로 샌드박스(sandbox)를 구성하고, 기존 하이퍼바이저의 권한을 낮춘 후 샌드박스에서 구동하는 것이 핵심 아이디어입니다. 샌드박스를 위해 중첩 가상화(nested virtualization)를 직접 구현했고, 발생하는 부하를 줄이기 위해 Intel 프로세서의 VPID, VMCS Shadowing 같은 기술도 같이 활용했는데... 이 부분은 Black Hat USA 2021 발표와 함께 자세히 말씀드리겠습니다.

끝으로, 항상 지지해 주시는 본부장님과 실장님을 비롯한 동료분들께 감사드립니다. 여러분들의 도움이 없었다면 아이디어도 발굴하지 못했을 겁니다. 앞으로도 많이 부탁드립니다. ^^)-b

ps) 발표는 아래에서 확인하실 수 있습니다.

https://www.blackhat.com/us-21/briefings/schedule/index.html?fbclid=IwAR3TWHTC1UXsa54x9VcdpG82Jdgd0QWgFYgAc5LWqUNxd6CWR6Hp9Dcpvqs#alcatraz-a-practical-hypervisor-sandbox-to-prevent-escapes-from-the-kvmqemu-and-kvm-based-microvms-22875 

 

Black Hat

Black Hat

www.blackhat.com

 

+ Recent posts