Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3e723aa
adds a fire-and-forget recon tool that does OS-detect, VLAN mapping, …
brent-mills-engineering Jun 13, 2026
f143d98
Adds a passive scan for LLDP + CDP, mDNS, SSDP, NetBIOS, passive ARP,…
brent-mills-engineering Jun 13, 2026
5f71970
adds a passive scan that sets up a DHCP client and profiles the offer…
brent-mills-engineering Jun 13, 2026
8c731ea
Adds a quickhits scan for easy wins. Searches for SMBv1, SNMP default…
brent-mills-engineering Jun 13, 2026
c1bbbb8
Adds a new scanner for risky or rogue devices. Outputs OUI, port, ban…
brent-mills-engineering Jun 13, 2026
26fc9e4
Adds a recon scan for network segment reachability, egress filtering,…
brent-mills-engineering Jun 13, 2026
995987c
Merge pull request #1 from brent-mills-engineering/bmills-new-recon
brent-mills-engineering Jun 13, 2026
c389d46
Add a .gitignore file to keep DS_Store files out of git. Add a new to…
brent-mills-engineering Jun 14, 2026
92e519d
Add a new payload to scan for cameras and VOIP devices
brent-mills-engineering Jun 14, 2026
21b7848
Add a new payload that generates network maps that can be formed into…
brent-mills-engineering Jun 14, 2026
f4b2927
Add a payload that allows scanning of target devices by offering dhcp…
brent-mills-engineering Jun 14, 2026
a4cf6d5
Fix vlan traversal
brent-mills-engineering Jun 14, 2026
aba595b
Add a payload that does a quick survey of unknown ethernet jacks
brent-mills-engineering Jun 14, 2026
b83912b
Add a recon payload that uses lldp to determine poe capabilities.
brent-mills-engineering Jun 14, 2026
db37a07
Add a remote debug bridge that creates an ssh tunnel to a known remot…
brent-mills-engineering Jun 14, 2026
45b33c9
Use lldp and cdp to fetch switchport id where available and display o…
brent-mills-engineering Jun 14, 2026
30bbfe8
clean up doc formatting around vlan traversal
brent-mills-engineering Jun 14, 2026
33e7ecd
Standardize payload header information
brent-mills-engineering Jun 14, 2026
9cd34c7
Standardize payload header information
brent-mills-engineering Jun 14, 2026
0bbff29
Merge pull request #2 from brent-mills-engineering/bmills-new-recon
brent-mills-engineering Jun 14, 2026
3f6282f
remove glow() wrapper and use LED directly instead
brent-mills-engineering Jun 15, 2026
b54cf1c
replace instances of with
brent-mills-engineering Jun 16, 2026
b1f0e88
Replace instances of with
brent-mills-engineering Jun 16, 2026
84b7d2f
replace calls to `say` with with `SCREEN_WRITE`.
brent-mills-engineering Jun 16, 2026
1828376
Merge pull request #3 from brent-mills-engineering/bmills-standardize…
brent-mills-engineering Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.DS_Store
154 changes: 154 additions & 0 deletions payloads/library/general/remote_debug_bridge/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
# Shark Jack Display - Remote Debug Bridge

Turns the Shark Jack into a temporary remote-troubleshooting appliance. Plug it
into the stranded network, walk to a desk, and reach the gear from a laptop
instead of from a cold aisle. It pulls an address, opens a **reverse SSH tunnel**
out to a jump host you control, exposes its own sshd back through that tunnel, and
**auto-reconnects** if the link blips, all while showing live status on the OLED
and logging every attempt on-device.

> Born from a three-day production outage debugging isolated Brocade VCS fabric
> nodes with five people plugged directly into switches in a datacenter. The
> point of this payload is to never have to do that again: drop it on the
> segment, and triage from the conference room.

> **Authorized use only.** This opens a remote-access path *into* a network. Run
> it only on networks you own or are contracted to support, with written
> authorization, and tear it down when the work is done. Auth is key-only to a
> jump host you control, with strict host-key checking; nothing is exfiltrated and
> local logs stay on-device under `/root/loot/debug_bridge/`. See the Legal and
> Disclaimer sections of the repository README.

## How it works

| Phase | What happens |
|-------|--------------|
| **Setup** | preflight: `ssh` present, key present, `JUMP_HOST` configured, jump host key pinned in `KNOWN_HOSTS`. Any miss fails fast with a screen hint |
| **Network up** | `NETMODE` per `NET_MODE` (DHCP / static / auto), waits for carrier and an IPv4 address, best-effort NTP sync |
| **Reachability** | pings the jump host (informational; it still tries SSH if ICMP is filtered) and writes the connection summary |
| **Bridge loop** | a short auth'd command proves egress + key auth and drops an "I'm alive" breadcrumb on the jump host, then opens `ssh -N -R` and holds it. On exit it reconnects (see below) |

## What you connect to

The reverse forward maps a port on the jump host's loopback back to the Shark
Jack's own sshd:

```
jump host 127.0.0.1:REMOTE_PORT -> Shark Jack 127.0.0.1:22
```

So from the jump host you run:

```
ssh -p 2222 root@127.0.0.1
```

and you land on the **Shark Jack**, which is L2-present on the stranded segment.
From that shell you reach the switches / fabric (`ssh admin@<switch-mgmt-ip>`,
set up further local forwards, etc). The Shark Jack is your beachhead; it is not
forwarding the switches directly.

## One-time setup

**1. Jump host** (a small always-on VM with a public IP). Create an unprivileged
user for the bridge:

```
sudo adduser --disabled-password sharkdebug
```

**2. Keypair on the Shark Jack:**

```
ssh-keygen -t ed25519 -f /root/.ssh/sharkjack_debug -N ""
```

**3. Authorize the key on the jump host** (paste `sharkjack_debug.pub` into the
jump user's `authorized_keys`). Optionally lock the key down to just this tunnel:

```
restrict,permitlisten="127.0.0.1:2222" ssh-ed25519 AAAA... sharkjack_debug
```

**4. Pin the jump host's key** on the Shark Jack (required, because the payload
enforces `StrictHostKeyChecking=yes`):

```
ssh-keyscan -p 22 <JUMP_HOST> >> /root/.ssh/known_hosts
```

**5. Edit the CONFIG block** of [`payload.txt`](payload.txt): set at least
`JUMP_HOST` (and `JUMP_USER` if you changed it). Deploy.

## Output (loot)

`/root/loot/debug_bridge/<UTC-timestamp>_<pid>/`:

```
summary.txt <- connection info + how to connect + final status (read this first)
bridge.log <- chronological attempt timeline (dials, uptimes, exit codes)
```

## Auto-reconnect behavior

The loop distinguishes a healthy session from a hard failure:

- A tunnel that stays up at least `STABLE_SECS` is treated as **healthy**. When it
later drops, the consecutive-failure counter resets, so a long triage session
survives any number of transient blips.
- Only **consecutive fast failures** (bad key, jump host down, `REMOTE_PORT`
already bound) count toward `MAX_RETRIES`, with a backoff that grows on each
repeat (capped at 60s). After `MAX_RETRIES` in a row, it gives up and ends red.

## Configuration (top of [`payload.txt`](payload.txt))

| Knob | Default | Notes |
|------|---------|-------|
| `JUMP_USER` | `sharkdebug` | unprivileged user on the jump host |
| `JUMP_HOST` | `X.X.X.X` | **must set**: jump host public IP/DNS. Placeholder fails fast |
| `JUMP_PORT` | `22` | jump host sshd port |
| `REMOTE_BIND` | `127.0.0.1` | bind the listener to the jump host loopback (not public) |
| `REMOTE_PORT` | `2222` | jump-host port that maps back to the Shark Jack |
| `SSH_KEY` | `/root/.ssh/sharkjack_debug` | private key for the bridge |
| `KNOWN_HOSTS` | `/root/.ssh/known_hosts` | pinned jump host key |
| `NET_MODE` | `DHCP_CLIENT` | `STATIC` for a datacenter mgmt drop (fill `STATIC_*`), or `AUTO` |
| `STATIC_IP` / `STATIC_SUBNET` / `STATIC_GATEWAY` | `192.168.1.*` | used only when `NET_MODE=STATIC` |
| `DHCP_WAIT_SECS` | `30` | wait for an address before failing |
| `CONNECT_TIMEOUT` | `10` | per-dial TCP/auth timeout |
| `MAX_RETRIES` | `15` | give up after this many *consecutive fast* failures |
| `RETRY_SLEEP` | `10` | base wait between dials (grows on repeats) |
| `STABLE_SECS` | `60` | a tunnel up this long resets the failure counter |
| `LOOT_BASE` | `/root/loot/debug_bridge` | where loot lands |

## Requirements

- **`openssh-client` on the Shark Jack.** The options used (`ServerAliveInterval`,
`ExitOnForwardFailure`, `UserKnownHostsFile`, etc.) are OpenSSH syntax. If your
firmware ships only Dropbear, install it: `opkg update && opkg install
openssh-client`.
- **The Shark Jack's own sshd** listening on `127.0.0.1:22` (this is how you
manage the device, so it is normally already running).
- **A reachable jump host** running sshd with TCP forwarding allowed (the default).
Binding to `127.0.0.1` needs no `GatewayPorts`.

## LED / screen cues

`SETUP` -> `SPECIAL` (network up) -> `STAGE1` (jump reachability) -> `STAGE2`
(dialing) -> `ATTACK` (tunnel up, solid). It cycles `STAGE2`/`ATTACK` across
reconnects. Red `FAIL` means a preflight failed (no `ssh`, missing key,
unconfigured `JUMP_HOST`, empty `KNOWN_HOSTS`), no network, or the reconnect
budget was exhausted. The OLED shows the current dial count, the reverse-forward
target, and reconnect state.

## Notes / limitations

- **The drop must have egress to the jump host.** The Shark Jack has one port: the
network you land on must be able to reach `JUMP_HOST` on `JUMP_PORT` outbound. A
fully air-gapped segment cannot form the tunnel.
- **Pick a free `REMOTE_PORT`.** If it is already bound on the jump host,
`ExitOnForwardFailure` makes the tunnel exit immediately (counts as a fast
failure). One Shark Jack per `REMOTE_PORT`.
- **Tear it down.** Pull the device when finished, and remove the key from the
jump host's `authorized_keys` at the end of the engagement.
- **Keep the listener on loopback.** `REMOTE_BIND=127.0.0.1` means the bridge is
reachable only by someone already on the jump host, not the public internet.
217 changes: 217 additions & 0 deletions payloads/library/general/remote_debug_bridge/payload.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
#!/bin/bash
# ============================================================================
# Title: Remote Debug Bridge (Shark Jack Display)
# Author: Brent Mills
# Target: An authorized wired drop you need to reach back into remotely.
# Category: general / remote-access / troubleshooting
# Version: 1.1
# Firmware: Tested on Shark Jack Display 1.0.1
# ----------------------------------------------------------------------------
# WHAT IT DOES
# Turns the Shark Jack into a temporary remote-troubleshooting appliance, so
# you can debug stranded gear from a desk instead of from a cold aisle. It:
# - Brings up eth0 (DHCP, or a static datacenter mgmt drop).
# - Opens a reverse SSH tunnel to a jump host you control, exposing the
# Shark Jack's own sshd back through it.
# - Auto-reconnects: a tunnel that was healthy re-dials on a drop; only
# repeated fast failures count toward giving up.
# - Shows live status on the OLED and logs every attempt on-device.
# From the jump host you then: ssh -p <REMOTE_PORT> root@127.0.0.1
# which lands you on the Shark Jack. It is L2-present on the stranded
# segment, so you reach the switches/fabric from there.
#
# AUTHORIZATION
# This opens a remote-access path INTO a network. Deploy ONLY on networks you
# own or are contracted to support, with written authorization, and tear it
# down when the work is finished. Auth is key-only to a jump host you control,
# with strict host-key checking. Local logs stay on-device under /root/loot.
#
# PREREQUISITES (one-time; see README)
# - A keypair at SSH_KEY, its public key in the jump user's authorized_keys.
# - The jump host's key pinned in KNOWN_HOSTS (ssh-keyscan), because this
# enforces StrictHostKeyChecking=yes.
# - openssh-client on the Shark Jack (the options below are OpenSSH syntax).
# ============================================================================

# ------------------------------- CONFIG -------------------------------------
# Jump host: a small always-on VM you control. The reverse tunnel lands here.
JUMP_USER="sharkdebug"
JUMP_HOST="X.X.X.X" # cloud VM public IP or DNS. MUST be set.
JUMP_PORT="22"

# Reverse forward: on the jump host this port maps back to the Shark Jack sshd.
REMOTE_BIND="127.0.0.1" # keep the listener local to the jump host
REMOTE_PORT="2222" # then, on the jump host: ssh -p 2222 root@127.0.0.1

# Key-only auth and a pinned host key (no passwords, no blind TOFU).
SSH_KEY="/root/.ssh/sharkjack_debug"
KNOWN_HOSTS="/root/.ssh/known_hosts"

# Network bring-up. DHCP_CLIENT suits a normal drop; a stranded datacenter mgmt
# port is often static, so fill STATIC_* and set NET_MODE="STATIC".
NET_MODE="DHCP_CLIENT" # DHCP_CLIENT | AUTO | STATIC
STATIC_IP="192.168.1.250" # used only when NET_MODE=STATIC
STATIC_SUBNET="255.255.255.0" # used only when NET_MODE=STATIC
STATIC_GATEWAY="192.168.1.1" # used only when NET_MODE=STATIC
DHCP_WAIT_SECS=30

# Tunnel behavior.
CONNECT_TIMEOUT=10 # per-dial TCP/auth timeout
MAX_RETRIES=15 # give up after this many CONSECUTIVE fast fails
RETRY_SLEEP=10 # base wait between dials (grows on repeat fails)
STABLE_SECS=60 # a tunnel up at least this long counts as healthy
# and resets the consecutive-failure counter

LOOT_BASE="/root/loot/debug_bridge"
# ----------------------------------------------------------------------------

have() { command -v "$1" >/dev/null 2>&1; }

# Shared SSH options: key-only, pinned host key, dead-peer detection.
SSH_OPTS="-i $SSH_KEY -p $JUMP_PORT \
-o BatchMode=yes \
-o IdentitiesOnly=yes \
-o PasswordAuthentication=no \
-o ConnectTimeout=$CONNECT_TIMEOUT \
-o ServerAliveInterval=15 \
-o ServerAliveCountMax=2 \
-o ExitOnForwardFailure=yes \
-o StrictHostKeyChecking=yes \
-o UserKnownHostsFile=$KNOWN_HOSTS"

# ------------------------------ SETUP ---------------------------------------
LED SETUP
CLEAR_SCREEN 2>/dev/null
SCREEN_WRITE "Remote Debug Bridge"

RUN="$LOOT_BASE/$(date -u +%Y%m%dT%H%M%SZ 2>/dev/null || echo run)_$$"
mkdir -p "$RUN"
REPORT="$RUN/summary.txt"
LOG="$RUN/bridge.log"

fail() { # fail <screen msg> <report reason>
LED FAIL
SCREEN_WRITE "$1"
echo "FAILED: $2" > "$REPORT"
ALERT "$2" 8 2>/dev/null
exit 1
}

have ssh || fail "ssh missing" "ssh client missing (opkg install openssh-client)"
[ -f "$SSH_KEY" ] || fail "missing SSH key" "missing SSH key at $SSH_KEY"
[ "$JUMP_HOST" = "X.X.X.X" ] || [ -z "$JUMP_HOST" ] && fail "set JUMP_HOST" "JUMP_HOST is not configured (still the placeholder)"
[ -s "$KNOWN_HOSTS" ] || fail "pin host key" "KNOWN_HOSTS empty; pin the jump host: ssh-keyscan -p $JUMP_PORT $JUMP_HOST >> $KNOWN_HOSTS"

chmod 600 "$SSH_KEY" 2>/dev/null

# --------------------------- PHASE: NETWORK UP ------------------------------
LED SPECIAL
SCREEN_WRITE "Bringing up eth0 ($NET_MODE)"
case "$NET_MODE" in
STATIC) NETMODE STATIC IP_${STATIC_IP} SUBNET_${STATIC_SUBNET} GATEWAY_${STATIC_GATEWAY} 2>/dev/null ;;
AUTO) NETMODE AUTO 2>/dev/null ;;
*) NETMODE DHCP_CLIENT 2>/dev/null ;;
esac
WAIT_FOR_LINK 2>/dev/null # block until carrier is up (the race fix)

IP=""; PREFIX=""
for _ in $(seq 1 "$DHCP_WAIT_SECS"); do
line=$(ip -o -4 addr show eth0 2>/dev/null | awk '{print $4}' | head -1)
if [ -n "$line" ]; then IP=${line%/*}; PREFIX=${line#*/}; break; fi
sleep 1
done
[ -z "$IP" ] && fail "no network" "no IPv4 on eth0 after ${DHCP_WAIT_SECS}s (NET_MODE=$NET_MODE; NAC/802.1X, no DHCP, or dead drop)"

GW=$(ip route 2>/dev/null | awk '/default/{print $3; exit}')
ENABLE_NTP 2>/dev/null # best-effort clock sync for honest timestamps
SCREEN_WRITE "IP $IP/$PREFIX"

# --------------------------- PHASE: REACHABILITY ----------------------------
LED STAGE1
SCREEN_WRITE "Testing jump reachability"
if ! ping -c 1 -W 3 "$JUMP_HOST" >/dev/null 2>&1; then
echo "$(date -u 2>/dev/null) ping to $JUMP_HOST failed (ICMP may be filtered); trying SSH anyway" >> "$LOG"
fi

{
echo "=========================================================="
echo " SHARK JACK DISPLAY - REMOTE DEBUG BRIDGE"
echo "=========================================================="
echo " Run time : $(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null)"
echo " Local IP : $IP/$PREFIX (NET_MODE=$NET_MODE)"
echo " Gateway : ${GW:-unknown}"
echo " Jump host : $JUMP_USER@$JUMP_HOST:$JUMP_PORT"
echo " Reverse fwd : $REMOTE_BIND:$REMOTE_PORT -> Shark Jack 127.0.0.1:22"
echo ""
echo " From the jump host, connect to the Shark Jack with:"
echo " ssh -p $REMOTE_PORT root@127.0.0.1"
echo ""
echo " Live attempt timeline is in bridge.log."
echo "=========================================================="
} > "$REPORT"

# --------------------------- PHASE: BRIDGE LOOP -----------------------------
# Reverse tunnel with resilient reconnect. A tunnel that stays up past
# STABLE_SECS is "healthy", so a later drop resets the failure counter; only
# repeated FAST failures (bad key, port in use, jump host down) count toward
# MAX_RETRIES, with a growing backoff between them.
attempt=0
fails=0
while [ "$fails" -lt "$MAX_RETRIES" ]; do
attempt=$((attempt + 1))
LED STAGE2
SCREEN_WRITE "Dial $((fails + 1))/$MAX_RETRIES"

STAMP=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null)
echo "$STAMP attempt $attempt (consecutive fails $fails)" >> "$LOG"

# Pre-check + breadcrumb: a quick auth'd command proves egress and key auth
# before we commit to the blocking tunnel, and leaves an "I'm alive" marker.
if ! ssh $SSH_OPTS "$JUMP_USER@$JUMP_HOST" \
"printf '%s\n' \"shark bridge up from $IP/$PREFIX at $STAMP\" >> ~/sharkjack-bridge.log" \
>> "$LOG" 2>&1; then
fails=$((fails + 1))
SCREEN_WRITE "Jump unreachable"
echo "$STAMP jump host unreachable or auth failed" >> "$LOG"
back=$((RETRY_SLEEP * fails)); [ "$back" -gt 60 ] && back=60
sleep "$back"
continue
fi

LED ATTACK
SCREEN_WRITE "Tunnel up"
SCREEN_WRITE "$REMOTE_BIND:$REMOTE_PORT"

start=$(date +%s 2>/dev/null)
ssh -N $SSH_OPTS -R "$REMOTE_BIND:$REMOTE_PORT:127.0.0.1:22" "$JUMP_USER@$JUMP_HOST" >> "$LOG" 2>&1
rc=$?
end=$(date +%s 2>/dev/null)
up=$((end - start))
echo "$(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null) tunnel exited rc=$rc after ${up}s" >> "$LOG"

if [ "$up" -ge "$STABLE_SECS" ]; then
fails=0 # healthy session; a drop is transient
SCREEN_WRITE "Reconnecting"
sleep "$RETRY_SLEEP"
else
fails=$((fails + 1)) # fast failure; count it, back off
SCREEN_WRITE "Drop, retrying"
back=$((RETRY_SLEEP * fails)); [ "$back" -gt 60 ] && back=60
sleep "$back"
fi
done

# ------------------------------ PHASE: ENDED --------------------------------
LED FAIL
SCREEN_WRITE "Bridge ended"
SCREEN_WRITE "$MAX_RETRIES fails"
{
echo ""
echo "---- FINAL STATUS ----------------------------------------"
echo " Ended at : $(date -u '+%Y-%m-%d %H:%M:%SZ' 2>/dev/null)"
echo " Dials made : $attempt"
echo " Reason : $MAX_RETRIES consecutive fast failures (see bridge.log)"
echo "=========================================================="
} >> "$REPORT"
ALERT "Debug bridge ended after $MAX_RETRIES consecutive failures" 10 2>/dev/null
exit 0
Loading