| Layer | Tools | What it sees |
|---|---|---|
| Disk / Volume | mmls, mmstat, mmcat | Partition table, MBR/GPT |
| Filesystem | fsstat | Superblock, block size, layout |
| File / Name | fls, ffind | Directory entries, filenames, deleted |
| Inode / Meta | ils, istat, icat | Inode table, timestamps, block pointers |
| Block / Data | blkls, blkcat, blkstat | Raw data blocks, allocated/unallocated |
| Journal | jls, jcat | ext3/4 journal entries |
| Flag | Meaning |
|---|---|
| -o <sector> | Partition offset in 512-byte sectors (from mmls) |
| -b <size> | Sector size override (default 512) |
| -f <type> | Force filesystem type: ext4, ntfs, fat32, hfs… |
| -i <type> | Force image type: raw, ewf, vmdk… |
| -z <tz> | Timezone for timestamps |
| -v | Verbose |
# List all partitions (MBR or GPT) mmls disk.img # Force partition table type mmls -t dos disk.img # MBR mmls -t gpt disk.img # GPT mmls -t mac disk.img # Apple partition map # Output columns: # Slot | Start | End | Length | Description # → use Start sector as -o value for other tools
# Example output: # 002: 000:000 0000002048 0000616447 Linux (0x83) # 003: 000:001 0000616448 0001140735 Linux Swap # 004: 000:002 0001140736 0002097151 Linux (0x83) # Partition types by ID: # 0x83 = Linux ext2/3/4 # 0x82 = Linux swap (check for plaintext data!) # 0x07 = NTFS / exFAT # 0x0b/0x0c = FAT32 # 0xee = GPT protective MBR # 0x05/0x0f = Extended partition # Swap partition — no filesystem but may hold data dd if=disk.img bs=512 skip=616448 count=524288 | strings | grep "picoCTF"
# Single partition image fsstat partition.img # Partition inside disk image (-o in sectors) fsstat -o 2048 disk.img fsstat -o 1140736 disk.img # Key info to note from output: # - File System Type (ext4, NTFS, FAT32…) # - Block Size (usually 4096) # - Block Count (total blocks) # - Inode Range (first/last inode) # - Journal Inode (usually 8 for ext4) # - Volume Label / UUID # - Last mounted / written time
| Field | CTF relevance |
|---|---|
| FS Type | Determines which tools to use |
| Last mount time | Timeline anchor |
| Volume label | May contain a hint or flag part |
| Free blocks | Low = lots of data; compare with apparent content |
| Block size | Needed for manual block math |
| Journal inode | Use icat to extract journal raw data |
# Basic listing fls partition.img fls -o 2048 disk.img # Recursive fls -r partition.img # Deleted files only ← most useful in CTF fls -rd partition.img fls -rd -o 1140736 disk.img # Full paths (easier to read) fls -rp partition.img # For MAC timeline bodyfile fls -r -m "/" partition.img > body.txt fls -r -m "/" -o 2048 disk.img > body_p1.txt
# Output format: type/type inode filename # r/r = regular file (allocated) # d/d = directory # * r/r = DELETED file (the * is the key) # Example: # r/r 2043: etc/passwd # * r/r 4945: bin/bcab ← deleted, inode 4945 # d/d 1024: home # Extract any file by inode: icat partition.img 4945 > /tmp/out icat -o 1140736 disk.img 4945 > /tmp/out # Pipe to grep for specific names fls -rp partition.img | grep -i "flag\|secret\|pass\|key"
# Show full inode info: timestamps, size, blocks istat partition.img <inode> istat -o 2048 disk.img <inode> # Output includes: # Size, UID/GID, mode/permissions # Accessed / Modified / Changed / Created times # Direct/indirect block pointers # Timestomping tells: # - All 4 times identical → set by tool at once # - Modified < Created → impossible, flag it # - Epoch 0 (1970-01-01) → zeroed out
# Extract file content by inode number icat partition.img <inode> > /tmp/recovered icat -o 1140736 disk.img <inode> > /tmp/recovered # Always check what you got file /tmp/recovered xxd /tmp/recovered | head -4 strings /tmp/recovered binwalk /tmp/recovered # Special inodes (ext4): # 1 = bad blocks # 2 = root directory # 8 = journal ← icat img 8 > journal.bin # 11 = lost+found
# List all inodes (allocated + unallocated) ils partition.img ils -o 2048 disk.img # Unallocated (deleted) inodes only ils -u partition.img # Allocated only ils -a partition.img # Pipe to sort by size (field 7) ils partition.img | sort -t'|' -k7 -rn | head # ffind: find filename for a given inode ffind partition.img <inode>
# Step 1: create bodyfile from each partition fls -r -m "/" -o 2048 disk.img > body_p1.txt fls -r -m "/" -o 1140736 disk.img > body_p3.txt cat body_p1.txt body_p3.txt > body_all.txt # Step 2: generate timeline mactime -b body_all.txt -d > timeline.csv mactime -b body_all.txt -d -z UTC > timeline_utc.csv # Step 3: filter for anomalies grep "197[0-9]\|198[0-9]\|Jan 01 1970" timeline.csv sort timeline.csv | head -30 # oldest first # Filter by date range mactime -b body_all.txt 1970-01-01,1990-01-01
# Pipe-delimited: 11 fields # MD5|name|inode|mode|uid|gid|size|atime|mtime|ctime|crtime # 1 2 3 4 5 6 7 8 9 10 11 # Sort directly by mtime (field 9) sort -t'|' -k9 -n body_all.txt | head -20 # Timeline CSV output format: # Date,Size,Type,Mode,UID,GID,Inode,Filename # e.g: Tue Jan 01 1985 18:00:00,41,macb,...,4945,"/bin/bcab" # ↑ ↑inode # macb = Modified+Accessed+Changed+Born all same → timestomped # Extract the suspicious file: icat -o <offset> disk.img <inode> > /tmp/sus
# blkls: dump unallocated (free) blocks blkls partition.img > unalloc.bin blkls -o 2048 disk.img > unalloc.bin strings unalloc.bin | grep "picoCTF" foremost -i unalloc.bin -o ./carved/ # blkls with flags blkls -e partition.img # allocated + unallocated blkls -a partition.img # allocated only blkls -s partition.img # slack space only # blkcat: dump a specific block number blkcat partition.img <block> blkcat -o 2048 disk.img <block> | xxd # blkstat: info about a block blkstat partition.img <block> # shows: allocated/free, which inode owns it
# Convert sector offset → byte offset python3 -c "print(2048 * 512)" # → 1048576 # Convert block number → byte offset within partition python3 -c "print(1234 * 4096)" # block_num * block_size # Extract a block with dd dd if=partition.img of=block.bin \ bs=4096 skip=<block_num> count=1 # Extract partition from disk image dd if=disk.img of=p1.img \ bs=512 skip=2048 count=614400
# jls: list journal entries jls partition.img jls -o 2048 disk.img # jcat: dump a journal entry block jcat partition.img <seq_num> jcat -o 2048 disk.img <seq_num> | xxd # Extract entire journal (inode 8 in ext4) icat partition.img 8 > journal.bin strings journal.bin | grep -i "flag\|picoCTF\|pass" # The journal may contain: # - Previous versions of files (before deletion) # - Old content of overwritten files # - Metadata changes
# Open image interactively debugfs partition.img debugfs -i disk.img # disk image mode # Useful debugfs commands: # ls -l / list root, including deleted # lsdel list deleted inodes # stat <file> inode info # dump <file> /tmp/out extract file # cat <file> print file content # show_super_stats superblock details # logdump journal dump
file img.img && fsstat img.img fls -rp img.img | grep -i "flag\|key\|secret" # filenames fls -rd img.img # deleted files fls -r -m "/" img.img > body.txt && mactime -b body.txt -d | grep "197\|198" blkls img.img | strings | grep "picoCTF" # unallocated data icat img.img <inode> > /tmp/f && file /tmp/f && strings /tmp/f
mmls disk.img # partition map fsstat -o <p1_start> disk.img # identify each FS fls -rd -o <p1_start> disk.img # deleted in p1 fls -r -m "/" -o <p1_start> disk.img > b1.txt # bodyfile p1 fls -r -m "/" -o <p3_start> disk.img > b3.txt # bodyfile p3 mactime -b <(cat b1.txt b3.txt) -d | grep "197\|198" # anomalies dd if=disk.img bs=512 skip=<swap_start> count=<swap_len> | strings | grep "CTF"
mmls partition layout
· fsstat -o filesystem info
· fls -rd -o deleted files
· fls -r -m "/" -o → bodyfile → mactime timeline
· icat -o extract by inode
· blkls -o unallocated space
· icat img 8 journal