Files
mandiant-gopacket/install.sh
T
psycep 362639a77e install.sh: add --target flag for portable and windows builds
Extends the installer to produce CGO_ENABLED=0 static Linux binaries
and GOOS=windows .exe cross-compiles, not just the default native
cgo build. New flag:

  --target native    (default) Linux/macOS cgo + install (today's flow)
  --target portable  CGO_ENABLED=0, host OS, output to ./dist/portable/
  --target windows   GOOS=windows amd64, output to ./dist/windows/
  --target all       build every target in one run

When run with no --target and a TTY on stdin, the installer prints an
interactive menu that explains each target briefly (tool set, proxy
path, output location). Non-TTY invocations (CI, pipes) default
silently to native so existing automation keeps working unchanged.

Cross-compile targets never install to /usr/local/bin. They drop
binaries in ./dist/<target>/ for the user to copy to the right host.
The libpcap check and GCC check are now conditional: required for the
native target, skipped for portable/windows since those paths use
build-tag stubs for libpcap/raw-socket tools.
2026-04-22 11:20:48 -05:00

369 lines
11 KiB
Bash
Executable File

#!/bin/bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# gopacket installer
# Builds all tools and installs them as gopacket-<toolname> on your PATH
#
set -e
# Default install directory
INSTALL_DIR="${INSTALL_DIR:-/usr/local/bin}"
BUILD_DIR="./bin"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
usage() {
cat <<'EOF'
Usage: ./install.sh [OPTIONS]
Build and install gopacket tools. Run with no flags to get an interactive
prompt explaining the build targets.
Options:
--target NAME Build target. One of:
native (default) Linux/macOS cgo, installs to
/usr/local/bin. All 63 tools; proxychains
and -proxy both work. Needs GCC + libpcap-dev.
portable Static host-OS binaries, no system libs.
sniff/split stubbed; use -proxy (proxychains
won't hook). Output: ./dist/portable/
windows Windows amd64 cross-compile. sniff/split/
sniffer stubbed; -proxy only (no LD_PRELOAD
on Windows). Output: ./dist/windows/
all Build every target.
--prefix DIR For the native target, install to DIR instead of
/usr/local/bin. Has no effect on cross-compile targets.
--build-only Build but don't install (native target only).
--uninstall Remove previously installed gopacket-* binaries from
$INSTALL_DIR. Affects native installs only.
-h, --help Show this help.
Environment:
INSTALL_DIR Same as --prefix.
Examples:
./install.sh Interactive, prompts for target
./install.sh --target native Today's default (build + install)
./install.sh --target windows Cross-compile Windows .exe files
./install.sh --target all Build every target in one run
EOF
}
target=""
build_only=false
uninstall=false
while [[ $# -gt 0 ]]; do
case "$1" in
--target)
target="$2"
shift 2
;;
--prefix)
INSTALL_DIR="$2"
shift 2
;;
--build-only)
build_only=true
shift
;;
--uninstall)
uninstall=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown option: $1"
usage
exit 1
;;
esac
done
# Uninstall mode
if $uninstall; then
echo "Removing gopacket tools from ${INSTALL_DIR}..."
count=0
for f in "${INSTALL_DIR}"/gopacket-*; do
if [ -f "$f" ]; then
echo " removing $(basename "$f")"
rm -f "$f"
((count++))
fi
done
if [ $count -eq 0 ]; then
echo "No gopacket tools found in ${INSTALL_DIR}"
else
echo -e "${GREEN}Removed ${count} tools${NC}"
fi
exit 0
fi
# Determine script directory (where go.mod lives)
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$SCRIPT_DIR"
if [ ! -f go.mod ]; then
echo -e "${RED}Error: go.mod not found. Run this script from the gopacket directory.${NC}"
exit 1
fi
# Interactive prompt if no --target given and stdin is a terminal.
if [ -z "$target" ]; then
if [ -t 0 ]; then
cat <<'EOF'
gopacket installer
Pick a build target. If unsure, pick (1).
(1) native
Linux/macOS cgo, installs to /usr/local/bin. All 63 tools; supports
both proxychains (LD_PRELOAD) and the -proxy SOCKS5 flag.
Needs GCC + libpcap-dev installed.
(2) portable
Static single-file binaries for your host OS, no system libs needed.
sniff/split are stubs (they need libpcap). proxychains can't hook a
cgo-off Go binary, so use -proxy instead. Output: ./dist/portable/
(3) windows
Windows amd64 .exe cross-compile. sniff/split/sniffer are stubs.
No LD_PRELOAD on Windows, so -proxy is the only proxy path.
Output: ./dist/windows/
(4) all
Build every target.
EOF
read -r -p "Choice [1-4, default 1]: " choice
choice="${choice:-1}"
case "$choice" in
1) target="native" ;;
2) target="portable" ;;
3) target="windows" ;;
4) target="all" ;;
*) echo -e "${RED}Invalid choice: $choice${NC}"; exit 1 ;;
esac
echo ""
else
# Non-TTY (piped, CI). Default to native without prompting.
target="native"
fi
fi
# Validate target
case "$target" in
native|portable|windows|all) ;;
*)
echo -e "${RED}Error: unknown target '$target'${NC}"
echo "Valid targets: native, portable, windows, all"
exit 1
;;
esac
# Check the Go toolchain is present (all targets need this).
if ! command -v go &>/dev/null; then
echo -e "${RED}Error: go is not installed or not in PATH${NC}"
echo "Install Go from https://go.dev/dl/"
exit 1
fi
# build_target runs the build for one of: native, portable, windows.
# Sets the right GOOS/CGO_ENABLED, picks an output directory, and iterates
# over every tool in tools/. Stubs defined in the tools handle the cases
# where a tool cannot be built for the target.
build_target() {
local t="$1"
local goos goarch cgo outdir exe_suffix pcap_check label
case "$t" in
native)
goos=""
goarch=""
cgo=1
outdir="$BUILD_DIR"
exe_suffix=""
pcap_check=true
label="native (host OS, cgo on)"
;;
portable)
goos="" # inherit host OS
goarch="" # inherit host arch
cgo=0
outdir="./dist/portable"
exe_suffix=""
pcap_check=false
label="portable (host OS, cgo off, sniff/split stubbed)"
;;
windows)
goos="windows"
goarch="amd64"
cgo=0
outdir="./dist/windows"
exe_suffix=".exe"
pcap_check=false
label="windows (GOOS=windows amd64, sniff/split/sniffer stubbed)"
;;
esac
echo ""
echo -e "${GREEN}=== Building ${label} ===${NC}"
# Native target needs GCC for cgo and benefits from libpcap to get real
# sniff/split. Cross-compile targets use stubs and need neither.
if [ "$t" = "native" ]; then
if ! command -v gcc &>/dev/null; then
echo -e "${RED}Error: gcc is required for the native target (cgo)${NC}"
echo "Install with: apt install build-essential (Debian/Ubuntu) or yum install gcc (RHEL/CentOS)"
return 1
fi
fi
local has_libpcap=true
if $pcap_check; then
if ! [ -f /usr/include/pcap.h ] \
&& ! [ -f /usr/include/pcap/pcap.h ] \
&& ! [ -f /usr/local/include/pcap.h ] \
&& ! [ -f /opt/homebrew/include/pcap.h ] \
&& ! pkg-config --exists libpcap 2>/dev/null; then
has_libpcap=false
echo -e "${YELLOW}Warning: libpcap development headers not found${NC}"
echo " sniff and split will be skipped in this target."
echo " Install with: apt install libpcap-dev (Debian/Ubuntu/Kali)"
echo " or yum install libpcap-devel (RHEL/CentOS)"
echo " or brew install libpcap (macOS)"
echo ""
fi
fi
local tools
tools=($(ls tools/))
local total=${#tools[@]}
mkdir -p "$outdir"
local failed=0 skipped=0
for tool in "${tools[@]}"; do
# On native without libpcap, skip sniff/split entirely (same as the
# old behavior). On other targets, stubs handle the skip automatically.
if [ "$t" = "native" ] && ! $has_libpcap && { [ "$tool" = "sniff" ] || [ "$tool" = "split" ]; }; then
echo -e " ${tool}... ${YELLOW}skipped (libpcap-dev not installed)${NC}"
skipped=$((skipped + 1))
continue
fi
echo -n " ${tool}... "
local out_path="${outdir}/${tool}${exe_suffix}"
local build_cmd=(go build -o "$out_path")
if [ "$t" = "native" ]; then
# Static-link libgcc so binaries run on minimally-versioned hosts.
build_cmd+=(-ldflags '-linkmode external -extldflags "-static-libgcc"')
fi
build_cmd+=("./tools/${tool}")
if err=$(GOOS="$goos" GOARCH="$goarch" CGO_ENABLED="$cgo" "${build_cmd[@]}" 2>&1); then
echo -e "${GREEN}ok${NC}"
else
echo -e "${RED}failed${NC}"
echo "$err" | sed 's/^/ /'
failed=$((failed + 1))
fi
done
if [ $failed -gt 0 ]; then
echo -e "\n${RED}${failed} tool(s) failed to build for ${t}${NC}"
return 1
fi
local built=$((total - skipped))
echo -e "\n${GREEN}Built ${built}/${total} tools for ${t} in ${outdir}/${NC}"
if [ $skipped -gt 0 ]; then
echo -e "${YELLOW}Skipped ${skipped} tool(s), install libpcap-dev to enable them${NC}"
fi
return 0
}
# Install is only meaningful for the native target. Cross-compile outputs
# live in ./dist/ for the user to copy to the right host.
install_native() {
echo ""
echo "Installing to ${INSTALL_DIR}/ as gopacket-<toolname>..."
local SUDO=""
if [ ! -w "${INSTALL_DIR}" ]; then
echo -e "${YELLOW}Note: ${INSTALL_DIR} requires elevated permissions${NC}"
echo "Re-running install step with sudo..."
SUDO="sudo"
fi
local tools
tools=($(ls tools/))
local installed=0
for tool in "${tools[@]}"; do
if [ ! -f "${BUILD_DIR}/${tool}" ]; then
continue
fi
local normalized
normalized=$(echo "$tool" | tr '[:upper:]' '[:lower:]' | tr '_' '-')
local dest="${INSTALL_DIR}/gopacket-${normalized}"
$SUDO cp "${BUILD_DIR}/${tool}" "$dest"
$SUDO chmod +x "$dest"
installed=$((installed + 1))
done
echo -e "${GREEN}Installed ${installed} tools to ${INSTALL_DIR}/${NC}"
echo ""
echo "Tools are available as:"
echo " gopacket-secretsdump, gopacket-smbclient, gopacket-psexec, etc."
echo ""
echo "Run 'gopacket-<tool> -h' for help on any tool."
echo "To uninstall: $0 --uninstall"
}
# Run the requested target(s).
if [ "$target" = "all" ]; then
build_target native || exit 1
build_target portable || exit 1
build_target windows || exit 1
if ! $build_only; then
install_native
fi
echo ""
echo -e "${GREEN}All targets built.${NC}"
echo " native -> ${BUILD_DIR}/"
echo " portable -> ./dist/portable/"
echo " windows -> ./dist/windows/"
exit 0
fi
build_target "$target" || exit 1
if [ "$target" = "native" ] && ! $build_only; then
install_native
elif [ "$target" = "native" ] && $build_only; then
echo ""
echo "Binaries are in ${BUILD_DIR}/"
else
echo ""
echo "Binaries are in $([ "$target" = "portable" ] && echo ./dist/portable/ || echo ./dist/windows/)"
echo "Copy them to the target host and run."
fi