hacking track
week 11 · day 5

Bash Scripting for Hackers

Automate recon and glue your tools together

Easy 120 min 180 xp
After this you can
  • Write a runnable bash script with variables, conditionals, and loops
  • Read command exit codes and branch on success or failure
  • Loop over hosts, ports, or wordlist entries to automate repetitive work
  • Turn a manual recon routine into one reusable script

From typing commands to automating them

You already compose one-liners at the prompt. A script is just those commands saved in a file, with the logic — repeat this, only do that if this succeeded — made explicit. For a hacker, bash is the glue: it drives nmap across a subnet, hammers a login form with a wordlist, parses tool output, and chains ten tools into one command you run once. You will not write beautiful software in bash. You will write fast, ugly, effective automation, which is exactly what the job needs.

The skeleton

Every script starts the same way and must be made executable before it will run:

hello.sh — the anatomysh
1#!/bin/bash
2# ^ the "shebang": tells the OS to run this file with /bin/bash
3
4target="10.10.14.7" # a variable — NO spaces around the =
5echo "Scanning $target" # $target expands to its value
6
7# run it:
8# chmod +x hello.sh (make it executable — recall the x bit from Linux I)
9# ./hello.sh
Watch out
Bash is fussy about spaces in ways that catch everyone. x="1" is assignment; x = "1" is an error (bash thinks x is a command). Inside tests, [ "$x" = "1" ] needs spaces *inside* the brackets. And always quote your variables — "$target" not $target — or a value with a space or empty value will silently break the command. Quoting bugs are the number-one source of "why doesn't my script work."

Exit codes: how a script knows if something worked

Every command, when it finishes, sets a hidden number: $?, its exit code. 0 means success, anything else means failure. This is how your script makes decisions — not by parsing output, but by asking "did that succeed?"

branching on successsh
1ping -c1 -W1 "$target" > /dev/null 2>&1 # silence it; we only want the exit code
2if [ $? -eq 0 ]; then
3 echo "$target is UP"
4else
5 echo "$target is down"
6fi
7
8# the idiomatic short form — && runs on success, || runs on failure:
9ping -c1 -W1 "$target" >/dev/null 2>&1 && echo "UP" || echo "down"

Why this is the core idea

ping returns 0 if the host replied, non-zero if it didn't. You never had to read ping's text — you asked $?. That pattern (run a command, branch on its exit code) is how you build tools that *react*: is the port open? did the login succeed? did the exploit land? The && / || short form is the same logic compressed: A && B means "do B only if A succeeded," A || B means "do B only if A failed."

Loops: the reason to automate at all

Recon is repetitive by nature — the same check across many hosts, ports, or words. A loop turns "do this 254 times by hand" into three lines. The for loop walks a list; the shell's brace expansion {1..254} generates one.

sweeping a subnet for live hostssh
1#!/bin/bash
2subnet="192.168.56"
3for i in {1..254}; do
4 ip="$subnet.$i"
5 # background each ping so all 254 fire nearly at once, not one-by-one:
6 ping -c1 -W1 "$ip" >/dev/null 2>&1 && echo "$ip is up" &
7done
8wait # block until all the backgrounded pings finish
looping over a wordlist (the shape of every brute-forcer)sh
1#!/bin/bash
2# try each word from a file as input to some check
3while read -r word; do
4 echo "trying: $word"
5 # e.g. hydra/curl/ssh attempt goes here, branching on its exit code
6done < wordlist.txt

The subnet sweep shows two techniques worth naming. for i in {1..254} loops the numbers 1 to 254. Appending & to the ping backgrounds each one so all 254 run concurrently instead of taking a slow second each; wait then pauses the script until they've all reported. That single trick — fan out with &, collect with wait — turns a four-minute serial scan into a four-second parallel one. The while read loop is the skeleton of every password/subdomain/directory brute-forcer you'll ever write: read a line, try it, react.

Functions: naming a routine

When a block of logic repeats, wrap it in a function. Arguments arrive as $1, $2, and so on. Functions turn a sprawling script into named steps you can reuse and reason about.

a reusable checksh
1#!/bin/bash
2scan_port() { # $1 = host, $2 = port
3 # bash can open a TCP socket itself via /dev/tcp — no nmap needed
4 (echo > "/dev/tcp/$1/$2") >/dev/null 2>&1 \
5 && echo " [$2] open" \
6 || echo " [$2] closed"
7}
8
9host="$1"
10echo "Scanning $host"
11for port in 21 22 80 139 443 445 3306 8080; do
12 scan_port "$host" "$port"
13done
Key idea
That last script is a real, working port scanner in fifteen lines, and it uses no external tools at all — bash's built-in /dev/tcp/host/port opens a TCP connection, and its success or failure (the exit code again) tells you whether the port is open. This matters on a stripped-down victim box where nmap isn't installed but bash always is. You'll reach for exactly this when you land somewhere bare.

That is the whole toolkit: variables, exit codes, if/&&/||, for and while loops, and functions. It is not much, and it is enough. Everything from a subnet sweeper to a login brute-forcer to a "grab all the interesting files and tar them up" looter is these pieces arranged for a purpose. Bash won't make you a software engineer; it will make you fast, and speed is leverage.

finished reading?
Your task, you write the code

Write a recon script

Write recon.sh that takes a target IP as its argument ($1) and does three things: (1) checks the host is up with a single ping and prints UP/DOWN, branching on the exit code; (2) if it's up, scans a list of common ports using bash's /dev/tcp (no nmap) and prints which are open; (3) writes the open ports to a file named <ip>.ports. Make it executable and run it against a box in your lab. Then extend it: accept a whole /24 (loop 1..254, backgrounded) and only port-scan the hosts that answered.

deliverable: recon.sh
build & run
$ chmod +x recon.sh
$ ./recon.sh 192.168.56.102
$ cat 192.168.56.102.ports
self-review before running
  • The script branches on $? (or && / ||), not on parsing text
  • Port scanning works with /dev/tcp and no external tools
  • Variables are quoted ("$1", "$ip") throughout
  • The subnet version fans out with & and collects with wait
stretchAdd a while-read loop that takes a small wordlist and, for a host with port 22 open, prints 'would try: <user>' for each word — the skeleton of an SSH brute-forcer, without actually attacking anything. You're practicing the control flow real tools use.

Self-check

01What does an exit code ($?) of 0 mean?
02In `cmd1 && cmd2`, when does cmd2 run?
03In the subnet sweep, what do the trailing `&` and the final `wait` accomplish?
04Why can bash scan a TCP port with no nmap installed?
0/4 correct · 0/4 checked