wuptracker 0.1.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.
- wuptracker/__init__.py +3 -0
- wuptracker/capture.py +342 -0
- wuptracker/cli.py +187 -0
- wuptracker/common.py +382 -0
- wuptracker/config.py +156 -0
- wuptracker/configtool.py +561 -0
- wuptracker/generate.py +282 -0
- wuptracker/llm.py +315 -0
- wuptracker/paths.py +25 -0
- wuptracker/profiles.py +293 -0
- wuptracker-0.1.0.dist-info/METADATA +442 -0
- wuptracker-0.1.0.dist-info/RECORD +15 -0
- wuptracker-0.1.0.dist-info/WHEEL +4 -0
- wuptracker-0.1.0.dist-info/entry_points.txt +2 -0
- wuptracker-0.1.0.dist-info/licenses/LICENSE +21 -0
wuptracker/profiles.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
"""Perfis (domínio) e estilos (formato) do writeup.
|
|
2
|
+
|
|
3
|
+
DOMÍNIO — o que a sessão é, define o prompt de visão e o foco do conteúdo:
|
|
4
|
+
"thm" : pentest / CTF (TryHackMe, HTB...)
|
|
5
|
+
"generico" : qualquer sessão de trabalho no computador
|
|
6
|
+
|
|
7
|
+
ESTILO — como o writeup é escrito:
|
|
8
|
+
"tecnico" : denso e preciso, com seções fixas e âncoras de horário (padrão)
|
|
9
|
+
"corrido" : narrativa fluida em texto corrido
|
|
10
|
+
"resumo" : resumo executivo curto
|
|
11
|
+
"blog" : artigo técnico didático (dev.to / Medium)
|
|
12
|
+
"linkedin" : post pronto para publicar no LinkedIn
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# ===========================================================================
|
|
16
|
+
# DOMÍNIOS
|
|
17
|
+
# ===========================================================================
|
|
18
|
+
THM_ANALYZE = """Você está analisando um screenshot de uma sessão de pentest/CTF no TryHackMe.
|
|
19
|
+
|
|
20
|
+
Analise a imagem e responda SOMENTE em JSON válido, sem markdown, sem texto fora do JSON:
|
|
21
|
+
|
|
22
|
+
{
|
|
23
|
+
"phase": "reconhecimento | enumeracao | acesso_inicial | pos_exploracao | privesc | web | crypto | stego | misc",
|
|
24
|
+
"title": "Título curto descritivo (max 60 chars)",
|
|
25
|
+
"description": "Descrição técnica do que está acontecendo na tela",
|
|
26
|
+
"commands": ["lista de comandos visíveis no terminal, se houver"],
|
|
27
|
+
"findings": ["informações relevantes encontradas: IPs, portas, usuários, hashes, flags"],
|
|
28
|
+
"tool": "ferramenta principal visível (nmap, burp, gobuster, metasploit, etc.)",
|
|
29
|
+
"is_key_moment": true,
|
|
30
|
+
"key_moment_reason": "por que é importante (flag encontrada, shell obtida, vuln confirmada) - null se não for key moment",
|
|
31
|
+
"crop": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Em "crop", indique o retângulo que contém APENAS o conteúdo relevante (a janela do
|
|
35
|
+
terminal, o painel do Burp, o diálogo, o trecho de saída importante) — descartando
|
|
36
|
+
barra de tarefas, área de trabalho vazia, outras janelas irrelevantes. Use frações
|
|
37
|
+
de 0 a 1 relativas à largura/altura da imagem: x,y = canto superior esquerdo;
|
|
38
|
+
w,h = largura/altura. Se a tela inteira for relevante, use x:0, y:0, w:1, h:1."""
|
|
39
|
+
|
|
40
|
+
GEN_ANALYZE = """Você está analisando um screenshot de uma sessão de trabalho no computador
|
|
41
|
+
(pode ser desenvolvimento, pesquisa, configuração de sistema, debugging, design,
|
|
42
|
+
administração, estudo — qualquer coisa).
|
|
43
|
+
|
|
44
|
+
Analise a imagem e responda SOMENTE em JSON válido, sem markdown, sem texto fora do JSON:
|
|
45
|
+
|
|
46
|
+
{
|
|
47
|
+
"category": "codigo | terminal | navegador | config | leitura | design | comunicacao | erro | outro",
|
|
48
|
+
"title": "Título curto descritivo do que está acontecendo (max 60 chars)",
|
|
49
|
+
"description": "Descrição objetiva do que está sendo feito ou mostrado na tela",
|
|
50
|
+
"actions": ["passos, comandos, edições ou ações concretas visíveis"],
|
|
51
|
+
"findings": ["descobertas, resultados, valores, mensagens de erro, decisões relevantes"],
|
|
52
|
+
"app": "aplicativo ou ferramenta principal visível (VS Code, terminal, Firefox, etc.)",
|
|
53
|
+
"is_key_moment": true,
|
|
54
|
+
"key_moment_reason": "por que este momento importa (algo funcionou, um bug foi encontrado, uma decisão foi tomada, um marco foi atingido) - null se não for um momento-chave",
|
|
55
|
+
"crop": {"x": 0.0, "y": 0.0, "w": 1.0, "h": 1.0}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
Em "crop", indique o retângulo que contém APENAS o conteúdo relevante (a janela ou
|
|
59
|
+
painel em foco, o trecho de código, o diálogo, a mensagem de erro) — descartando
|
|
60
|
+
barra de tarefas, área de trabalho vazia e janelas irrelevantes ao fundo. Use
|
|
61
|
+
frações de 0 a 1 relativas à largura/altura da imagem: x,y = canto superior
|
|
62
|
+
esquerdo; w,h = largura/altura. Se a tela inteira for relevante, use x:0, y:0,
|
|
63
|
+
w:1, h:1."""
|
|
64
|
+
|
|
65
|
+
THM_SECTIONS = """# {room_name} — TryHackMe
|
|
66
|
+
|
|
67
|
+
## Reconhecimento
|
|
68
|
+
(nmap, scans iniciais, portas abertas)
|
|
69
|
+
|
|
70
|
+
## Enumeração
|
|
71
|
+
(serviços explorados, informações coletadas)
|
|
72
|
+
|
|
73
|
+
## Acesso Inicial
|
|
74
|
+
(como foi obtido o primeiro acesso)
|
|
75
|
+
|
|
76
|
+
## Pós-Exploração
|
|
77
|
+
(movimentação, arquivos encontrados, flags de usuário)
|
|
78
|
+
|
|
79
|
+
## Escalação de Privilégio
|
|
80
|
+
(vetor usado, como foi obtido root)
|
|
81
|
+
|
|
82
|
+
## Flags
|
|
83
|
+
| Flag | Valor |
|
|
84
|
+
|---|---|
|
|
85
|
+
| User | ... |
|
|
86
|
+
| Root | ... |
|
|
87
|
+
|
|
88
|
+
## Ferramentas Utilizadas
|
|
89
|
+
(lista com link para cada ferramenta mencionada)
|
|
90
|
+
|
|
91
|
+
## Lições Aprendidas
|
|
92
|
+
(técnicas principais, conceitos importantes da room)
|
|
93
|
+
|
|
94
|
+
## Referências
|
|
95
|
+
(CVEs mencionados, links relevantes)"""
|
|
96
|
+
|
|
97
|
+
GEN_SECTIONS = """# {room_name}
|
|
98
|
+
|
|
99
|
+
## Resumo
|
|
100
|
+
(2-4 frases: qual era o objetivo da sessão e onde ela chegou)
|
|
101
|
+
|
|
102
|
+
## Linha do Tempo
|
|
103
|
+
(narrativa cronológica do que foi feito, passo a passo, usando os horários
|
|
104
|
+
[HH:MM:SS] das capturas como âncora; agrupe passos relacionados)
|
|
105
|
+
|
|
106
|
+
## Descobertas e Resultados
|
|
107
|
+
(o que foi aprendido, valores/configurações importantes, o que passou a funcionar)
|
|
108
|
+
|
|
109
|
+
## Problemas Encontrados
|
|
110
|
+
(erros, becos sem saída, pendências — com a mensagem de erro quando disponível)
|
|
111
|
+
|
|
112
|
+
## Estado Final e Próximos Passos
|
|
113
|
+
(onde as coisas ficaram ao fim da sessão; o que falta fazer)
|
|
114
|
+
|
|
115
|
+
## Ferramentas e Referências
|
|
116
|
+
(apps usados; links/documentação que apareceram)"""
|
|
117
|
+
|
|
118
|
+
DOMAINS = {
|
|
119
|
+
"thm": {
|
|
120
|
+
"analyze_prompt": THM_ANALYZE,
|
|
121
|
+
"label": "sessão de pentest/CTF no TryHackMe",
|
|
122
|
+
"focus": ("Sessão de segurança ofensiva. Foque em vetores de ataque, "
|
|
123
|
+
"comandos, vulnerabilidades, credenciais e flags."),
|
|
124
|
+
"sections": THM_SECTIONS,
|
|
125
|
+
"tags": "thm, writeup",
|
|
126
|
+
},
|
|
127
|
+
"generico": {
|
|
128
|
+
"analyze_prompt": GEN_ANALYZE,
|
|
129
|
+
"label": "sessão de trabalho no computador",
|
|
130
|
+
"focus": ("Pode ser desenvolvimento, pesquisa, configuração, debugging "
|
|
131
|
+
"ou estudo. Foque no que foi tentado, no que funcionou, no que "
|
|
132
|
+
"deu errado e no que foi descoberto."),
|
|
133
|
+
"sections": GEN_SECTIONS,
|
|
134
|
+
"tags": "writeup, log",
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
# ===========================================================================
|
|
139
|
+
# ESTILOS
|
|
140
|
+
# ===========================================================================
|
|
141
|
+
STYLES = {
|
|
142
|
+
"tecnico": """Markdown técnico e preciso, começando com frontmatter YAML:
|
|
143
|
+
---
|
|
144
|
+
tags: [{tags}, {fase_tags}]
|
|
145
|
+
criado: {data_hoje}
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
Depois, estruture EXATAMENTE com estas seções (omita uma seção se realmente não
|
|
149
|
+
houver conteúdo para ela; não invente):
|
|
150
|
+
|
|
151
|
+
{sections}
|
|
152
|
+
|
|
153
|
+
- Mantenha as âncoras de horário [HH:MM:SS] das capturas ao longo do texto
|
|
154
|
+
- Bloco de código para todo comando, trecho de código e mensagem de erro
|
|
155
|
+
- Denso em informação, direto ao ponto""",
|
|
156
|
+
|
|
157
|
+
"corrido": """Markdown em texto corrido: uma narrativa fluida, impessoal ou em 1ª pessoa
|
|
158
|
+
do plural, em ordem cronológica — o que foi feito, o que se descobriu, como terminou.
|
|
159
|
+
- Frontmatter mínimo (tags: [{tags}, {fase_tags}], criado: {data_hoje})
|
|
160
|
+
- Um título H1 e poucos ou nenhum subtítulo; deixe o texto fluir em parágrafos
|
|
161
|
+
- Sem tabelas e sem listas de "fases"; incorpore os detalhes na prosa
|
|
162
|
+
- Ainda use blocos de código para comandos e trechos relevantes
|
|
163
|
+
- Cite horários pontualmente só quando ajudar""",
|
|
164
|
+
|
|
165
|
+
"resumo": """Resumo executivo curto em Markdown (máx. ~250 palavras):
|
|
166
|
+
- Frontmatter mínimo (tags: [{tags}], criado: {data_hoje})
|
|
167
|
+
- Um título H1
|
|
168
|
+
- 1 parágrafo de contexto (qual era o objetivo)
|
|
169
|
+
- 5 a 10 bullets com o essencial do que foi feito e descoberto
|
|
170
|
+
- 1 parágrafo de conclusão / estado final / próximos passos
|
|
171
|
+
- Sem linha do tempo detalhada, sem tabelas""",
|
|
172
|
+
|
|
173
|
+
"blog": """Artigo de blog técnico em Markdown (estilo dev.to / Medium):
|
|
174
|
+
- Frontmatter (tags: [{tags}, {fase_tags}], criado: {data_hoje})
|
|
175
|
+
- Título H1 chamativo, porém honesto
|
|
176
|
+
- Introdução que situa o leitor (o problema, o desafio)
|
|
177
|
+
- Desenvolvimento em seções com subtítulos temáticos (não por "fase"), em ordem lógica
|
|
178
|
+
- Blocos de código comentados; explique o raciocínio, não só o comando
|
|
179
|
+
- Seção final de conclusão e "o que aprendi"
|
|
180
|
+
- Tom didático: um leitor competente que não acompanhou a sessão""",
|
|
181
|
+
|
|
182
|
+
"linkedin": """Post pronto para publicar no LinkedIn. TEXTO PURO — não use markdown,
|
|
183
|
+
nem frontmatter, nem títulos com #.
|
|
184
|
+
- 1ª pessoa, tom pessoal e acessível, mas tecnicamente correto
|
|
185
|
+
- Primeira linha = gancho forte (o que você fez ou aprendeu)
|
|
186
|
+
- Corpo curto (120–220 palavras) contando a jornada em poucas frases
|
|
187
|
+
- 3 a 5 aprendizados práticos em bullets (use "•" ou "-")
|
|
188
|
+
- Encerre com uma pergunta ou convite à discussão
|
|
189
|
+
- 3 a 6 hashtags relevantes na última linha
|
|
190
|
+
- NÃO exponha dados sensíveis: IPs/hosts internos, flags, credenciais, nomes de
|
|
191
|
+
cliente — generalize ("uma máquina alvo", "um serviço web")""",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
STYLES_WITH_IMAGES = {"tecnico", "corrido", "blog"}
|
|
195
|
+
|
|
196
|
+
WRITEUP_TEMPLATE = """Você está produzindo um registro ("writeup") de uma {domain_label},
|
|
197
|
+
a partir dos screenshots capturados ao longo da sessão e da análise de cada um.
|
|
198
|
+
|
|
199
|
+
{domain_focus}
|
|
200
|
+
|
|
201
|
+
METADADOS DA SESSÃO:
|
|
202
|
+
- Título/Room: {room_name}
|
|
203
|
+
- Duração: {duration}
|
|
204
|
+
- Total de capturas: {total_captures}
|
|
205
|
+
- Ferramentas/apps: {tools_list}
|
|
206
|
+
|
|
207
|
+
CAPTURAS (em ordem cronológica):
|
|
208
|
+
{captures_context}
|
|
209
|
+
|
|
210
|
+
FORMATO DE SAÍDA — estilo "{style_name}":
|
|
211
|
+
{style_block}
|
|
212
|
+
|
|
213
|
+
REGRAS GERAIS:
|
|
214
|
+
- Baseie-se somente no que aparece nas capturas e análises; se não tiver certeza,
|
|
215
|
+
omita em vez de inventar
|
|
216
|
+
- Não force um enquadramento que não existe (se não é pentest, não escreva como pentest)
|
|
217
|
+
- {image_rule}
|
|
218
|
+
- Responda SOMENTE com o conteúdo final do writeup, sem nenhum comentário seu."""
|
|
219
|
+
|
|
220
|
+
IMAGE_RULE_ON = (
|
|
221
|
+
'IMAGENS: quando uma captura trouxer a linha "Imagem: CAMINHO", incorpore-a no '
|
|
222
|
+
"ponto do texto onde é discutida, com a sintaxe exata  "
|
|
223
|
+
"usando o CAMINHO fornecido sem alterá-lo, cada imagem no máximo uma vez")
|
|
224
|
+
IMAGE_RULE_OFF = 'IMAGENS: ignore as linhas "Imagem:" das capturas — este estilo não usa imagens'
|
|
225
|
+
|
|
226
|
+
# ===========================================================================
|
|
227
|
+
# Resolução de nomes
|
|
228
|
+
# ===========================================================================
|
|
229
|
+
DOMAIN_ALIASES = {
|
|
230
|
+
"generic": "generico", "geral": "generico", "trabalho": "generico",
|
|
231
|
+
"log": "generico", "tryhackme": "thm", "pentest": "thm", "ctf": "thm", "htb": "thm",
|
|
232
|
+
}
|
|
233
|
+
STYLE_ALIASES = {
|
|
234
|
+
"técnico": "tecnico", "technical": "tecnico", "default": "tecnico", "padrao": "tecnico",
|
|
235
|
+
"narrativo": "corrido", "prosa": "corrido", "narrative": "corrido",
|
|
236
|
+
"resumido": "resumo", "tldr": "resumo", "breve": "resumo", "executivo": "resumo",
|
|
237
|
+
"artigo": "blog", "medium": "blog", "devto": "blog",
|
|
238
|
+
"social": "linkedin", "post": "linkedin", "li": "linkedin",
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def resolve_domain(name):
|
|
243
|
+
if not name:
|
|
244
|
+
return "thm"
|
|
245
|
+
name = name.strip().lower()
|
|
246
|
+
name = DOMAIN_ALIASES.get(name, name)
|
|
247
|
+
if name not in DOMAINS:
|
|
248
|
+
raise SystemExit(
|
|
249
|
+
f"[!] Perfil inválido: {name!r}. Use: {', '.join(DOMAINS)}")
|
|
250
|
+
return name
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def resolve_style(name):
|
|
254
|
+
if not name:
|
|
255
|
+
return "tecnico"
|
|
256
|
+
name = name.strip().lower()
|
|
257
|
+
name = STYLE_ALIASES.get(name, name)
|
|
258
|
+
if name not in STYLES:
|
|
259
|
+
raise SystemExit(
|
|
260
|
+
f"[!] Estilo inválido: {name!r}. Use: {', '.join(STYLES)}")
|
|
261
|
+
return name
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
# compat com chamadas antigas (capture.py usa o analyze_prompt)
|
|
265
|
+
resolve = resolve_domain
|
|
266
|
+
PROFILES = DOMAINS
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def get(name):
|
|
270
|
+
return DOMAINS[resolve_domain(name)]
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def build_writeup_prompt(domain, style, *, room_name, duration, total_captures,
|
|
274
|
+
tools_list, captures_context, fase_tags, data_hoje):
|
|
275
|
+
d = DOMAINS[resolve_domain(domain)]
|
|
276
|
+
sname = resolve_style(style)
|
|
277
|
+
|
|
278
|
+
sections = d["sections"].replace("{room_name}", room_name)
|
|
279
|
+
style_block = STYLES[sname].format(
|
|
280
|
+
sections=sections, tags=d["tags"], fase_tags=fase_tags, data_hoje=data_hoje)
|
|
281
|
+
|
|
282
|
+
return WRITEUP_TEMPLATE.format(
|
|
283
|
+
domain_label=d["label"],
|
|
284
|
+
domain_focus=d["focus"],
|
|
285
|
+
room_name=room_name,
|
|
286
|
+
duration=duration,
|
|
287
|
+
total_captures=total_captures,
|
|
288
|
+
tools_list=tools_list,
|
|
289
|
+
captures_context=captures_context,
|
|
290
|
+
style_name=sname,
|
|
291
|
+
style_block=style_block,
|
|
292
|
+
image_rule=IMAGE_RULE_ON if sname in STYLES_WITH_IMAGES else IMAGE_RULE_OFF,
|
|
293
|
+
)
|