Table of Contents
Home
Bash EscapeRoom Labs


Decryption Tip
If the content doesn’t appear after entering the password in the selected room, please refresh the page (F5 or Ctrl+R).
Usage
- There are several ways to run the Bash-EscapeRoom Labs.
- Choose the method that works best for you.
Using Docker is the easiest way to get started locally with the labs:
# GitHub Container Registry
docker run -it --pull=always -p 3000:3000 ghcr.io/nirgeier/bash-escaperoom
Prerequisites:
- Docker and Docker Compose installed on your system
- No additional setup required
- The easiest way to get started with the labs
- Learn in your browser without any local installation
๐ Launch on Killercoda
Benefits:
- No installation required
- Pre-configured environment
- Works on any device with a web browser
- All tools pre-installed
For those who prefer to run it directly on their machine:
# Clone the repository
git clone https://github.com/nirgeier/Bash-EscapeRoom
# Change to the Labs directory
cd Bash-EscapeRoom/Labs
# Start with the setup lab
cd assets
# Follow the instructions in the README of each lab
cat README.md
Prerequisites:
- A Unix-like operating system (Linux, macOS, or Windows with WSL)
- Basic command-line tools
- Google Cloud Shell provides a free, browser-based environment with all necessary tools pre-installed.
- Click on the
Open in Google Cloud Shellbutton below:
- The repository will automatically be cloned into a free Cloud instance.
- Use CTRL + click to open it in a new window.
- Follow the instructions in the README of each lab.
Benefits:
- No local installation required
- Pre-configured environment
- Works on any device with a web browser
- All tools pre-installed
- Free tier available
Intro
- This tutorial is for teaching bash scripting through puzzles designed as escape rooms.
- Each room is packaged in its own folder and includes the files, scripts, and assets required to play.
- Every room folder includes a
READMEthat describes the room’s objectives, hints, rules, and how to verify the solution. - The Bash Escape Room is a series of bash puzzles (Escape-rooms, rooms similar to the real-life attractions of Escape rooms).
- The Bash Escape Room puzzles are designed to teach the players Bash and Linux skills & features.
- The inspiration for this project is the real-life Escape rooms.
Pre-Requirements
- This tutorial will test your
LinuxandBashskills. - You should be familiar with the following topics:
- Basic Linux commands
- Linux File system navigation
- Linux Text processing tools (like
grep,sed,awk) - Linux Shell scripting basics
- Linux Understanding of environment variables
- Basic knowledge of
Docker(if you choose to run it with Docker) - Basic knowledge of
Vim
Room Links
| Room | Title | Key Commands | Status |
|---|---|---|---|
| Room-01 | The Lost Expedition | find, cat, sort, xargs |
|
| Room-02 | The Broken Radio | grep, grep -c, wc -l |
|
| Room-03 | The Time Capsule | tac, rev, head, tail, wc |
|
| Room-04 | The Spy Cipher | sed, sed 's/old/new/g' |
|
| Room-05 | The Decoder Ring | base64, tr (ROT13), rev |
|
| Room-06 | The Duplicate Detective | sort, uniq, comm, diff |
|
| Room-07 | The Permission Maze | chmod, stat, ls -l |
|
| Room-08 | The Environment Lab | export, env, source, alias |
|
| Room-09 | The Ghost Process | ps, kill, jobs, bg, fg |
|
| Room-10 | The Data Mine | awk, awk -F, NR, NF |
|
| Room-11 | The Nested Archive | base64 -d, gzip, tar, file |
|
| Room-12 | The Grand Pipeline | cut, tr, pipes |
|
| Room-13 | The Mirror Maze | ln -s, readlink, readlink -f |
|
| Room-14 | The Web Crawler | curl, curl -H, curl -s |
|
| Room-15 | The JSON Vault | jq, jq -r, select() |
|
| Room-16 | The Space Station | df -h, du -sh, sort -rh |
|
| Room-17 | The Clockwork Fortress | crontab -l, cron expressions |
|
| Room-18 | The Twin Blueprints | diff, diff -u, patch |
|
| Room-19 | The Integrity Check | md5sum, sha256sum, sha256sum -c |
|
| Room-20 | The Hex Dungeon | xxd, xxd -r, od, hexdump |
|
| Room-21 | The Binary Library | strings, strings -n |
|
| Room-22 | The Calculator Cave | bc, expr, $(( )) |
|
| Room-23 | The Time Machine | date, date -d @TIMESTAMP |
|
| Room-24 | The Formatter’s Workshop | printf, echo -e |
|
| Room-25 | The Signal Crossroads | tee, tee -a, paste |
|
| Room-26 | The Variable Vault | ${var##*/}, ${var%/*}, parameter expansion |
|
| Room-27 | The Array Arsenal | arr=(), ${arr[@]}, mapfile |
|
| Room-28 | The Loop Labyrinth | for loops, $(( )) |
|
| Room-29 | The Endless Corridor | while read, while IFS= read -r |
|
| Room-30 | The Fork in the Road | if/elif/else, [ ], [[ ]] |
|
| Room-31 | The Decision Chamber | case ... in, pattern matching |
|
| Room-32 | The Function Factory | func(){}, local, return |
|
| Room-33 | The Argument Decoder | getopts, $OPTARG |
|
| Room-34 | The Ancient Scroll | << 'EOF', here-documents |
|
| Room-35 | The Nested Worlds | $(), <(cmd), process substitution |
|
| Room-36 | The Signal Tower | trap, kill -SIGUSR1 |
|
| Room-37 | The Interactive Gateway | read, read -p, read -s |
|
| Room-38 | The Time Bomb | timeout, watch, sleep |
|
| Room-39 | The Network Hub | ss -tlnp, netstat -tlnp |
|
| Room-40 | The DNS Oracle | dig, host, nslookup |
|
| Room-41 | The Netcat Tunnel | nc, nc -l |
|
| Room-42 | The Open Files Archive | lsof, lsof -p, lsof -i |
|
| Room-43 | The System Call Observatory | strace, ltrace |
|
| Room-44 | The Mirror Sync | rsync -av, rsync --delete |
|
| Room-45 | The Cryptographer’s Den | openssl enc -d, openssl dgst |
|
| Room-46 | The Vi Vortex | vim, NG, /pattern |
|
| Room-47 | The Remote Gateway | ssh-keygen, ssh -i, scp |
|
| Room-48 | The Version Vault | git log, git show HASH:file |
|
| Room-49 | The Grand Pipeline II | awk, sort, uniq -c, sort -rn |
|
| Room-50 | The Master Terminal | find, base64, grep, bc, sha256sum |
|
| Room-51 | The Command Assembler | xargs, xargs -I{}, xargs -P, xargs -0, find \| xargs |
|
| Room-52 | The Ownership Vault | chown, chgrp, umask, id, groups, stat |
|
| Room-53 | The Network Probe | ping, traceroute, tracepath, wget, wget -O - |
|
| Room-54 | The System Monitor | top, free, uptime, vmstat, /proc/meminfo, nproc |
|
| Room-55 | The System Inspector | uname, hostname, whoami, id, who, w, lscpu |
|
| Room-56 | The Process Controller | pgrep, pkill, nohup, nice, renice, killall |
|
| Room-99 | Final Exam | All commands |
Good Luck!
๐ Part 1 - File & Text Basics
(01) ๐งญ The Lost Expedition
โ ๏ธ sudo Access
- You might need sudo access for some commands.
- The sudo password is required for elevated privileges.
- The sudo password is:
escape. - Write it down, you will need it later.
A famous explorer vanished deep in the jungle, leaving 9 map fragments scattered across a maze of directories, mixed in with thousands of noise files, only the .map files contain the clues.
FIND THE MAP FRAGMENTS!
- Navigate into the
expedition/directory. - The
expedition/camp is littered with noise files (.rock,.leaf,.twig) everywhere. Ignore them all. - The explorer left exactly
9 map fragments(files ending in.map) hidden across the sub-directories. Find them all. - Once you locate all
.mapfiles, sort their full paths alphabetically and read (cat) their contents in that order. - Concatenate the letters - they spell the password for the next room.
Key Commands
| Command | Purpose |
|---|---|
find . -name "*.txt" |
Find files by name pattern |
find . -type f |
Find only files (not dirs) |
find . -type d |
Find only directories |
find . -mtime -7 |
Files modified in last 7 days |
find . -size +1M |
Files larger than 1 MB |
find . -perm 644 |
Files with exact permissions |
find . -name "*.log" -delete |
Find and delete matching files |
find . -maxdepth 2 |
Limit search depth |
find . -not -name "*.txt" |
Exclude pattern |
cat file |
Print file to stdout |
cat -n file |
Show with line numbers |
cat -A file |
Show non-printing chars (tabs, EOL) |
cat file1 file2 |
Concatenate files |
sort file |
Sort lines alphabetically |
sort -n file |
Numeric sort |
sort -r file |
Reverse sort |
sort -k2 file |
Sort by 2nd field |
sort -u file |
Sort and deduplicate |
xargs cmd |
Build cmd args from stdin |
xargs -I{} cmd {} |
Replace {} with each item |
How find Works
# Find by name pattern
find . -name "*.txt" # files ending in .txt
find . -name "report.txt" # exact name match
find . -iname "*.TXT" # case-insensitive match
# Find by type
find . -type f # regular files only
find . -type d # directories only
find . -type l # symbolic links only
# Find by depth
find . -maxdepth 2 -name "*.txt" # search at most 2 levels deep
find . -mindepth 2 -name "*.txt" # skip the top level
# Find by size
find . -size +1k # larger than 1KB
find . -size -100c # smaller than 100 bytes
find . -size +1M -size -10M # between 1MB and 10MB
# Find by modification time
find . -mtime -1 # modified in the last 24 hours
find . -mtime +7 # modified more than 7 days ago
find . -newer reference.txt # newer than a reference file
# Find by permissions
find . -perm 755 # exact permission match
find . -perm /u+x # executable by owner
# Execute a command on each result
find . -name "*.log" -exec rm {} \; # delete each found file
find . -name "*.txt" -exec cat {} + # cat all found files at once
Piping Commands Together
# Pipe basics: stdout of cmd1 becomes stdin of cmd2
cmd1 | cmd2 | cmd3
# xargs: build and run commands from stdin input
echo "file.txt" | xargs cat # cat file.txt
find . -name "*.txt" | xargs wc -l # count lines in all .txt files
find . -name "*.map" | sort | xargs cat # sort paths then read all in order
# xargs options
find . -name "*.txt" | xargs -I{} cp {} /backup/ # use {} as placeholder
find . -name "*.txt" | xargs -n1 cat # process one file at a time
find . -print0 | xargs -0 ls -l # handle filenames with spaces
The
findcommand searches through ALL subdirectories by default.
sortwithout flags sorts alphabetically -perfect for getting the right order.
xargs catreads each file found byfindand displays its content.
[Protected] (02) ๐ป The Broken Radio
- You intercepted 200 enemy radio transmissions.
- Each message has a status tag -
OK,SOS,LOST, orNOISE. - Count the
SOSsignals to crack the code.
COUNT THE DISTRESS SIGNALS!
- Count the number of lines containing
"SOS"inradio_intercepts.txt.grep -ccounts matching lines - The password is the word
signalfollowed by that count *(no space)*. Example: if there are 23 SOS lines โ password issignal23
Key Commands
| Command | Purpose |
|---|---|
grep pattern file |
Search for pattern in file |
grep -i pattern file |
Case-insensitive search |
grep -v pattern file |
Invert match (lines NOT matching) |
grep -c pattern file |
Count matching lines |
grep -n pattern file |
Show line numbers |
grep -l pattern dir/ |
List files containing pattern |
grep -r pattern dir/ |
Recursive search |
grep -w word file |
Match whole word only |
grep -E "pat1\|pat2" file |
Extended regex (OR) |
grep -o pattern file |
Print only matched part |
grep -A 2 pattern file |
Show 2 lines after match |
grep -B 2 pattern file |
Show 2 lines before match |
grep -C 2 pattern file |
Show 2 lines around match |
grep --include="*.sh" -r pattern . |
Grep only .sh files |
wc file |
Count lines, words, bytes |
wc -l file |
Count lines only |
wc -w file |
Count words only |
wc -c file |
Count bytes |
wc -m file |
Count characters |
How grep Works
# Basic usage
grep "pattern" file.txt # search for pattern in a file
grep "pattern" *.txt # search across multiple files
grep "pattern" file1.txt file2.txt # search in listed files
# Common flags
grep -c "pattern" file.txt # count matching lines (not the lines themselves)
grep -i "pattern" file.txt # case-insensitive search
grep -v "pattern" file.txt # invert: show lines that do NOT match
grep -w "word" file.txt # whole-word match only
grep -n "pattern" file.txt # show line numbers alongside matches
grep -l "pattern" *.txt # list filenames that match (not the lines)
grep -r "pattern" dir/ # recursive search through a directory
# Context around matches
grep -A 2 "pattern" file.txt # 2 lines after each match
grep -B 2 "pattern" file.txt # 2 lines before each match
grep -C 2 "pattern" file.txt # 2 lines before AND after
# Regex patterns
grep "^start" file.txt # lines starting with "start"
grep "end$" file.txt # lines ending with "end"
grep -E "cat|dog" file.txt # extended regex: match either word
grep -E "^[0-9]+" file.txt # lines starting with digits
# Combine with other commands
grep "ERROR" app.log | wc -l # count error lines
grep -v "^#" config.txt # skip comment lines
grep "pattern" file.txt | sort | uniq # unique matching lines
* This will return a single number - the count of lines that contain “STo count lines containing “SOS”, use:
grep -c gives you a single number -the count of matching lines.
[Protected] (03) โณ The Time Capsule
Use tac, rev, head, tail, and wc to decode a time capsule journal.
- An ancient journal was found buried underground.
- Its last entry holds a secret - but the characters are written backwards.
- Use line and character reversal tools to decode it.
DECODE THE TIME CAPSULE!
- Count how many lines the file has.
wc -lcounts lines - Reverse the line order so the last line becomes the first.
tacreverses line order (it iscatspelled backwards!) - Take the first line of the reversed output. pipe
tacoutput tohead -1 - That line has its characters reversed -unreverse it!
revreverses character order within each line - The decoded line reveals a secret word.
- The password is that word followed by the total line count *(no space)*. Example: if the word is "hello" and there are 50 lines โ
hello50
Key Commands
| Command | Purpose |
|---|---|
head file |
Show first 10 lines |
head -n 20 file |
Show first 20 lines |
head -c 100 file |
Show first 100 bytes |
tail file |
Show last 10 lines |
tail -n 20 file |
Show last 20 lines |
tail -f file |
Follow file as it grows (live log) |
tail -F file |
Follow even if file is rotated |
tail -c 100 file |
Show last 100 bytes |
tail -n +5 file |
Show from line 5 to end |
tac file |
Print lines in reverse order |
rev file |
Reverse each line character by character |
wc -l file |
Count lines |
wc -w file |
Count words |
wc -c file |
Count bytes |
nl file |
Number lines (like cat -n) |
head -1 file \| wc -c |
Count chars in first line |
How Text Processing Tools Work
# tac - reverse line order (cat spelled backwards)
tac file.txt # print lines in reverse order
tac file1.txt file2.txt # reverse and concatenate files
tac file.txt | head -1 # get the very last line of a file
# rev - reverse characters on each line
rev file.txt # reverse chars on every line
echo "hello" | rev # outputs: olleh
rev file.txt | tac # reverse chars AND line order
# head - show the beginning of a file
head file.txt # first 10 lines (default)
head -n 5 file.txt # first N lines
head -n -5 file.txt # all lines EXCEPT the last 5
head -c 100 file.txt # first 100 bytes
# tail - show the end of a file
tail file.txt # last 10 lines (default)
tail -n 5 file.txt # last N lines
tail -n +2 file.txt # skip first line, print the rest
tail -f file.txt # follow: live-stream new appended lines
# wc - count lines, words, bytes
wc file.txt # lines, words, bytes (all three)
wc -l file.txt # count lines only
wc -w file.txt # count words only
wc -c file.txt # count bytes
wc -m file.txt # count characters
wc -l *.txt # count lines across multiple files
taciscatspelled backwards -and it reverses line order.
revreverses the characters within each line.The password format is: the secret word + the line count (e.g.,
word50).
[Protected] (04) ๐ต๏ธ The Spy Cipher
Use sed substitutions to decode an encrypted spy message.
- A spy left an encoded message.
- Certain letters were replaced with codes.
- Use
sedto reverse the substitutions and reveal the hidden password.
CRACK THE SPY CODE!
- Use
sedto reverse all substitutions oncipher.txt.sed 's/ENCODED/ORIGINAL/g; s/ENCODED2/ORIGINAL2/g' cipher.txt - Read the decoded output -the password is in the decoded text.
Key Commands
| Command | Purpose |
|---|---|
sed 's/old/new/' file |
Replace first occurrence per line |
sed 's/old/new/g' file |
Replace all occurrences (global) |
sed 's/old/new/I' file |
Case-insensitive replace |
sed -n '5p' file |
Print only line 5 |
sed -n '5,10p' file |
Print lines 5-10 |
sed '5d' file |
Delete line 5 |
sed '/pattern/d' file |
Delete lines matching pattern |
sed 's/^/ /' file |
Add indent to every line |
sed 's/ *$//' file |
Strip trailing whitespace |
sed -i 's/old/new/g' file |
Edit file in-place |
sed -i.bak 's/old/new/g' file |
In-place edit with backup |
sed 'y/abc/ABC/' file |
Transliterate characters |
sed -n '/start/,/end/p' file |
Print between two patterns |
sed '1i\header' file |
Insert line before line 1 |
sed '$a\footer' file |
Append line after last line |
sed 's/\t/ /g' file |
Replace tabs with spaces |
How sed Works
# Basic substitution: replace first match per line
sed 's/old/new/' file.txt
# Replace ALL matches per line (global flag)
sed 's/old/new/g' file.txt
# Multiple substitutions chained in one command
sed 's/foo/bar/g; s/baz/qux/g' file.txt
# Case-insensitive replacement
sed 's/hello/world/gi' file.txt
# Delete lines matching a pattern
sed '/pattern/d' file.txt
# Print only specific lines (use -n to suppress default output)
sed -n '5p' file.txt # line 5 only
sed -n '1,10p' file.txt # lines 1 through 10
sed -n '/pattern/p' file.txt # lines matching pattern
# Use alternate delimiters (useful when value contains /)
sed 's|/usr/local|/opt|g' file.txt
# Edit file in-place
sed -i 's/old/new/g' file.txt
sed -i.bak 's/old/new/g' file.txt # keep .bak backup
The Substitution Table
| Original Letter | Was Replaced With |
|---|---|
| s | Z7 |
| e | Q3 |
| d | X9 |
| m | K1 |
| a | J2 |
| t | W8 |
| r | P6 |
The decoded text contains the password in its final line.
[Protected] (05) ๐ The Decoder Ring
Chain base64, tr (ROT13), and rev to decode a triple-encoded message.
- A message was encoded with 3 layers of transformation.
- Peel them off in reverse order using pipes.
DECODE THE TRIPLE-ENCODED MESSAGE!
- Characters were reversed (
rev) - ROT13 was applied (each letter shifted 13 positions)
- Result was Base64-encoded To decode, undo the layers in reverse order:
- Decode Base64 โ
base64 -d - Undo ROT13 โ
tr 'a-zA-Z' 'n-za-mN-ZA-M' - Reverse characters โ
rev - Chain all three with pipes in one command.
- The decoded output is the password.
The 3 Encoding Layers (applied in order)
- Reversed characters (
rev) - ROT13 applied (
trcharacter shift) - Base64 encoded
Key Commands
| Command | Purpose |
|---|---|
base64 -d |
Decode base64 encoding |
tr 'a-zA-Z' 'n-za-mN-ZA-M' |
Apply/reverse ROT13 cipher |
rev |
Reverse characters |
\| (pipe) |
Send output of one command to the next |
How Encoding Tools Work
# base64 - encode/decode binary-safe text
base64 file.txt # encode a file to stdout
base64 -d file.b64 # decode base64 back to original
echo "hello" | base64 # encode a string
echo "aGVsbG8=" | base64 -d # decode a string
base64 -d file.b64 > output.bin # decode and save to file
base64 -w 0 file.txt # encode with no line wrapping
# tr - translate or delete characters
tr 'a-z' 'A-Z' < file.txt # lowercase to uppercase
tr 'A-Z' 'a-z' < file.txt # uppercase to lowercase
tr -d '\n' < file.txt # delete all newlines
tr -d '[:space:]' < file.txt # delete all whitespace
tr -s ' ' < file.txt # squeeze repeated spaces to one
tr '[:upper:]' '[:lower:]' # use character class names
tr 'a-mn-z' 'n-za-m' # ROT13 (lowercase only)
tr 'a-zA-Z' 'n-za-mN-ZA-M' # ROT13 (full alphabet)
# rev - reverse characters on each line
echo "hello" | rev # outputs: olleh
rev file.txt # reverse each line in a file
echo "racecar" | rev # outputs: racecar (palindrome)
ROT13 is its own inverse -applying it twice gets you back to the original.
The decoded output IS the password -one single word.
[Protected] (06) ๐ The Duplicate Detective
Use sort, uniq, comm, and wc to find unique gems between two vaults.
Two treasure vaults contain gem inventories. Some gems appear in both vaults, some are unique to one. Find the gems that exist ONLY in vault A.
FIND THE UNIQUE GEMS!
- Remove duplicate entries within each file.
sort+uniqremoves duplicates from sorted input - Find gems that appear only in
vault_a.txt(not invault_b.txt).comm -23 file1 file2shows lines only in file1 (both must be sorted) - Count those unique-to-vault-a gems. pipe to
wc -l - The password is the word
uniquefollowed by that count *(no space)*. Example: if there are 15 unique gems โunique15
Key Commands
| Command | Purpose |
|---|---|
sort |
Sort lines alphabetically |
uniq |
Remove adjacent duplicate lines (input must be sorted) |
comm |
Compare two sorted files line by line |
comm -23 |
Show lines only in file 1 (not in file 2) |
diff |
Show differences between two files |
wc -l |
Count lines |
How These Commands Work
# sort - order lines
sort file.txt # alphabetical (default)
sort -r file.txt # reverse order
sort -n file.txt # numeric sort
sort -k2 file.txt # sort by 2nd field
sort -t',' -k2 -n file.txt # CSV, numeric sort on field 2
sort -u file.txt # sort and remove duplicates
# uniq - filter duplicate adjacent lines (input must be sorted!)
uniq file.txt # remove consecutive duplicates
uniq -c file.txt # prefix count of occurrences
uniq -d file.txt # show only duplicate lines
uniq -u file.txt # show only unique lines
uniq -i file.txt # case-insensitive comparison
# comm - compare two sorted files line by line
comm file1.txt file2.txt # 3 columns: only-1 | only-2 | both
comm -12 file1.txt file2.txt # lines in BOTH files only
comm -23 file1.txt file2.txt # lines only in file1
comm -13 file1.txt file2.txt # lines only in file2
comm -3 file1.txt file2.txt # suppress lines in both
# Process substitution (skip temp files)
comm -23 <(sort fileA.txt | uniq) <(sort fileB.txt | uniq)
The password format is:
unique+ the count (e.g.,unique15).
๐ Part 2 - System & Permissions
[Protected] (07) ๐ The Permission Maze
Fix file permissions using chmod to unlock the gates.
- Seven gates block your path, each with wrong permissions.
- Set them correctly to proceed.
FIX THE PERMISSIONS!
- Check current permissions:
ls -l gate_* - Fix each gate using
chmod. - Once all gates are correct, run
./getKey.shto get the password.
Key Commands
| Command | Purpose |
|---|---|
chmod 755 file |
Set rwxr-xr-x (octal) |
chmod 644 file |
Set rw-r–r– (octal) |
chmod u+x file |
Add execute for owner |
chmod go-w file |
Remove write for group and others |
chmod a+r file |
Add read for all |
chmod -R 755 dir/ |
Recursive chmod |
chmod u=rwx,go=rx file |
Explicit symbolic notation |
stat file |
Detailed file metadata |
stat -c "%a" file |
Show octal permissions only |
stat -c "%U %G" file |
Show owner and group |
stat -c "%s" file |
Show file size in bytes |
ls -l file |
Long listing with permissions |
ls -la |
Include hidden files |
ls -lh |
Human-readable sizes |
ls -lt |
Sort by modification time |
ls -lS |
Sort by file size |
ls -ld dir/ |
Show dir itself, not contents |
umask |
Show default permission mask |
How chmod Works
# Numeric (octal) mode
chmod 755 file # rwxr-xr-x (owner: all, group+others: read+execute)
chmod 644 file # rw-r--r-- (owner: read+write, others: read)
chmod 700 file # rwx------ (owner only, no access for others)
chmod 777 file # rwxrwxrwx (full access for everyone)
chmod 000 file # --------- (no permissions for anyone)
chmod 600 file # rw------- (owner read+write only)
chmod 444 file # r--r--r-- (read-only for everyone)
# Symbolic mode - add, remove, or set permissions
chmod u+x file # add execute for user (owner)
chmod g-w file # remove write from group
chmod o=r file # set others to read only (exact)
chmod a+r file # add read for all (user, group, others)
chmod u=rwx,g=rx,o=r file # set all three at once
# Apply recursively to a directory
chmod -R 755 mydir/
# Check current permissions
ls -l file # shows symbolic: -rwxr-xr-x
stat -c "%a %n" file # shows numeric: 755 file
stat -c "%A %n" file # shows symbolic: -rwxr-xr-x file
Linux Permission System
rwxrwxrwx = User, Group, Others
r = read (4), w = write (2), x = execute (1)
Examples:
755 = rwxr-xr-x (owner: full, others: read+execute)
644 = rw-r--r-- (owner: read+write, others: read only)
700 = rwx------ (owner only)
Required Permissions
| File | Permission | Numeric |
|---|---|---|
| gate_1 | rwxr-xr-x | 755 |
| gate_2 | rw-r–r– | 644 |
| gate_3 | rwx------ | 700 |
| gate_4 | r–r–r– | 444 |
| gate_5 | rwxrwxr-x | 775 |
| gate_6 | rw-rw---- | 660 |
| gate_7 | r-x–x–x | 511 |
[Protected] (08) ๐งช The Environment Lab
Configure environment variables, aliases, and source config files.
Your shell environment must be configured precisely to unlock the lab door.
CONFIGURE THE ENVIRONMENT!
- Find the hidden configuration file in this room. hidden files start with
.-usels -a - Source the hidden config file to load its settings.
source .filenameor. .filename - The config tells you what environment variables to set. Set them with
export. - Create an alias named
labstatusthat printsready.alias labstatus='echo ready' - Once everything is set, run
./getKey.shto verify and get the password.
Key Commands
| Command | Purpose |
|---|---|
export |
Set an environment variable |
env |
Display all environment variables |
source / . |
Execute commands from a file in the current shell |
alias |
Create a command shortcut |
ls -a |
List all files including hidden ones (starting with .) |
How Environment Variables Work
# Set and export a variable (available to child processes)
export MY_VAR=hello
export PATH="$PATH:/new/dir" # append to existing variable
# Access and inspect variables
echo $MY_VAR # print one variable
echo ${MY_VAR:-default} # print value or default if unset
env # list all environment variables
env | grep MY_VAR # search for a specific one
printenv MY_VAR # print a single variable
# Unset a variable
unset MY_VAR
# Source a file (run it in the current shell, not a subshell)
source config.sh # bash syntax
. config.sh # POSIX syntax (same effect)
# Aliases
alias ll='ls -la' # define a shortcut
alias grep='grep --color=auto' # alias with flags
alias # list all defined aliases
unalias ll # remove an alias
# Run a command with a temporary variable (doesn't persist)
MY_VAR=hello ./script.sh
Hidden files start with a dot (
.) -usels -ato find them.Source the hidden config file first -it tells you what to set.
All commands (
export,alias) must be run in the same shell session.
[Protected] (09) ๐ป The Ghost Process
Create users, manage background processes, and find PIDs.
- A ghost process haunts this room.
- Create it, send it to the background, find its PID, and capture it!
SUMMON AND CAPTURE THE GHOST!
- Create a new user named
ghost_user.sudo adduser -D ghost_user(Alpine) orsudo useradd ghost_user - Create a bash script
ghost_loop.shthat runs an infinite loop silently. Example:while true; do sleep 1; done - Run the script as
ghost_userin the background.sudo -u ghost_user bash ./ghost_loop.sh & - Find the process ID (PID) of the running ghost script.
ps aux | grep ghostorps -eo pid,user,comm | grep ghost - Pass the PID to
./getKey.shto capture the ghost.
Key Commands
| Command | Purpose |
|---|---|
ps |
Show current shell’s processes |
ps aux |
Show all processes (all users) |
ps -ef |
Full-format listing |
ps aux --sort=-%cpu |
Sort by CPU usage |
ps aux --sort=-%mem |
Sort by memory usage |
ps -u username |
Processes for a specific user |
ps -p PID |
Show specific PID info |
ps --forest |
Show process tree |
kill PID |
Send SIGTERM to process |
kill -9 PID |
Force kill (SIGKILL) |
kill -l |
List all signal names |
kill -SIGSTOP PID |
Pause a process |
kill -SIGCONT PID |
Resume a paused process |
jobs |
List background jobs |
jobs -l |
List with PIDs |
bg %1 |
Resume job 1 in background |
fg %1 |
Bring job 1 to foreground |
Ctrl+Z |
Suspend foreground process |
Ctrl+C |
Interrupt foreground process |
nohup cmd & |
Run immune to hangup |
How Background Processes Work
# Run a command in the background
./script.sh & # append & to any command
nohup ./script.sh & # keep running after logout
nohup ./script.sh > output.log 2>&1 & # redirect output + background
# Job control
jobs # list background jobs in current shell
jobs -l # include PIDs
fg %1 # bring job 1 to foreground
bg %1 # resume suspended job in background
Ctrl+Z # suspend the current foreground process
# View running processes
ps aux # all processes, full detail (BSD style)
ps -ef # all processes, full format (Unix style)
ps -eo pid,user,comm # custom columns: PID, user, command name
ps -eo pid,user,args --sort=user # sort output by user
ps -u username # processes owned by a specific user
# Find a process by name
pgrep nginx # print PIDs matching name
pgrep -u root nginx # match by name AND user
ps aux | grep processname # manual grep approach
ps aux | grep processname | grep -v grep # exclude the grep itself
# Signals and kill
kill PID # send SIGTERM (graceful stop)
kill -15 PID # same as above (explicit)
kill -9 PID # send SIGKILL (force stop, no cleanup)
kill -l # list all available signals
killall processname # kill all processes by name
pkill -u username # kill all processes by user
Create a simple infinite loop script:
while true; do sleep 1; done
grep -v grepfilters out the grep process itself frompsoutput.Pass the PID as argument to
getKey.sh:./getKey.sh 12345
โ๏ธ Part 3 - Data Processing
[Protected] (10) โ๏ธ The Data Mine
Use awk to filter CSV data and calculate sums based on conditions.
- A CSV file contains 100 mineral survey records.
- Use
awkto filter rows by depth and sum the weight values.
MINE THE DATA!
- Use
awkto processmine_data.csv: - Skip the header line (line 1) - Filter rows wheredepth(field 2) is greater than 50 - Sum theweightvalues (field 3) of those filtered rows - The password is
awkfollowed by the calculated sum *(no space)*. Example: if the sum is 1234 โawk1234`awk -F',' 'NR>1 && $2>50 {sum+=$3} ..'
Key Commands
| Command | Purpose |
|---|---|
awk '{print $1}' file |
Print first field |
awk '{print $NF}' file |
Print last field |
awk -F: '{print $1}' file |
Use : as delimiter |
awk 'NR==5' file |
Print only line 5 |
awk 'NR>=3 && NR<=7' file |
Print lines 3-7 |
awk '/pattern/' file |
Print lines matching pattern |
awk '!/pattern/' file |
Print non-matching lines |
awk '$3>100' file |
Print where field 3 > 100 |
awk '{sum+=$2} END{print sum}' file |
Sum field 2 |
awk 'BEGIN{FS=","} {print $2}' file |
CSV processing |
awk '{print NR": "$0}' file |
Number all lines |
awk 'END{print NR}' file |
Count total lines |
awk '{gsub(/old/,"new"); print}' file |
Global substitution |
awk '{print toupper($0)}' file |
Convert to uppercase |
awk -v OFS=, '{print $1,$3}' file |
Custom output separator |
awk 'length>80' file |
Lines longer than 80 chars |
How awk Works
# Basic syntax: awk 'pattern { action }' file
awk '{print}' file.txt # print all lines (like cat)
awk '{print $1}' file.txt # print first field
awk '{print $1, $3}' file.txt # print fields 1 and 3
awk '{print NR, $0}' file.txt # line number + full line
awk '{print NF}' file.txt # number of fields per line
# Field separators
awk -F',' '{print $2}' file.csv # comma-separated
awk -F':' '{print $1}' /etc/passwd # colon-separated
awk -F'\t' '{print $1}' file.tsv # tab-separated
# Filtering
awk '/pattern/ {print}' file.txt # lines matching a regex
awk '!/pattern/ {print}' file.txt # lines NOT matching
awk '$2 > 50 {print}' file.txt # numeric comparison
awk '$1 == "gold" {print}' file.txt # exact string match
awk 'NR > 1 {print}' file.txt # skip first line (header)
awk 'NR >= 5 && NR <= 10' file.txt # line range
# Calculations
awk '{sum += $1} END {print sum}' file.txt # sum a column
awk '{count++} END {print count}' file.txt # count lines
awk '{if ($1>max) max=$1} END {print max}' file.txt # find max value
awk '{total += $1} END {print total/NR}' file.txt # average
# BEGIN and END blocks
awk 'BEGIN {print "Start"} {print} END {print "Done"}' file.txt
The CSV Format
NR>1skips the header line.
$2>50filters for depth greater than 50.The password format is:
awk+ the calculated sum (e.g.,awk1234).
[Protected] (11) ๐ช The Nested Archive
Unwrap a multi-layered archive using base64, gzip, and tar.
A mysterious artifact has been encoded in 3 layers -like a Russian nesting doll. Peel each layer to reveal the secret.
UNWRAP THE MATRYOSHKA!
- Decode Base64 โ
base64 -d artifact.b64 > artifact.tar.gz - Decompress gzip โ
gunzip artifact.tar.gz - Extract tar โ
tar xf artifact.tar - Read the extracted file -it contains the password!
The 3 Layers (outermost to innermost)
- Base64 encoding
- Gzip compression
- Tar archive
Key Commands
| Command | Purpose |
|---|---|
base64 -d |
Decode base64 to binary |
gunzip / gzip -d |
Decompress gzip files |
tar xf |
Extract tar archives |
file |
Identify file type |
How Archive Tools Work
# base64 - encode/decode binary data as text
base64 file.bin > file.b64 # encode binary to base64 text
base64 -d file.b64 > file.bin # decode base64 back to binary
base64 -d file.b64 | file - # decode and identify type on the fly
base64 -w 0 file.bin > file.b64 # encode with no line wrapping
# gzip - compress / decompress files
gzip file.txt # compress (creates file.txt.gz, removes original)
gzip -d file.txt.gz # decompress
gunzip file.txt.gz # same as gzip -d
gzip -k file.txt # compress and keep original
gzip -l file.gz # list compression ratio and sizes
gzip -t file.gz # test archive integrity
# tar - archive (bundle) files
tar cf archive.tar dir/ # create archive from directory
tar czf archive.tar.gz dir/ # create and gzip-compress in one step
tar xf archive.tar # extract archive
tar xzf archive.tar.gz # extract gzip-compressed archive
tar tf archive.tar # list contents without extracting
tar xf archive.tar -C /dest/ # extract to a specific directory
# file - identify an unknown file type
file mystery # prints: "gzip compressed data", "POSIX tar archive", etc.
file * # identify all files in current directory
Pro Tip: Use file to Identify Unknown Types
file artifact.b64 # "ASCII text"
file artifact.tar.gz # "gzip compressed data"
file artifact.tar # "POSIX tar archive"
After each step, use
lsto see what appeared andfileto identify it.
[Protected] (12) ๐ผ The Grand Pipeline
Build a multi-stage pipeline using cut, tr, and pipes.
Master the art of chaining commands with pipes. Extract data from a structured file, transform it step by step, and reveal the password.
CONDUCT THE PIPELINE!
- Extract city names (first field,
|delimiter).cut -d'|' -f1 stations.txt - Take the first letter of each city name.
cut -c1 - Combine all letters into one line (remove newlines).
tr -d ' ' - Convert to lowercase.
tr 'A-Z' 'a-z' - The result is the password!
Key Commands
| Command | Purpose |
|---|---|
cut -d'\|' -f1 |
Extract field 1 using \| delimiter |
cut -c1 |
Extract the first character |
tr -d '\n' |
Remove newline characters |
tr 'A-Z' 'a-z' |
Convert to lowercase |
\| (pipe) |
Send output of one command to the next |
The Data Format
How Pipeline Tools Work
# cut - extract specific fields or characters
cut -d',' -f1 file.csv # field 1 from comma-delimited file
cut -d':' -f1,3 /etc/passwd # fields 1 and 3, colon-delimited
cut -d'|' -f2- file.txt # from field 2 to end of line
cut -c1 file.txt # first character of each line
cut -c1-5 file.txt # characters 1 through 5
cut -c1,5,10 file.txt # characters 1, 5, and 10
# tr - translate or delete characters
tr 'a-z' 'A-Z' < file.txt # lowercase to uppercase
tr 'A-Z' 'a-z' < file.txt # uppercase to lowercase
tr -d '\n' < file.txt # remove all newlines (join lines)
tr -d ' ' < file.txt # remove all spaces
tr -s ' ' < file.txt # squeeze repeated spaces into one
tr -d '[:punct:]' < file.txt # remove all punctuation
tr '[:upper:]' '[:lower:]' < file.txt # using character class names
# Pipes - chain commands together
cmd1 | cmd2 | cmd3 # output of each feeds the next
cmd1 | tee file.txt | cmd2 # save mid-pipeline to file AND continue
cmd1 | head -5 # take only first 5 lines
cmd1 | tail -5 # take only last 5 lines
cmd1 | wc -l # count lines of output
cmd1 | sort | uniq -c | sort -rn # frequency count pattern
The first letters of the city names spell the password!
[Protected] (13) ๐ช The Mirror Maze
Follow a chain of symbolic links to find the hidden treasure.
- Someone has created a labyrinth of symbolic links, each mirror pointing to the next.
- Follow the chain from start to finish to reach the treasure.
FOLLOW THE MIRRORS!
- List all files in
mirrors/including symlinks.ls -la mirrors/ - Start from
start.linkand follow the chain - each symlink points to the next.readlink filenameshows what a symlink points to - Reach the final file and read its contents to get the password.
catfollows symlinks automatically
Key Commands
| Command | Purpose |
|---|---|
ln -s target link |
Create a symbolic link |
ln target link |
Create a hard link |
readlink file |
Show what a symlink points to |
readlink -f file |
Resolve the full chain to the final target |
ls -la |
List files showing symlink arrows |
How ln Works
# Create a symbolic (soft) link
ln -s /path/to/target linkname # symlink - points to a path
ln -s ../other/file shortcut.txt # relative symlink
ln -s /etc/hosts myhosts # symlink to a system file
# Create a hard link (same inode, same filesystem only)
ln original.txt hardlink.txt
# Inspect links
ls -la # shows: linkname -> target
readlink linkname # print the symlink target
readlink -f linkname # resolve all symlinks, print absolute path
file linkname # shows "symbolic link to ..."
# Difference between hard and soft links
# Hard link: same data, same inode, deleting original keeps data
# Soft link: pointer to a path; broken if original is deleted
# Remove a link (does NOT delete the target)
unlink linkname
rm linkname
readlink -f start.linkwill resolve the entire chain at once.
catautomatically follows symlink chains - trycat start.link.
๐ Part 4 - Web & Storage
[Protected] (14) ๐ The Web Crawler
Use curl to fetch and process data from a local web server.
- A local web server is hiding the password behind several endpoints.
- Use
curlto fetch the right URL and extract the clue.
SURF THE WEB!
- Fetch the index page to see what endpoints are available.
curl http://localhost:3000/ - The server has a
/secretendpoint - but it requires a custom headerX-Access-Key: escape.curl -H "X-Access-Key: escape" http://localhost:3000/secret - The response contains a JSON body - extract the
passwordfield. pipe togreporjq - The value of
passwordin the response is the password.
Key Commands
| Command | Purpose |
|---|---|
curl URL |
Fetch URL to stdout |
curl -o file URL |
Save output to file |
curl -O URL |
Save with remote filename |
curl -s URL |
Silent (no progress bar) |
curl -I URL |
Show response headers only |
curl -i URL |
Show headers + body |
curl -L URL |
Follow redirects |
curl -X POST URL |
HTTP POST request |
curl -d "key=val" URL |
POST with form data |
curl -H "Header: Val" URL |
Add custom header |
curl -u user:pass URL |
Basic authentication |
curl -k URL |
Skip SSL certificate verification |
curl --max-time 5 URL |
Timeout after 5 seconds |
curl -w "%{http_code}" URL |
Print HTTP status code |
curl -x proxy:port URL |
Use a proxy |
wget -q -O - URL |
wget alternative, output to stdout |
wget -O file URL |
wget: save to file |
wget --spider URL |
Check URL without downloading |
How curl Works
# Basic fetch
curl https://example.com # fetch and print to stdout
curl -s https://example.com # silent (no progress meter)
curl -o output.html https://example.com # save to file
curl -O https://example.com/file.tar.gz # save with remote filename
# HTTP methods
curl -X GET https://api.example.com/data # explicit GET (default)
curl -X POST https://api.example.com/data # POST request
curl -X DELETE https://api.example.com/item/1 # DELETE request
# Headers and authentication
curl -H "Authorization: Bearer TOKEN" URL # auth header
curl -H "Content-Type: application/json" URL # content type header
curl -u username:password URL # basic auth
curl -b "session=abc123" URL # send cookie
# POST with data
curl -d "field1=value1&field2=value2" -X POST URL # form data
curl -d '{"key":"value"}' -H "Content-Type: application/json" -X POST URL # JSON body
# Follow redirects and inspect response
curl -L URL # follow redirects
curl -I URL # headers only (HEAD request)
curl -v URL # verbose: show headers + body
# Download with progress
curl --progress-bar -o file.zip URL # show progress bar
Use
curl -sto suppress the progress meter and get clean output.Custom headers use the
-Hflag:curl -H "Header-Name: value" URL.
[Protected] (15) ๐ฆ The JSON Vault
Parse a JSON data file using jq to extract the hidden password.
- The database has been exported as a JSON file.
- The password is buried inside nested fields - only
jqcan crack it open.
CRACK THE JSON VAULT!
- First, explore the top-level keys.
jq 'keys' database.json - The password is stored at
.agents[] | select(.status == "active") | .code- find all active agents' codes.jq '.agents[] | select(.status == "active") | .code' database.json - Sort the codes alphabetically and concatenate them (no separator). pipe to
sortandtr -d ' ' - Remove the surrounding quotes from the
jqoutput. usejq -rfor raw output (no quotes) - The concatenated result is the password.
Key Commands
| Command | Purpose |
|---|---|
jq '.' |
Pretty-print JSON |
jq '.key' |
Access a field |
jq '.arr[]' |
Iterate over an array |
jq 'select(.field == val)' |
Filter elements |
jq -r |
Raw output (no quotes) |
How jq Works
# Basic access
jq '.' file.json # pretty-print the whole file
jq '.name' file.json # access top-level field
jq '.user.email' file.json # nested field access
jq '.items[0]' file.json # first element of array
jq '.items[-1]' file.json # last element of array
# Array operations
jq '.items[]' file.json # iterate: print each element
jq '.items | length' file.json # count array elements
jq '[.items[] | .name]' file.json # extract field from each element
# Filtering with select
jq '.users[] | select(.active == true)' file.json # filter by boolean
jq '.users[] | select(.age > 30)' file.json # filter by number
jq '.users[] | select(.role == "admin")' file.json # filter by string
# Transforming output
jq -r '.name' file.json # raw output (no quotes around strings)
jq '.items[] | "\(.name): \(.value)"' file.json # string interpolation
jq '{name: .user.name, id: .user.id}' file.json # create new object
# Combining fields
jq '.items[] | .name' file.json | sort | tr -d '\n' # extract, sort, join
jq -r '[.items[] | .name] | sort | join("")' file.json # same using jq built-ins
Use
jq -r(raw output) to get strings without surrounding quotes.
jq '.agents[] | select(.status == "active") | .code' database.jsonis the key filter.Sort the extracted codes before concatenating to ensure the right order.
[Protected] (16) ๐ The Space Station
Use df and du to investigate disk usage and find the heaviest directory.
- The space station’s storage system is overloaded.
- Find which module is consuming the most disk space to unlock the next door.
MAP THE SPACE STATION!
- Check available disk space on the filesystem.
df -hshows disk usage in human-readable format - Find which direct subdirectory of
station/uses the most space.du -sh station/*/shows sizes of each subdirectory - The password is the word
modulefollowed by the name of the largest subdirectory *(no space)*. Example: ifstation/reactor/is largest โ password ismodulereactor
Key Commands
| Command | Purpose |
|---|---|
df -h |
Show filesystem disk space usage (human-readable) |
du -sh dir/ |
Show total size of a directory |
du -sh */ |
Show sizes of all subdirectories |
du -sh * \| sort -h |
Sort directories by size |
How df and du Work
# df - disk free: filesystem-level usage
df -h # all filesystems, human-readable (KB/MB/GB)
df -H # same but uses powers of 1000 not 1024
df /home # specific filesystem
df -T # show filesystem type
df --total # add total row at bottom
df -i # show inode usage instead of blocks
# du - disk usage: directory/file-level usage
du -sh /path/to/dir # summarized total in human-readable form
du -h /path/to/dir # size of each file and subdirectory
du -sh */ # sizes of all items in current dir
du -sh * | sort -h # sort by size (smallest to largest)
du -sh * | sort -rh # sort by size (largest to smallest)
du -sh * | sort -rh | head -5 # top 5 largest items
du --max-depth=1 -h /var # only one level deep
du -a -h /path # include files (not just directories)
# Combine to find largest items
du -sh /var/* | sort -rh | head -10 # top 10 largest in /var
find . -size +10M -exec du -sh {} \; # files larger than 10MB
du -sh station/*/lists sizes for each direct subdirectory.Pipe through
sort -rhto sort by size (largest first) - the first line is your answer.
๐ Part 5 - System Operations
[Protected] (17) โฐ The Clockwork Fortress
Decode cron expressions to determine how many times a job runs per day.
- An ancient fortress runs on a clockwork schedule.
- Decode the cron expressions to find how many times the alarm fires per day.
CRACK THE CLOCKWORK CODE!
- Read
schedule.cronto see all the scheduled jobs. - Count how many times per day the job labelled
# ALARMwould execute. Analyze the cron expression -minute hour day-of-month month day-of-week - The password is
cronfollowed by the total number of daily executions *(no space)*. Example: if the alarm fires 12 times per day โcron12
Key Commands
| Command | Purpose |
|---|---|
crontab -l |
List current user’s crontab |
crontab -e |
Edit crontab in default editor |
crontab -r |
Remove (delete) current crontab |
crontab file |
Install crontab from file |
* * * * * cmd |
Run cmd every minute |
0 * * * * cmd |
Every hour at :00 |
0 9 * * * cmd |
Every day at 09:00 |
0 9 * * 1 cmd |
Every Monday at 09:00 |
0 9 1 * * cmd |
1st of every month at 09:00 |
*/5 * * * * cmd |
Every 5 minutes |
0 9-17 * * 1-5 cmd |
Every hour 9am-5pm weekdays |
@reboot cmd |
Run once at startup |
@daily cmd |
Equivalent to 0 0 * * * |
@hourly cmd |
Equivalent to 0 * * * * |
@weekly cmd |
Equivalent to 0 0 * * 0 |
MAILTO=user@host |
Email cron output |
How Cron Works
# Cron expression format:
# โโโโโโโโโโ minute (0-59)
# โ โโโโโโโโโโ hour (0-23)
# โ โ โโโโโโโโโโ day of month (1-31)
# โ โ โ โโโโโโโโโโ month (1-12)
# โ โ โ โ โโโโโโโโโโ day of week (0-6, Sunday=0)
# โ โ โ โ โ
# * * * * * command to execute
# Special characters:
# * = every (any value)
# , = list of values (e.g., 1,3,5)
# - = range (e.g., 1-5)
# / = step (e.g., */5 = every 5)
# Examples:
0 * * * * # every hour at minute 0 (24x/day)
*/15 * * * * # every 15 minutes (96x/day)
0 9 * * 1-5 # 9am Monday through Friday
30 2 * * 0 # 2:30am every Sunday
0 0 1 * * # midnight on first day of month
0 8,12,18 * * * # 8am, noon, and 6pm every day
# Manage crontabs
crontab -l # list current jobs
crontab -e # open editor to modify
crontab -r # DANGER: removes ALL your cron jobs
crontab /path/to/file # install jobs from file
sudo crontab -u username -l # list another user's crons
# System-wide cron locations
cat /etc/crontab # system crontab (includes user field)
ls /etc/cron.d/ # drop-in cron files
ls /etc/cron.daily/ # scripts run once daily
ls /etc/cron.hourly/ # scripts run once hourly
Cron Expression Quick Reference
| Expression | Meaning |
|---|---|
* * * * * |
Every minute |
0 * * * * |
Every hour |
*/30 * * * * |
Every 30 minutes |
0 9 * * 1-5 |
9am weekdays |
0 0 * * * |
Midnight daily |
Count each valid time combination in a 24-hour day (00:00 to 23:59).
*/Nmeans “every N units” - e.g.,*/4in hours = 6 times per day (0,4,8,12,16,20).
[Protected] (18) ๐๏ธ The Twin Blueprints
Use diff and patch to identify changes between blueprint versions.
- Two versions of a building blueprint exist.
- A saboteur changed something between version 1 and version 2.
- Find exactly what changed and read the altered line.
SPOT THE SABOTAGE!
- Compare the two files to see what changed.
diff blueprint_v1.txt blueprint_v2.txt - Lines starting with
<are in v1 only; lines starting with>are in v2 only. - The saboteur inserted a line containing the password in v2. Find the added line (starts with
>). - The value on that line is the password.
Key Commands
| Command | Purpose |
|---|---|
diff file1 file2 |
Show differences between two files |
diff -u file1 file2 |
Unified diff format (more readable) |
diff -y file1 file2 |
Side-by-side comparison |
patch file < changes.patch |
Apply a patch to a file |
How diff and patch Work
# diff - compare files line by line
diff file1.txt file2.txt # default output
diff -u file1.txt file2.txt # unified format (easier to read)
diff -U 3 file1.txt file2.txt # unified with 3 lines of context
diff -y file1.txt file2.txt # side-by-side
diff -y --width=80 file1.txt file2.txt # side-by-side with width limit
diff -q file1.txt file2.txt # just say if files differ (quiet)
diff -r dir1/ dir2/ # recursive directory diff
# Understanding default diff output:
# 5c5 = change: line 5 in file1 changed to line 5 in file2
# 3a4 = add: after line 3 in file1, add line 4 from file2
# 7d6 = delete: line 7 in file1 was deleted
# < line = line from file1
# > line = line from file2
# patch - apply a diff to a file
diff -u original.txt modified.txt > changes.patch # create a patch file
patch original.txt < changes.patch # apply patch (in place)
patch -p1 < changes.patch # strip 1 path component
patch --dry-run < changes.patch # test without applying
patch -R file.txt < changes.patch # reverse: undo a patch
Lines prefixed with
>indiffoutput are lines that exist in file2 (v2) but not file1.Use
diff blueprint_v1.txt blueprint_v2.txt | grep '^>'to see only added lines.
[Protected] (19) ๐ The Integrity Check
Use md5sum and sha256sum to verify file integrity and find the authentic document.
- Seven documents were delivered, but only one is authentic.
- The authentic document matches a known checksum. Find it!
FIND THE AUTHENTIC DOCUMENT!
- Read
authentic.sha256to see the expected hash.cat authentic.sha256 - Calculate the SHA-256 hash of each document.
sha256sum documents/*.txt - Find which document's hash matches the one in
authentic.sha256. - Read that document - its contents are the password.
Key Commands
| Command | Purpose |
|---|---|
sha256sum file |
Compute SHA-256 checksum |
sha256sum -c sums.txt |
Verify checksums from file |
sha256sum * > checksums.txt |
Compute checksums for all files |
md5sum file |
Compute MD5 checksum |
md5sum -c sums.txt |
Verify MD5 checksums |
sha1sum file |
Compute SHA-1 checksum |
sha512sum file |
Compute SHA-512 checksum |
echo "text" \| sha256sum |
Hash a string |
sha256sum file \| cut -d' ' -f1 |
Extract hash only |
diff <(sha256sum a) <(sha256sum b) |
Compare two file hashes |
openssl dgst -sha256 file |
OpenSSL alternative |
openssl dgst -md5 file |
MD5 via OpenSSL |
b2sum file |
BLAKE2 checksum (faster) |
cksum file |
Simple CRC checksum |
How Checksum Tools Work
# Calculate checksums
md5sum file.txt # 32-character hex hash
sha1sum file.txt # 40-character hex hash
sha256sum file.txt # 64-character hex hash
sha512sum file.txt # 128-character hex hash
# Multiple files at once
md5sum *.txt # hash all .txt files
sha256sum dir/* # hash all files in a directory
# Verify against a checksum file
sha256sum -c checksums.sha256 # check all listed files
md5sum -c hashes.md5 # verify using md5 hashes
# Output: "filename: OK" or "filename: FAILED"
# Create a checksum file
sha256sum file1.txt file2.txt > checksums.sha256 # save hashes to file
sha256sum * > all_checksums.sha256 # hash everything in dir
# Compare a single file to a known hash
echo "EXPECTEDHASH filename" | sha256sum -c # verify one file inline
# Quick comparison
sha256sum file1.txt file2.txt # same hash = identical content
sha256sum documents/*.txtwill show hashes for all documents at once.Use
grepto match the hash fromauthentic.sha256against the computed hashes.
[Protected] (20) ๐ข The Hex Dungeon
Decode a hex dump using xxd to reveal the hidden message.
- An ancient computer left a message encoded in hexadecimal.
- Reverse the hex dump to read the original text.
DECODE THE HEX DUNGEON!
- View the hex dump file.
cat hex_message.hex - Reverse the hex dump back to its original text.
xxd -r hex_message.hex - The decoded text is the password.
Key Commands
| Command | Purpose |
|---|---|
xxd file |
Create a hex dump of a file |
xxd -r file |
Reverse a hex dump back to binary/text |
od -c file |
Octal dump with character representation |
od -x file |
Octal dump in hex format |
hexdump -C file |
Canonical hex+ASCII dump |
How xxd Works
# Create hex dumps
xxd file.txt # hex dump (16 bytes per line)
xxd -l 32 file.txt # dump only first 32 bytes
xxd -s 16 file.txt # skip first 16 bytes, then dump
xxd -c 8 file.txt # 8 bytes per line (default: 16)
xxd -p file.txt # plain hex (no offsets/ASCII)
xxd -b file.txt # binary dump (bits) instead of hex
# Reverse a hex dump
xxd -r hexdump.hex > original.bin # restore binary file from hex dump
xxd -r -p plain_hex.txt > original.bin # restore from plain hex
# Useful for inspecting binary files
xxd /bin/ls | head -5 # peek at binary file structure
file unknown.bin # identify file type before dumping
# od - alternative dump tool
od -c file.txt # character representation
od -x file.txt # hex format
od -An -tx1 file.txt # hex bytes, no address prefix
od -An -tu1 file.txt # unsigned decimal bytes
# hexdump
hexdump -C file.txt # canonical: offset | hex | ASCII
hexdump -e '"%08.8_ax " 16/1 "%02x " "\n"' file.txt # custom format
xxd -r hex_message.hexconverts the hex dump back to the original text.If the output has binary noise, redirect to a file and use
catorstringson it.
๐ ๏ธ Part 6 - Tools & Utilities
[Protected] (21) ๐ The Binary Library
Use the strings command to extract readable text from a compiled binary.
- A compiled program contains a hidden password in its binary data.
- The
stringscommand can extract human-readable text from any binary file.
RAID THE BINARY LIBRARY!
- Run
stringson the binary to extract all readable text.strings vault_binary - The output will contain many strings - filter for lines that look like a password.
strings vault_binary | grep "pass" - The password line follows the format
PASSWORD=. - Extract just the value after
=. pipe tocut -d'=' -f2
Key Commands
| Command | Purpose |
|---|---|
strings file |
Extract printable strings from a binary file |
strings -n 8 file |
Only strings of minimum length 8 |
nm binary |
List symbols in a binary (compiled names) |
objdump -s file |
Full binary disassembly |
How strings Works
# Basic usage
strings binary_file # extract all printable strings (default min 4 chars)
strings -n 8 binary_file # only strings of 8+ characters
strings -a binary_file # scan all sections (not just initialized data)
strings -t x binary_file # show offset in hex before each string
strings -t d binary_file # show offset in decimal
# Combine with filtering
strings binary | grep -i "password" # look for password-related strings
strings binary | grep -E "^[a-z]+[0-9]+$" # look for word+number patterns
strings binary | sort | uniq # deduplicate strings
# Inspect other binary formats
strings /usr/bin/ls | head -20 # strings from a system binary
strings library.so | grep "version" # version strings from a .so file
strings core_dump | grep "error" # error messages from a core dump
# Alternative: hexdump for raw inspection
xxd binary | grep "pass" # search hex dump for "pass" in ASCII
strings vault_binary | grep "PASSWORD"will narrow down the results.
cut -d'=' -f2extracts everything after the=sign.
[Protected] (22) ๐งฎ The Calculator Cave
Use bc and expr to perform mathematical calculations and crack the code.
- The door lock requires solving a series of mathematical equations.
- Linux has built-in calculators - use them!
SOLVE THE EQUATION!
- Read
equations.txtto see the problems. - Solve each equation using
bc(supports floating point and complex math).echo "2^10 + 15 * 3" | bc - The password is the word
calcfollowed by the sum of all three answers *(no space)*. Example: if answers are 5, 10, and 20 โcalc35
Key Commands
| Command | Purpose |
|---|---|
bc |
Arbitrary precision calculator |
echo "EXPR" \| bc |
Evaluate a math expression |
echo "scale=2; EXPR" \| bc |
Set decimal places |
expr A + B |
Simple integer arithmetic |
$(( A + B )) |
Bash arithmetic expansion |
How bc and expr Work
# bc - arbitrary precision calculator
echo "2 + 2" | bc # basic addition โ 4
echo "10 / 3" | bc # integer division โ 3
echo "scale=4; 10 / 3" | bc # 4 decimal places โ 3.3333
echo "2^10" | bc # power: 2^10 = 1024
echo "sqrt(144)" | bc # square root = 12
echo "l(100)" | bc -l # natural log (requires -l for math lib)
echo "s(3.14159/2)" | bc -l # sine function (radians)
echo "obase=16; 255" | bc # convert 255 to hex: FF
echo "ibase=16; FF" | bc # convert hex FF to decimal: 255
# Multi-line bc
bc << 'EOF'
scale=2
a = 10
b = 3
a / b
EOF
# expr - simple integer arithmetic (older style)
expr 5 + 3 # 8
expr 10 \* 4 # 40 (escape the asterisk)
expr 10 % 3 # 1 (modulo)
expr length "hello" # 5 (string length)
# Bash arithmetic ($(( )))
echo $(( 2 + 3 )) # 5
echo $(( 10 ** 3 )) # 1000 (power)
echo $(( 100 % 7 )) # 2 (modulo)
result=$(( 5 * 8 )) # assign to variable
Use
echo "expression" | bcfor each equation, then add all results together.For floating point results, use
echo "scale=0; expression" | bcto get integers.
[Protected] (23) ๐ฐ๏ธ The Time Machine
Use the date command to calculate time differences and decode timestamps.
- A time traveler left encrypted coordinates encoded as UNIX timestamps.
- Decode them using the
datecommand.
DECODE THE TIMESTAMPS!
- Read
timestamps.txtto get the three timestamps. - Convert each timestamp to a human-readable date.
date -d @TIMESTAMP(Linux) ordate -r TIMESTAMP(macOS) - Extract just the year from each converted date.
date -d @TIMESTAMP +%Y - The password is the word
epochfollowed by the sum of the three years *(no space)*. Example: if the years are 2001, 2010, 2015 โepoch6026
Key Commands
| Command | Purpose |
|---|---|
date |
Display current date and time |
date +FORMAT |
Format date output |
date -d @TIMESTAMP |
Convert UNIX timestamp to date (Linux) |
date -r TIMESTAMP |
Convert UNIX timestamp to date (macOS) |
date -d "STRING" |
Parse a date string |
How date Works
# Display dates
date # current date and time
date +%Y-%m-%d # YYYY-MM-DD format
date +%H:%M:%S # HH:MM:SS time
date +%Y%m%d%H%M%S # compact timestamp
date +%s # current UNIX timestamp (seconds since epoch)
date +%A # full weekday name
date +%B # full month name
# Format codes (use with +)
# %Y year (4 digits) %m month (01-12) %d day (01-31)
# %H hour (00-23) %M minute (00-59) %S second (00-59)
# %s UNIX timestamp %A weekday name %B month name
# %j day of year %u day of week(1=Mon)
# Convert UNIX timestamps
date -d @0 # epoch: Thu Jan 1 00:00:00 UTC 1970
date -d @1000000000 # 2001-09-09
date -d @1700000000 +%Y # extract year only
# macOS syntax (uses -r instead of -d @)
date -r 1000000000 # macOS: convert timestamp
date -r 1000000000 +%Y # macOS: extract year
# Date arithmetic (Linux)
date -d "2024-01-15 + 30 days" # add 30 days to a date
date -d "2024-12-31 - 1 year" # subtract 1 year
date -d "next Monday" # next Monday's date
date -d "yesterday" # yesterday's date
# Calculate a time difference
start=$(date -d "2024-01-01" +%s)
end=$(date -d "2024-12-31" +%s)
echo $(( (end - start) / 86400 )) days
date -d @TIMESTAMP +%Yextracts just the year from a UNIX timestamp (Linux).On macOS, use
date -r TIMESTAMP +%Yinstead ofdate -d @TIMESTAMP.
[Protected] (24) ๐จ๏ธ The Formatter's Workshop
Use printf to format a template precisely and assemble the password.
- A broken printer is garbling messages.
- Only precisely formatted output will unlock the next door.
FORMAT THE TRANSMISSION!
- Read
template.txtto see the required format and the values. - Use
printfto format the output exactly as specified.printf "%-10s %05d " "word" 42 - The formatted output contains the password on the last line.
- The password is the last word on the last output line.
Key Commands
| Command | Purpose |
|---|---|
printf "FORMAT" ARGS |
Formatted output (like C printf) |
printf "%s\n" "text" |
Print string with newline |
printf "%d" 42 |
Print integer |
printf "%05d" 42 |
Print integer padded to 5 digits |
echo -n "text" |
Print without newline |
echo -e "a\tb" |
Print with escape sequences |
How printf Works
# Basic printf
printf "Hello, World!\n" # print with explicit newline
printf "%s\n" "hello" # %s = string
printf "%d\n" 42 # %d = integer
printf "%f\n" 3.14 # %f = float
printf "%.2f\n" 3.14159 # 2 decimal places: 3.14
# Width and padding
printf "%10s\n" "right" # right-align in 10-wide field: " right"
printf "%-10s\n" "left" # left-align in 10-wide field: "left "
printf "%05d\n" 42 # zero-pad to 5 digits: "00042"
printf "%+d\n" 42 # always show sign: "+42"
# Multiple arguments
printf "%s: %d\n" "count" 42 # "count: 42"
printf "%-8s %4d %6.2f\n" "item" 3 9.5 # formatted table row
# Escape sequences in printf
printf "line1\nline2\n" # newline
printf "col1\tcol2\n" # tab
printf "bell\a\n" # bell
printf "Name: %s\tAge: %d\n" "Bob" 25 # tab-separated
# printf vs echo
echo "Hello" # adds newline automatically
echo -n "Hello" # no newline
echo -e "a\tb" # process escape sequences
printf "Hello\n" # explicit newline needed
printf "%s %s\n" "Hello" "World" # formatted with args
printfformat codes:%sfor strings,%dfor integers,%-10sfor left-aligned 10-wide.The last line of the formatted output contains the password as the final word.
[Protected] (25) ๐ The Signal Crossroads
Use tee to split output to both a file and the screen simultaneously.
- Incoming data must be routed simultaneously to two destinations.
- Use
teeto split the stream and write a log while still processing the data.
SPLIT THE SIGNAL!
- Run
generate_signal.shand useteeto write the output tosignal.logwhile also piping it onward../generate_signal.sh | tee signal.log | grep "CODE" - From the tee'd stream, filter lines containing
CODE:and extract the code values.grep "CODE:" | cut -d':' -f2 - Sort the codes numerically and sum them.
sort -n | paste -sd'+' | bc - The password is
teefollowed by the total sum *(no space)*. Example: if the sum is 99 โtee99
Key Commands
| Command | Purpose |
|---|---|
tee file |
Write stdin to both stdout and a file |
tee -a file |
Append to file instead of overwriting |
tee file1 file2 |
Write to multiple files at once |
paste -sd'+' |
Join lines with a delimiter |
How tee Works
# Basic tee: write to file AND pass to stdout
cmd | tee output.txt # tee to file, output still visible
cmd | tee output.txt | next_cmd # tee mid-pipeline
# Append mode
cmd | tee -a logfile.txt # append to file (don't overwrite)
# Multiple output files
cmd | tee file1.txt file2.txt # write to two files at once
cmd | tee file1.txt file2.txt | wc -l # also count lines
# Use tee as a debug probe mid-pipeline
cmd1 | tee /dev/stderr | cmd2 # peek at data without breaking pipeline
cmd1 | tee debug.log | cmd2 # save intermediate state
# Process substitution with tee
cmd | tee >(grep "ERROR" > errors.log) >(grep "WARN" > warnings.log) > /dev/null
# Write to file with elevated privileges
echo "data" | sudo tee /etc/config # write to root-owned file
echo "data" | sudo tee -a /etc/config # append to root-owned file
# paste - join lines
paste file1.txt file2.txt # merge files side by side with tab
paste -d',' file1.txt file2.txt # merge with comma delimiter
echo -e "1\n2\n3" | paste -sd'+' # join lines: "1+2+3" (for bc)
tee signal.logwrites to the file AND passes data along to the next pipe.
paste -sd'+'turns a vertical list of numbers into1+2+3+4whichbccan evaluate.
๐ Part 7 - Bash Scripting
[Protected] (26) ๐งฉ The Variable Vault
Master bash parameter expansion to extract and transform variable values.
- The combination to the vault is hidden inside shell variable values.
- Use Bash parameter expansion to extract and transform the pieces.
EXPAND THE VARIABLES!
- Source the file to load the variables.
source vault_env.sh - The variable
TREASURE_PATHcontains something like/deep/in/the/vault/jewel. Extract just the filename part (after the last/).${TREASURE_PATH##*/} - The variable
GEMCODEcontains something likegem-SECRETWORD-2024. Extract the middle part between the two dashes. Use${GEMCODE#*-}then${result%-*} - Combine the extracted filename and middle part (separated by a
-) - that is the password.
Key Commands
| Syntax | Purpose |
|---|---|
${var} |
Basic variable expansion |
${var:-default} |
Use default if var is unset |
${var#pattern} |
Remove shortest prefix matching pattern |
${var##pattern} |
Remove longest prefix matching pattern |
${var%pattern} |
Remove shortest suffix matching pattern |
${var%%pattern} |
Remove longest suffix matching pattern |
${var/old/new} |
Replace first match |
${var//old/new} |
Replace all matches |
${#var} |
Length of variable |
How Parameter Expansion Works
# Basic access
name="Alice"
echo ${name} # Alice
echo ${name:-Unknown} # Alice (use default only if unset)
unset name
echo ${name:-Unknown} # Unknown (var is unset, use default)
echo ${name:=Default} # Default AND sets name=Default
# Substring removal (pattern matching)
path="/home/user/docs/file.txt"
echo ${path##*/} # file.txt (remove longest */ prefix)
echo ${path#*/} # home/user/docs/file.txt (remove shortest)
echo ${path%/*} # /home/user/docs (remove shortest suffix)
echo ${path%%/*} # (empty - longest /* suffix)
echo ${path%.txt} # /home/user/docs/file (remove .txt suffix)
# String replacement
str="hello world hello"
echo ${str/hello/bye} # bye world hello (first match)
echo ${str//hello/bye} # bye world bye (all matches)
echo ${str/#hello/Hi} # Hi world hello (anchor to start)
echo ${str/%hello/end} # hello world end (anchor to end)
# Case conversion (Bash 4+)
text="Hello World"
echo ${text,,} # hello world (all lowercase)
echo ${text^^} # HELLO WORLD (all uppercase)
echo ${text,} # hELLO WORLD (first char lowercase)
echo ${text^} # Hello World (first char uppercase)
# Length
echo ${#text} # 11 (character count)
# Slice
str="Hello, World!"
echo ${str:7:5} # World (start at 7, length 5)
echo ${str: -6} # orld! (from 6 chars from end)
${TREASURE_PATH##*/}removes everything up to and including the last/.To extract the middle of
gem-WORD-2024: first do${GEMCODE#*-}to getWORD-2024, then${result%-*}to getWORD.
[Protected] (27) ๐๏ธ The Array Arsenal
Use bash arrays to collect, sort, and process a list of items.
- The armory has been scrambled - weapons are unordered and some are duplicates.
- Use Bash arrays to organize the inventory and count unique weapons.
INVENTORY THE ARSENAL!
- Write a small bash script (or one-liner) to read all weapon names into an array.
mapfile -t weapons < weapons.txt - Count the total number of items in the array.
${#weapons[@]} - Count how many unique weapon names there are. pipe through
sort | uniq | wc -l - The password is
arrayfollowed by the count of unique weapons *(no space)*. Example: if there are 10 unique weapons โarray10
Key Commands
| Syntax | Purpose |
|---|---|
arr=(a b c) |
Declare an array |
${arr[0]} |
Access element by index |
${arr[@]} |
All elements |
${#arr[@]} |
Number of elements |
arr+=(d) |
Append an element |
mapfile -t arr < file |
Read file lines into array |
How Bash Arrays Work
# Declare and access arrays
fruits=("apple" "banana" "cherry") # declare array
echo ${fruits[0]} # apple (index starts at 0)
echo ${fruits[1]} # banana
echo ${fruits[-1]} # cherry (last element)
echo ${fruits[@]} # all elements: apple banana cherry
echo ${#fruits[@]} # number of elements: 3
# Add and modify elements
fruits+=("date") # append element
fruits[1]="blueberry" # replace element at index 1
unset fruits[2] # remove element (leaves gap)
# Iterate over array
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# Array slices
echo ${fruits[@]:1:2} # elements 1 and 2 (banana cherry)
echo ${fruits[@]: -2} # last 2 elements
# Read file into array
mapfile -t lines < file.txt # read each line into array element
IFS=$'\n' read -r -a lines <<< "$(cat file.txt)" # alternative
# Array of unique items
IFS=$'\n' sorted=($(sort -u <<< "${fruits[*]}"))
# Associative arrays (Bash 4+)
declare -A colors
colors["red"]="#FF0000"
colors["green"]="#00FF00"
echo ${colors["red"]} # #FF0000
echo ${!colors[@]} # all keys: red green
echo ${colors[@]} # all values
# Indices
echo ${!fruits[@]} # 0 1 3 (note: gap from unset[2])
mapfile -t weapons < weapons.txtreads all lines into the array.
printf '%s\n' "${weapons[@]}" | sort | uniq | wc -lcounts unique elements.
[Protected] (28) ๐ The Loop Labyrinth
Use a for loop to process files and accumulate a result.
- 50 numbered chambers each contain a single digit.
- Loop through them all and sum the digits to unlock the exit.
LOOP THROUGH THE LABYRINTH!
- Write a
forloop to iterate over all chamber files.for f in chambers/chamber_*.txt; do ... - Read each file's value and add it to a running total.
total=$(( total + $(cat "$f") )) - After the loop, print the total.
- The password is
loopfollowed by the total *(no space)*. Example: if the sum is 250 โloop250
Key Commands
| Syntax | Purpose |
|---|---|
for x in LIST; do ... done |
Iterate over a list |
for (( i=0; i<N; i++ )); do |
C-style numeric for loop |
$(( expr )) |
Arithmetic expansion |
$(cat file) |
Command substitution |
How for Loops Work
# Iterate over a list of values
for color in red green blue; do
echo "Color: $color"
done
# Iterate over files (glob)
for file in *.txt; do
echo "Processing: $file"
wc -l "$file"
done
# Iterate over a range of numbers
for i in {1..10}; do
echo "Number: $i"
done
for i in {1..10..2}; do # step by 2
echo "Odd: $i"
done
# C-style for loop
for (( i=0; i<5; i++ )); do
echo "i = $i"
done
# Iterate over array elements
fruits=("apple" "banana" "cherry")
for fruit in "${fruits[@]}"; do
echo "$fruit"
done
# Iterate over command output
for user in $(cut -d: -f1 /etc/passwd); do
echo "User: $user"
done
# Accumulate values
total=0
for f in numbers/*.txt; do
val=$(cat "$f")
total=$(( total + val ))
done
echo "Total: $total"
# break and continue
for i in {1..10}; do
[ $i -eq 5 ] && continue # skip 5
[ $i -eq 8 ] && break # stop at 8
echo $i
done
Initialize
total=0before the loop and add each file’s value inside.
$(cat "$f")reads the content of a file and substitutes it inline.
[Protected] (29) ๐ The Endless Corridor
Use a while loop to read and process a file line by line.
- A long corridor has doors on both sides.
- Read the door log line by line and count how many doors were left open.
TRAVERSE THE ENDLESS CORRIDOR!
- Use a
while readloop to process each line ofdoor_log.txt. - Count lines where the status field is
OPEN. - The password is
whilefollowed by the count of open doors *(no space)*. Example: if 73 doors are OPEN โwhile73
Key Commands
| Syntax | Purpose |
|---|---|
while read line; do ... done < file |
Read file line by line |
while IFS= read -r line; do |
Read preserving whitespace/backslashes |
read var1 var2 <<< "$line" |
Split a line into variables |
[[ "$str" == "pattern" ]] |
String comparison |
How while Loops Work
# Basic while loop
count=0
while [ $count -lt 5 ]; do
echo "count: $count"
(( count++ ))
done
# Read a file line by line
while IFS= read -r line; do
echo "Line: $line"
done < file.txt
# Read specific fields from each line
while read -r id status; do
echo "ID=$id STATUS=$status"
done < data.txt
# Count matching lines
count=0
while IFS= read -r line; do
if [[ "$line" == *"ERROR"* ]]; then
(( count++ ))
fi
done < logfile.txt
echo "Errors: $count"
# Read output of a command
while IFS= read -r line; do
echo "Processing: $line"
done < <(grep "pattern" bigfile.txt) # process substitution
# until loop (opposite of while)
x=0
until [ $x -ge 5 ]; do
echo $x
(( x++ ))
done
# Infinite loop with break
while true; do
read -p "Enter value (q to quit): " val
[[ "$val" == "q" ]] && break
echo "You entered: $val"
done
while read -r id status; dosplits each line into two variables automatically.
[[ "$status" == "OPEN" ]]tests for exact equality.
[Protected] (30) ๐ The Fork in the Road
Use if/else conditions and test operators to navigate a decision tree.
- A decision tree guards the exit.
- Evaluate conditions correctly to follow the right path.
CHOOSE YOUR PATH!
- Read
decision_tree.shto understand the conditions.cat decision_tree.sh - The script expects two arguments: a number and a string.
- Find the number and string that satisfy ALL the if/else branches to reach
echo $PASSWORD. - Run:
bash decision_tree.sh - The output is the password.
Key Commands
| Syntax | Purpose |
|---|---|
if [ condition ]; then |
Basic if statement |
elif [ condition ]; then |
Else-if branch |
else |
Default branch |
[[ str == pattern ]] |
String comparison (bash) |
-eq -ne -lt -gt -le -ge |
Numeric comparisons |
-z -n |
Test if string is empty/non-empty |
-f -d -e |
Test if file/dir/path exists |
How if Statements Work
# Basic if/else
if [ $x -gt 0 ]; then
echo "positive"
elif [ $x -lt 0 ]; then
echo "negative"
else
echo "zero"
fi
# Numeric comparisons
[ $a -eq $b ] # equal
[ $a -ne $b ] # not equal
[ $a -lt $b ] # less than
[ $a -gt $b ] # greater than
[ $a -le $b ] # less than or equal
[ $a -ge $b ] # greater than or equal
# String comparisons (use [[ ]])
[[ "$str" == "hello" ]] # exact match
[[ "$str" != "hello" ]] # not equal
[[ "$str" == *"ell"* ]] # wildcard: contains "ell"
[[ "$str" =~ ^[0-9]+$ ]] # regex match: all digits
[[ -z "$str" ]] # true if string is empty
[[ -n "$str" ]] # true if string is non-empty
# File tests
[ -f file.txt ] # true if regular file exists
[ -d dir/ ] # true if directory exists
[ -e path ] # true if path exists (any type)
[ -r file ] # true if file is readable
[ -x file ] # true if file is executable
[ -s file ] # true if file is non-empty
# Combining conditions
if [ $x -gt 0 ] && [ $x -lt 100 ]; then # AND
if [ $x -eq 0 ] || [ $x -eq 1 ]; then # OR
if [[ $x -gt 0 && $x -lt 100 ]]; then # AND in double brackets
Trace through each
if/elif/elsebranch carefully to find which values satisfy all conditions.The conditions narrow down exact values - process of elimination works here.
[Protected] (31) ๐ญ The Decision Chamber
Use case statements to route inputs through a pattern-matching decision engine.
- The chamber has five doors, each labeled with a symbol.
- Only the
casestatement can correctly route each symbol to the right door.
CHOOSE YOUR DESTINY!
- Write a script or one-liner using a
casestatement to categorize each symbol. - Count how many lines contain
$(the dollar sign). loop + case, or simplygrep -c '\$' symbols.txt - The password is
matchedfollowed by that count *(no space)*. Example: if 7 lines contain$โmatched7
Key Commands
| Syntax | Purpose |
|---|---|
case $var in pattern) ... ;; esac |
Pattern-matching switch |
*) |
Default/catch-all case |
pattern1\|pattern2) |
Match multiple patterns |
How case Statements Work
# Basic case statement
case $fruit in
apple)
echo "It's an apple"
;;
banana|mango)
echo "It's tropical"
;;
*)
echo "Unknown fruit"
;;
esac
# Case with variable input
read -p "Enter a letter: " letter
case $letter in
[a-z]) echo "lowercase letter" ;;
[A-Z]) echo "uppercase letter" ;;
[0-9]) echo "a digit" ;;
*) echo "special character" ;;
esac
# Case inside a loop
count=0
while IFS= read -r line; do
case $line in
\$) (( count++ )) ;;
\#) echo "comment symbol" ;;
*) echo "other: $line" ;;
esac
done < symbols.txt
echo "Dollar count: $count"
# Case with pattern matching
filename="report.pdf"
case $filename in
*.pdf) echo "PDF document" ;;
*.txt) echo "Text file" ;;
*.sh) echo "Shell script" ;;
*.tar.*) echo "Archive" ;;
esac
In a
casepattern,$must be escaped as\$to avoid variable expansion.You can also just use
grep -c '\$' symbols.txtdirectly without a case statement.
[Protected] (32) โ๏ธ The Function Factory
Write and call bash functions to process a complex multi-step task.
- The factory machines only work when the right functions are called in the right order.
- Write bash functions to process the assembly line.
POWER UP THE FACTORY!
- Write a function
validatethat checks if a code starts withPROD-. - Write a function
extract_idthat returns the numeric part afterPROD-. - For each valid code, extract its ID and sum all IDs.
- The password is
funcfollowed by the total sum *(no space)*. Example: if the sum is 550 โfunc550
Key Commands
| Syntax | Purpose |
|---|---|
function_name() { ... } |
Define a function |
function func_name { ... } |
Alternative syntax |
return N |
Return an exit code (0-255) |
echo value |
Return a value via stdout |
local var=value |
Declare a local variable |
$1 $2 $@ |
Function arguments |
How Bash Functions Work
# Define a function
greet() {
echo "Hello, $1!"
}
greet "World" # Hello, World!
# Function with return value (via stdout)
add() {
echo $(( $1 + $2 ))
}
result=$(add 3 5) # capture output
echo $result # 8
# Function with exit code (return)
is_even() {
if (( $1 % 2 == 0 )); then
return 0 # success = true
else
return 1 # failure = false
fi
}
is_even 4 && echo "even" || echo "odd"
# Local variables
counter=0
increment() {
local step=${1:-1} # local var with default
counter=$(( counter + step ))
}
increment 5
echo $counter # 5
# Functions in scripts
validate() {
[[ "$1" == PROD-* ]] # returns 0 if true, 1 if false
}
extract_id() {
echo "${1#PROD-}" # remove PROD- prefix
}
total=0
while IFS= read -r code; do
if validate "$code"; then
id=$(extract_id "$code")
total=$(( total + id ))
fi
done < assembly.txt
echo "Total: $total"
Use
[[ "$1" == PROD-* ]]in the validate function - it returns exit code 0 (true) if matched.
echo "${1#PROD-}"strips thePROD-prefix, leaving just the number.
โก Part 8 - Advanced Scripting
[Protected] (33) ๐๏ธ The Argument Decoder
Use getopts to parse command-line arguments and unlock a protected script.
- A protected script only responds to the correct combination of flags.
- Use
getoptsto build a command that passes all validations.
CRACK THE ARGUMENT DECODER!
- Read
locked_program.shto understand what flags it expects.cat locked_program.sh - The script expects flags
-u-pand optionally-v(verbose). - Call it with
-u agent -p 1337 -v. - The output when all conditions pass is the password.
Key Commands
| Syntax | Purpose |
|---|---|
getopts "abc:" opt |
Parse flags; : means arg required |
$OPTARG |
Value of the option argument |
$OPTIND |
Index of next arg to be processed |
shift $(( OPTIND-1 )) |
Move past parsed options |
How getopts Works
# Basic getopts loop
while getopts "abc:" opt; do
case $opt in
a) echo "Flag -a was set" ;;
b) echo "Flag -b was set" ;;
c) echo "Flag -c with value: $OPTARG" ;;
?) echo "Unknown option: $opt" ; exit 1 ;;
esac
done
# Script with named options
#!/bin/bash
verbose=false
username=""
port=8080
while getopts "u:p:vh" opt; do
case $opt in
u) username="$OPTARG" ;;
p) port="$OPTARG" ;;
v) verbose=true ;;
h) echo "Usage: $0 -u user -p port [-v]"; exit 0 ;;
?) echo "Invalid option"; exit 1 ;;
esac
done
# Shift past parsed options to get positional args
shift $(( OPTIND - 1 ))
remaining_args="$@"
# Validate
[ -z "$username" ] && { echo "Username required"; exit 1; }
if $verbose; then
echo "Connecting to $username on port $port"
fi
# Calling the script
./script.sh -u alice -p 9090 -v
./script.sh -u bob -p 22 # no verbose flag
getopts "u:p:v"- the:afteruandpmeans those flags take arguments.Run
bash locked_program.sh -u agent -p 1337 -vand read the output.
[Protected] (34) ๐ The Ancient Scroll
Use here-documents to write a multi-line config and run embedded commands.
- The vault door requires a precisely formatted configuration scroll.
- Use a here-document to write it without creating temp files.
INSCRIBE THE ANCIENT SCROLL!
- Use a heredoc to pass multi-line input to
./verify_config.sh: ``bash ./verify_config.sh << 'EOF' MODE=escape LEVEL=master KEY=ancient EOF`` - The script checks the values and outputs the password if all fields are correct.
- The output of that command is the password.
Key Commands
| Syntax | Purpose |
|---|---|
cmd << 'EOF' ... EOF |
Here-document: pass multi-line string |
cmd <<- 'EOF' ... EOF |
Here-doc ignoring leading tabs |
var=$(cat << 'EOF' ... EOF) |
Capture heredoc in a variable |
cat << EOF |
Print a heredoc (variable expansion ON) |
cat << 'EOF' |
Print a heredoc (no variable expansion) |
How Here-Documents Work
# Basic heredoc (variable expansion enabled)
name="World"
cat << EOF
Hello, $name!
Today is $(date)
EOF
# Heredoc with no variable expansion (single-quoted marker)
cat << 'EOF'
Literal: $HOME is not expanded here
Neither is $(date)
EOF
# Heredoc piped to a command
grep "error" << 'EOF'
this is fine
error: something went wrong
also fine
EOF
# Heredoc as stdin to a script
./configure.sh << 'EOF'
HOST=localhost
PORT=8080
DEBUG=true
EOF
# Capture heredoc in a variable
config=$(cat << 'EOF'
key1=value1
key2=value2
EOF
)
echo "$config"
# Heredoc in a function
write_file() {
cat << 'EOF' > "$1"
#!/bin/bash
echo "Generated script"
EOF
chmod +x "$1"
}
# Here-string (single-line heredoc)
grep "pattern" <<< "this is a pattern string"
wc -w <<< "count these words please"
Use
<< 'EOF'(quoted marker) to prevent variable expansion inside the heredoc.The heredoc content ends when the marker (
EOF) appears alone on a line.
[Protected] (35) ๐ The Nested Worlds
Use command substitution and process substitution to combine command outputs.
- Some commands can only run inside other commands.
- Master the art of nesting commands within commands.
ENTER THE NESTED WORLDS!
- Use process substitution to diff the sorted contents without creating temp files.
diff <(sort world_a.txt) <(sort world_b.txt) - Count words that appear in BOTH files (the intersection).
comm -12 <(sort world_a.txt) <(sort world_b.txt) | wc -l - The password is
nestedfollowed by the intersection count *(no space)*. Example: if 42 words are in both โnested42
Key Commands
| Syntax | Purpose |
|---|---|
$(command) |
Command substitution: replace with output |
`command` |
Old-style command substitution |
<(command) |
Process substitution: command output as file |
>(command) |
Process substitution: pipe into command |
How Command and Process Substitution Work
# Command substitution - use output as value
today=$(date +%Y-%m-%d) # capture date into variable
files=$(ls *.txt) # capture ls output
count=$(wc -l < file.txt) # capture line count
echo "Today is $(date)" # inline in string
# Nested command substitution
dir=$(basename $(pwd)) # basename of current directory
longest=$(sort -n file.txt | tail -1) # get largest number
# Process substitution - treat command output as a file
diff <(sort file1.txt) <(sort file2.txt) # diff without temp files
comm -12 <(sort a.txt) <(sort b.txt) # intersection
wc -l <(grep "error" app.log) # count errors without temp file
# Process substitution for output
tee >(gzip > output.gz) < input.txt # write compressed copy
command > >(tee output.txt) 2>&1 # tee stderr+stdout
# Practical combinations
diff <(cut -d: -f1 /etc/passwd | sort) \
<(cut -d: -f1 /etc/group | sort) # compare user vs group names
# Arithmetic with command substitution
size=$(du -sb dir/ | cut -f1)
echo "Size in MB: $(( size / 1048576 ))"
<(command)creates a virtual file from a command’s output - no temp file needed.
comm -12shows only lines common to BOTH files (both must be sorted).
[Protected] (36) ๐ก The Signal Tower
Use trap to catch signals and ensure cleanup tasks always run.
- A process sends signals that must be caught and handled.
- Set up signal traps to intercept the correct signal and extract the password.
CATCH THE SIGNAL!
- Write a script that sets a
trapforSIGUSR1that writescaughtto a file. - Run the script in the background.
- Run
./signal_sender.shwith your script's PID. - Check the file your trap wrote to - it contains the password. your trap handler should:
echo "sigcatch" > trap_result.txt
Key Commands
| Syntax | Purpose |
|---|---|
trap 'cmd' SIGNAL |
Run cmd when signal is received |
trap 'cmd' EXIT |
Run cmd on script exit (always) |
trap '' SIGINT |
Ignore a signal |
trap - SIGNAL |
Reset signal to default handler |
kill -SIGUSR1 PID |
Send SIGUSR1 to a process |
How trap Works
# Trap a signal
trap 'echo "Caught SIGINT!"' SIGINT # catch Ctrl+C
trap 'echo "Caught TERM"' SIGTERM # catch kill
# Cleanup on exit (always runs, even on error)
cleanup() {
echo "Cleaning up..."
rm -f /tmp/myapp.lock
}
trap cleanup EXIT
# Trap multiple signals with same handler
trap 'echo "Signal received"' SIGINT SIGTERM SIGHUP
# Ignore a signal
trap '' SIGINT # Ctrl+C does nothing
# Reset to default behavior
trap - SIGINT # restore default Ctrl+C behavior
# Trap with function
handle_usr1() {
echo "Got SIGUSR1 at $(date)" >> events.log
}
trap handle_usr1 SIGUSR1
# Send signals to processes
kill -SIGTERM 1234 # graceful stop
kill -SIGKILL 1234 # force stop
kill -SIGUSR1 1234 # user-defined signal 1
kill -SIGUSR2 1234 # user-defined signal 2
kill -l # list all signal names
# Example: wait for signal
trap 'echo "done" > result.txt; exit 0' SIGUSR1
echo "My PID is $$"
while true; do sleep 1; done # wait for signal
Use
$$to get your script’s own PID and pass it to the signal sender.
trap 'handler' SIGUSR1sets up the handler before entering the wait loop.
[Protected] (37) ๐ช The Interactive Gateway
Use the read command to interactively respond to a prompt-driven challenge.
- A guardian blocks the gate and asks you a series of questions.
- Answer correctly using a script with
readto pass the challenge.
ANSWER THE GUARDIAN!
- Run
guardian.shinteractively and answer the questions.bash guardian.sh - Alternatively, pipe all answers at once.
printf "answer1 answer2 answer3 " | bash guardian.sh - The correct answers are:
bash,escape, and the current year.$(date +%Y)gives the current year - When all answers are correct, the script prints the password.
Key Commands
| Syntax | Purpose |
|---|---|
read var |
Read one line from stdin into var |
read -p "prompt" var |
Read with a prompt message |
read -s var |
Read silently (no echo, for passwords) |
read -t 5 var |
Read with 5-second timeout |
read -n 1 var |
Read exactly 1 character |
IFS= read -r line |
Read preserving whitespace/backslashes |
How read Works
# Basic read
read name # wait for input, store in $name
echo "Hello, $name"
# Read with prompt
read -p "Enter your name: " name # prompt on same line
read -p "Password: " -s pass # silent (hide input)
# Read with timeout
read -t 10 -p "Quick! Enter code: " code
if [ $? -ne 0 ]; then
echo "Timed out!"
fi
# Read single character
read -n 1 -p "Press any key..." key
echo "" # newline after single-char read
# Read multiple variables (splits on IFS)
read first last <<< "John Doe" # first=John last=Doe
read -r line < file.txt # read first line of a file
# Interactive script pattern
ask_question() {
local prompt="$1"
local expected="$2"
read -p "$prompt: " answer
if [[ "$answer" == "$expected" ]]; then
echo "Correct!"
return 0
else
echo "Wrong!"
return 1
fi
}
# Piping answers to an interactive script
printf "answer1\nanswer2\nanswer3\n" | ./interactive_script.sh
# Read all lines from stdin
while IFS= read -r line; do
echo "Got: $line"
done
printf "bash\nescape\n$(date +%Y)\n" | bash guardian.shpipes all answers at once.For interactive mode, just run
bash guardian.shand type each answer when prompted.
[Protected] (38) ๐ฃ The Time Bomb
Use timeout and watch to manage time-limited and repeated commands.
- A countdown timer is ticking.
- Use
timeoutto run a command safely andwatchto monitor the clock.
DEFUSE THE TIME BOMB!
- Run
countdown.shwith atimeoutof 5 seconds.timeout 5 ./countdown.sh - The script writes partial output to
progress.logas it runs. - After the timeout, read
progress.logto find the password fragment.cat progress.log - The last non-empty line of
progress.logcontainsBOMB_CODE=. - Extract the value - it is the password.
Key Commands
| Command | Purpose |
|---|---|
timeout N cmd |
Kill cmd if it runs longer than N seconds |
timeout -s SIGNAL N cmd |
Use specific signal to kill |
watch -n N cmd |
Run cmd every N seconds and display output |
watch -d cmd |
Highlight differences between runs |
sleep N |
Wait N seconds |
How timeout and watch Work
# timeout - run a command with a time limit
timeout 10 sleep 100 # sleep for 100s but kill after 10s
timeout 5 ./long_script.sh # kill script after 5 seconds
timeout --preserve-status 5 cmd # preserve cmd's exit code
# Check if timeout occurred (exit code 124 = timed out)
timeout 5 ./script.sh
if [ $? -eq 124 ]; then
echo "Script timed out"
fi
# Use a different signal (default is SIGTERM)
timeout -s SIGKILL 5 ./stubborn.sh # use SIGKILL instead of SIGTERM
# watch - run a command repeatedly and display output
watch ls # run 'ls' every 2 seconds (default)
watch -n 1 'date; ls -la' # every 1 second
watch -d 'cat /proc/meminfo' # highlight changes between runs
watch -t -n 5 'ps aux | head' # no title bar, every 5 seconds
# Practical uses
watch -n 1 'tail -5 access.log' # live tail of a log
watch -n 2 'df -h' # monitor disk space
watch -n 1 'ls -lt | head' # watch for new files
# sleep - pause execution
sleep 1 # 1 second
sleep 0.5 # 500 milliseconds
sleep 1m # 1 minute
sleep 2h # 2 hours
# Combine with other tools
( sleep 3; echo "done" ) & # background timer
timeout 10 bash -c 'while true; do echo tick; sleep 1; done'
timeout 5 ./countdown.shwill kill the script after 5 seconds, butprogress.logalready has data.
grep "BOMB_CODE" progress.log | tail -1 | cut -d'=' -f2extracts the value.
๐ Part 9 - Networking & Debugging
[Protected] (39) ๐ธ๏ธ The Network Hub
Use ss and netstat to inspect network connections and find the listening port.
- Hidden services are running on this machine.
- Use network inspection tools to find which port holds the secret.
MAP THE NETWORK HUB!
- List all listening TCP ports.
ss -tlnpornetstat -tlnp - Find which port in the range 8000-9000 has a listener.
- Connect to it and read what it says.
curl http://localhost:ornc localhost - The response is the password.
Key Commands
| Command | Purpose |
|---|---|
ss -tlnp |
Show TCP listening ports with process info |
ss -tuln |
Show TCP and UDP listening ports |
netstat -tlnp |
Similar to ss (older tool) |
ss -an \| grep LISTEN |
Filter for listening sockets |
How ss and netstat Work
# ss - socket statistics (modern replacement for netstat)
ss -t # TCP connections
ss -u # UDP connections
ss -l # listening sockets only
ss -n # numeric (don't resolve names)
ss -p # show process using socket
ss -tlnp # TCP listening, numeric, with processes
# Useful ss filters
ss -tlnp | grep :80 # find what's on port 80
ss -tlnp | grep LISTEN # all listeners
ss state ESTABLISHED # established connections only
ss dst 192.168.1.1 # connections to specific host
# netstat (older, may need net-tools package)
netstat -tlnp # TCP listening with processes
netstat -an | grep LISTEN # all listeners
netstat -r # routing table
netstat -i # interface statistics
# Find what's using a specific port
ss -tlnp | grep :8080 # using ss
lsof -i :8080 # using lsof
fuser 8080/tcp # using fuser
# Check if a port is open
nc -zv localhost 80 # test port 80 (verbose)
nc -zv localhost 8000-9000 # scan a range
timeout 3 bash -c 'cat < /dev/tcp/localhost/8080' # bash TCP test
# Port scanning basics
for port in {8000..9000}; do
(echo >/dev/tcp/localhost/$port) 2>/dev/null && echo "OPEN: $port"
done
ss -tlnp | awk '/808[0-9]/ {print $5}'helps filter for ports in the 8080s.Once you find the port,
curl http://localhost:<PORT>ornc localhost <PORT>connects to it.
[Protected] (40) ๐ฎ The DNS Oracle
Use dig and host to resolve DNS records and trace a domain to its secret.
- An ancient domain holds the password encoded in its DNS records.
- Consult the oracle to reveal it.
CONSULT THE DNS ORACLE!
- Query the TXT records for
secret.escape.local.dig TXT secret.escape.localorhost -t TXT secret.escape.local - The TXT record value is the password. Note: In the Docker environment, this domain is configured in
/etc/hostsor a local DNS server. - If DNS is not available, the password is stored in
/etc/escape/dns_secret.txt.
Key Commands
| Command | Purpose |
|---|---|
dig domain |
Query DNS (A record by default) |
dig TXT domain |
Query TXT records |
dig MX domain |
Query mail exchange records |
host domain |
Simple DNS lookup |
nslookup domain |
Interactive DNS query tool |
getent hosts domain |
Resolve using system resolver |
How dig and host Work
# dig - DNS lookup tool
dig example.com # A record (IPv4 address)
dig AAAA example.com # AAAA record (IPv6)
dig MX example.com # mail exchange records
dig TXT example.com # text records (SPF, DKIM, etc.)
dig NS example.com # name server records
dig CNAME www.example.com # canonical name alias
# Short output
dig +short example.com # just the answer
dig +short TXT example.com # just TXT values
dig @8.8.8.8 example.com # use specific DNS server (Google)
# Reverse DNS lookup (IP to hostname)
dig -x 8.8.8.8 # PTR record
dig -x 8.8.8.8 +short # just the hostname
# host - simpler alternative
host example.com # A record
host -t MX example.com # MX records
host -t TXT example.com # TXT records
host 8.8.8.8 # reverse lookup
# nslookup - interactive or one-shot
nslookup example.com # simple lookup
nslookup -type=TXT example.com # TXT records
nslookup -type=MX example.com # MX records
nslookup example.com 8.8.8.8 # use specific server
# System resolver
getent hosts example.com # uses /etc/hosts + DNS
cat /etc/resolv.conf # see configured DNS servers
cat /etc/hosts # local host overrides
dig TXT secret.escape.local +shortgives just the TXT record value.If
digisn’t available, tryhost -t TXT secret.escape.localor check/etc/escape/dns_secret.txt.
[Protected] (41) ๐ The Netcat Tunnel
Use netcat (nc) to communicate with a local service and retrieve the key.
- A secret server is listening, waiting for the magic word.
- Send it the right message using
ncand receive the key.
OPEN THE NETCAT TUNNEL!
- Connect to the server using
nc.nc localhost 4444 - The server will ask:
SEND THE MAGIC WORD: - Type the magic word:
OPEN - The server will respond with the password.
Key Commands
| Command | Purpose |
|---|---|
nc host port |
Connect to a TCP server |
nc -l -p PORT |
Listen on a port (create a server) |
nc -z host port |
Test if port is open (no data) |
nc -u host port |
Use UDP instead of TCP |
echo "msg" \| nc host port |
Send a message and exit |
How nc (netcat) Works
# Connect to a server
nc localhost 8080 # connect and interact manually
nc 192.168.1.1 22 # connect to SSH port
echo "hello" | nc localhost 1234 # send one message and exit
echo -e "GET / HTTP/1.0\n\n" | nc example.com 80 # manual HTTP request
# Listen / create a server
nc -l 4444 # listen on port 4444
nc -l -p 4444 # explicit -p flag
nc -l -k 4444 # keep listening (accept multiple connections)
# File transfer with nc
# Receiver side:
nc -l 4444 > received_file.txt
# Sender side:
nc receiver-host 4444 < file_to_send.txt
# Port scanning
nc -zv localhost 20-25 # scan ports 20-25 (verbose)
nc -z -w 2 host port # timeout after 2 seconds
# Test if a port is open
nc -z localhost 80 && echo "Port 80 is open"
# UDP communication
nc -u localhost 514 # UDP connection
nc -u -l 514 # UDP listener
# Send data one-shot
printf "OPEN\n" | nc localhost 4444 # send "OPEN" and close
echo "OPEN" | nc localhost 4444sends the magic word and prints the response.If the connection hangs, try
echo -e "OPEN\n" | nc -q 1 localhost 4444to close after 1 second.
[Protected] (42) ๐๏ธ The Open Files Archive
Use lsof to list open files and identify which process holds the secret.
- A process has opened a secret file but hasn’t released it.
- Use
lsofto find which process is holding the key.
OPEN THE FILE ARCHIVE!
- Find which process has
secret_key.txtopen.lsof | grep secret_key.txt - Note the PID of that process.
- See what other files that process has open.
lsof -p - Among those files, find one named
password.txt- read it. use the path fromlsofoutput, thencatit
Key Commands
| Command | Purpose |
|---|---|
lsof |
List all open files |
lsof -p PID |
Files opened by a specific process |
lsof -u username |
Files opened by a user |
lsof -i :PORT |
Process using a specific port |
lsof /path/to/file |
Processes that have this file open |
How lsof Works
# List all open files (can be huge output)
lsof # all open files, all processes
lsof | head -20 # first 20 results
# Filter by process
lsof -p 1234 # all files opened by PID 1234
lsof -p 1234,5678 # multiple PIDs
# Filter by user
lsof -u alice # files opened by user alice
lsof -u ^root # files NOT opened by root
# Filter by file/path
lsof /var/log/syslog # who has syslog open
lsof +D /var/log/ # all files open in directory
lsof /dev/null # processes with /dev/null open
# Network connections
lsof -i # all network connections
lsof -i :80 # processes on port 80
lsof -i TCP # TCP connections only
lsof -i TCP:8080-9090 # TCP in port range
# Combine filters
lsof -u alice -i TCP # alice's TCP connections
lsof -u alice +D /home/alice # alice's open files in her home
# Find deleted files still held open (disk space issue)
lsof | grep deleted # files deleted but still open
# Output fields
# COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
# FD: cwd=current dir, txt=executable, mem=memory mapped, 0=stdin, 1=stdout, 2=stderr
lsof | grep secret_keyfinds the process; note the PID in the second column.
lsof -p <PID> | grep REGshows all regular files open by that process.
[Protected] (43) ๐ญ The System Call Observatory
Use strace to trace system calls and discover what a mystery program is doing.
- A mystery program is doing something secret.
- Use
straceto observe its every system call and intercept the secret.
OBSERVE THE SYSTEM CALLS!
- Run
mystery_programunderstraceand capture the output.strace ./mystery_program 2>&1 - Search the strace output for
writesystem calls to find the password.strace ./mystery_program 2>&1 | grep 'write' - The content of the write call contains the password.
- Alternatively, just run
./mystery_programand check the output file it creates.
Key Commands
| Command | Purpose |
|---|---|
strace cmd |
Trace system calls of a command |
strace -e trace=open,read,write cmd |
Trace only specific calls |
strace -o file.log cmd |
Write trace to a file |
strace -p PID |
Attach to a running process |
ltrace cmd |
Trace library calls |
How strace Works
# Basic tracing
strace ls # trace all system calls of ls
strace -o trace.log ls # save trace to file
strace ./myprog arg1 arg2 # trace a program with arguments
# Filter specific system calls
strace -e trace=open,read,write ls # only open, read, write calls
strace -e trace=network ls # only network-related calls
strace -e trace=file ls # only file-related calls
strace -e trace=process ls # only process-related calls
# Attach to running process
strace -p 1234 # attach to PID 1234
strace -p $(pgrep nginx) # attach to nginx by name
# Useful options
strace -t cmd # add timestamps
strace -T cmd # show time spent in each call
strace -c cmd # count/statistics summary only
strace -f cmd # follow forks (trace child processes)
strace -s 200 cmd # increase string length displayed (default 32)
# Find file access
strace -e trace=openat ls 2>&1 | grep "\.txt" # see which .txt files are opened
# Find writes
strace -e trace=write ./program 2>&1 | grep '"' # see what's written to files
# ltrace - trace library function calls
ltrace ./program # show libc and other library calls
ltrace -e malloc,free ./program # only malloc and free calls
strace -e trace=write ./mystery_program 2>&1shows only write system calls.Look for
write(1, "...", N)orwrite(fd, "PASSWORD", N)in the output.
๐ Part 10 - Security & Version Control
[Protected] (44) ๐ The Mirror Sync
Use rsync to synchronize files between two directories and verify the transfer.
- Two archives must be kept in sync, but something has drifted.
- Use
rsyncto synchronize them and then verify the result.
SYNC THE MIRROR ARCHIVE!
- Sync
source_archive/tomirror_archive/using rsync.rsync -av source_archive/ mirror_archive/ - After syncing, count the total number of files in
mirror_archive/.find mirror_archive/ -type f | wc -l - The password is
syncfollowed by the file count *(no space)*. Example: if there are 28 files โsync28
Key Commands
| Command | Purpose |
|---|---|
rsync -av src/ dst/ |
Archive mode + verbose |
rsync -n src/ dst/ |
Dry run (no changes) |
rsync --delete src/ dst/ |
Delete files not in src |
rsync -z src/ dst/ |
Compress during transfer |
rsync -P src/ dst/ |
Progress + partial files |
rsync -e ssh src/ user@host:dst/ |
Transfer over SSH |
rsync --exclude="*.log" src/ dst/ |
Exclude pattern |
rsync --include="*.txt" --exclude="*" src/ dst/ |
Include only .txt |
rsync -u src/ dst/ |
Skip newer files in dst |
rsync --checksum src/ dst/ |
Compare by checksum, not mtime |
rsync --bwlimit=1000 src/ dst/ |
Throttle to 1 MB/s |
rsync -a --stats src/ dst/ |
Show transfer statistics |
rsync -a --log-file=sync.log src/ dst/ |
Log to file |
rsync --backup --backup-dir=backup/ src/ dst/ |
Keep old versions |
How rsync Works
# Basic local sync
rsync -av source/ destination/ # sync with verbose output
rsync -a source/ destination/ # archive mode (preserves permissions, times, etc.)
rsync -n source/ destination/ # dry run: show what WOULD happen, don't copy
# Archive mode (-a) includes:
# -r recursive -l copy symlinks -p preserve permissions
# -t preserve timestamps -g preserve group -o preserve owner
# Delete files in destination that don't exist in source
rsync -av --delete source/ destination/
# Exclude files
rsync -av --exclude='*.log' source/ destination/
rsync -av --exclude-from='exclude.txt' source/ destination/
# Remote sync via SSH
rsync -av source/ user@remote:/path/to/dest/ # push to remote
rsync -av user@remote:/path/to/src/ destination/ # pull from remote
rsync -avz source/ user@remote:/dest/ # with compression
# Progress and partial transfers
rsync -av --progress source/ dest/ # show per-file progress
rsync -av --partial source/ dest/ # keep partial transfers (resume)
rsync -avP source/ dest/ # --partial --progress shorthand
# Verify transfer (checksum mode)
rsync -avc source/ destination/ # compare by checksum not timestamp
# Useful flags summary
# -a = archive (recursive + preserve metadata)
# -v = verbose
# -z = compress during transfer
# -n = dry-run
# -P = --partial --progress
# --delete = remove extra files in destination
rsync -av source_archive/ mirror_archive/- note the trailing slash on source is important!
find mirror_archive/ -type f | wc -lcounts only regular files, not directories.
[Protected] (45) ๐ The Cryptographer's Den
Use openssl to decrypt a message encrypted with a symmetric cipher.
- A message was encrypted with
opensslusing a known algorithm and key. - Decrypt it to reveal the secret.
CRACK THE CRYPTOGRAPHER’S DEN!
- Find and read the encryption password.
cat keyfile.txt - Decrypt the file using
opensslwith the password you found.openssl enc -aes-256-cbc -d -a -pbkdf2 -in encrypted_message.enc -pass pass:PASSWORD - The decrypted output is the password for Room 46.
Key Commands
| Command | Purpose |
|---|---|
openssl enc -e |
Encrypt a file |
openssl enc -d |
Decrypt a file |
openssl enc -aes-256-cbc |
Use AES-256-CBC cipher |
openssl dgst -sha256 |
Compute SHA-256 hash |
openssl genrsa |
Generate RSA private key |
openssl rand -hex N |
Generate N random hex bytes |
How openssl Works
# Symmetric encryption/decryption
# Encrypt
openssl enc -aes-256-cbc -e -a -pbkdf2 \
-in plaintext.txt \
-out encrypted.enc \
-pass pass:mypassword
# Decrypt
openssl enc -aes-256-cbc -d -a -pbkdf2 \
-in encrypted.enc \
-out plaintext.txt \
-pass pass:mypassword
# Flags:
# -e = encrypt -d = decrypt
# -a = base64 encode/decode (ASCII output)
# -pbkdf2 = use PBKDF2 key derivation (recommended)
# -pass pass:PASSWORD = specify password inline
# -pass env:VARNAME = read password from env variable
# -pass file:key.txt = read password from file
# Common ciphers
openssl enc -list # list all available ciphers
openssl enc -aes-128-cbc ... # AES 128-bit
openssl enc -aes-256-cbc ... # AES 256-bit (strongest)
openssl enc -des3 ... # Triple-DES
# Hashing with openssl
openssl dgst -md5 file.txt # MD5 hash
openssl dgst -sha256 file.txt # SHA-256 hash
openssl dgst -sha256 -hmac "key" file # HMAC-SHA256
# Generate random data
openssl rand -hex 16 # 16 random hex bytes (32 chars)
openssl rand -base64 24 # 24 random base64 bytes
openssl rand 32 > keyfile # 32 random raw bytes
# Check openssl version and capabilities
openssl version # version info
openssl help # list all commands
Copy the exact decrypt command from the Mission Briefing - the password is
cryptokey2024.
-aflag is needed if the encrypted file was base64-encoded.-pbkdf2should match what was used to encrypt.
[Protected] (46) ๐ The Vi Vortex
Navigate vim to find and decode a password hidden in a large text file.
- A critical document is locked inside a file that can only be navigated with
vim. - The password is hidden on a specific line - find it without scrolling forever.
ENTER THE VI VORTEX!
- Open the file in vim.
vim ancient_tome.txt - Jump directly to line 777. In vim, type
777Gor:777 - The line reads
SECRET:- note the password word. - Exit vim without saving.
:q! - The word after
SECRET:is the password.
Key Commands
| Vim Command | Purpose |
|---|---|
i |
Enter insert mode |
Esc |
Return to normal mode |
:q! |
Quit without saving |
:wq |
Save and quit |
NG |
Jump to line N |
:N |
Jump to line N |
/pattern |
Search forward for pattern |
n |
Next search match |
How vim Works
# Open a file
vim filename.txt
vim +100 filename.txt # open at line 100
vim +/pattern filename.txt # open at first match of pattern
# Normal mode navigation
gg # go to first line
G # go to last line
50G # go to line 50
:50 # go to line 50 (command mode)
Ctrl+f # page forward
Ctrl+b # page backward
0 # beginning of line
$ # end of line
w # next word
b # previous word
# Searching
/pattern # search forward
?pattern # search backward
n # next match
N # previous match
:%s/old/new/g # replace all occurrences
# Insert mode
i # insert before cursor
a # append after cursor
o # open new line below
O # open new line above
# Saving and exiting
:w # write (save) file
:q # quit (fails if unsaved changes)
:wq # write and quit
:q! # quit without saving (discard changes)
ZZ # save and quit (normal mode shortcut)
# Other useful commands
u # undo
Ctrl+r # redo
dd # delete current line
yy # yank (copy) current line
p # paste after cursor
Once in vim, type
777G(capital G) to jump directly to line 777.Alternatively, avoid vim entirely:
sed -n '777p' ancient_tome.txtprints just line 777.
[Protected] (47) ๐ฐ๏ธ The Remote Gateway
Generate an SSH key pair and use it to authenticate to a local service.
- A remote vault only accepts SSH key authentication.
- Generate a key pair and use it to retrieve the secret.
OPEN THE REMOTE GATEWAY!
- Generate an SSH key pair.
ssh-keygen -t rsa -f /tmp/escape_key -N "" - Add the public key to the authorized list.
./setup_ssh_access.sh /tmp/escape_key.pub - Connect using your new key.
ssh -i /tmp/escape_key -o StrictHostKeyChecking=no escape@localhost - The SSH session will show the password immediately upon login.
Key Commands
| Command | Purpose |
|---|---|
ssh user@host |
Connect to remote host |
ssh -p 2222 user@host |
Connect on non-default port |
ssh -i ~/.ssh/key user@host |
Use specific identity file |
ssh -L 8080:localhost:80 user@host |
Local port forwarding |
ssh -R 8080:localhost:80 user@host |
Remote port forwarding |
ssh -N -f user@host |
Background tunnel (no shell) |
ssh -o StrictHostKeyChecking=no user@host |
Skip host key check |
ssh user@host 'cmd' |
Run command remotely |
ssh-keygen -t rsa -f ~/.ssh/id_rsa |
Generate rsa key |
ssh-keygen -t rsa -b 4096 |
Generate 4096-bit RSA key |
ssh-keygen -p -f ~/.ssh/key |
Change passphrase |
ssh-copy-id user@host |
Copy public key to remote |
ssh-add ~/.ssh/key |
Add key to SSH agent |
ssh-agent bash |
Start SSH agent |
scp file user@host:/path/ |
Copy file to remote |
scp user@host:/path/file . |
Copy file from remote |
scp -r dir/ user@host:/path/ |
Recursive copy |
scp -P 2222 file user@host:/path/ |
SCP on custom port |
How SSH Works
# Generate key pairs
ssh-keygen -t rsa -b 4096 # RSA 4096-bit key
ssh-keygen -t rsa # rsa key (modern, recommended)
ssh-keygen -t rsa -f ~/.ssh/mykey # specify filename
ssh-keygen -t rsa -N "" # no passphrase (for automation)
# Creates: ~/.ssh/id_rsa (private) and ~/.ssh/id_rsa.pub (public)
# Connect with SSH
ssh user@hostname # connect (uses default key)
ssh -p 2222 user@hostname # non-standard port
ssh -i ~/.ssh/mykey user@host # specify key file
ssh -o StrictHostKeyChecking=no user@host # skip host key verification
# Copy public key to remote server
ssh-copy-id user@hostname # copies ~/.ssh/id_*.pub
ssh-copy-id -i mykey.pub user@host # specify key to copy
# Run commands remotely
ssh user@host "ls -la /home" # run a single command
ssh user@host "cat /etc/hostname" # read remote file
ssh user@host < local_script.sh # run local script on remote
# SCP - secure copy
scp file.txt user@host:/remote/path/ # copy to remote
scp user@host:/remote/file.txt . # copy from remote
scp -r dir/ user@host:/path/ # recursive directory copy
scp -P 2222 file.txt user@host:/path/ # non-standard port
# SSH config file (~/.ssh/config)
# Host myserver
# HostName 192.168.1.100
# User alice
# IdentityFile ~/.ssh/mykey
# Port 2222
# Then just: ssh myserver
ssh-keygen -t rsa -f /tmp/escape_key -N ""creates the key without a passphrase.
-o StrictHostKeyChecking=noprevents the “unknown host” prompt when connecting.
[Protected] (48) ๐ฟ The Version Vault
Use git to explore a repository’s history and recover a deleted secret.
- A secret was committed to a git repository, then immediately deleted.
- Nothing is truly gone from git history - find the lost commit!
RECOVER THE LOST COMMIT!
- Navigate into the repository.
cd vault_repo - View the commit history.
git log --oneline - Find the commit where
secret.txtwas added (then later deleted). - Check out or show that specific commit's content.
git show:secret.txt - The content of
secret.txtfrom that commit is the password.
Key Commands
| Command | Purpose |
|---|---|
git log |
Show commit history |
git log --oneline |
Compact one-line history |
git log --graph --oneline |
Graph view of branches |
git log -n 10 |
Last 10 commits |
git log --all |
Show all branches |
git log --author="name" |
Filter by author |
git log --since="2 weeks ago" |
Filter by date |
git log -- file |
History for specific file |
git show HASH |
Show a specific commit |
git show HASH:file |
Show file at specific commit |
git diff HEAD~1 |
Changes since last commit |
git diff branch1..branch2 |
Compare two branches |
git checkout HASH -- file |
Restore file from old commit |
git stash |
Stash uncommitted changes |
git stash pop |
Restore stashed changes |
git bisect start |
Binary search for bug commit |
git reflog |
Show all HEAD movements |
git blame file |
Show who changed each line |
How git History Works
# View history
git log # full commit log
git log --oneline # compact: hash + message
git log --oneline --graph # visual branch graph
git log --oneline --all # include all branches
git log --follow -- filename.txt # history for a specific file
git log --oneline -10 # last 10 commits
# Inspect commits
git show abc1234 # full diff for commit
git show abc1234:path/to/file # file content at that commit
git show HEAD # most recent commit
git show HEAD~1 # one commit before HEAD
git show HEAD~3:config.json # file 3 commits ago
# Compare commits
git diff abc1234 def5678 # diff between two commits
git diff HEAD~2 HEAD -- file.txt # how file changed in last 2 commits
# Find when something changed
git log -p -- secret.txt # show patches for a file
git log --diff-filter=A -- secret.txt # find when file was Added
git log --diff-filter=D -- secret.txt # find when file was Deleted
git log -S "password" --oneline # find commits that added "password"
# Restore from history
git checkout abc1234 -- file.txt # restore file from a commit
git show abc1234:file.txt > recovered.txt # save to new file
git log --onelineshows a concise list - look for a commit message mentioning “secret”.
git show <HASH>:secret.txtshows the file content without checking out anything.
๐ญ Part 11 - Advanced Challenges
[Protected] (49) ๐ญ The Grand Pipeline II
Build an advanced multi-stage pipeline combining awk, sed, sort, and uniq.
- The final lock requires you to build a sophisticated data processing pipeline.
- Combine your knowledge of multiple tools to crack it.
BUILD THE GRAND PIPELINE!
- Filter lines where
STATUSisSUCCESS.awk '$3 == "SUCCESS"' - Extract the
MACHINE_IDfield (field 2).awk '{print $2}' - Sort and count occurrences of each machine ID.
sort | uniq -c | sort -rn - Find the machine with the most successes - note the count.
- The password is
pipelinefollowed by that count *(no space)*. Example: if the top machine had 47 successes โpipeline47
The Complete Pipeline
awk '$3 == "SUCCESS" {print $2}' factory_log.txt \
| sort \
| uniq -c \
| sort -rn \
| head -1 \
| awk '{print $1}'
Key Commands
| Command | Purpose |
|---|---|
awk '$N == "val"' |
Filter rows by field value |
sort \| uniq -c |
Count occurrences of each unique line |
sort -rn |
Sort numerically, descending |
head -1 |
Take only the top result |
Full Pipeline Reference
# Data filtering and transformation chain
cat data.txt \
| grep "pattern" # pre-filter with grep
| awk '{print $2, $4}' # extract fields
| sort # sort for uniq
| uniq -c # count duplicates
| sort -rn # sort by count descending
| head -10 # top 10
# Aggregate with awk
awk 'BEGIN {max=0; maxid=""}
$3=="SUCCESS" {
count[$2]++
if (count[$2] > max) { max=count[$2]; maxid=$2 }
}
END { print max, maxid }' factory_log.txt
# Multi-condition filtering
awk '$3 == "SUCCESS" && $4 > 100' file.txt # success with 100+ units
awk 'NR > 1 && $2 ~ /^M/' file.txt # skip header, machine IDs starting with M
# Join two files
join <(sort file1.txt) <(sort file2.txt) # inner join on first field
uniq -cprefixes each line with its count - the format isCOUNT VALUE.After
sort -rn, the first line has the highest count.head -1 | awk '{print $1}'extracts just the number.
[Protected] (50) ๐ The Master Terminal
The final challenge - combine all skills to defeat the Master Terminal.
- You’ve made it to the final room.
- The Master Terminal requires proof that you’ve mastered ALL the skills.
- This challenge combines multiple tools in one epic puzzle.
โ ๏ธ Final Challenge
This is the last room before the exit exam. Give it everything you’ve got!
CONQUER THE MASTER TERMINAL!
- Find: Locate all
.keyfiles recursively infinal_challenge/.find final_challenge/ -name "*.key" - Decode: Each
.keyfile contains a base64-encoded value. Decode all of them.base64 -d < each_file - Filter: Keep only decoded values that are purely numeric.
grep -E '^[0-9]+$' - Calculate: Sum all the numeric values.
paste -sd'+' | bc - Verify: The sum must be verified against
final_challenge/expected_checksum.txt.echo "sum" | sha256sumand compare - Once verified, run
./final_challenge/unlock.shto get the exit code. - The exit code printed by the script is the password for the Final Exam (Room 99).
The Master Pipeline
find final_challenge/ -name "*.key" \
| sort \
| xargs -I{} base64 -d {} \
| grep -E '^[0-9]+$' \
| paste -sd'+' \
| bc
Skills Checklist
| Skill | Command |
|---|---|
| Find files | find |
| Decode data | base64 -d |
| Filter output | grep -E |
| Sum numbers | bc |
| Verify integrity | sha256sum |
| Run a script | bash ./script.sh |
Final Boss Tips
Run each stage of the pipeline separately before combining them. Use
| headafter each step to preview the intermediate output. Usetee /tmp/debug.txtbetween pipe stages to capture intermediate output.echo "your_sum" | sha256sum- compare the output withexpected_checksum.txt.
๐ Congratulations!
If you’ve made it this far, you are a true Bash Master. The final exam awaits - prove everything you’ve learned!
โญ Bonus Track - Complete the 100
[Protected] (51) ๐ง The Command Assembler
Use xargs to build and execute commands from standard input, processing files in bulk.
- The Command Assembler’s workshop is full of orphaned parts scattered across directories.
- Each part file contains a numeric VALUE. You must assemble them all using xargs to find the total.
ASSEMBLE THE COMMANDS!
- Find all
.partfiles recursively usingfind. - Use
xargsto pass each file togrepand extract theVALUE=lines. - Use
cutto extract just the number after the=sign. - Sum all numbers using
pasteto build an expression andbcto evaluate it. - Pass the total to
./getKey.shto retrieve the password.
Key Commands
| Command | Purpose |
|---|---|
xargs cmd |
Build and run cmd with arguments read from stdin |
xargs -I{} cmd {} |
Replace {} with each input item individually |
xargs -n N cmd |
Pass at most N arguments per command execution |
xargs -P N cmd |
Run up to N processes in parallel |
xargs -0 cmd |
Use null-separated input (safe for filenames with spaces) |
find . -print0 \| xargs -0 cmd |
Null-safe combined find and xargs pattern |
xargs -t cmd |
Print each command to stderr before executing it |
xargs -r cmd |
Do not run command if input is empty |
xargs --max-args=N cmd |
Long form of -n N |
xargs --max-procs=N cmd |
Long form of -P N |
echo "a b c" \| xargs |
Strip and normalize whitespace from input |
cat files.txt \| xargs rm |
Delete every file listed in files.txt |
xargs -I{} sh -c 'cmd {}' |
Use a subshell for complex per-item logic |
ls *.log \| xargs wc -l |
Count lines across many files at once |
find . -name "*.tmp" \| xargs -r rm -f |
Safely delete temp files (skip if none found) |
How xargs Works
# Basic usage - append all stdin items as arguments
echo "file1.txt file2.txt file3.txt" | xargs cat
# -I{} - substitute each item individually
ls *.txt | xargs -I{} cp {} /backup/{}
# -n N - limit arguments per invocation
echo "a b c d e f" | xargs -n 2 echo
# Output:
# a b
# c d
# e f
# -P N - parallel execution (run 4 jobs at once)
find images/ -name "*.png" | xargs -P 4 -I{} convert {} {}.jpg
# -0 with find -print0 - safe for filenames containing spaces
find . -name "* *" -print0 | xargs -0 -I{} mv {} "$(echo {} | tr ' ' '_')"
# -t - debug: print each command before running
find logs/ -name "*.log" | xargs -t grep -l "ERROR"
# -r - skip execution if stdin is empty
find /tmp -name "*.sock" | xargs -r -0 rm -f
# Complex per-file logic using a subshell
find data/ -name "*.enc" | xargs -I{} sh -c 'base64 -d < {} | gzip -d > ${1%.enc}.txt' _ {}
# Sum values from multiple files
find dir/ -name "*.ext" | xargs grep -h "KEY=" | cut -d= -f2 | paste -sd+ | bc
For complex per-file processing, use
xargs -I{} sh -c '...'to invoke a subshell.
Example: find . -name "*.b64" | xargs -I{} sh -c 'base64 -d < {} | grep KEY'
Add the
-tflag to debug your pipeline - it prints every command to stderr before running it so you can see exactly whatxargsis doing.
[Protected] (52) ๐๏ธ The Ownership Vault
Fix file ownership with chown and chgrp, and control default permissions with umask.
- Files in the Ownership Vault have been scrambled - wrong owners, wrong groups.
- The vault’s locking mechanism checks ownership before granting access.
RECLAIM THE VAULT!
- Inspect the current ownership of all files in
vault/.ls -la vault/ - Check your own user and group identity.
id - Read
requirements.txtto see what each file's owner and group should be.cat requirements.txt - Fix ownership of each file using
chownorchgrpas required.chown user:group vault/filename - Set the correct default permission mask using
umask.umask 022 - Run
./getKey.shto validate all ownership and retrieve the password../getKey.sh
Key Commands
| Command | Purpose |
|---|---|
chown user file |
Change the owner of a file |
chown user:group file |
Change both owner and group in one step |
chown :group file |
Change only the group (owner unchanged) |
chown -R user:group dir/ |
Recursively change owner and group |
chown --reference=ref file |
Copy ownership from a reference file |
chgrp group file |
Change the group of a file |
chgrp -R group dir/ |
Recursively change group of a directory |
umask |
Display the current umask value |
umask 022 |
Set umask (removes write for group and others) |
umask 077 |
Restrictive: new files are owner-only (600/700) |
umask -S |
Show symbolic umask (e.g. u=rwx,g=rx,o=rx) |
id |
Show current user’s uid, gid, and all groups |
id username |
Show uid/gid/groups for a specific user |
groups |
List all groups the current user belongs to |
stat -c "%U %G %a" file |
Show owner, group, and octal permissions |
ls -la |
Long listing with owner and group columns |
How chown, chgrp, and umask Work
# --- chown ---
chown alice file.txt # change owner to alice
chown alice:devs file.txt # change owner to alice, group to devs
chown :devs file.txt # change group only (same as chgrp devs)
chown -R www-data:www-data /var/www/html # recursive, common web server pattern
chown --reference=/etc/passwd shadow.bak # copy ownership from /etc/passwd
# --- chgrp ---
chgrp staff report.pdf # change group to staff
chgrp -R docker /var/run/docker.sock # recursive group change
# --- umask math ---
# umask removes bits from the default permissions:
# Default file permissions: 666 (rw-rw-rw-)
# Default dir permissions: 777 (rwxrwxrwx)
# umask 022 removes write (2) from group and other:
# 666 - 022 = 644 (rw-r--r--) for new files
# 777 - 022 = 755 (rwxr-xr-x) for new dirs
umask # show current: e.g. 0022
umask 022 # standard: owner full, group/other read only
umask 027 # stricter: group read only, others no access
umask 077 # private: owner only
umask -S # symbolic view: u=rwx,g=rx,o=rx
# --- stat ---
stat -c "%U %G %a" /etc/passwd # root root 644
stat -c "%n %U %G" vault/* # name, owner, group for all files
# --- check identity ---
id # uid=1000(alice) gid=1000(alice) groups=...
id -u # numeric uid only
id -g # numeric gid only
id -un # username only (same as whoami)
groups # alice adm sudo docker
chown user:group filesets both owner and group in a single command.
chown :group file sets only the group - useful when you don’t want to change the owner.
chgrp group file is equivalent to chown :group file.
Use
stat -c "%U %G %a" <file>to quickly verify the owner, group, and octal permissions of a file after you make changes - much faster than readingls -laoutput manually.
[Protected] (53) ๐ก The Network Probe
Probe the network with ping and traceroute, then retrieve a remote resource using wget.
- A signal is being transmitted from a remote beacon somewhere on the network.
- You must verify connectivity, trace the route, and retrieve the transmission.
TRACE THE SIGNAL!
- Verify network connectivity by pinging the loopback address.
ping -c 3 127.0.0.1 - Trace the network route to confirm the path.
traceroute 127.0.0.1ortracepath 127.0.0.1 - Start the local beacon server.
./start_server.sh - Use
wgetto retrieve the secret key file from the beacon. - The content of
key.txtis the next room's password.
Key Commands
| Command | Purpose |
|---|---|
ping host |
Send ICMP echo packets to test connectivity |
ping -c N host |
Send exactly N packets then stop |
ping -i N host |
Set interval to N seconds between packets |
ping -W N host |
Timeout after N seconds waiting for a reply |
ping -q host |
Quiet mode - show only the summary at end |
ping -s N host |
Set packet size to N bytes |
traceroute host |
Trace each network hop to the destination |
traceroute -n host |
Skip DNS resolution (faster output) |
traceroute -m N host |
Set maximum number of hops to N |
tracepath host |
Traceroute alternative requiring no root |
mtr host |
Combines ping + traceroute in live display |
wget URL |
Download a file from a URL |
wget -O file URL |
Save downloaded content to a specific filename |
wget -O - URL |
Stream download output directly to stdout |
wget -q URL |
Quiet mode - suppress all progress output |
wget --spider URL |
Check if URL is reachable without downloading |
wget -r -l 1 URL |
Recursive download, depth limited to 1 |
wget --limit-rate=100k URL |
Throttle download to 100 KB/s |
curl -s URL |
Alternative to wget: fetch URL silently to stdout |
How ping, traceroute, and wget Work
# --- ping ---
ping 127.0.0.1 # continuous ping (Ctrl-C to stop)
ping -c 3 127.0.0.1 # send exactly 3 packets
ping -c 5 -i 0.5 google.com # 5 packets, 0.5 s interval
ping -c 4 -W 2 192.168.1.1 # timeout 2 s per reply
ping -q -c 10 8.8.8.8 # quiet: just the final stats
ping -s 1400 gateway.local # large packet (test MTU)
# --- traceroute ---
traceroute 8.8.8.8 # trace to Google DNS
traceroute -n 8.8.8.8 # no DNS lookups (show IPs only)
traceroute -m 15 8.8.8.8 # max 15 hops
tracepath 8.8.8.8 # no-root alternative
mtr --report --report-cycles 10 8.8.8.8 # batch MTU report
# --- wget ---
wget http://host/file.txt # download to current directory
wget -O /tmp/file.txt http://host/file.txt # save to specific path
wget -q -O - http://host/file.txt # print to stdout, no noise
wget --spider http://host/ # check server without downloading
wget -r -l 1 -q http://host/ # spider all links one level deep
wget --limit-rate=500k -O archive.tar.gz http://host/big.tar.gz
# --- wget vs curl comparison ---
wget -q -O - http://host/file.txt # wget to stdout
curl -s http://host/file.txt # curl equivalent
wget -q -O - http://host/filestreams the file content directly to your terminal without saving a local copy and without any progress noise. Pipe it totr -d '\n'if you need to strip the trailing newline.If
tracerouteis not available on your system, trytracepath(available without root on most Linux systems) ormtrfor a real-time combined ping+trace view.
[Protected] (54) ๐ The System Monitor
Monitor system resources: memory, CPU load, and uptime using top, free, and vmstat.
- The System Monitor room has a resource leak - a hidden process is consuming memory.
- Parse the system snapshot to identify the culprit and report what you find.
WATCH THE SYSTEM!
- Check live memory usage.
free -h - Check the system's current load averages and uptime.
uptime - Read the static snapshot file generated from monitoring tools.
cat system_snapshot.txt - Parse
system_snapshot.txtto find: - Total RAM in megabytes (integer) - The 1-minute load average - Number of currently running processes - Pass all three values to
./getKey.shto validate and retrieve the password../getKey.sh
Key Commands
| Command | Purpose |
|---|---|
free |
Show RAM and swap usage in kilobytes |
free -h |
Human-readable output (KB/MB/GB auto-scaled) |
free -m |
Show all values in megabytes |
free -g |
Show all values in gigabytes |
free -s N |
Repeat output every N seconds |
uptime |
Show current time, user count, and load averages |
uptime -p |
Pretty format (e.g. “up 2 hours, 14 minutes”) |
top |
Interactive real-time process and resource monitor |
top -bn1 |
Batch mode, single iteration - scriptable/parseable |
top -p PID |
Monitor a specific process by PID |
top -u user |
Show only processes belonging to a user |
vmstat |
Summary of virtual memory, CPU, and I/O stats |
vmstat 1 5 |
Sample stats every 1 second, 5 times |
vmstat -s |
Detailed memory event summary |
vmstat -d |
Disk statistics |
cat /proc/meminfo |
Detailed kernel-level memory information |
cat /proc/loadavg |
Raw load averages (1, 5, 15 min + process counts) |
nproc |
Print number of available CPU processors |
iostat |
CPU and disk I/O statistics (from sysstat package) |
How free, uptime, top, and vmstat Work
# --- free ---
free # output in kibibytes
free -h # auto-scaled: 7.6G, 512M, etc.
free -m # megabytes (good for scripting)
free -s 2 # update every 2 seconds
# Parse total RAM in MB with awk:
free -m | awk '/^Mem:/ {print $2}'
# --- uptime ---
uptime # 14:32:10 up 3 days, 2:15, 2 users, load average: 0.45, 0.60, 0.55
uptime -p # up 3 days, 2 hours, 15 minutes
# Extract 1-min load average:
uptime | awk -F'average:' '{print $2}' | cut -d, -f1 | tr -d ' '
# --- top (batch mode for scripting) ---
top -bn1 # one snapshot, all processes
top -bn1 | head -5 # show just the summary lines
top -bn1 | grep "^%Cpu" # CPU usage line
top -bn1 -u www-data # processes for www-data only
# Count running processes from top output:
top -bn1 | grep "Tasks:" | grep -o '[0-9]* running' | cut -d' ' -f1
# --- /proc files (always available, no external command needed) ---
cat /proc/meminfo # MemTotal, MemFree, Buffers, Cached, ...
cat /proc/loadavg # 0.45 0.60 0.55 1/342 12345
# ^1min ^5min ^15min ^run/total ^lastpid
# Extract 1-min load from /proc directly:
cut -d' ' -f1 /proc/loadavg
# --- vmstat ---
vmstat # one-line snapshot
vmstat 1 5 # 5 samples, 1 second apart
vmstat -s # detailed memory stats (human-readable totals)
vmstat -s | grep "total memory" # total memory in kB
Parse
free -moutput withawkto get a clean integer value:
free -m | awk '/^Mem:/ {print $2}' prints total RAM in MB with no units attached.
top -bn1runstopin batch mode for a single iteration - perfect for scripting. The header lines contain load averages, running process counts, and memory totals all in one shot without needing an interactive terminal.
[Protected] (55) ๐ The System Inspector
Inspect system identity: kernel version, hostname, user info, and logged-in sessions.
- You’ve entered an uncharted system with no documentation and no map.
- Your mission: identify it completely using system inspection commands.
IDENTIFY THE SYSTEM!
- Get the complete system information in one line.
uname -a - Find out the system's network name.
hostname - Confirm your user identity - uid, gid, and group memberships.
id - See who else is currently logged in.
who - Run
./getKey.shwith your username and OS kernel name as arguments../getKey.sh "$(whoami)" "$(uname -s)"
Key Commands
| Command | Purpose |
|---|---|
uname |
Print the OS/kernel name |
uname -a |
All system info: kernel, hostname, release, arch |
uname -r |
Kernel release version (e.g. 5.15.0-91-generic) |
uname -n |
Network node hostname (same as hostname) |
uname -m |
Machine hardware architecture (e.g. x86_64, arm64) |
uname -s |
Kernel name (Linux, Darwin, FreeBSD) |
uname -v |
Kernel build version string |
uname -p |
Processor type |
hostname |
Display the system’s short hostname |
hostname -f |
Display the fully qualified domain name (FQDN) |
hostname -I |
Show all assigned IP addresses |
whoami |
Print the effective (current) username |
id |
Show uid, gid, and all supplementary groups |
id username |
Show uid/gid/groups for a named user |
id -u |
Print only the numeric user ID |
id -un |
Print only the username (equivalent to whoami) |
who |
Show currently logged-in users and their terminals |
w |
Like who but also shows what each user is running |
last |
Show login history from /var/log/wtmp |
last -n 10 |
Show the 10 most recent login records |
lscpu |
Detailed CPU architecture and topology info |
arch |
Print the machine architecture (short form) |
cat /etc/os-release |
Linux distribution name, version, and ID |
How uname, hostname, id, and who Work
# --- uname ---
uname # Linux
uname -s # Linux (Darwin on macOS)
uname -r # 5.15.0-91-generic
uname -m # x86_64 (arm64 on Apple Silicon)
uname -n # myhost.local
uname -a # Linux myhost 5.15.0 #1 SMP x86_64 GNU/Linux
# Use in scripts to detect the OS:
OS=$(uname -s)
if [ "$OS" = "Linux" ]; then
echo "Running on Linux"
elif [ "$OS" = "Darwin" ]; then
echo "Running on macOS"
fi
# --- hostname ---
hostname # myhost
hostname -f # myhost.corp.example.com
hostname -I # 192.168.1.42 10.0.0.1
# --- whoami / id ---
whoami # alice
id # uid=1000(alice) gid=1000(alice) groups=1000(alice),27(sudo),998(docker)
id -u # 1000
id -g # 1000
id -un # alice (same as whoami)
id -Gn # alice sudo docker (all group names)
id root # uid=0(root) gid=0(root) groups=0(root)
# --- who / w / last ---
who # alice pts/0 2024-03-10 09:15 (192.168.1.5)
w # shows user, terminal, login time, idle, CPU, command
last -n 5 # 5 most recent logins
last reboot # show all reboots
# --- architecture & distro ---
lscpu | grep "Architecture" # Architecture: x86_64
arch # x86_64
cat /etc/os-release # NAME="Ubuntu" VERSION="22.04.3 LTS" ...
# Combine for a system fingerprint:
echo "Host: $(hostname) | OS: $(uname -s) $(uname -r) | Arch: $(uname -m) | User: $(id -un)"
uname -sreturnsLinuxon Linux systems andDarwinon macOS - the result is case-sensitive. Use it in scripts to write cross-platform code that branches based on the operating system.
id -ureturns just the numeric uid;id -unreturns just the username string - both are more reliable in scripts than parsing the fullidoutput. Either is equivalent to runningwhoami.
[Protected] (56) โ๏ธ The Process Controller
Control processes with pgrep, pkill, nohup, nice, and renice - advanced process management.
- Rogue processes are multiplying in the background, consuming resources and hiding secrets.
- The Process Controller must find them, read their secrets, and terminate them in the correct order.
CONTROL THE CHAOS!
- Launch the background agents.
./launch_agents.sh - Find all running agent processes with their full command lines.
- The agents write output to log files - find and read the key log.
- Stop the main keeper process to begin the shutdown sequence.
- Run
./getKey.shafter all agents are stopped to retrieve the final code.
Key Commands
| Command | Purpose |
|---|---|
pgrep name |
Find PIDs of processes matching the name |
pgrep -l name |
Show PID and process name |
pgrep -a name |
Show PID and full command line |
pgrep -u user |
Find all processes belonging to a user |
pgrep -f pattern |
Match against the full command line string |
pgrep -c name |
Print only the count of matching processes |
pkill name |
Send SIGTERM to all processes matching name |
pkill -9 name |
Force kill with SIGKILL (cannot be caught) |
pkill -f pattern |
Kill by matching full command line |
pkill -u user |
Kill all processes belonging to a user |
pkill -SIGNAL name |
Send any named signal (e.g. -HUP, -USR1) |
nohup cmd & |
Run command immune to terminal hangup (SIGHUP) |
nohup cmd > out.log 2>&1 & |
nohup with explicit output capture |
disown %1 |
Remove job from shell’s job table after backgrounding |
nice -n 10 cmd |
Run command with lower CPU priority (niceness 10) |
nice -n -5 cmd |
Higher priority (requires root; negative value) |
renice -n 5 -p PID |
Adjust priority of an already-running process |
renice -n 10 -u user |
Lower priority of all processes for a user |
killall name |
Kill all processes with the exact given name |
wait PID |
Pause the shell until the specified PID finishes |
How pgrep, pkill, nohup, and nice Work
# --- pgrep ---
pgrep bash # list PIDs of all bash processes
pgrep -l python3 # PID + name: "12345 python3"
pgrep -a gunicorn # PID + full argv: "12345 gunicorn app:create_app"
pgrep -u www-data # all PIDs owned by www-data
pgrep -f "python.*worker" # match full command line with regex
pgrep -c nginx # just the count (e.g. "4")
# --- pkill ---
pkill nginx # graceful stop (SIGTERM) of nginx workers
pkill -HUP nginx # reload config (SIGHUP)
pkill -9 zombie_proc # force kill unresponsive process
pkill -f "python manage.py" # kill by matching command string
pkill -u testuser # kill all processes owned by testuser
# --- nohup ---
nohup ./long_job.sh & # run in background, immune to logout
nohup ./server.sh > server.log 2>&1 & # capture all output explicitly
echo $! # PID of the last backgrounded process
disown %1 # detach from shell without nohup
# --- nice / renice ---
nice -n 10 make -j8 # compile with low CPU priority
nice -n 19 rsync -a /src /dst # lowest possible priority for backup
sudo nice -n -10 ./latency_sensitive_task # elevate (root required)
renice -n 15 -p $(pgrep ffmpeg) # lower a running encode job
renice -n 10 -u batch_user # throttle all processes of a user
ps -o pid,ni,comm -p $(pgrep ffmpeg) # verify the new nice value
# --- killall / wait ---
killall -q firefox # quiet kill (no error if not found)
./launch_agents.sh &
BGPID=$!
wait $BGPID # block until background job finishes
echo "exit code: $?"
pgrep -af "agent"shows full command lines alongside PIDs, making it easy to distinguish between multiple agent processes. Use the output to identify which PID belongs toagent_keeperbefore runningpkill.
nohupredirects stdout and stderr tonohup.outin the current directory by default if you don’t redirect them yourself. Always redirect explicitly with> logfile 2>&1 &to know exactly where the output goes.
๐ Bonus Track Complete!
You’ve conquered all 6 bonus rooms and mastered the complete set of 100 bash commands.
You are now truly a Bash Master.
Use masterkey to take the Exit Exam if you haven’t already:
[Protected] ๐ Exit Exam
Answer 5 random Linux command questions to escape the final room.
You’ve made it to the final room! Prove your Linux mastery by answering 5 random questions about commands, symbols, and operators.
THE EXIT EXAM!
This room tests your knowledge of the Linux commands you’ve used throughout the escape rooms.
1. Run `./getKey.sh` to start the quiz.
2. Answer all **5 questions** correctly.
3. Questions are selected **randomly** from a pool each time.
4. One wrong answer and you must start over!
5. Answer all correctly to receive the final password.
Topics Covered
| Category | Examples |
|---|---|
| File & Directory | find, ls, cat, grep, wc, sort, uniq |
| Text Processing | sed, awk, tr, cut, tac, rev, head, tail |
| Archives | tar, gzip, base64 |
| System & Processes | ps, kill, chmod, chown, export, alias |
| Symbols & Operators | \|, >>, &&, ~, # |
How to Run
Hint 1
Each answer is the exact command name (or symbol) -case-sensitive, no spaces.
Hint 2
The question tells you the character count: e.g., (4 chars) means the answer is 4 characters long.
Hint 3
Think back through all the commands you used in Rooms 01-12!