recon-setup 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ build-and-publish:
10
+ name: Build and Publish
11
+ runs-on: ubuntu-latest
12
+
13
+ permissions:
14
+ contents: read
15
+ id-token: write
16
+
17
+ steps:
18
+ - name: Checkout code
19
+ uses: actions/checkout@v5
20
+ with:
21
+ ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.ref }}
22
+
23
+ - name: Set up Python
24
+ uses: actions/setup-python@v5
25
+ with:
26
+ python-version: '3.11'
27
+
28
+ - name: Install build dependencies
29
+ run: |
30
+ python -m pip install --upgrade pip
31
+ python -m pip install build
32
+
33
+ - name: Build package
34
+ run: python -m build
35
+
36
+ - name: Publish to PyPI (OIDC / Trusted Publishing)
37
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,20 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.pyo
5
+ *.pyd
6
+ .venv/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+
11
+ # Environment
12
+ .env
13
+
14
+ # Test
15
+ .pytest_cache
16
+
17
+ # AI
18
+ AGENTS.md
19
+ ARCHITECTURE.md
20
+ backlog/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mojo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: recon-setup
3
+ Version: 0.1.0
4
+ Summary: Automate the setup and enumeration process for offensive security labs.
5
+ License-File: LICENSE
6
+ Requires-Python: >=3.10
7
+ Requires-Dist: impacket
8
+ Requires-Dist: libtmux==0.53.1
9
+ Requires-Dist: requests
10
+ Requires-Dist: rich
11
+ Requires-Dist: urllib3
12
+ Requires-Dist: watchdog
@@ -0,0 +1,103 @@
1
+ # recon-setup
2
+
3
+ ![Python](https://img.shields.io/badge/python-3.10+-blue.svg)
4
+ ![License](https://img.shields.io/badge/license-MIT-blue)
5
+
6
+ **recon-setup** is a CLI tool that automates tmux session creation, nmap scanning, and port-driven enumeration for offensive security labs.
7
+
8
+ ## Features
9
+
10
+ - [x] Structured tmux session with dedicated panes for OpenVPN, Ligolo, file serving, nmap, and task logging.
11
+ - [x] Port-driven automation: watches nmap output in real time and dispatches enumeration tasks as ports are discovered.
12
+ - [x] Credential-aware handlers: pass initial credentials to pre-populate authenticated scans across SMB, LDAP, Kerberos, MSSQL, and more.
13
+ - [x] Credential watcher: monitors `creds.txt` for new entries and re-runs relevant handlers automatically.
14
+ - [x] HTB CLI integration: spawn machines directly by name without manually retrieving an IP.
15
+
16
+ ## Requirements
17
+
18
+ - tmux
19
+ - `/opt/scripts/scan_machine.py` — nmap wrapper script that drives the scan and emits port results to stdout
20
+ - Tools used by enumeration handlers: `nxc`, `bloodhound-ce-python`, `bloodhound-cli`, `bloodyAD`, `certipy`, `aliasr`, `kerbrute`, `hashcat`, `ffuf`, `feroxbuster`, `nuclei`, `wpscan`, `powerview`, `impacket`
21
+
22
+ ## Install
23
+
24
+ ```bash
25
+ # Latest commit (GitHub)
26
+ uv tool install git+https://github.com/Mojo8898/recon-setup
27
+ pipx install git+https://github.com/Mojo8898/recon-setup
28
+
29
+ # Local development
30
+ git clone https://github.com/Mojo8898/recon-setup
31
+ cd recon-setup
32
+ uv sync
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```
38
+ usage: recon-setup [-h] [-v VPN_PATH] [-s SESSION_PATH] [-i IP]
39
+ [--spawn SPAWN] [-n] [-a] [-u USERNAME] [-p PASSWORD] [-d]
40
+ session_name
41
+
42
+ Automate the setup and enumeration process for offensive security labs.
43
+
44
+ options:
45
+ -h, --help show this help message and exit
46
+
47
+ Session Arguments:
48
+ Arguments related to session configuration
49
+
50
+ session_name Name of the tmux session to be created
51
+ -v VPN_PATH, --vpn_path VPN_PATH
52
+ Path of your VPN file
53
+ -s SESSION_PATH, --session_path SESSION_PATH
54
+ Path to where the session will be created (default:
55
+ /workspace/machines)
56
+ -i IP, --ip IP IP address of the target machine
57
+
58
+ HTB CLI Arguments:
59
+ Arguments related to HTB CLI functionality
60
+
61
+ --spawn SPAWN Spawn the target machine using the HTB CLI instead of
62
+ providing an IP (requires htb-cli)
63
+ -n, --new_release Wait for the scheduled release time (7pm UTC) and
64
+ spawn automatically
65
+
66
+ Automation Arguments:
67
+ Arguments related to automated tasking
68
+
69
+ -a, --automate Optional flag to enable automated tasks on the fly
70
+ from nmap scan results
71
+ -u USERNAME, --username USERNAME
72
+ Username to supply automated tasks (AD only)
73
+ -p PASSWORD, --password PASSWORD
74
+ Password to supply automated tasks (AD only)
75
+ -d, --debug Enable debug mode for automation
76
+ ```
77
+
78
+ Standard session without automation:
79
+
80
+ ```bash
81
+ recon-setup pirate -v ~/vpn/lab.ovpn -i 10.10.11.40
82
+ ```
83
+
84
+ Session with full automation and initial credentials:
85
+
86
+ ```bash
87
+ recon-setup pirate -v ~/vpn/lab.ovpn -i 10.10.11.40 -a -u pentest -p 'Password123!'
88
+ ```
89
+
90
+ Spawn an HTB machine by name and automate:
91
+
92
+ ```bash
93
+ recon-setup pirate -v ~/vpn/lab.ovpn --spawn pirate -a
94
+ ```
95
+
96
+ ## Contributing
97
+
98
+ Contributions are welcome. Feel free to open an issue for bugs or feature requests.
99
+
100
+ ## Acknowledgments
101
+
102
+ - Built on top of [libtmux](https://github.com/tmux-python/libtmux)
103
+ - Enumeration handlers integrate [aliasr](https://github.com/Mojo8898/aliasr), [NetExec](https://github.com/Pennyw0rth/NetExec), and the [Impacket](https://github.com/fortra/impacket) suite
@@ -0,0 +1,20 @@
1
+ [project]
2
+ name = "recon-setup"
3
+ version = "0.1.0"
4
+ description = "Automate the setup and enumeration process for offensive security labs."
5
+ requires-python = ">=3.10"
6
+ dependencies = [
7
+ "impacket",
8
+ "libtmux==0.53.1",
9
+ "requests",
10
+ "rich",
11
+ "urllib3",
12
+ "watchdog",
13
+ ]
14
+
15
+ [project.scripts]
16
+ recon-setup = "recon_setup.main:main"
17
+
18
+ [build-system]
19
+ requires = ["hatchling"]
20
+ build-backend = "hatchling.build"
File without changes
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/python3
2
+
3
+ import argparse
4
+ import ctypes
5
+ import libtmux
6
+ import os
7
+ import signal
8
+ import subprocess
9
+ import sys
10
+ from time import sleep
11
+
12
+ from recon_setup.utils.context import Context
13
+ from recon_setup.utils.htb_cli import spawn_machine
14
+ from recon_setup.watchers.nmap_watcher import watch_nmap
15
+
16
+ def set_death_signal(sig=signal.SIGHUP):
17
+ """Send SIGHUP to the forked process when its parent dies"""
18
+ libc = ctypes.CDLL("libc.so.6")
19
+ PR_SET_PDEATHSIG = 1
20
+ libc.prctl(PR_SET_PDEATHSIG, sig)
21
+
22
+ def verify_connection(ip):
23
+ """Verify connection with target IP."""
24
+ reachable = False
25
+ while not reachable:
26
+ try:
27
+ subprocess.check_call(
28
+ ["ping", "-c1", "-W.5", ip],
29
+ stdout=subprocess.DEVNULL,
30
+ stderr=subprocess.DEVNULL
31
+ )
32
+ reachable = True
33
+ except subprocess.CalledProcessError:
34
+ reachable = False
35
+ sleep(0.5)
36
+ sleep(1)
37
+
38
+ def main():
39
+ # Initialize parser
40
+ parser = argparse.ArgumentParser(description="Automate the setup and enumeration process for offensive security labs.")
41
+
42
+ # Initialize session arguments
43
+ session_group = parser.add_argument_group("Session Arguments", "Arguments related to session configuration")
44
+ session_group.add_argument("session_name", type=str, help="Name of the tmux session to be created")
45
+ session_group.add_argument("-v", "--vpn_path", type=str, help="Path of your VPN file")
46
+ session_group.add_argument("-s", "--session_path", type=str, default="/workspace/machines", help="Path to where the session will be created (default: /workspace/machines)")
47
+ session_group.add_argument("-i", "--ip", type=str, help="IP address of the target machine")
48
+
49
+ # Initialize HTB CLI arguments
50
+ htb_cli_group = parser.add_argument_group("HTB CLI Arguments", "Arguments related to HTB CLI functionality")
51
+ htb_cli_group.add_argument("--spawn", type=str, help="Spawn the target machine via the HTB API instead of providing an IP (requires HTB_TOKEN env var)")
52
+ htb_cli_group.add_argument("-n", "--new_release", action="store_true", help="Wait until T-60s before the scheduled release time (7pm UTC) and spawn automatically via the HTB API")
53
+
54
+ # Initialize automation arguments
55
+ automation_group = parser.add_argument_group("Automation Arguments", "Arguments related to automated tasking")
56
+ automation_group.add_argument("-a", "--automate", action="store_true", help="Optional flag to enable automated tasks on the fly from nmap scan results")
57
+ automation_group.add_argument("-u", "--username", type=str, help="Username to supply automated tasks (AD only)")
58
+ automation_group.add_argument("-p", "--password", type=str, help="Password to supply automated tasks (AD only)")
59
+ automation_group.add_argument("-d", "--debug", action="store_true", help="Enable debug mode for automation")
60
+
61
+ # Initialize parser
62
+ args = parser.parse_args()
63
+
64
+ # Define local variables
65
+ session_name = args.session_name
66
+ vpn_path = os.path.abspath(args.vpn_path) if args.vpn_path is not None else None
67
+ session_path = args.session_path
68
+ ip = args.ip
69
+ spawn = args.spawn
70
+ new_release = args.new_release
71
+ automate = args.automate
72
+ user = args.username
73
+ password = args.password
74
+ debug = args.debug
75
+
76
+ # Define file paths
77
+ scan_script_file = "/opt/scripts/scan_machine.py"
78
+ target_dir = os.path.join(session_path, session_name)
79
+ nmap_dir = os.path.join(target_dir, "nmap")
80
+ log_dir = os.path.join(target_dir, "logs")
81
+ tmux_pipe_file = os.path.join(log_dir, "tmux_pipe.log")
82
+ log_file = os.path.join(log_dir, "task.log")
83
+ debug_file = os.path.join(log_dir, "debug.log")
84
+ users_file = os.path.join(target_dir, "valid_users.txt")
85
+ creds_file = os.path.join(target_dir, "creds.txt")
86
+
87
+ # Check for nmap wrapper script
88
+ if not os.path.isfile(scan_script_file):
89
+ print(f"Error: Required nmap wrapper script not found at {scan_script_file}", file=sys.stderr)
90
+ sys.exit(1)
91
+
92
+ # Initialize HTB machine if relevant arguments are included
93
+ if spawn:
94
+ ip = spawn_machine(spawn, new_release)
95
+ if not ip:
96
+ print("Error: HTB CLI failed to provide an IP")
97
+ sys.exit(1)
98
+ else:
99
+ ip = args.ip
100
+
101
+ # Initialize environment
102
+ os.makedirs(nmap_dir, exist_ok=True)
103
+ os.chdir(target_dir)
104
+
105
+ # Initialize tmux session
106
+ server = libtmux.Server()
107
+ session = server.new_session(
108
+ session_name=session_name,
109
+ window_name="services",
110
+ attach=False
111
+ )
112
+ initial_window = session.active_window
113
+ openvpn_pane = initial_window.active_pane
114
+ if os.path.isfile(vpn_path):
115
+ openvpn_pane.send_keys(f"sudo openvpn {vpn_path}")
116
+ ligolo_pane = openvpn_pane.split(direction=libtmux.pane.PaneDirection.Below)
117
+ ligolo_pane.send_keys("mkdir -p ligolo; cd ligolo; proxy -selfcert")
118
+ ip_pane = openvpn_pane.split(direction=libtmux.pane.PaneDirection.Right)
119
+ ip_pane.send_keys("command -v htb-gui &>/dev/null && htb-gui; ipa")
120
+ updog_pane = ligolo_pane.split(direction=libtmux.pane.PaneDirection.Right)
121
+ updog_pane.send_keys("updog -p 8000 -d ~/staging")
122
+ base_window = session.new_window(window_name=ip)
123
+
124
+ # Initialize panes
125
+ nmap_pane = base_window.active_pane
126
+ base_pane = base_window.split(direction=libtmux.pane.PaneDirection.Right)
127
+
128
+ # Fork process to attach to tmux session while executing additional tasks
129
+ pid = os.fork()
130
+ if pid == 0:
131
+ set_death_signal()
132
+
133
+ # Establish connection with VPN and initialize the base window
134
+ if os.path.isfile(vpn_path):
135
+ verify_connection(ip)
136
+ sleep(1)
137
+ session.select_window(2)
138
+ base_pane.select()
139
+
140
+ # Initialize automation if argument is provided
141
+ if automate:
142
+ os.makedirs(log_dir, exist_ok=True)
143
+ for file in [tmux_pipe_file, debug_file, users_file, creds_file]:
144
+ if os.path.isfile(file):
145
+ os.remove(file)
146
+
147
+ # Clear log file while also making sure it exists
148
+ with open(log_file, 'w'):
149
+ pass
150
+
151
+ # Initialize stdio depending on debug flag
152
+ with open(os.devnull, "wb") as null:
153
+ if debug:
154
+ with open(debug_file, "wb") as log:
155
+ os.dup2(null.fileno(), 0)
156
+ os.dup2(log.fileno(), 1)
157
+ os.dup2(log.fileno(), 2)
158
+ else:
159
+ os.dup2(null.fileno(), 0)
160
+ os.dup2(null.fileno(), 1)
161
+ os.dup2(null.fileno(), 2)
162
+
163
+ # Initialize context
164
+ context = Context(session, nmap_pane, nmap_dir, tmux_pipe_file, log_file, users_file, creds_file, ip)
165
+ if user and password:
166
+ context.add_initial_cred(user.lower(), password=password)
167
+
168
+ # Initialize task logging
169
+ log_pane = base_pane.split(size=20)
170
+ sleep(1)
171
+ log_pane.send_keys(f"clear && tail -n +0 -f {log_file}")
172
+
173
+ # Initialize nmap scanning and automation
174
+ os.remove(tmux_pipe_file)
175
+ nmap_pane.cmd("pipe-pane", f"cat >> {tmux_pipe_file}")
176
+ nmap_pane.send_keys(f"{scan_script_file} {ip}")
177
+ watch_nmap(context)
178
+ elif ip:
179
+ nmap_pane.send_keys(f"{scan_script_file} {ip}")
180
+ os._exit(0)
181
+ else:
182
+ os.execvp("tmux", ["tmux", "attach-session", "-t", session_name])
183
+
184
+ if __name__ == "__main__":
185
+ main()
File without changes
@@ -0,0 +1,93 @@
1
+ import re
2
+ import subprocess
3
+
4
+ from recon_setup.utils.logger import write_log
5
+
6
+ _NEGATIVE = re.compile(r"\[\-\]")
7
+
8
+
9
+ def anonymous_bind(context):
10
+ cmd = ["nxc", "ldap", context.ip, "-u", "", "-p", ""]
11
+ try:
12
+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=8, check=False)
13
+ except Exception as e:
14
+ write_log(context.log_file, f"NetExec ldap subprocess error: {e}", "ERROR")
15
+ return False
16
+ out = p.stdout or ""
17
+ return not _NEGATIVE.search(out)
18
+
19
+
20
+ def enum_smb_shares(context):
21
+ def run_nxc(user, password):
22
+ cmd = f"nxc smb {context.ip} -u {user} "
23
+ if password:
24
+ cmd += f"-p '{password}' -k "
25
+ else:
26
+ cmd += f"-p '' "
27
+ cmd += f"--shares"
28
+ # write_log(context.log_file, f"Running: {cmd}", "INFO")
29
+ try:
30
+ p = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=8, check=False)
31
+ return p.stdout or ""
32
+ except Exception as e:
33
+ write_log(context.log_file, f"Subprocess exception: {e}", "ERROR")
34
+ return ""
35
+
36
+ def parse_shares(raw):
37
+ lines = [ln.rstrip("\n") for ln in raw.splitlines()]
38
+ hdr_idx = None
39
+ for i, ln in enumerate(lines):
40
+ if "Share" in ln and "Permissions" in ln and "Remark" in ln:
41
+ hdr_idx = i
42
+ break
43
+ if hdr_idx is None or hdr_idx + 2 >= len(lines):
44
+ return []
45
+
46
+ header = lines[hdr_idx]
47
+ try:
48
+ c_share = header.index("Share")
49
+ c_perm = header.index("Permissions")
50
+ c_remark = header.index("Remark")
51
+ except ValueError:
52
+ return []
53
+
54
+ shares = []
55
+ for ln in lines[hdr_idx + 2 :]:
56
+ if "SMB" not in ln or not ln.strip() or ln.strip().startswith("-----"):
57
+ continue
58
+ pad = ln + " " * max(0, c_remark + 1 - len(ln))
59
+ name = pad[c_share:c_perm].strip()
60
+ perms = pad[c_perm:c_remark].strip()
61
+ remark = pad[c_remark:].strip()
62
+ if not name or name.startswith("-----"):
63
+ continue
64
+ access = [p.strip() for p in perms.split(",") if p.strip()] if perms else []
65
+ shares.append({"name": name, "remark": remark, "access": access})
66
+
67
+ return shares
68
+
69
+ # 1) user/pass
70
+ if context.creds_exist():
71
+ user, password = context.get_initial_cred()
72
+ if user is not None:
73
+ out = run_nxc(user, password)
74
+ shares = parse_shares(out)
75
+ if shares:
76
+ return shares, "user/pass"
77
+ write_log(context.log_file, "Share enum with provided credentials failed; trying null session", "INFO")
78
+
79
+ # 2) null
80
+ out = run_nxc("''", "")
81
+ shares = parse_shares(out)
82
+ if shares:
83
+ write_log(context.log_file, "Null authentication is enabled for reading shares", "SUCCESS")
84
+ return shares, "null"
85
+
86
+ # 3) guest
87
+ out = run_nxc("a", "")
88
+ shares = parse_shares(out)
89
+ if shares:
90
+ write_log(context.log_file, "Guest authentication is enabled for reading shares", "SUCCESS")
91
+ return shares, "guest"
92
+
93
+ return None, None