It is about the hypothetical filesystem "ext5", which is obviously
based on ext4. Actually, I patched lwext4 a bit to make it different
from a standard ext4 filesystem. Contestants were supposed to reverse
engineer rwext5-mkfs and rwext5-import and
find the nuance.
1 2 3 4
% tar tf rwext5.tar.xz rwext5-import rwext5-mkfs rwext5.img
After successful deobfuscation, the following files can be found in
rwext5.img:
The image includes some form of the flag file, a copy of lwext4 (as a
hint that the two distributed tools rwext5-mkfsrwext5-import should be reversed and compared with standard
ones). It would be interesting to put some other stuff (also served as
market promotion purpose for my project and the LSP ecosystem :)
.config/nvim/init.vim: language clients of
Vim/NeoVim:ale, coc.nvim, LanguageClient-neovim, vim-lsp。
.config/Code/User/settings.json: VSCode configuration
of ccls
Create a file flag of 1111 blocks (512 bytes per block)
filled with random bytes and place the plaintext flag in the 555th
block. Encrypt it with
openssl enc -aes-128-cbc -pass pass:filesystem -in flag.plain -out flag.
% cat clang I am not used in the challenge, but I empowers language servers. % cat ld.lld I can concatenate sections. % cat llvm-objcopy I can dump sections.
1 2 3 4 5 6 7 8 9 10 11
$(flag0): flag head -c 1000000 flag > $@ llvm-objcopy -I binary -B powerpc:common64 $@ llvm-objcopy --rename-section=.data=.openssl_aes-128-cbc_pass:filesystem $@ echo empty > empty; llvm-objcopy --add-section=.turkey_for_thanksgiving=empty $@; rm empty
I modified ext4_ext_find_goal (lwext4 uses a first-fit
algorithm to find the first free block) so that it would return a random
block, otherwise the extent block tended to be continuous and the
contestants wouldn't need to decrypt the metadata to recover the two
object files.
diff --git a/include/ext4_types.h b/include/ext4_types.h index c9cdd34..a4c5050 100644 --- a/include/ext4_types.h +++ b/include/ext4_types.h @@ -78,9 +78,10 @@ struct ext4_sblock { uint32_t first_data_block; /* First Data Block */ uint32_t log_block_size; /* Block size */ uint32_t log_cluster_size; /* Obsoleted fragment size */ - uint32_t blocks_per_group; /* Number of blocks per group */ - uint32_t frags_per_group; /* Obsoleted fragments per group */ + // CTF swap uint32_t inodes_per_group; /* Number of inodes per group */ + uint32_t frags_per_group; /* Obsoleted fragments per group */ + uint32_t blocks_per_group; /* Number of blocks per group */ uint32_t mount_time; /* Mount time */ uint32_t write_time; /* Write time */ uint16_t mount_count; /* Mount count */ @@ -245,6 +246,8 @@ struct ext4_sblock { #define EXT4_FINCOM_MMP 0x0100 #define EXT4_FINCOM_FLEX_BG 0x0200 #define EXT4_FINCOM_EA_INODE 0x0400 /* EA in inode */ +// CTF +#define EXT4_FINCOM_CTF 0x800 #define EXT4_FINCOM_DIRDATA 0x1000 /* data in dirent */ #define EXT4_FINCOM_BG_USE_META_CSUM 0x2000 /* use crc32c for bg */ #define EXT4_FINCOM_LARGEDIR 0x4000 /* >2GB or 3-lvl htree */ @@ -281,7 +284,7 @@ struct ext4_sblock { #define EXT4_SUPPORTED_FINCOM \ (EXT4_FINCOM_FILETYPE | EXT4_FINCOM_META_BG | \ EXT4_FINCOM_EXTENTS | EXT4_FINCOM_FLEX_BG | \ - EXT4_FINCOM_64BIT) + EXT4_FINCOM_64BIT | EXT4_FINCOM_CTF)
#define EXT4_SUPPORTED_FRO_COM \ (EXT4_FRO_COM_SPARSE_SUPER | \ @@ -371,13 +374,14 @@ struct ext4_bgroup { * Structure of an inode on the disk */ struct ext4_inode { - uint16_t mode; /* File mode */ uint16_t uid; /* Low 16 bits of owner uid */ - uint32_t size_lo; /* Size in bytes */ + uint16_t mode; /* File mode */ + // CTF uint32_t access_time; /* Access time */ uint32_t change_inode_time; /* I-node change time */ uint32_t modification_time; /* Modification time */ uint32_t deletion_time; /* Deletion time */ + uint32_t size_lo; /* Size in bytes */ uint16_t gid; /* Low 16 bits of group id */ uint16_t links_count; /* Links count */ uint32_t blocks_count_lo; /* Blocks count */ diff --git a/src/ext4_extent.c b/src/ext4_extent.c index 791f8a7..375a2f6 100644 --- a/src/ext4_extent.c +++ b/src/ext4_extent.c @@ -92,10 +92,11 @@ struct ext4_extent_tail * It's used at the bottom of the tree. */ struct ext4_extent { - uint32_t first_block; /* First logical block extent covers */ - uint16_t block_count; /* Number of blocks covered by extent */ - uint16_t start_hi; /* High 16 bits of physical block */ - uint32_t start_lo; /* Low 32 bits of physical block */ + // CTF + uint16_t block_count; /* Number of blocks covered by extent */ + uint16_t start_hi; /* High 16 bits of physical block */ + uint32_t start_lo; /* Low 32 bits of physical block */ + uint32_t first_block; /* First logical block extent covers */ };
/*
Two tools compiled from the obfuscated lwext4 are provided:
rwext5-mkfs: used to create a ext5 filesystem
rwext5-import: used to import files into the
filesystem
Fill rwext5.img with dd if=/dev/urandom,
run rwext5-mkfs, and then populate it with
flag.0.o, flag.1.o, lwext4 and other files.
The final rwext5.img was distributed among teams.
By reversing the two programs contestants can figure out how struct
members are reordered. They may write an export tool (I wrote a
rwext5-export) to copy the two object files out or assemble
extent blocks by hand. Both object files have the section with a weird
name ".openssl_aes-128-cbc_pass:filesystem". A linker concatenates the
section contents and llvm-objcopy may be used to dump the section. It is
straightforward to decrypt the contents with openssl and get the
plaintext flag: