Introduction
The Linux command line is the single most important skill you can have going into a CTF competition. Nearly every picoCTF challenge category touches it: General Skills challenges run entirely inside a terminal, forensics challenges require tools that only exist on the command line, and binary exploitation challenges connect to remote services over SSH or netcat. If you are not comfortable at a Linux prompt, you will hit a wall fast.
General Skills challenges in picoCTF are intentionally designed as a tutorial for the command line. They cover reading files, navigating directories, decoding encoded data, working with archives, and connecting to remote services. Mastering the commands in this guide means you will solve those challenges quickly and build a foundation that carries over to every other category.
This guide covers the commands that appear over and over in real CTF workflows. Each section includes copy-pasteable examples and links to specific picoCTF challenges where the technique was the key to finding the flag.
Getting a Linux environment: On Windows, install WSL2 (Windows Subsystem for Linux) from the Microsoft Store and choose Ubuntu. On macOS, the built-in Terminal is close enough for most commands. For a ready-to-go CTF setup, download Kali Linux as a VM or WSL image. It comes with most CTF tools pre-installed.
Raw TCP connections: This guide focuses on local file and system commands. For connecting to challenge servers over raw TCP (the nc host port pattern), see the netcat guide.
Reading file contents
Most CTF flags are sitting in a file somewhere. The question is whether you can see them. Different tools are better for different file types.
cat, less, head, tail
# Print an entire file to the terminalcat flag.txt# Scroll through a large file (press q to quit)less large-output.txt# Print only the first 20 lineshead -n 20 log.txt# Print only the last 20 linestail -n 20 log.txt# Watch a file as it grows in real timetail -f log.txt
file: detect the real file type
Challenge files often have misleading extensions, or no extension at all. The file command inspects the actual file header bytes and tells you what the file really is. This is one of the first things you should run on any unknown file.
# What type is this file really?file mystery# mystery: PNG image data, 800 x 600, 8-bit/color RGBA, non-interlacedfile archive.dat# archive.dat: gzip compressed data, was 'flag.txt', ...file binary# binary: ELF 64-bit LSB executable, x86-64, ...
strings: pull text from binaries
strings scans a file for sequences of printable ASCII characters and prints them. It is invaluable for binary files: executables, images, or any file where the flag might be embedded as plain text inside binary data.
# Extract all printable strings from a binarystrings binary-file# Filter for strings that look like a picoCTF flagstrings binary-file | grep picoCTF# Set a minimum string length (default is 4)strings -n 8 binary-file
xxd: hex dump
xxd prints a hex dump of a file: every byte as a two-digit hex value, with the printable ASCII representation on the right. Use it when you need to inspect binary data at the byte level, look for magic bytes (file signatures), or spot data that strings would miss because the characters are too far apart.
# Full hex dumpxxd file.bin# Show only the first 64 bytesxxd file.bin | head -n 4# Search the hex dump for the flag prefixxxd file.bin | grep -i '7069636f' # '7069636f' = 'pico' in hex
Related challenges
Searching and filtering
You will rarely need to read an entire file manually. These commands let you search through large outputs and narrow them down to the line that contains the flag.
grep: search for patterns
# Search for a string inside a filegrep 'picoCTF' output.txt# Case-insensitive searchgrep -i 'picoctf' output.txt# Search recursively through all files in a directorygrep -r 'picoCTF' ./# Show 2 lines before and after the match (context)grep -C 2 'picoCTF' output.txt# Show only the filename, not the matching linegrep -rl 'picoCTF' ./# Invert: show lines that do NOT matchgrep -v 'WARNING' log.txt
Pipes: chaining commands together
The pipe character (|) sends the output of one command into the input of the next. This is the most powerful pattern in the Linux command line. Most CTF one-liners are a chain of two or three commands connected by pipes.
# Classic pattern: extract strings from a binary, then grep for the flagstrings binary-file | grep picoCTF# Pipe through less to scroll through long outputstrings binary-file | less# Count how many lines matchstrings binary-file | grep pico | wc -l
find: locate files by name or type
# Find all .txt files starting from the current directoryfind . -name '*.txt'# Find a file with a specific namefind . -name 'flag.txt'# Find all files modified in the last 24 hoursfind . -mtime -1# Find files larger than 1 MBfind . -size +1M
sort and uniq: clean up output
# Sort lines alphabeticallysort words.txt# Remove duplicate lines (input must be sorted first)sort words.txt | uniq# Count occurrences of each unique linesort words.txt | uniq -c | sort -rn# Count total lines / words / characters in a filewc -l output.txtwc -w output.txt
Related challenges
File permissions and execution
You download a script or a compiled binary from a challenge. You try to run it and get Permission denied. This section explains why that happens and how to fix it.
Reading ls -la output
The first column in ls -la output is the permissions string. It looks like -rwxr-xr-x. Here is how to read it:
# Example ls -la output:-rwxr-xr-x 1 user user 12345 Apr 11 12:00 challenge-binary-rw-r--r-- 1 user user 256 Apr 11 12:00 flag.txtdrwxr-xr-x 2 user user 4096 Apr 11 12:00 secret-dir/# Position breakdown:# - (or d) : file (-) or directory (d)# rwx : owner permissions (read, write, execute)# r-x : group permissions# r-x : everyone else (world) permissions# challenge-binary has the x (execute) bit set -> you can run it# flag.txt has no x bit -> it is a data file, not a program
Making a file executable
If you download a script or binary and the execute bit is not set, use chmod +x to add it. Then run the file with ./filename (the ./ prefix tells the shell to look in the current directory).
# Add execute permission for the ownerchmod +x challenge-script.sh# Run it./challenge-script.sh# Same for a compiled binarychmod +x vuln./vuln# Give everyone execute permissionchmod a+x script.sh
sudo basics
sudo runs a command as the superuser (root). You will mainly need it to install tools. Avoid running CTF challenge binaries with sudo unless the challenge explicitly requires it.
# Install a toolsudo apt install netcat-openbsd# See what commands you are allowed to run as rootsudo -l
Related challenges
Encoding and decoding
Flags in CTF challenges are frequently encoded to make them less obvious. Base64 is by far the most common encoding in General Skills and forensics challenges. The Linux command line handles it natively.
base64
# Decode a base64 stringecho 'cGljb0NURntlbmNvZGluZ19mdW59' | base64 -d# Output: picoCTF{encoding_fun}# Decode a base64-encoded filebase64 -d encoded.txt# Encode text to base64echo -n 'picoCTF{test}' | base64# Decode and save the result to a filebase64 -d encoded.txt > decoded-output# Chain: decode multiple rounds of base64cat encoded.txt | base64 -d | base64 -d | base64 -d
Python one-liners for decoding
When the shell tools are not flexible enough, Python is your next stop. These one-liners run without creating a file.
# Decode base64python3 -c "import base64; print(base64.b64decode('cGljb0NURntmbGFnfQ==').decode())"# Decode hexpython3 -c "print(bytes.fromhex('7069636f435446').decode())"# Decode URL encodingpython3 -c "from urllib.parse import unquote; print(unquote('%70%69%63%6f'))"# XOR a byte with a keypython3 -c "print(bytes([b ^ 0x42 for b in b'\x12\x2b\x21\x2d']))"
xxd: hex encoding and decoding
# Encode binary to hexxxd -p flag.bin# Decode hex back to binaryecho '7069636f435446' | xxd -r -p# Convert hex string to binary fileecho '7069636f435446' | xxd -r -p > output.bin
Related challenges
Working with archives and compressed files
Challenge files often arrive as zips, tarballs, or gzip archives. Sometimes they are nested: you unzip a zip to get a gz file, decompress that to get another zip, and so on. Use file at each step to identify what you actually have before trying to decompress.
zip files
# Extract a zip fileunzip challenge.zip# Extract into a specific directoryunzip challenge.zip -d extracted/# List contents without extractingunzip -l challenge.zip# Create a zip filezip -r archive.zip directory/
tar archives
# Extract a .tar filetar -xf archive.tar# Extract a .tar.gz (gzip compressed) filetar -xzf archive.tar.gz# Extract a .tar.bz2 (bzip2 compressed) filetar -xjf archive.tar.bz2# List contents without extractingtar -tf archive.tar.gz# Extract to a specific directorytar -xzf archive.tar.gz -C output/
gzip and other formats
# Decompress a .gz file (replaces the compressed file)gzip -d file.gz# Keep the original and write decompressed to stdoutgzip -dc file.gz > file# Decompress bzip2bzip2 -d file.bz2# Decompress xzxz -d file.xz# Always check the real type firstfile mystery-file# mystery-file: bzip2 compressed data, block size = 900k# -> rename it and use bzip2 -d
The chained compression pattern
Some CTF challenges chain multiple layers of compression. The workflow is always: check type with file, rename if needed, decompress, repeat.
file mystery# mystery: Zip archive datamv mystery mystery.zip && unzip mystery.zipfile extracted-file# extracted-file: gzip compressed datamv extracted-file extracted-file.gz && gzip -d extracted-file.gzfile extracted-file# extracted-file: ASCII textcat extracted-file
Related challenges
SSH and remote connections
SSH (Secure Shell) lets you log into a remote machine and run commands on it. Some picoCTF challenges give you an SSH address instead of (or in addition to) a netcat connection. SSH is authenticated and encrypted, unlike raw netcat. For raw TCP connections without authentication, see the netcat guide.
Connecting with SSH
# Basic connection: ssh user@hostssh ctf-player@challenge.picoctf.org# Connect on a non-standard portssh ctf-player@challenge.picoctf.org -p 64542# Accept the host key fingerprint on first connection# Type 'yes' when prompted:# Are you sure you want to continue connecting (yes/no)? yes# Specify a password directly (use with caution in scripts)# Better to type it interactively when prompted
SSH keys
Some challenges provide an SSH private key file instead of a password. You pass it with the -i flag. The key file must have restrictive permissions or SSH will refuse to use it.
# Fix permissions on the key file firstchmod 600 ctf-key.pem# Connect using the keyssh -i ctf-key.pem ctf-player@challenge.picoctf.org -p 64542# Generate your own SSH key pair (for challenges that ask you to supply a public key)ssh-keygen -t ed25519 -C 'ctf'# Your public key is at ~/.ssh/id_ed25519.pub
scp: copy files over SSH
Once you are logged into a remote challenge box and have generated or found a file, use scp to copy it back to your local machine for analysis with your full tool set.
# Copy a remote file to your local current directoryscp ctf-player@challenge.picoctf.org:/home/ctf-player/flag.txt .# Copy with a custom portscp -P 64542 ctf-player@challenge.picoctf.org:/home/ctf-player/binary .# Copy using a key filescp -i ctf-key.pem ctf-player@challenge.picoctf.org:/home/ctf-player/secret .
Related challenges
Quick one-liners for CTF
These are copy-pasteable one-liners that experienced CTFers reach for regularly. Each one combines the commands from earlier sections into a pattern that solves a specific type of problem.
Search every file in a directory for a flag
for f in *; do echo "=== $f ==="; strings "$f" | grep pico; done
Loops over every file, prints its name, then searches its printable strings for the flag prefix. Useful when a challenge drops you into a directory full of files and you need to find which one contains the flag.
Decode base64 with Python
python3 -c "import base64; print(base64.b64decode('cGljb0NURntmbGFnfQ==').decode())"
Replace the string inside the quotes with whatever encoded value the challenge gives you. Add .decode() to get a string output instead of a bytes object.
Hex dump search
xxd file.bin | grep -i 'pico'
Dumps the file as hex with ASCII on the right, then filters for the flag prefix. The -i flag on grep makes the search case-insensitive, useful if the flag prefix is mixed case in the hex output.
ROT13 decoding
echo 'cvpbPGS{flag}' | tr 'a-zA-Z' 'n-za-mN-ZA-M'
ROT13 rotates each letter by 13 positions. The tr command translates characters in bulk. This is the fastest way to apply ROT13 without opening a website or writing a script.
Find and print files containing a string
grep -rl 'picoCTF' . | xargs cat
grep -rl finds all files that contain the string and prints their paths. xargs cat feeds those paths to cat to print the contents. Useful for quickly reading every file that matched.
Strip non-printable characters
strings file | tr -d '\000-\037\177-\377'
Removes control characters and high bytes from output, leaving only clean ASCII. Helpful when cat on a binary file garbles your terminal.
Decode multiple rounds of base64
python3 -c "import base64data = open('encoded.txt').read().strip()for i in range(10):try:data = base64.b64decode(data).decode()except Exception:breakprint(data)"
Some challenges encode a flag in base64 many times over. This script tries to decode up to 10 times, stopping when it can no longer decode. Adjust the range for more rounds.
Quick reference
The commands you will use in almost every CTF session. Bookmark this and refer to it while you are working through General Skills challenges.
| Command | What it does |
|---|---|
| ls -la | List all files including hidden ones, with permissions |
| pwd | Print your current directory path |
| cd dir/ | Change into a directory |
| cat file.txt | Print a file to the terminal |
| file mystery | Detect the real file type from its header bytes |
| strings binary | grep pico | Pull ASCII text from a binary, search for flag |
| xxd file.bin | head | Hex dump the start of a file |
| grep -r 'picoCTF' . | Search recursively for a string in all files |
| find . -name '*.txt' | Find files by name pattern |
| echo 'base64==' | base64 -d | Decode a base64 string |
| chmod +x script.sh && ./script.sh | Make a file executable and run it |
| unzip challenge.zip | Extract a zip archive |
| tar -xzf archive.tar.gz | Extract a gzip-compressed tar archive |
| gzip -d file.gz | Decompress a gzip file |
| ssh user@host -p PORT | Connect to a remote machine over SSH |
| scp user@host:/path/file . | Copy a file from a remote machine |
| sort file | uniq -c | sort -rn | Count and sort unique lines |
| command | less | Scroll through long output |
| echo 'text' | tr 'a-z' 'n-za-m' | Apply ROT13 to text |
Recommended first-pass workflow for a General Skills challenge
- If you receive a file: run
fileon it first to find out what you actually have. Do not trust the extension. - If it is a text file:
catit and look for the flag directly. If it is long, pipe throughgrep picoCTF. - If it is a binary or image: run
strings file | grep picoCTF. Also tryxxd file | grep -i pico. - If it is a compressed file: decompress it with the right tool, then go back to step 1. Repeat until you reach plain text.
- If the data looks like base64 (only A-Z, a-z, 0-9, +, /, =): decode it with
echo '...' | base64 -d. Some challenges layer this multiple times. - If the challenge gives you a host and port: check if it is SSH (try
ssh user@host -p PORT) or raw TCP (usenc host PORT). The challenge description usually says which.