-
mount the vm disk, we could have the vmlinuz file and rootfs.gz file
normally we adopt the extract-vmlinux script to get the vmlinux image from an compressed kernel file, but on this version, it doesn't work as expected
the script searches for predefined magic number of mainline-supported compression output header, which in this case does not exist in the vmlinuxz file
best guess is that the kernel is decrypted, there could be two possibilities:
a. bootloader
extlinux
is responsible for decrypting the kernel vmlinux b. early stage code of vmlinuz is responsible for decrypting
considering that first stage of extlinux bootloader is too tiny in size, jump to the extlinux.sys file, after inspecting the extlinux.sys file, first possibilities is out
-
boot up the vm with qemu in debug mode
To boot up the FMG, we need an additional disk as data disk,
qemu-img create -f qcow2 virtiob.qcow2 20G
now there is two disks:
virtioa.qcow2
andvirtiob.qcow2
append
-S
and-s
flag to qemu to enable gdb remote debug listeningqemu-system-x86_64 -drive file=virtioa.qcow2 -drive file=virtiob.qcow2 -m 4G -smp 4 -s -S -netdev tap,id=net0,ifname=tap0,script=no,downscript=no -device virtio-net-pci,netdev=net0 -netdev tap,id=net1,ifname=tap1,script=no,downscript=no -device virtio-net-pci,netdev=net1 -netdev tap,id=net2,ifname=tap2,script=no,downscript=no -device virtio-net-pci,netdev=net2 -netdev tap,id=net3,ifname=tap3,script=no,downscript=no -device virtio-net-pci,netdev=net3
-
open ida64 and connect to the qemu gdb stub
so as connection has been setup, add a manual memory region starting at 0x0
and ending at 0xFFFFFFFFFFFFFFF0
like this:
-
from the linux kernel boot process and the related code we know that kernel has several boot stages, out best guess is that the decryption of vmlinux is happening right before decompression of vmlinux
from the boot protocol we could have some addresses for setting breakpoints
set a breakpoint at 0x100000 and look around in first, where the early stage begins,
-
after some time we could find the function at
sub_13C5D00
, which isextract_kernel
function isarch/x86/boot/compressed/misc.c
in source,the prototype of this extract_kernel function goes like this, by breaking at the entrance we can obtain the
output
andoutput_len
two args, which aredecompressed kernel pointer
anddecompressed kernel data length integer
respectivelyasmlinkage __visible void *extract_kernel(void *rmode, memptr heap, unsigned char *input_data, unsigned long input_len, unsigned char *output, unsigned long output_len)
change the function prototype in ida like:
void *__usercall sub_13C5D00@<rax>(void *rmode@<rdi>, void *heap@<rsi>, unsigned __int8 *input_data@<rdx>, unsigned int input_len@<ecx>, unsigned __int8 *output@<r8>, unsigned int output_len@<r9d>)
in this run:
location type arguement name value rdi void * rmode 0x44050 rsi memptr heap 0x13C7100 rdx unsigned char input_data 0x10B0255 rcx unsigned long input_len 0x312E28 ~3MB r8 unsigned char output 0x200000 r9 unsigned long output_len 0x11A07D8 ~17MB inspecting data at
input_data
:search the bytes of input_data, match found in the vmlinuz file at rather small offset:
-
inside the
extract_kernel
function select a breakpoint right after the compressed kernel is decrypted and decompressed, and right before the kernel is ELF-parsed and relocatedafter this piece of code,
input_data
is decrypted, a header begin with0xFD377A585A
shows up like some magic number of compressionand after the decompression, we could dump the vmlinux at 0x200000
-
in th ida python console, dump the kernel, we need two arguments, output
and output_len
, noted at the entrance of this procedure
-
at this point, we could use
vmlinux-to-elf
to convert the dumped kernel to elf, and the load it with ida
now we have kernel-elf, examine it statically, looking for populate_rootfs
, seems that there is no additional code for decryption of rootfs.gz
but, after some digging, looking at the cross reference to global variable initrd_start
and initrd_end
, found some thing:
functions: forti_load
and forti_verify
and forti_decrypt
that's it
- to take a shortcut, still we dump the memory right after the rootfs.gz is decrypted,
a little typo here, the dump file is rootfs.cpio.gz actually, this dump process could take a while as the file is ~68MB
finnaly
FMG version 7.6.0 as example
mostly firmware developers use mainline kernel code as base, added their proprietary code to implement the secured bootstrap chain, but the original mainline code could still help one get better understandings of the kernel vmlinuz binary
header.S is an assembly file that would linked to kernel image at the text beginning, which run under real mode and does some preparation work for entering protected/long mode(x86 exclusively), like setting up some registers or initializing memory management.
EG. https://elixir.bootlin.com/linux/v5.15.109/source/arch/x86/boot/compressed/head_64.S
to be noted that, asm code in source is written in at&t flavor, as of ida it is intel style.
to be noted, the message "Loading vmlinuz" and "Loading /rootfs.gz" is from bootloader, not the kernel.
the breakpoint won't be hit until you see "ready" is on console, which is the point the bootloader transfers cpu to vmlinuz.
in simple terms, not in a very formal way, that on source code level, extract_kernel
is called by .Lrelocated
, and .Lrelocated
is called by startup_64
/ startup_32
all we need to do is in to findout the address of these functoins the debug view
cld cli leacall pop mark the beginning
at 0x100f3
find out where startup_64
is from register eax
at the end of startup_64 0x102bd
, .Lrelocate
is found
at this point we successfully located the extract_kernel
from the call instruction at 0x166f7f1
extract_kernel
in this version of fmg is at 0x1672430