4

I have a Linux kernel file and I need to tweak the contents of its corresponding initramfs. I did it in the past for standard distros where kernel and initramfs were separate. However, this specific kernel comes with an embedded initramfs which makes it all a lot more complicated. Worse still, all I have is a binary image, so recompiling is not possible. Binwalk describes the kernel structure as follows:

Microsoft executable, portable (PE)

gzip compressed data, maximum compression, from Unix, last modified: 1970-01-01 00:00:00 (null date)

Object signature in DER format (PKCS header length: 4, sequence length: 1509

Certificate in DER format (x509 v3), header length: 4, sequence length: 769

Extracting and modifying the embedded initramfs is pretty straightforward - the second section of the kernel (as per binwalk output above) is a gzipped vmlinux which I can extract either using extract-vmlinux or by manually dding the .gz archive out of the kernel image and then extracting it with gunzip. The last section of resulting vmlinux is a gzipped cpio archive with initramfs I intend to modify. Unfortunately, sticking it all back together (with a great care taken to keep the unmodified fragments of the original kernel image intact) renders the kernel unbootable. Here's a more detailed description of my approach:

  1. with dd, I separate the original kernel image into 3 parts: stuff before the gzipped vmlinux (call it a KERNEL_HEADER), the gzipped vmlinux, and stuff after it (call it a KERNEL_TRAILER).

  2. I extract vmlinux from the middle part using gunzip.

  3. like in step 1 above, I use dd to separate vmlinux into 3 parts: stuff before the gzipped cpio archive (call it a VMLINUX_HEADER), the gzipped cpio archive with initramfs, and stuff after it (call it a VMLINUX_TRAILER).

  4. I extract the cpio archive from the middle part using gunzip, then extract initramfs from it using cpio and modify it according to my needs.

  5. I embed modified initramfs into a new cpio archive and gzip it.

  6. I concatenate VMLINUX_HEADER, new gzipped cpio archive and VMLINUX_TRAILER to recreate vmlinux.

  7. I gzip new vmlinux.

  8. I concatenate KERNEL_HEADER, new gzipped vmlinux and KERNEL_TRAILER into a new kernel image which, unfortunately, fails to boot.

Side notes:

  1. I'm aware of the fact that .gz archive can have trailing bytes appended to it and still get extracted correctly to produce the desired result. Since such bytes would be discarded by gunzip as "trailing garbage", I took extra care to separate both .gz archives from surrounding bytes in such a way as to avoid any redundancy - after all, those "trailing garbage" bytes are part of the kernel and can't simply be discarded. I did that by searching for ISIZE members at the end of each archive. I naively assumed that if I'd modify only the .gz archives and then prepend and append exactly the same data blocks they were originally attached to to them, I'd end up with a new working kernel. Unfortunately, it doesn't seem that simple.

  2. while experimenting I noticed that mere gunzipping a gzipped vmlinux and gzipping it back again is enough to destroy the kernel image - unless the new .gz archive has exactly the same contents (or at least the same length) as the original one, prepending and appending the aforementioned KERNEL_HEADER and KERNEL_TRAILER to it, respectively, results in a new, unbootable kernel. This holds true even if old and new .gz archives have identical payload bytes and differ only by metadata. Depending on exact difference between old and new .gz archives, the following invocation:

qemu-system-x86_64 -kernel <NEW KERNEL IMAGE FILE>

results either in a freeze or an infinite boot loop with a "Booting from ROM..." message displayed in both cases.

My wild guess is that even a slight change in gzipped vmlinux data layout confuses the kernel decompression routine located earlier in the image. Is there a simple way to fix it by means of a raw binary edit? I don't have a detailed knowledge of Linux kernel internal structure (I do know about piggy.o and other object files a kernel image consists of, but a serious research is still ahead of me). I'm ready to learn, but I hope someone more experienced will at least point me in the right direction. My question boils down to: how to safely embed a (possibly modified) vmlinux back into its corresponding kernel image? Any help will be appreciated.

Peter
  • 141
  • 1
  • At which stage does the original working kernel show output? During kernel boot, initramfs stage or later? Does it work with qemu? For which system is this kernel normally used? The output of binwalk shows a certificate section. Binwalk does not always get things 100% right but maybe it is a signed kernel image (UKI). So there might be some verification involved and changing contents will render the kernel unbootable. In general I would recommend the kernel-newbies irc. Maybe there is already a tool/script in the kernel src for extracting/reassembling such images. – secfren Mar 08 '23 at 12:25
  • Slightly older related thread. Not sure if helpful. https://reverseengineering.stackexchange.com/questions/23547/repacking-an-embedded-initramfs?rq=1 – secfren Mar 08 '23 at 12:27
  • @secfren With the original working kernel the startup sequence definitely reaches initramfs, I've verified that both with qemu and a real machine. The kernel is part of a full bootable image consisting of several partitions, some of which are encrypted, but that's irrelevant at the moment. Currently the boot process does not go beyond initramfs, but that's not a problem, either - I'm able to access init script in initramfs and have an idea how to modify it in order to proceed. The whole point is that I can't assemble the fragmented kernel back to a state where it's bootable. – Peter Mar 08 '23 at 18:43
  • @secfren As for the kernel itself: fragments of text data found in it suggest it might be based on 3.13 x86 64-bit Linux kernel. I don't have detailed info about the system as a whole, but it could be some custom version of Ubuntu (definitely not a regular one, as standard ones come with separate kernel and initramfs). – Peter Mar 08 '23 at 19:07
  • When does the original kernel show output during boot? Already at kernel boot? Depending on the kernel there is not output until initramfs stage. So you might not know where your new kernel stops. If it is quiet by default it could just be due to the initramfs failing. Under which circumstances does boot freeze and when does it reboot? If the original kernel is working shouldn't it be possible to get more info on the kernel version from within initramfs? – secfren Mar 08 '23 at 21:01
  • @secfren I know for sure with unmodified kernel the boot stage advances at least far enough to process most of the initramfs init script. Boot messages are covered by splash screen, but when I switch to tty1 I can see them. I'd like to debug certain issue by printing debug messages at certain points or even halt the init script mid-way by injecting a debug shell. As I already said, I'm unable to do so for reasons stated before. With modified kernel, boot via qemu freezes or enters and infinite loop. IIRC, on a real machine boot attempt with modified kernel results in a freeze and black screen. – Peter Mar 09 '23 at 10:06
  • @secfren To sum up, my problem is a general one, not kernel specific. All I'd like for now is to find a way to safely modify the contents of a kernel (specifically, its embedded initramfs) in such a way that after rebuilding it remains bootable. As I said before, I used to successfully modify initramfs in the past, but only in a typical case where kernel and initramfs were separate. This is the first time I want to do this in a (rare?) case of initramfs embedded into kernel. I was hoping someone already did this before me and perhaps there are some third-party tools to aid me in such task. – Peter Mar 09 '23 at 10:15
  • I can't really help with the general issue (reassembly). I'd again recommend the kernel newbies irc for that. – secfren Mar 09 '23 at 18:01
  • I'm not sure if this is helpful, but you may be working with a Unified Kernel Image (you have kernel, initramfs, and Secure Boot signatures all combined into a single thing). There's a bunch of info about UKI images here: https://0pointer.de/blog/brave-new-trusted-boot-world.html Also, Secure Boot is probably going to throw a wrench in the works of what you're doing when you go to boot this on physical hardware unless you are able to either turn Secure Boot off or re-sign the modified blob yourself. – ArrayBolt3 Mar 10 '23 at 03:19
  • This kernel boots both in legacy and uefi mode, so secure boot is not much of a problem here. Anyway, thanks for all you attention and suggestions. Luckily, my initial assumptions were wrong - it turned out manual kernel manipulation was not necessary in this particular case, as my full image advances past early userspace/initramfs. Initially I thought it got stuck there, but this was due to a misleading plymouth splash screen message which happens to appear twice - first in initramfs and then once again after the real root mount. – Peter Mar 14 '23 at 18:24
  • While I can consider my problem solved, the general question remains open. In case anyone's interested, some time ago I found this forum thread: https://forum.xda-developers.com/t/script-repack-zimage-sh-unpack-and-repack-a-zimage-without-kernel-source-v-5.901152/ where you can find a third-party script which supposedly rebuilds a kernel the way I expected. I ran it without paying too much attention to the code. In my case it ran for many hours without success, but maybe it's a good starting point and some of you may find it beneficial. – Peter Mar 14 '23 at 18:36

0 Answers0