- 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.
139 lines
4.9 KiB
Python
139 lines
4.9 KiB
Python
#!/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())
|