Post

Initramfs on linux & Qemu setup

how to extract initramfs image on archlinux

Initramfs on linux & Qemu setup

About

I was following the book “Linux Kernel Programming 2nd Edition” to study the kernel. One of the chapters had a section about initramfs, which I was quite familiar with, having used it several times in kernel exploits. But for better understanding, I wanted to extract the current OS’s initramfs image and analyze it. But it seemed that there were some differences in the initramfs image compared to what I was used to in CTF challenges.

Also, I appended a section about setting up a QEMU environment on Arch Linux to run Ubuntu, which I used for building the kernel.

Usual way to extract initramfs

In a nicely configured “challenge”, the usual way to extract the initramfs image is like this:

1
2
3
mkdir initramfs
cd initramfs
cpio -idv < ../initramfs.img

This is the way I would have usually done it. Not a big deal, just extract it using cpio.

However, when I tried this on Arch Linux, the extracted image seemed a bit off.

1
2
$ ls
bin early_cpio lib lib64 sbin usr var

The extracted image only had a few directories like bin, lib, usr, var, etc. And init was missing as well.

Linux’s initramfs format

After some research, I found out that actual distros use a different format for the initramfs image. It uses layers or multiple cpio archives concatenated together and compressed using gzip or other compression algorithms. For example, to extract Arch Linux’s initramfs image, use

1
2
3
mkdir -p initramfs
cd initramfs
lsinitcpio -x /boot/initramfs-linux.img

For the case of Ubuntu,

1
2
3
mkdir -p initramfs
cd initramfs
unmkinitramfs /boot/initrd.img-$(uname -r) initramfs

From there, the expected files such as init and other scripts were present.

ASCII cpio archive (SVR4 with no CRC)" followed by garbage
Implies that the file is not a single cpio archive, but rather a concatenation of multiple cpio archives, possibly with compression applied.

Having a look

Ubuntu

1
2
$ ls
early  early2  main

This is Ubuntu’s initramfs structure.

  • early: CPU microcode update blobs. NOT an initramfs, but a patcher for the CPU microcode. early/kernel/x86/microcode/AuthenticAMD.bin
  • early2: Ubuntu-specific initramfs scripts and binaries.
  • main: The initramfs we want. Contains the actual initramfs files and scripts.

Arch Linux

1
2
3
$ ls
VERSION  buildconfig  dev         etc    init            keymap.bin   lib    new_root  run   sys  usr
bin      config       early_cpio  hooks  init_functions  keymap.utf8  lib64  proc      sbin  tmp  var

It gets extracted immediately without any subdirectories.

Extra notes

  • Nowadays, sbin/init is replaced with systemd in most distros.
  • Arch Linux uses busybox to initialize the system during boot.
  • Ubuntu uses scripting to initialize the system during boot.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$ cat init  # ubuntu's init script
#!/bin/sh

# Default PATH differs between shells, and is not automatically exported
# by klibc dash.  Make it consistent.
# Furthermore, this PATH ends up being used by the init, set it to the
# Standard PATH, without /snap/bin as documented in
# https://wiki.ubuntu.com/PATH
# This also matches /etc/environment, but without games path
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

[ -d /dev ] || mkdir -m 0755 /dev
[ -d /root ] || mkdir -m 0700 /root
[ -d /sys ] || mkdir /sys
[ -d /proc ] || mkdir /proc
[ -d /tmp ] || mkdir /tmp
mkdir -p /var/lock
mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
mount -t proc -o nodev,noexec,nosuid proc /proc
...

# Move virtual filesystems over to the real filesystem
mount -n -o move /sys ${rootmnt}/sys
mount -n -o move /proc ${rootmnt}/proc

# Chain to real filesystem
# shellcheck disable=SC2086,SC2094
exec run-init ${drop_caps} "${rootmnt}" "${init}" "$@" <"${rootmnt}/dev/console" >"${rootmnt}/dev/console" 2>&1
echo "Something went badly wrong in the initramfs."
panic "Please file a bug on initramfs-tools."

Appendix: dev environment setup on Arch Linux

Use them as needed.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/bin/bash

set -euo pipefail

# Variables
PROJECT_DIR="YOUR_PROJECT_PATH_HERE"  # Set this to your project directory path
VM_DIR="${PROJECT_DIR}/vm/QEMU"			# Directory to store VM images
IMG_NAME="ubuntu-desktop-22.04.qcow2"  # Name of the VM image
CD_PATH="${PROJECT_DIR}/iso/ubuntu-22.04-desktop-amd64.iso"  # Path to Ubuntu ISO

SSH_PORT=2222

# VM will be saved under VM_DIR
mkdir -p "${VM_DIR}"

# adjust image size as needed.
qemu-img create -f qcow2 "${VM_DIR}/${IMG_NAME}" 20G


qemu-system-x86_64 \
    -m 4096 \
    -smp 2 \
    -drive file="${VM_DIR}/${IMG_NAME}",if=virtio,format=qcow2 \
    -netdev user,id=net0,hostfwd=tcp::${SSH_PORT}-:22 \
    -device e1000,netdev=net0 \
	-cdrom "${CD_PATH}" \
    -enable-kvm \
    -cpu host 

# Be sure to create swap file or partition during installation!!
# It may run out of memory otherwise.
# 
# If you want to mount a directory from host, add the following lines:
# 	-virtfs local,path="${PROJECT_DIR}/src",mount_tag=hostsrc,security_model=none \
# Note that kernel should NOT be built under the shared directory to avoid mmap related issues.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/bin/bash

set -euo pipefail

# Variables
PROJECT_DIR="YOUR_PROJECT_PATH_HERE"  # Set this to your project directory path
VM_DIR="${PROJECT_DIR}/vm/QEMU"			# Directory to store VM images
IMG_NAME="ubuntu-desktop-22.04.qcow2"  # Name of the VM image

# Resources
CPU_COUNT=8
RAM_SIZE=4G

SSH_PORT=2222


echo "allocating CPU cores: ${CPU_COUNT}"
echo "allocating RAM size: ${RAM_SIZE}"

qemu-system-x86_64 \
    -m ${RAM_SIZE} \
    -smp ${CPU_COUNT} \
    -drive file="${VM_DIR}/${IMG_NAME}",if=virtio,format=qcow2 \
    -netdev user,id=net0,hostfwd=tcp::${SSH_PORT}-:22 \
    -device e1000,netdev=net0 \
    -enable-kvm \
    -cpu host \
    -nographic

# You may have to disable nographic mode to troubleshoot VM related issues.
# Just remove the -nographic option above.
# 
# Access via ssh:
# ssh -p 2222 user@localhost

How to build a kernel inside the VM

The flow is as follows:

  1. install required packages
  2. clone linux repo
  3. checkout desired version
  4. configure the kernel. I used localmodconfig for simplicity.
  5. build the kernel
  6. install the kernel modules (make modules_install )
  7. install the kernel (make install )
  8. configure bootloader (grub, /etc/default/grub and sudo update-grub )
This post is licensed under CC BY 4.0 by the author.