April 11, 2026

Linux Command Line Basics for CTF Competitions

The essential Linux commands every CTF beginner needs: file inspection, text searching, permissions, base64 decoding, ssh, and more -- with examples from real picoCTF General Skills challenges.

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 terminal
cat flag.txt
# Scroll through a large file (press q to quit)
less large-output.txt
# Print only the first 20 lines
head -n 20 log.txt
# Print only the last 20 lines
tail -n 20 log.txt
# Watch a file as it grows in real time
tail -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-interlaced
file 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 binary
strings binary-file
# Filter for strings that look like a picoCTF flag
strings 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 dump
xxd file.bin
# Show only the first 64 bytes
xxd file.bin | head -n 4
# Search the hex dump for the flag prefix
xxd file.bin | grep -i '7069636f' # '7069636f' = 'pico' in hex

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 file
grep 'picoCTF' output.txt
# Case-insensitive search
grep -i 'picoctf' output.txt
# Search recursively through all files in a directory
grep -r 'picoCTF' ./
# Show 2 lines before and after the match (context)
grep -C 2 'picoCTF' output.txt
# Show only the filename, not the matching line
grep -rl 'picoCTF' ./
# Invert: show lines that do NOT match
grep -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 flag
strings binary-file | grep picoCTF
# Pipe through less to scroll through long output
strings binary-file | less
# Count how many lines match
strings binary-file | grep pico | wc -l

find: locate files by name or type

# Find all .txt files starting from the current directory
find . -name '*.txt'
# Find a file with a specific name
find . -name 'flag.txt'
# Find all files modified in the last 24 hours
find . -mtime -1
# Find files larger than 1 MB
find . -size +1M

sort and uniq: clean up output

# Sort lines alphabetically
sort words.txt
# Remove duplicate lines (input must be sorted first)
sort words.txt | uniq
# Count occurrences of each unique line
sort words.txt | uniq -c | sort -rn
# Count total lines / words / characters in a file
wc -l output.txt
wc -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.txt
drwxr-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 owner
chmod +x challenge-script.sh
# Run it
./challenge-script.sh
# Same for a compiled binary
chmod +x vuln
./vuln
# Give everyone execute permission
chmod 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 tool
sudo apt install netcat-openbsd
# See what commands you are allowed to run as root
sudo -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 string
echo 'cGljb0NURntlbmNvZGluZ19mdW59' | base64 -d
# Output: picoCTF{encoding_fun}
# Decode a base64-encoded file
base64 -d encoded.txt
# Encode text to base64
echo -n 'picoCTF{test}' | base64
# Decode and save the result to a file
base64 -d encoded.txt > decoded-output
# Chain: decode multiple rounds of base64
cat 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 base64
python3 -c "import base64; print(base64.b64decode('cGljb0NURntmbGFnfQ==').decode())"
# Decode hex
python3 -c "print(bytes.fromhex('7069636f435446').decode())"
# Decode URL encoding
python3 -c "from urllib.parse import unquote; print(unquote('%70%69%63%6f'))"
# XOR a byte with a key
python3 -c "print(bytes([b ^ 0x42 for b in b'\x12\x2b\x21\x2d']))"

xxd: hex encoding and decoding

# Encode binary to hex
xxd -p flag.bin
# Decode hex back to binary
echo '7069636f435446' | xxd -r -p
# Convert hex string to binary file
echo '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 file
unzip challenge.zip
# Extract into a specific directory
unzip challenge.zip -d extracted/
# List contents without extracting
unzip -l challenge.zip
# Create a zip file
zip -r archive.zip directory/

tar archives

# Extract a .tar file
tar -xf archive.tar
# Extract a .tar.gz (gzip compressed) file
tar -xzf archive.tar.gz
# Extract a .tar.bz2 (bzip2 compressed) file
tar -xjf archive.tar.bz2
# List contents without extracting
tar -tf archive.tar.gz
# Extract to a specific directory
tar -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 stdout
gzip -dc file.gz > file
# Decompress bzip2
bzip2 -d file.bz2
# Decompress xz
xz -d file.xz
# Always check the real type first
file 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 data
mv mystery mystery.zip && unzip mystery.zip
file extracted-file
# extracted-file: gzip compressed data
mv extracted-file extracted-file.gz && gzip -d extracted-file.gz
file extracted-file
# extracted-file: ASCII text
cat extracted-file

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@host
ssh ctf-player@challenge.picoctf.org
# Connect on a non-standard port
ssh 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 first
chmod 600 ctf-key.pem
# Connect using the key
ssh -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 directory
scp ctf-player@challenge.picoctf.org:/home/ctf-player/flag.txt .
# Copy with a custom port
scp -P 64542 ctf-player@challenge.picoctf.org:/home/ctf-player/binary .
# Copy using a key file
scp -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 base64
data = open('encoded.txt').read().strip()
for i in range(10):
try:
data = base64.b64decode(data).decode()
except Exception:
break
print(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.

CommandWhat it does
ls -laList all files including hidden ones, with permissions
pwdPrint your current directory path
cd dir/Change into a directory
cat file.txtPrint a file to the terminal
file mysteryDetect the real file type from its header bytes
strings binary | grep picoPull ASCII text from a binary, search for flag
xxd file.bin | headHex 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 -dDecode a base64 string
chmod +x script.sh && ./script.shMake a file executable and run it
unzip challenge.zipExtract a zip archive
tar -xzf archive.tar.gzExtract a gzip-compressed tar archive
gzip -d file.gzDecompress a gzip file
ssh user@host -p PORTConnect to a remote machine over SSH
scp user@host:/path/file .Copy a file from a remote machine
sort file | uniq -c | sort -rnCount and sort unique lines
command | lessScroll through long output
echo 'text' | tr 'a-z' 'n-za-m'Apply ROT13 to text

Recommended first-pass workflow for a General Skills challenge

  1. If you receive a file: run file on it first to find out what you actually have. Do not trust the extension.
  2. If it is a text file: cat it and look for the flag directly. If it is long, pipe through grep picoCTF.
  3. If it is a binary or image: run strings file | grep picoCTF. Also try xxd file | grep -i pico.
  4. If it is a compressed file: decompress it with the right tool, then go back to step 1. Repeat until you reach plain text.
  5. 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.
  6. If the challenge gives you a host and port: check if it is SSH (try ssh user@host -p PORT) or raw TCP (use nc host PORT). The challenge description usually says which.