## Reverse Engineering - Fat Module

We are given x86 and arm virtual machine images. x86 is more familiar - let's
start with it:

x86$ sudo apt install qemu-system
x86$ sudo ./run.sh
[ 0.000000] Linux version 5.1.16 (user@host) (gcc version 7.4.0 (Buildroot 2019.05.1-gb18f532-dirty)) #1 SMP Mon Sep 2 14:14:04 EEST 2019
none login: root

Nice, we can login as `root` without a password - no need to hack the image.
Ditto the arm one:

arm$ sudo ./run.sh
Linux version 5.1.16 (user@host) (gcc version 7.4.0 (Buildroot 2019.05.1-gaed32c5-dirty)) #1 Mon Sep 2 14:36:22 EEST 2019
none login: root

That took a while compared to x86 (no KVM, TCG all the way), but the image
seems to work as well. Now let's implement the module - quick googling gives us
[an inspiration](

Instead of manually messing with cross-compilers, let's just take buildroot:

FatModule$ git clone [email protected]:buildroot/buildroot.git
buildroot$ git worktree add ../buildroot-x86 2019.05.1
buildroot$ cp ../x86/linux_x86_config ../buildroot-x86
buildroot$ git worktree add ../buildroot-arm 2019.05.1
buildroot$ cp ../arm/linux_arm_config ../buildroot-arm

Let's build the x86 one:

buildroot-x86$ make menuconfig
buildroot-x86$ make -j$(getconf _NPROCESSORS_ONLN)

and the arm one:

buildroot-arm$ make menuconfig
buildroot-arm$ make -j$(getconf _NPROCESSORS_ONLN)

This is going to take a while. In the meantime, let's try reversing the kernel.
First, extract and uncompress vmlinux (ELF image) from bzImage (bootable image).
There is a [script](
) for this in the kernel source tree:

x86$ ./extract-vmlinux bzImage >vmlinux

vmlinux can be loaded into IDA. Module-related stuff lives in
) - in particular, we are interested in `load_module()` and
`elf_header_check()`. Let's locate the latter by looking for comparisons with
the ELF signature `\x7FELF`:

IDA$ Alt+B
Binary Search$ 7F 45 4C 46

There are about 10 entries, but only one is close to the beginning of a
function. Let's reverse it a little bit:

.text:FFFFFFFF810DFF40 load_module proc near


.text:FFFFFFFF810DFF66 cmp [rdi+load_info.len], (size Elf_Ehdr) - 1


.text:FFFFFFFF810DFF7C jbe return_enoexec

This is supposed to be

if (info->len < sizeof(*(info->hdr)))
return -ENOEXEC;

but `load_info.len` is at offset `0x20`, while it [should be at `0x18`](

struct load_info {
const char *name;
/* pointer to module in temporary copy, freed at end of load_module() */
struct module *mod;
Elf_Ehdr *hdr;
unsigned long len;

Is there a new field? We'll see.

.text:FFFFFFFF810DFF82 cmp [r14+Elf_Ehdr.e_machine], EM_PWNTHYBYTES
.text:FFFFFFFF810DFF89 jz em_is_pwnthybytes
.text:FFFFFFFF810DFF8F again:

Hey, new machine type `0xE6`! It does not match [any of the existing ones](
). How is it handled? Let's find out:

.text:FFFFFFFF810E0154 em_is_pwnthybytes:
.text:FFFFFFFF810E0154 lea rax, [r14+(size Elf_Ehdr)]
.text:FFFFFFFF810E0158 lea rdx, [r14+180h]
.text:FFFFFFFF810E015F jmp short em_is_pwnthybytes_cont
.text:FFFFFFFF810E0161 find_0x3e_loop:
.text:FFFFFFFF810E0161 add rax, size pwn_header
.text:FFFFFFFF810E0165 cmp rdx, rax
.text:FFFFFFFF810E0168 jz short return_enoexec
.text:FFFFFFFF810E016A em_is_pwnthybytes_cont:
.text:FFFFFFFF810E016A cmp [rax+pwn_header.magic], 3Eh ; '>'
.text:FFFFFFFF810E016D jnz short find_0x3e_loop
.text:FFFFFFFF810E016F mov [r15+load_info.pwn_header], r14
.text:FFFFFFFF810E0173 add r14, [rax+pwn_header.elf_offset]
.text:FFFFFFFF810E0177 mov [r15+load_info.hdr], r14
.text:FFFFFFFF810E017B mov rax, [rax+pwn_header.elf_size]
.text:FFFFFFFF810E017F mov [r15+load_info.len], rax
.text:FFFFFFFF810E0183 jmp again

So, between offsets `0x40` (right after the normal ELF header) and `0x180` we
now have 16 entries with the following format:

00000000 pwn_header struc
00000000 magic dd ?
00000004 elf_offset dq ?
0000000C elf_size dq ?
00000014 pwn_header ends

The loop tries to find the array entry with the magic value and saves the
pointer to it into the new `load_info.pwn_header` field.

In x86 kernel, `magic` appears to be `0x3E`, which corresponds to the existing
`EM_X86_64`. No obfuscation - long live best coding practices! Keep Linus happy!
Also let's pray that ARM counterpart is simply `EM_ARM` so that we wouldn't have
to reverse the ARM kernel.

The rest of the module loading flow appears to be unmodified. So we need to
build and concatenate x86 and arm versions of the module, prepend ELF and PWN
headers, nd the result should just work (TM).

The kernels have finished building, let's compile the stolen code:

FatModule$ cat >Makefile
obj-m += mod.o

make -C $(PWD)/buildroot-x86/output/build/linux-5.1.16 M=$(PWD)

PATH=$(PWD)/buildroot-arm/output/host/bin:$$PATH make -C $(PWD)/buildroot-arm/output/build/linux-5.1.16 ARCH=arm CROSS_COMPILE=arm-buildroot-linux-uclibcgnueabi- M=$(PWD)
FatModule$ make x86
FatModule$ mv mod.ko mod-x86.ko
FatModule$ make arm
FatModule$ mv mod.ko mod-arm.ko

Squash 'em:

FatModule$ cat >script
#!/usr/bin/env python3
import struct

x86_blob = open('mod-x86.ko', 'rb').read()
arm_blob = open('mod-arm.ko', 'rb').read()
master = b'\x7FELF' + b'\x00' * 14 + b'\xE6\x00' + b'\x00' * 0x2c
x86 = struct.pack('<IQQ', 0x3e, 0x180, len(x86_blob))
arm = struct.pack('<IQQ', 0x28, 0x180 + len(x86_blob), len(arm_blob))
others = b'\0' * (0x14 * 14)
open('fat.ko', 'wb').write(master + x86 + arm + others + x86_blob + arm_blob)
FatModule$ python3 script

Let's test whether this works... Oh, wait, how to copy this file into guests?
We'll need to configure the networking and use [py3tftp](
https://pypi.org/project/py3tftp/), which is great because it requires zero

FatModule$ sudo tunctl -u $USER -t tap0
FatModule$ sudo ifconfig tap0 up
FatModule$ py3tftp &
2019-10-02 22:37:52,677 [INFO] Starting TFTP server on

Now test on x86:

FatModule$ vim x86/run.sh
-netdev tap,id=mynet0,ifname=tap0,script=no,downscript=no -device e1000,netdev=mynet0,mac=52:55:00:d1:55:01
to qemu-system-x86_64 invocation.
FatModule$ cd x86
x86$ sudo ./run.sh
# ifconfig eth0 up
# tftp 9069 -gr fat.ko
# insmod fat.ko
# mknod foo c 400 0
# cat foo
Hello, World!

Cool! ARM works too, so our guess regarding `EM_ARM` was right. Let's submit:

FatModule$ cat >submit
#!/usr/bin/env python3
import struct
import sys

f=open('fat.ko', 'rb').read()

Original writeup (https://github.com/mephi42/ctf/tree/master/2019.09.28-PwnThyBytes_CTF_2019/reverse_engineering-Fat_Module).