Description
The agents interrupted the perpetrator's disk deletion routine. Can you recover this git repo?
Setup
Download and decompress the disk image.
Mount the image and inspect the filesystem for a damaged git repository.
gunzip disk.img.gzsudo mount -o loop disk.img /mnt/diskSolution
Walk me through it- Step 1Decompress and quick strings checkExtract the disk image. The deletion routine was interrupted, so git objects may still be present. Run strings first as a fast check.bash
gunzip disk.img.gzbashstrings disk.img | grep picoCTFLearn more
When a deletion process is interrupted, the filesystem may be in an inconsistent state: some files are deleted (their directory entries removed) but their data blocks have not yet been overwritten. The raw bytes of the deleted content remain on disk until the operating system reuses those blocks for new data.
strings | grep picoCTFscans the raw image and finds these bytes even though the filesystem no longer has a path to them.This is the principle behind file carving: recovering files by their content patterns rather than their filesystem metadata. Known file types have recognizable headers (magic bytes): for example, JPEG files start with
FF D8 FF, ZIP files with50 4B 03 04, and git objects are zlib-compressed with a characteristic header. Carving tools like Foremost and PhotoRec scan raw disk images looking for these magic bytes and extract complete files even from unallocated space. - Step 2Detect partition layout and mountUse mmls to identify the partition offset, then mount the filesystem.bash
mmls disk.imgbashsudo mount -o loop,offset=$((512*<start_sector>)) disk.img /mnt/diskbashls /mnt/diskLearn more
Even when files are deleted, the partition table and filesystem superblock are usually intact because they are written early in the deletion process and are often the last to be cleared.
mmlsreads the partition table to locate the data partition, and Linux can still mount and read the filesystem structure even if individual file inodes have been cleared.Linux ext4 filesystems mark deleted inodes as free in the inode bitmap but do not immediately zero the inode's block pointers. This means the Sleuth Kit's
ils(inode list) andifindtools can still see deleted inodes and their data block addresses for a period after deletion: long enough to recover recently deleted files. Thefls -r -dcommand from TSK lists deleted files and directories by scanning for inodes marked as unallocated. - Step 3If .git is fully gone, recover with TSK firstWhen the .git directory itself was unlinked, normal find/ls won't see it. Use The Sleuth Kit on the raw image (no mount needed) to enumerate deleted inodes and pull anything git-shaped back out before trying any git commands.bash
fls -r -d disk.img | grep -iE 'git|HEAD|objects|refs'bash# For each interesting deleted inode:bashicat disk.img <inode> > /tmp/recovered/<name>bashfile /tmp/recovered/<name> # 'zlib compressed data' = loose git objectbash# Decompress a zlib loose object to see its raw blob/commit/tree:pythonpython3 -c 'import sys, zlib; sys.stdout.buffer.write(zlib.decompress(open(sys.argv[1],"rb").read()))' /tmp/recovered/<name>Learn more
If the entire
.gitdirectory was unlinked before the deletion was interrupted, mounting the filesystem normally won't reveal it - the directory entries are gone. TSK bypasses the live filesystem and reads metadata structures directly from the image, so it sees deleted inodes that the kernel has marked as unallocated. This makes TSK the right starting point in this scenario, not the fallback.icatoutput for a recovered git object can be one of two things. Files that lived under.git/objects/XX/YY...are zlib-compressed loose objects, sofilewill report "zlib compressed data"; pipe them throughpython3 -m zliborpython3 -c '...zlib.decompress...'to get the raw blob/commit/tree text. Files in.git/lost-found/other/from a previousgit fsck --lost-foundrun are already plain text blobs, socatworks directly.filetells the two apart in one shot. - Step 4Find surviving .git directory and HEADIf TSK turned up nothing - or once you've staged recovered objects - check whether any of the standard git plumbing survived intact.bash
find /mnt/disk -name '.git' -type d 2>/dev/nullbashfind /mnt/disk -name 'HEAD' 2>/dev/nullbashfind /mnt/disk -path '*/objects/*/*' 2>/dev/null | headLearn more
Git loose object files are stored in
.git/objects/XX/YYYYYYYY...whereXXis the first two hex characters of the object's SHA-1 hash. Each file is zlib-compressed and contains either a blob (file content), tree (directory snapshot), commit (history entry), or tag. Even if the.gitdirectory itself was deleted, individual object files that haven't been overwritten can be recovered and reassembled.The
HEADfile in a git repository contains a ref pointer likeref: refs/heads/mainor a raw SHA-1 hash (detached HEAD). Finding an intactHEADfile tells you the repository's root and whether the ref structure survived. IfHEADis gone but object files remain, you can still reconstruct history by runninggit fsckin a directory containing those object files.Git also supports pack files (
.git/objects/pack/*.pack) which bundle many objects into a single compressed archive with a corresponding index file. Pack files are more space-efficient than loose objects and are created bygit gcor when fetching from a remote. If pack files survived, they may contain all the repository's history even if loose objects were deleted. - Step 5Recover the repositoryCopy any surviving git directory or objects to a writable path and run git fsck to reconstruct reachable history.bash
mkdir /tmp/recovered && cp -r /mnt/disk/<path>/.git /tmp/recovered/.gitbashcd /tmp/recovered && git fsck --unreachablebashgit log --all --oneline 2>/dev/nullbashgit fsck --lost-found && ls .git/lost-found/other/Learn more
git fsck(filesystem check) verifies the integrity of the object database. It traverses all refs and objects, checks SHA-1 hashes for corruption, and reports dangling (unreachable) objects. Running it after recovering partial object files tells you which parts of the history survived and whether any objects are corrupt (truncated or overwritten blocks).If
git logreports errors about missing objects, you can sometimes reconstruct the missing pieces by finding their raw bytes in the disk image usinggrepfor the partial SHA-1 hash, or by usinggit hash-object -wto re-add a found blob. The recovery process is iterative: find surviving objects, identify which are missing, search for them in unallocated space, and repeat until the history is complete enough to read the flag. - Step 6Extract the flagRead flag content from whichever commit, blob, or recovered file contains it.bash
git cat-file -p <blob-hash>bashgit show <commit>bashcat .git/lost-found/other/<hash>Learn more
After recovery,
git cat-file -pandgit showlet you read the content of any recovered object. The.git/lost-found/other/directory (created bygit fsck --lost-found) contains the dangling blob objects already decompressed and named by their SHA-1 hash, so plaincatworks directly. Loose objects under.git/objects/XX/YY...are still zlib-compressed; usegit cat-file -pfor those, or decompress manually withpython3 -m zlib < .git/objects/XX/YY....This challenge is a microcosm of real incident response: a threat actor attempted to destroy evidence by deleting a repository, the deletion was interrupted, and forensic investigators recover what they can from the surviving artifacts. The same skills (mounting disk images, using TSK, recovering git objects) are used in real forensic investigations of developer workstations, source code repositories, and cloud storage buckets.
For more on the surrounding shell workflow, see Linux CLI for CTF.
Flag
picoCTF{...}
The repository was partially deleted. Surviving git objects or deleted inodes on the disk hold the flag. Use git fsck --lost-found and TSK icat to recover them.
How to prevent this
How to prevent this
If an attacker (or insider) reaches a developer machine, file deletion is reversible. Plan for full-disk compromise.
- Encrypt developer disks at rest (FileVault, BitLocker, LUKS). Recovered inodes from an encrypted volume are useless without the key.
- Don't store production credentials in repos at all, even briefly. Use a secrets manager (AWS Secrets Manager, Doppler, 1Password CLI, Vercel env vars) and pull at runtime. Then disk recovery yields nothing useful.
- For high-value repos, enforce
secure_erase/shredon decommission and full-disk wipe (NIST 800-88 purge) on hardware retirement.rm -rfalone leaves recoverable inodes for weeks.