49

I'm using debian live-build to work on a bootable system. By the end of the process i get the typical files used to boot a live system: a squashfs file, some GRUB modules and config files, and an initrd.img file.

I can boot just fine using those files, passing the initrd to the kernel via

initrd=/path/to/my/initrd.img

on the bootloader command line. But when I try to examine the contents of my initrd image, like so:

$file initrd.img
initrd.img: ASCII cpio archive (SVR4 with no CRC)
$mkdir initTree && cd initTree
$cpio -idv < ../initrd.img

the file tree i get looks like this:

$tree --charset=ASCII
.
`-- kernel
    `-- x86
        `-- microcode
            `-- GenuineIntel.bin

Where is the actual filesystem tree, with the typical /bin , /etc, /sbin ... containing the actual files used during boot?

2
  • 7
    The 'lsinitramfs' command was designed for this.
    – earlgrey
    Aug 5, 2018 at 15:50
  • 5
    install dracut and use lsinitramfs to unpack initramfs images Apr 28, 2021 at 3:44

6 Answers 6

41

The cpio block skip method given doesn't work reliably. That's because the initrd images I was getting myself didn't have both archives concatenated on a 512 byte boundary.

Instead, do this:

apt-get install binwalk
legolas [mc]# binwalk initrd.img 
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             ASCII cpio archive (SVR4 with no CRC), file name: "kernel", file name length: "0x00000007", file size: "0x00000000"
120           0x78            ASCII cpio archive (SVR4 with no CRC), file name: "kernel/x86", file name length: "0x0000000B", file size: "0x00000000"
244           0xF4            ASCII cpio archive (SVR4 with no CRC), file name: "kernel/x86/microcode", file name length: "0x00000015", file size: "0x00000000"
376           0x178           ASCII cpio archive (SVR4 with no CRC), file name: "kernel/x86/microcode/GenuineIntel.bin", file name length: "0x00000026", file size: "0x00005000"
21004         0x520C          ASCII cpio archive (SVR4 with no CRC), file name: "TRAILER!!!", file name length: "0x0000000B", file size: "0x00000000"
21136         0x5290          gzip compressed data, from Unix, last modified: Sat Feb 28 09:46:24 2015

Use the last number (21136) which is not on a 512 byte boundary for me:

legolas [mc]# dd if=initrd.img bs=21136 skip=1 | gunzip | cpio -tdv | head
drwxr-xr-x   1 root     root            0 Feb 28 09:46 .
drwxr-xr-x   1 root     root            0 Feb 28 09:46 bin
-rwxr-xr-x   1 root     root       554424 Dec 17  2011 bin/busybox
lrwxrwxrwx   1 root     root            7 Feb 28 09:46 bin/sh -> busybox
-rwxr-xr-x   1 root     root       111288 Sep 23  2011 bin/loadkeys
-rwxr-xr-x   1 root     root         2800 Aug 19  2013 bin/cat
-rwxr-xr-x   1 root     root          856 Aug 19  2013 bin/chroot
-rwxr-xr-x   1 root     root         5224 Aug 19  2013 bin/cpio
-rwxr-xr-x   1 root     root         3936 Aug 19  2013 bin/dd
-rwxr-xr-x   1 root     root          984 Aug 19  2013 bin/dmesg
6
  • Indeed, your answer beats mine. I haven't ever thought that alignment would be a problem. I wonder, though, if cpio would give some more interesting output if the first image contained within the multi-image file was not 512B-lined.
    – user986730
    Mar 25, 2015 at 16:52
  • How to revert it back (repack to original state) after modifying, with the same folder hierarchy ?
    – EdiD
    May 25, 2016 at 9:39
  • 2
    Just cd into the directory where you extracted your cpio archive, run find | cpio -H newc -o > /tmp/my_archive.cpio, then gzip it with gzip /tmp/my_archive.cpio and finally, concatenate it with the with the microcode image, if you had one: cat my_microcode_image.cpio /tmp/my_archive.cpio.gz > mynewinitrd.img. If you didn't have a microcode image, then you can just use you gzipped file as is in your bootloader
    – user986730
    Jun 2, 2016 at 12:50
  • In reading this answer it seems obvious this will only work if the gzipped contents are past halfway in the file. Otherwise, you should change the blocksize to 1 and set skip to the number of bytes to skip. Any reason not to always do that? Sep 24, 2019 at 21:01
  • 2
    binwalk listed way too much noise on my Ubuntu 20.04 initrd.
    – Ole Tange
    May 2, 2020 at 16:18
38

If you know your initrd.img consists of an uncompressed cpio archive followed by a gz-compressed cpio archive, you can use the following to extract all files (from both archives) into your current working directory (tested in bash):

(cpio -id; zcat | cpio -id) < /path/to/initrd.img

The above command-line passes the contents of initrd.img as standard input into a subshell which executes the two commands cpio -id and zcat | cpio -id sequentially. The first command (cpio -id) terminates once it has read all the data belonging to the first cpio archive. The remaining content is then passed to zcat | cpio -id, which decompresses and unpacks the second archive.

5
  • 3
    Variant (for current ubuntu): (cpio -i; cpio -i; unlz4 |cpio -i ) < /initrd.img
    – Tobu
    May 1, 2020 at 10:00
  • Variant SUSE 15 - (cpio -id; unxz | cpio -id) < /boot/initrd Mar 29, 2021 at 11:51
  • 1
    I found on MacOS, this does not work. Error: zcat: unknown compression format. The reason is that the cpio consumed stdin more data than necessary, causing the second zcat think rest data from stdin is not a gz format. But fortunately I can run it in a Docker container.
    – osexp2000
    Dec 3, 2021 at 10:39
  • To determine which decompressor you need in your Linux distribution today: (cpio -t >/dev/null; file - ) < /boot/initrd
    – sizif
    Jul 12, 2023 at 13:13
  • 1
    For my system I used (Ubuntu 22.04) (cpio -i; zstd -dc | cpio -i) < /boot/initrd.img
    – Didi Kohen
    Apr 28 at 14:30
21

It turns out the initrd generated by Debian's live-build (and to my surprise, accepted by the kernel) is actually the concatenation of two images:

  • a CPIO archive containing microcode updates to be applied on the processor;
  • a gzip-ed cpio archive, which actually contains the initrd file tree (with the /etc /bin /sbin /dev ... directories that were expected).

Upon extracting the original initrd.img, straight out of the live-build output, I got this output:

$cpio -idv ../initrd.img
kernel
kernel/x86
kernel/x86/microcode
kernel/x86/microcode/GenuineIntel.bin
896 blocks

Which means that the cpio extraction ended after parsing 896 blocks of 512 Bytes each. But the original initrd.img was way bigger than 896*512 = 458752B = 448 KB :

$ls -liah initrd.img
3933924 -r--r--r-- 1 root root 21M Oct 21 10:05 initrd.img

So the actual initrd image I was looking for was appended right after the first cpio archive (the one containing the microcode updates) and could be accessed using dd:

$dd if=initrd.img of=myActualInitrdImage.img.gz bs=512 skip=896
1
  • Ubuntu 20.04 seems to have 2 of those processor firmware blocks. So you need to dd twice. And the .gz is a .lz4.
    – Ole Tange
    May 2, 2020 at 15:39
11

You can use unmkinitramfs from initramfs-tools >= 0.126, which is included since Debian 9 (stretch) and Ubuntu 18.04 (bionic).

On arch you can use lsinitcpio.

4

Based on the idea given in @woolpool's answer I wrote a recursive function that will work for any cpio archive regardless of the arrangement of the concatenated data and doesn't require any special tools like binwalk. For example my mkinitramfs was producing a cpio;cpio;gzip file. It works by extracting each part of the concatenated initrd file, saving the rest into a tempfile and then using the "file" program to decide what to do with the next part.

uncpio(){
if [[ $(wc -c $1 | cut -d ' ' -f1) -eq 0 ]]; then
    return
fi

type=$(cat $1 | file -)
local tmpfile=$(date +%s.%N)
echo -e "\n$type"
if [[ $type =~ .*cpio.* ]]; then
    cat $1 | (cpio -id; cat >$tmpfile)
elif [[ $type =~ .*gzip.* ]]; then
    zcat $1 | (cpio -id; cat >$tmpfile)
else
    return
fi
uncpio $tmpfile 
rm $tmpfile
}

To use type: uncpio initrdfilename

1

If you need to perform this task often, you may want to create a small bash function like the following (and maybe add it to your .bashrc):

initramfs-extract() {
    local target=$1
    local offset=$(binwalk -y gzip $1 | awk '$3 ~ /gzip/ { print $1; exit }')
    shift
    dd if=$target bs=$offset skip=1 | zcat | cpio -id --no-absolute-filenames $@
}

The code is based on Marc's answer, but it's significantly faster since binwalk will only look for gzip files. You can invoke it, like this:

$ initramfs-extract /boot/initrd.img -v

You will need binwalk installed to make it work.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .