saber: manuales cosechados e indice publico pre-construido
- manuales.py cosecha el --help de las herramientas que no traen man page (ffuf, sqlmap, suite impacket, netexec, hydra...) y lo escribe como markdown en conocimiento/09 - Manuales/. Es la referencia de opciones que la man page no da; complementa el cheatsheet de invocaciones comunes. - indexa.py --publico construye saber-publico.jsonl: glosario + pack de conocimiento con los vectores ya calculados, 100% publico y portable. - busca.py usa ese indice publico como fallback cuando no hay indice personal, asi un clon recien hecho consulta al instante sin reindexar nada. - .gitignore deja pasar saber-publico.jsonl; el personal (saber.jsonl) sigue fuera. - docs/rag.md y el README del pack documentan ambas cosas.
This commit is contained in:
parent
1dfb3513e6
commit
9fa0a40edf
12 changed files with 1206 additions and 45 deletions
139
nucleo/saber/manuales.py
Normal file
139
nucleo/saber/manuales.py
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Cosecha el manual de las herramientas que no traen man page.
|
||||
|
||||
./manuales.py regenera conocimiento/09 - Manuales/
|
||||
./manuales.py --lista di que herramientas cosecharia y cuales estan
|
||||
|
||||
Muchas herramientas de pentesting (ffuf, sqlmap, la suite impacket, netexec...)
|
||||
no instalan man page: su "manual" es lo que sueltan con --help. Este script lo
|
||||
captura, lo limpia de rutas y datos de la maquina, y lo escribe como markdown en
|
||||
el pack de conocimiento, donde indexa.py lo recoge como todo lo demas.
|
||||
|
||||
Solo cosecha lo que ESTA instalado en la maquina. El resultado (los .md) se
|
||||
commitea, asi que crece con lo que cada quien tenga: quien instale una
|
||||
herramienta mas, regenera y manda el .md nuevo. Es el complemento verificado del
|
||||
cheatsheet escrito a mano (que da las invocaciones comunes; esto da la lista
|
||||
completa de opciones).
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
AQUI = os.path.dirname(os.path.abspath(__file__))
|
||||
DESTINO = os.path.join(AQUI, "conocimiento", "09 - Manuales")
|
||||
|
||||
# Las herramientas cuyo manual vale la pena y que no suelen traer man page.
|
||||
# (comando, [argumentos para pedir la ayuda]). Se prueban en orden hasta que una
|
||||
# devuelva algo con sustancia.
|
||||
CATALOGO = [
|
||||
("ffuf", ["-h"]),
|
||||
("feroxbuster", ["--help"]),
|
||||
("gobuster", ["--help"]),
|
||||
("sqlmap", ["-hh"]),
|
||||
("nuclei", ["-h"]),
|
||||
("hydra", ["-h"]),
|
||||
("netexec", ["--help"]),
|
||||
("crackmapexec", ["--help"]),
|
||||
("evil-winrm", ["--help"]),
|
||||
("john", []), # john sin args ya imprime el uso
|
||||
("hashcat", ["--help"]),
|
||||
("wpscan", ["--help"]),
|
||||
("nikto", ["-Help"]),
|
||||
("smbmap", ["-h"]),
|
||||
("enum4linux-ng", ["-h"]),
|
||||
("wfuzz", ["-h"]),
|
||||
("dirsearch", ["-h"]),
|
||||
("responder", ["-h"]),
|
||||
("chisel", ["--help"]),
|
||||
("ligolo-proxy", ["-h"]),
|
||||
("bloodhound-python", ["-h"]),
|
||||
("certipy", ["-h"]),
|
||||
("kerbrute", ["-h"]),
|
||||
# La suite impacket: cada binario es una herramienta.
|
||||
("impacket-secretsdump", ["-h"]),
|
||||
("impacket-GetUserSPNs", ["-h"]),
|
||||
("impacket-GetNPUsers", ["-h"]),
|
||||
("impacket-getTGT", ["-h"]),
|
||||
("impacket-psexec", ["-h"]),
|
||||
("impacket-wmiexec", ["-h"]),
|
||||
("impacket-smbexec", ["-h"]),
|
||||
("impacket-smbserver", ["-h"]),
|
||||
("impacket-mssqlclient", ["-h"]),
|
||||
("impacket-ntlmrelayx", ["-h"]),
|
||||
]
|
||||
|
||||
ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")
|
||||
CASA = re.compile(re.escape(os.path.expanduser("~")))
|
||||
USUARIO = re.compile(re.escape(os.environ.get("USER", "\0nadie\0")))
|
||||
|
||||
|
||||
def limpia(texto):
|
||||
"""Quita colores y cualquier rastro de la maquina de esta cosecha."""
|
||||
texto = ANSI.sub("", texto)
|
||||
texto = CASA.sub("~", texto) # /home/quien-sea -> ~
|
||||
texto = USUARIO.sub("usuario", texto) # el login real -> generico
|
||||
texto = re.sub(r"[ \t]+\n", "\n", texto) # espacios al final de linea
|
||||
texto = re.sub(r"\n{3,}", "\n\n", texto) # como mucho una linea en blanco
|
||||
return texto.strip()
|
||||
|
||||
|
||||
def ayuda(cmd, args):
|
||||
"""La salida de ayuda de una herramienta, o None si no dice nada util."""
|
||||
if not shutil.which(cmd):
|
||||
return None
|
||||
for intento in (args, ["--help"], ["-h"], []):
|
||||
try:
|
||||
r = subprocess.run([cmd, *intento], capture_output=True, text=True,
|
||||
timeout=15, errors="ignore")
|
||||
except (subprocess.TimeoutExpired, OSError):
|
||||
continue
|
||||
salida = (r.stdout or "") + (r.stderr or "")
|
||||
salida = limpia(salida)
|
||||
if len(salida) >= 120: # menos que esto no es un manual
|
||||
return salida
|
||||
return None
|
||||
|
||||
|
||||
def escribe(cmd, texto):
|
||||
md = (f"# {cmd} — manual\n\n"
|
||||
f"Salida de `{cmd} {'--help' if not cmd.startswith('impacket') else '-h'}`, "
|
||||
f"capturada de la propia herramienta. Referencia completa de opciones "
|
||||
f"(las invocaciones del dia a dia estan en el cheatsheet de comandos).\n\n"
|
||||
f"```\n{texto}\n```\n")
|
||||
os.makedirs(DESTINO, exist_ok=True)
|
||||
ruta = os.path.join(DESTINO, f"{cmd}.md")
|
||||
with open(ruta, "w", encoding="utf-8") as f:
|
||||
f.write(md)
|
||||
return ruta
|
||||
|
||||
|
||||
def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--lista", action="store_true",
|
||||
help="di que cosecharia y que hay instalado, sin escribir")
|
||||
a = p.parse_args()
|
||||
|
||||
if a.lista:
|
||||
for cmd, _ in CATALOGO:
|
||||
hay = "instalada" if shutil.which(cmd) else "-"
|
||||
print(f" {cmd:26s} {hay}")
|
||||
return 0
|
||||
|
||||
hechos = faltan = 0
|
||||
for cmd, args in CATALOGO:
|
||||
texto = ayuda(cmd, args)
|
||||
if texto is None:
|
||||
faltan += 1
|
||||
continue
|
||||
ruta = escribe(cmd, texto)
|
||||
print(f" {cmd:26s} -> {os.path.relpath(ruta, AQUI)} ({len(texto)} car.)")
|
||||
hechos += 1
|
||||
print(f"\n {hechos} manuales cosechados, {faltan} no instalados en esta maquina")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Add a link
Reference in a new issue