Skip to content

Instantly share code, notes, and snippets.

@AeonDave
Created March 27, 2026 18:34
Show Gist options
  • Select an option

  • Save AeonDave/e9fb3b9628f833894f929a6660a5b96c to your computer and use it in GitHub Desktop.

Select an option

Save AeonDave/e9fb3b9628f833894f929a6660a5b96c to your computer and use it in GitHub Desktop.
Agent manager and builder for AdaptixC2 Extension Kit
#!/usr/bin/env bash
# ============================================================================
# compile-bofs.sh — Selectively compile BOF modules
#
# Auto-discovers all *-BOF directories with a Makefile.
# Interactive selector lets you pick which modules to build.
# Optionally syncs extension-kit.axs to include newly discovered .axs files.
# ============================================================================
set -euo pipefail
if [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
BLUE='\033[0;34m'
BOLD='\033[1m'
DIM='\033[2m'
NC='\033[0m'
else
RED='' GREEN='' YELLOW='' CYAN='' BLUE='' BOLD='' DIM='' NC=''
fi
error_exit() { echo -e "${RED}[ERROR]${NC} $1"; exit 1; }
info_msg() { echo -e "${GREEN}[+]${NC} $1"; }
warn_msg() { echo -e "${YELLOW}[!]${NC} $1"; }
step_msg() { echo -e "${CYAN}[*]${NC} $1"; }
show_banner() {
echo -e "${CYAN}"
cat << 'BANNER'
░█▀█░█▀▄░█▀█░█▀█░▀█▀░▀█▀░█░█░█▀▀░▀▀▄░░░█░█░▀█▀░▀█▀
░█▀█░█░█░█▀█░█▀▀░░█░░░█░░▄▀▄░█░░░▄▀░░░░█▀▄░░█░░░█░
░▀░▀░▀▀░░▀░▀░▀░░░░▀░░▀▀▀░▀░▀░▀▀▀░▀▀▀░░░▀░▀░▀▀▀░░▀░
BANNER
echo -e "${NC}"
echo -e "${BOLD} BOF Compiler${NC}"
echo ""
}
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ACTION=""
SYNC_AXS=false
FAILED=()
SUCCEEDED=()
# ── Usage ───────────────────────────────────────────────────────────────────
usage() {
cat <<EOF
Usage: $0 [-a <action>] [-m <module>] [--sync-axs]
Optional:
-a, --action <act> Action to perform (omit for interactive selector)
-m, --module <name> Build a single module by name (e.g. SAL-BOF)
--sync-axs Update extension-kit.axs with any missing .axs refs
-h, --help Show this help
Actions:
all | build Build all discovered BOF modules
clean Clean all discovered BOF modules
(none) Interactive selector — pick which modules to build
Examples:
$0 # interactive selector
$0 -a all # build everything
$0 -m SAL-BOF # build only SAL-BOF
$0 -m Creds-BOF -m AD-BOF # build Creds-BOF and AD-BOF
$0 -a all --sync-axs # build all + update extension-kit.axs
$0 -a clean # clean all
EOF
exit 0
}
# ── Parse arguments ─────────────────────────────────────────────────────────
EXPLICIT_MODULES=()
while [[ $# -gt 0 ]]; do
case $1 in
-a|--action) ACTION="$2"; shift 2 ;;
-m|--module) EXPLICIT_MODULES+=("$2"); shift 2 ;;
--sync-axs) SYNC_AXS=true; shift ;;
-h|--help) usage ;;
*) error_exit "Unknown parameter: $1" ;;
esac
done
show_banner
# ── Discover BOF modules ───────────────────────────────────────────────────
MODULES=()
discover_modules() {
for dir in "$SCRIPT_DIR"/*/; do
local name
name="$(basename "$dir")"
# Skip non-BOF dirs, hidden dirs, _include, _img, _bin
[[ "$name" == _* ]] && continue
[[ -f "$dir/Makefile" ]] || continue
MODULES+=("$name")
done
# Sort for consistent display
IFS=$'\n' MODULES=($(sort <<<"${MODULES[*]}")); unset IFS
}
discover_modules
(( ${#MODULES[@]} == 0 )) && error_exit "No BOF modules found in working directory"
# ── Interactive selector ────────────────────────────────────────────────────
interactive_select() {
local count=${#MODULES[@]}
local -a selected=()
local -a colors=()
for (( i=0; i<count; i++ )); do
selected+=( 1 )
if [[ -d "${SCRIPT_DIR}/${MODULES[$i]}/_bin" ]]; then
colors+=( "$BLUE" ) # already built
else
colors+=( "$GREEN" ) # never built
fi
done
local cursor=0 _menu_drawn=0
local saved_stty
saved_stty="$(stty -g)"
_restore_term() { stty "$saved_stty" 2>/dev/null; tput cnorm 2>/dev/null; }
trap _restore_term EXIT INT TERM
stty -echo -icanon min 1 time 0
tput civis 2>/dev/null
_render_menu() {
(( _menu_drawn )) && printf '\033[%dA' "$((count + 3))"
_menu_drawn=1
printf '\r\033[K'
echo -e "${BOLD}Select BOF modules to build ${DIM}(Space=toggle a=all n=none Enter=confirm q=quit)${NC}"
printf '\r\033[K\n'
for (( i=0; i<count; i++ )); do
local arrow=" "
(( i == cursor )) && arrow="> "
local check=" "
(( selected[i] )) && check="x"
local tag=""
if [[ -d "${SCRIPT_DIR}/${MODULES[$i]}/_bin" ]]; then
tag="${DIM}(built)${NC}"
else
tag="${DIM}(new)${NC}"
fi
echo -e "${arrow}[${check}] ${colors[$i]}${MODULES[$i]}${NC} ${tag}"
done
printf '\r\033[K\n'
}
_render_menu
while true; do
local key
IFS= read -rsN1 key
case "$key" in
$'\x1b')
read -rsN2 -t 0.1 seq
case "$seq" in
'[A') (( cursor > 0 )) && (( cursor-- )) ;; # Up
'[B') (( cursor < count-1 )) && (( cursor++ )) ;; # Down
esac
;;
' ')
(( selected[cursor] = !selected[cursor] ))
;;
'a'|'A')
for (( i=0; i<count; i++ )); do selected[$i]=1; done
;;
'n'|'N')
for (( i=0; i<count; i++ )); do selected[$i]=0; done
;;
''|$'\n')
break
;;
'q'|'Q')
_restore_term
echo -e "${YELLOW}Aborted.${NC}"
exit 0
;;
esac
_render_menu
done
_restore_term
trap - EXIT INT TERM
# Rebuild MODULES from selection
local -a chosen=()
for (( i=0; i<count; i++ )); do
(( selected[i] )) && chosen+=("${MODULES[$i]}")
done
MODULES=("${chosen[@]}")
(( ${#MODULES[@]} == 0 )) && { echo -e "${YELLOW}Nothing selected.${NC}"; exit 0; }
echo -e "${GREEN}Selected ${#MODULES[@]} module(s)${NC}"
}
# ── Build / Clean functions ─────────────────────────────────────────────────
build_module() {
local name="$1"
local dir="$SCRIPT_DIR/$name"
[[ -d "$dir" ]] || { warn_msg "$name: directory not found (skipping)"; FAILED+=("$name"); return 1; }
[[ -f "$dir/Makefile" ]] || { warn_msg "$name: no Makefile (skipping)"; FAILED+=("$name"); return 1; }
step_msg "Building ${BOLD}$name${NC}..."
local build_log
if build_log="$(make --no-print-directory -C "$dir" 2>&1)"; then
info_msg "$name ${DIM}${NC}"
SUCCEEDED+=("$name")
else
echo "$build_log"
echo -e "${RED}[FAIL]${NC} $name"
FAILED+=("$name")
return 1
fi
}
clean_module() {
local name="$1"
local dir="$SCRIPT_DIR/$name"
[[ -d "$dir" ]] || return 0
[[ -f "$dir/Makefile" ]] || return 0
step_msg "Cleaning ${BOLD}$name${NC}..."
make --no-print-directory -C "$dir" clean 2>/dev/null
}
# ── Sync extension-kit.axs ─────────────────────────────────────────────────
sync_extension_kit_axs() {
local axs_file="$SCRIPT_DIR/extension-kit.axs"
[[ -f "$axs_file" ]] || { warn_msg "extension-kit.axs not found"; return 1; }
local -a missing=()
for dir in "$SCRIPT_DIR"/*/; do
local name
name="$(basename "$dir")"
[[ "$name" == _* ]] && continue
# Find top-level .axs files in this module
for axs in "$dir"/*.axs; do
[[ -f "$axs" ]] || continue
local rel_path="${name}/$(basename "$axs")"
# Check if this .axs is already loaded in extension-kit.axs
if ! grep -qF "$rel_path" "$axs_file"; then
missing+=("$rel_path")
fi
done
done
if (( ${#missing[@]} == 0 )); then
info_msg "extension-kit.axs is up to date"
return 0
fi
echo ""
step_msg "Found ${#missing[@]} .axs file(s) not in extension-kit.axs:"
for m in "${missing[@]}"; do
echo -e " ${GREEN}+${NC} $m"
done
# Build the new load lines
local new_lines=""
for m in "${missing[@]}"; do
new_lines+="ax.script_load(path + \"${m}\");\n"
done
# Insert before the closing line (or append before last empty line)
# Find the last ax.script_load line number and insert after it
local last_load_line
last_load_line="$(grep -n 'ax\.script_load' "$axs_file" | tail -1 | cut -d: -f1)"
if [[ -n "$last_load_line" ]]; then
# Insert new lines after the last ax.script_load
local head_part tail_part
head_part="$(head -n "$last_load_line" "$axs_file")"
tail_part="$(tail -n +"$((last_load_line + 1))" "$axs_file")"
{
echo "$head_part"
echo -e "$new_lines"
echo "$tail_part"
} > "${axs_file}.tmp"
mv "${axs_file}.tmp" "$axs_file"
info_msg "Updated extension-kit.axs with ${#missing[@]} new entry(s)"
else
warn_msg "Could not find insertion point in extension-kit.axs"
fi
}
# ── Dispatch ────────────────────────────────────────────────────────────────
# If explicit modules given via -m, use those
if (( ${#EXPLICIT_MODULES[@]} > 0 )); then
MODULES=("${EXPLICIT_MODULES[@]}")
ACTION="build"
fi
# Interactive selector when no action and no explicit modules
if [[ -z "$ACTION" ]]; then
interactive_select
ACTION="build"
fi
case $ACTION in
all|build)
echo ""
step_msg "${BOLD}Building ${#MODULES[@]} module(s)${NC}"
echo ""
for name in "${MODULES[@]}"; do
build_module "$name"
done
;;
clean)
echo ""
step_msg "${BOLD}Cleaning ${#MODULES[@]} module(s)${NC}"
echo ""
for name in "${MODULES[@]}"; do
clean_module "$name"
done
info_msg "Clean complete"
;;
*)
error_exit "Unknown action: $ACTION"
;;
esac
# Sync extension-kit.axs if requested
if $SYNC_AXS && [[ "$ACTION" != "clean" ]]; then
sync_extension_kit_axs
fi
# ── Summary ─────────────────────────────────────────────────────────────────
if [[ "$ACTION" != "clean" ]]; then
echo ""
echo -e "${BOLD}━━━ Summary ━━━${NC}"
(( ${#SUCCEEDED[@]} > 0 )) && echo -e " ${GREEN}OK:${NC} ${SUCCEEDED[*]}"
(( ${#FAILED[@]} > 0 )) && echo -e " ${RED}FAIL:${NC} ${FAILED[*]}"
echo -e " ${DIM}Total: ${#SUCCEEDED[@]}/${#MODULES[@]}${NC}"
if (( ${#FAILED[@]} > 0 )); then
exit 1
fi
fi
#!/usr/bin/env bash
# manage-agents.sh — Interactively add or remove agent types from Extension Kit registrations.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Extended regex matching all registration call types
RE='(ax\.register_commands_group|menu\.add_session_access|menu\.add_processbrowser)\('
if [[ -t 1 ]]; then
B='\033[1m'; D='\033[2m'; G='\033[32m'; R='\033[31m'
C='\033[36m'; Y='\033[33m'; N='\033[0m'
else
B=''; D=''; G=''; R=''; C=''; Y=''; N=''
fi
show_banner() {
printf "${C}"
cat << 'BANNER'
░█▀█░█▀▄░█▀█░█▀█░▀█▀░▀█▀░█░█░█▀▀░▀▀▄░░░█░█░▀█▀░▀█▀
░█▀█░█░█░█▀█░█▀▀░░█░░░█░░▄▀▄░█░░░▄▀░░░░█▀▄░░█░░░█░
░▀░▀░▀▀░░▀░▀░▀░░░░▀░░▀▀▀░▀░▀░▀▀▀░▀▀▀░░░▀░▀░▀▀▀░░▀░
BANNER
printf "${N}"
printf "${B} Agent Type Manager${N}\n"
}
info() { printf "${G}>>>${N} %s\n" "$1"; }
# ---------------------------------------------------------------------------
find_files() {
grep -rlE "$RE" "$SCRIPT_DIR" --include='*.axs' 2>/dev/null | sort
}
# Extract content of the first [...] on a line (= agent-type array)
first_array() { sed 's/[^[]*\[//; s/\].*//'; }
collect_types() {
grep -rhE "$RE" "$SCRIPT_DIR" --include='*.axs' 2>/dev/null \
| first_array | tr ',' '\n' \
| sed 's/^[[:space:]]*//; s/[[:space:]]*$//' | tr -d '"' \
| sort -u | grep -v '^$'
}
show_status() {
printf "\n${B}Registered agent types${N}\n"
local types
types=$(collect_types)
if [[ -z "$types" ]]; then
echo " (none found)"
printf "\n"
return
fi
while IFS= read -r t; do
local cnt
cnt=$(grep -rhE "$RE" "$SCRIPT_DIR" --include='*.axs' 2>/dev/null \
| grep -c "\"${t}\"" || true)
printf " ${C}%-24s${N} %d line(s)\n" "$t" "$cnt"
done <<< "$types"
printf "\n${B}Detail by file${N}\n\n"
local files
files=$(find_files)
while IFS= read -r f; do
printf " ${C}%s${N}\n" "${f#"$SCRIPT_DIR"/}"
grep -nE "$RE" "$f" | while IFS= read -r match; do
local num="${match%%:*}" rest="${match#*:}"
local fn agents
fn=$(echo "$rest" | grep -oE 'register_commands_group|add_session_access|add_processbrowser')
agents=$(echo "$rest" | first_array)
printf " ${D}L%-4s${N} %-28s [%s]\n" "$num" "$fn" "$agents"
done
done <<< "$files"
printf "\n"
}
# ---------------------------------------------------------------------------
do_add() {
local agent="$1"
local files modified=0 skipped=0
files=$(find_files)
[[ -z "$files" ]] && { echo "No registration files found."; return 1; }
while IFS= read -r f; do
local rel="${f#"$SCRIPT_DIR"/}"
# Check if any registration line in this file is missing the type
if grep -E "$RE" "$f" | grep -qv "\"${agent}\""; then
# On matching lines that lack the type, append before closing ]
sed -i -E "/$RE/{/\"${agent}\"/!s/\"]/\", \"${agent}\"]/}" "$f"
printf " ${G}+${N} %s\n" "$rel"
modified=$((modified + 1))
else
printf " ${D}-${N} %s ${D}(already present)${N}\n" "$rel"
skipped=$((skipped + 1))
fi
done <<< "$files"
info "Added \"${agent}\"${modified} file(s) modified, ${skipped} unchanged."
}
do_remove() {
local agent="$1"
local files modified=0 skipped=0
files=$(find_files)
[[ -z "$files" ]] && { echo "No registration files found."; return 1; }
while IFS= read -r f; do
local rel="${f#"$SCRIPT_DIR"/}"
if ! grep -E "$RE" "$f" | grep -q "\"${agent}\""; then
printf " ${D}-${N} %s ${D}(not present)${N}\n" "$rel"
skipped=$((skipped + 1))
continue
fi
# Remove from beginning/middle ("type", ) then from end (, "type")
sed -i -E "/$RE/{s/\"${agent}\", //; s/, \"${agent}\"//}" "$f"
# Warn if any lines still contain it (sole-element — can't remove without empty array)
if grep -E "$RE" "$f" | grep -q "\"${agent}\""; then
printf " ${Y}!${N} %s ${Y}(sole type on some lines — skipped to avoid empty array)${N}\n" "$rel"
else
printf " ${R}x${N} %s\n" "$rel"
fi
modified=$((modified + 1))
done <<< "$files"
info "Removed \"${agent}\" from ${modified} file(s), ${skipped} unchanged."
}
# ---------------------------------------------------------------------------
# Main loop
# ---------------------------------------------------------------------------
show_banner
while true; do
show_status
printf " ${B}[a]${N}dd ${B}[r]${N}emove ${B}[q]${N}uit\n"
read -rp "> " action
case "${action,,}" in
a|add)
read -rp "Agent type to add: " agent
[[ -z "$agent" ]] && continue
if [[ ! "$agent" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
printf "${R}Invalid name.${N} Use [a-zA-Z_][a-zA-Z0-9_]*\n"
continue
fi
printf "\n"
do_add "$agent"
;;
r|remove|rm)
read -rp "Agent type to remove: " agent
[[ -z "$agent" ]] && continue
printf "\n"
do_remove "$agent"
;;
q|quit|exit)
exit 0
;;
*)
printf "Unknown action. Use ${B}a${N}, ${B}r${N}, or ${B}q${N}.\n"
;;
esac
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment