eva-exploit 2.5__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.
@@ -0,0 +1,457 @@
1
+ import json
2
+ import getpass
3
+ import os
4
+ import re
5
+ import signal
6
+ import subprocess
7
+ from datetime import datetime, timezone
8
+
9
+ from colorama import Fore,Back,Style
10
+
11
+ from config import API_ENDPOINT, MAPS_DIR, REPORTS_DIR, SESSIONS_DIR, username
12
+ from modules.attack_map import generate_attack_map_files, open_attack_map
13
+ from modules.llm import LLM
14
+ from modules.reporting import build_html_report, open_report_file, try_generate_pdf
15
+ from utils.system import (
16
+ checkAnthropicKey,
17
+ checkAPI,
18
+ checkGeminiKey,
19
+ checkOpenAIKey,
20
+ get_current_version,
21
+ graceful_exit,
22
+ )
23
+ from utils.ui import cyber, menu, raw_input, spinner_start, spinner_stop
24
+
25
+
26
+ # +-------------------------------------------+
27
+ # | Main core -██ |
28
+ # | Handler for Eva utilities and |
29
+ # | Initialization and session manag |
30
+ # +-------------------------------------------+
31
+
32
+
33
+ class Eva:
34
+ def __init__(self, session_path, backend, go_menu):
35
+ self.session_path = session_path
36
+ self.last_output = ""
37
+ self.backend = backend
38
+ self.sessionName = self.session_path.stem
39
+ self.memory = {
40
+ "backend": backend,
41
+ "timeline": [],
42
+ "attack_map_html": "",
43
+ "attack_map_json": "",
44
+ }
45
+ self.go_menu = go_menu
46
+ self.app_version = get_current_version()
47
+ if session_path.exists():
48
+ self.memory = json.loads(session_path.read_text())
49
+ self.backend = self.memory.get("backend", backend)
50
+
51
+ self.model = LLM(self.backend)
52
+ self._hydrate_model_context()
53
+
54
+ def _hydrate_model_context(self):
55
+ history = []
56
+ restored_last_output = ""
57
+ for item in self.memory.get("timeline", []):
58
+ item_type = item.get("type")
59
+ if item_type == "user":
60
+ content = str(item.get("content", "")).strip()
61
+ if content:
62
+ history.append({"role": "user", "content": content})
63
+ elif item_type == "analysis":
64
+ content = str(item.get("content", "")).strip()
65
+ if content:
66
+ history.append({"role": "assistant", "content": content})
67
+ elif item_type == "command":
68
+ output = item.get("output", "")
69
+ if isinstance(output, str):
70
+ restored_last_output = output
71
+
72
+ self.model.history = history
73
+ self.last_output = restored_last_output
74
+
75
+ def save(self):
76
+ self.session_path.write_text(json.dumps(self.memory, indent=2))
77
+
78
+ def change_model_menu(self):
79
+ """
80
+ Model menu during session
81
+ """
82
+ options = [
83
+ f"Use WhiteRabbit-Neo LLM locally {'::[SELECTED]' if self.backend=='ollama' else ''}",
84
+ f"Use OpenAI GPT-5 {'::[SELECTED]' if self.backend=='gpt' else ''}",
85
+ f"Use G4F.dev {'::[SELECTED]' if self.backend=='g4f' else ''}",
86
+ f"Use Anthropic Claude {'::[SELECTED]' if self.backend=='anthropic' else ''}",
87
+ f"Use Google Gemini {'::[SELECTED]' if self.backend=='gemini' else ''}",
88
+
89
+ f"Use Custom API endpoint [{API_ENDPOINT}] {'::[SELECTED]' if self.backend=='api' else ''}"
90
+ ]
91
+ sel = menu("CHANGE BACKEND", options)
92
+ if sel == 0:
93
+ self.backend = "ollama"
94
+ self.memory["backend"] = self.backend
95
+ self.save()
96
+ elif sel == 1:
97
+ self.backend = "gpt"
98
+ checkOpenAIKey()
99
+ self.memory["backend"] = self.backend
100
+ self.save()
101
+ elif sel == 2:
102
+ self.backend = "g4f"
103
+ self.memory["backend"] = self.backend
104
+ self.save()
105
+ elif sel == 3:
106
+ self.backend = "anthropic"
107
+ checkAnthropicKey()
108
+ self.memory["backend"] = self.backend
109
+ self.save()
110
+ elif sel == 4:
111
+ self.backend = "gemini"
112
+ checkGeminiKey()
113
+ self.memory["backend"] = self.backend
114
+ self.save()
115
+ elif sel == 5:
116
+ self.backend = "api"
117
+ checkAPI()
118
+ self.memory["backend"] = self.backend
119
+ self.save()
120
+ self.model = LLM(self.backend)
121
+ self._hydrate_model_context()
122
+
123
+ def rename_session(self):
124
+ cyber("Type in the desired name for this session")
125
+ new_name = raw_input("⯁⮞ ").strip()
126
+ if not new_name:
127
+ cyber("[!] Session name cannot be empty.", color=Fore.RED)
128
+ return
129
+ if new_name == self.sessionName:
130
+ cyber("[!] New name is the same as current name.", color=Fore.YELLOW)
131
+ return
132
+ #
133
+ invalid_chars = '<>:"/\\|?*'
134
+ if any(char in new_name for char in invalid_chars):
135
+ cyber("[!] Invalid characters in name. Avoid < > : \" / \\ | ? *", color=Fore.RED)
136
+ return
137
+ new_path = SESSIONS_DIR / f"{new_name}.json"
138
+ if new_path.exists():
139
+ cyber("[!] A session with that name already exists.", color=Fore.YELLOW)
140
+ return
141
+ # Rename the session file
142
+ self.session_path.rename(new_path)
143
+ self.session_path = new_path
144
+ self.sessionName = new_name
145
+ self.save()
146
+ cyber(f"Session renamed to {new_name}", color=Fore.GREEN)
147
+
148
+ def run_command(self, cmd):
149
+ cyber(f"EXECUTING → {cmd}")
150
+ run_cmd = cmd
151
+ sudo_password = None
152
+
153
+ stripped = cmd.lstrip()
154
+ if stripped.startswith("sudo "):
155
+ if not re.search(r"(^|\s)-S(\s|$)", stripped):
156
+ run_cmd = re.sub(r"^\s*sudo(\s+)", r"sudo -S -p ''\1", cmd, count=1)
157
+ try:
158
+ sudo_password = getpass.getpass("Input sudo password: ")
159
+ except KeyboardInterrupt:
160
+ print(Fore.YELLOW + "\n// 🜂 Sudo prompt cancelled")
161
+ return
162
+ if not sudo_password:
163
+ print(Fore.YELLOW + "// 🜂 Empty password, command skipped")
164
+ return
165
+
166
+ proc = subprocess.Popen(
167
+ run_cmd, shell=True,
168
+ stdin=subprocess.PIPE,
169
+ stdout=subprocess.PIPE,
170
+ stderr=subprocess.STDOUT,
171
+ text=True,
172
+ preexec_fn=os.setsid
173
+ )
174
+ out = ""
175
+ try:
176
+ if sudo_password and proc.stdin:
177
+ proc.stdin.write(sudo_password + "\n")
178
+ proc.stdin.flush()
179
+ proc.stdin.close()
180
+ for line in proc.stdout:
181
+ print(line, end="")
182
+ out += line
183
+ except KeyboardInterrupt:
184
+ os.killpg(os.getpgid(proc.pid), signal.SIGINT)
185
+ print(Fore.RED + "\n/// 🜂 Command stopped by user.")
186
+ self.last_output = out
187
+ self.memory["timeline"].append({
188
+ "type": "command",
189
+ "cmd": cmd,
190
+ "output": out
191
+ })
192
+ self.save()
193
+
194
+ def generate_report(self):
195
+ if not self.memory.get("timeline"):
196
+ cyber("[!] No session data available to report.", color=Fore.YELLOW)
197
+ return
198
+
199
+ ts = datetime.now(timezone.utc).strftime("%Y%m%d_%H%M%S")
200
+ base = f"{self.sessionName}_{ts}"
201
+ html_path = REPORTS_DIR / f"{base}.html"
202
+ pdf_path = REPORTS_DIR / f"{base}.pdf"
203
+
204
+ html_content = build_html_report(
205
+ self.sessionName,
206
+ self.backend,
207
+ self.memory["timeline"],
208
+ version=self.app_version,
209
+ )
210
+ html_path.write_text(html_content, encoding="utf-8")
211
+
212
+ pdf_ok = try_generate_pdf(html_path, pdf_path)
213
+ html_opened = open_report_file(html_path)
214
+ pdf_opened = open_report_file(pdf_path) if pdf_ok else False
215
+
216
+ if pdf_ok:
217
+ cyber(f"[ ✔ ] Report generated.", color=Fore.GREEN)
218
+ print(f"{Style.BRIGHT}{Fore.GREEN}{html_path}\n{pdf_path}\n")
219
+ if html_opened or pdf_opened:
220
+ cyber("[ ✔ ] Opened report in browser", color=Fore.GREEN)
221
+ print(f"{Fore.GREEN}{html_path}")
222
+ if pdf_opened:
223
+ print(f"{Fore.GREEN}{pdf_path}")
224
+ print()
225
+ else:
226
+ cyber("[!] Could not open browser automatically. Check report path in ~/.config/eva/reports", color=Fore.YELLOW)
227
+
228
+ else:
229
+ cyber(
230
+ f"[ ✔ ] HTML report generated.",
231
+ color=Fore.GREEN,
232
+ )
233
+ print(f"{Style.BRIGHT}{Fore.GREEN}{html_path}\n {Fore.RED} :: PDF was not created (wkhtmltopdf not found!) \n")
234
+ if html_opened:
235
+ cyber("[ ✔ ] Opened report in browser", color=Fore.GREEN)
236
+ print(f"{Fore.GREEN}{html_path}\n")
237
+ else:
238
+ cyber("[!] Could not open browser automatically. Check report path in ~/.config/eva/reports", color=Fore.YELLOW)
239
+
240
+ def generate_attack_map(self):
241
+ if not self.memory.get("timeline"):
242
+ cyber("[!] No session data available to map.", color=Fore.YELLOW)
243
+ return None
244
+
245
+ html_path, json_path, graph = generate_attack_map_files(
246
+ self.sessionName,
247
+ self.memory["timeline"],
248
+ MAPS_DIR,
249
+ version=self.app_version,
250
+ )
251
+ self.memory["attack_map_html"] = str(html_path)
252
+ self.memory["attack_map_json"] = str(json_path)
253
+ self.save()
254
+ cyber(
255
+ f"[ ✔ ] Attack map generated.",
256
+ color=Fore.GREEN,
257
+ )
258
+ print(f"{Style.BRIGHT}{Fore.BLUE}{html_path} (nodes={graph['meta']['node_count']}, edges={graph['meta']['edge_count']}) \n")
259
+ return html_path
260
+
261
+ def view_attack_map(self):
262
+ map_path = self.memory.get("attack_map_html", "").strip()
263
+ if not map_path:
264
+ generated = self.generate_attack_map()
265
+ if not generated:
266
+ return
267
+ map_path = str(generated)
268
+
269
+ opened = open_attack_map(map_path)
270
+ if opened:
271
+ cyber(f"[ ✔ ] Opened session map in browser", color=Fore.GREEN)
272
+ print(f"{Fore.GREEN} {map_path} \n")
273
+ else:
274
+ cyber("[!] Could not open browser automatically. Check map path in ~/.config/eva/attack_maps", color=Fore.YELLOW)
275
+
276
+ def _render_session_header(self):
277
+ os.system("clear")
278
+ cyber(":: 🍎 EVA ONLINE :: ")
279
+ print(Fore.GREEN + "⯁⮞ ˹E˼xploit ˹V˼ector ˹A˼gent \n⬢ Current Model: " + Fore.CYAN + self.backend + f"\n{Fore.GREEN}𖨠 Session Name: " + Fore.YELLOW + self.sessionName)
280
+ print(Fore.CYAN + "/// type /exit to quit the program anytime")
281
+ print(Fore.CYAN + "/// type /model to change current model")
282
+ print(Fore.CYAN + "/// type /rename to change a session name")
283
+ print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
284
+ print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
285
+ print(Fore.CYAN + "/// type /menu to go back to sessions menu\n\n")
286
+
287
+ def _render_timeline(self):
288
+ for item in self.memory["timeline"]:
289
+ if item["type"] == "user":
290
+ print(Fore.GREEN + f"{username.upper()} > {item['content']}\n")
291
+
292
+ elif item["type"] == "analysis":
293
+ cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
294
+ print(Style.BRIGHT + Fore.CYAN + item["content"] + Style.RESET_ALL + "\n")
295
+
296
+ elif item["type"] == "command":
297
+ cyber(f"EXECUTED → {item['cmd']}", color=Fore.CYAN)
298
+ print(item["output"] + "\n")
299
+
300
+ def chat(self):
301
+ self._render_session_header()
302
+ self._render_timeline()
303
+
304
+ while True:
305
+ try:
306
+ user = raw_input(Fore.GREEN + f"\n{username.upper()} > ")
307
+ except KeyboardInterrupt:
308
+ print()
309
+ continue
310
+ if user.lower() in ("exit", "quit", "/exit","/quit","q"):
311
+ self.save()
312
+ graceful_exit()
313
+ if user.lower() in ("menu", "/menu"):
314
+ return self.go_menu()
315
+ if user.lower() in ("rename", "/rename"):
316
+ self.rename_session()
317
+ self._render_session_header()
318
+ self._render_timeline()
319
+ continue
320
+
321
+ if user.lower() in ("help", "/help"):
322
+ print(Fore.CYAN + "/// type /exit to quit the program anytime")
323
+ print(Fore.CYAN + "/// type /model to change current model")
324
+ print(Fore.CYAN + "/// type /rename to change a session name")
325
+ print(Fore.CYAN + "/// type /viewmap to open attack map with last findings.")
326
+ print(Fore.CYAN + "/// type /report to generate a report in PDF and HTML.")
327
+ print(Fore.CYAN + "/// type /menu to go back to sessions menu\n\n")
328
+ print(Style.BRIGHT + Fore.YELLOW + "-> Ask any question to EVA to request assistance, if no commands are generated to run, ask EVA explicitly for command generation.")
329
+ continue
330
+
331
+ if user.lower() in ("model", "/model"):
332
+ self.change_model_menu()
333
+ self._render_session_header()
334
+ self._render_timeline()
335
+ continue
336
+ if user.lower() in ("viewmap", "/viewmap","map","/map"):
337
+ self.view_attack_map()
338
+ continue
339
+ if user.lower() in ("report", "/report"):
340
+ self.generate_report()
341
+ continue
342
+
343
+ spinner_start()
344
+ spinner_stopped = False
345
+ analysis_header_shown = False
346
+ on_stream_start = None
347
+ if self.backend == "ollama":
348
+ def _on_stream_start():
349
+ nonlocal spinner_stopped, analysis_header_shown
350
+ if not spinner_stopped:
351
+ spinner_stop()
352
+ spinner_stopped = True
353
+ if not analysis_header_shown:
354
+ cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
355
+ analysis_header_shown = True
356
+
357
+ on_stream_start = _on_stream_start
358
+
359
+ try:
360
+ resp = self.model.query(user, self.last_output, on_stream_start=on_stream_start)
361
+ except KeyboardInterrupt:
362
+ if not spinner_stopped:
363
+ spinner_stop()
364
+ print(Fore.YELLOW + "\n// 🜂 Request cancelled")
365
+ continue
366
+
367
+ if not spinner_stopped:
368
+ spinner_stop()
369
+ spinner_stopped = True
370
+ self.memory["timeline"].append({
371
+ "type": "user",
372
+ "content": user
373
+ })
374
+ self.memory["timeline"].append({
375
+ "type": "analysis",
376
+ "content": resp["analysis"]
377
+ })
378
+ self.save()
379
+ if not analysis_header_shown:
380
+ cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
381
+ analysis_header_shown = True
382
+ if not resp.get("__streamed"):
383
+ print(Style.BRIGHT + Fore.CYAN + resp["analysis"] + Style.RESET_ALL)
384
+ break_outer = False
385
+ for cmd in resp["commands"]:
386
+
387
+ while True:
388
+ try:
389
+ print("\n\n")
390
+ print(f" {Style.BRIGHT}{Back.RED}{Fore.CYAN}$ {cmd}{Style.RESET_ALL} \n")
391
+ print(f"{Style.BRIGHT}{Fore.CYAN} [| [R]un | [S]kip | [A]sk | [G]enerate HTML Report | [V]iew attack map | [Q]uit |]\n")
392
+ choice = raw_input("> ").strip().lower()
393
+ except KeyboardInterrupt:
394
+ print()
395
+ break_outer = True
396
+ break
397
+
398
+ if choice == "r":
399
+ self.run_command(cmd)
400
+ spinner_start()
401
+ spinner_stopped = False
402
+ analysis_header_shown = False
403
+ on_stream_start = None
404
+ if self.backend == "ollama":
405
+ def _on_stream_start():
406
+ nonlocal spinner_stopped, analysis_header_shown
407
+ if not spinner_stopped:
408
+ spinner_stop()
409
+ spinner_stopped = True
410
+ if not analysis_header_shown:
411
+ cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
412
+ analysis_header_shown = True
413
+
414
+ on_stream_start = _on_stream_start
415
+
416
+ try:
417
+ resp = self.model.query(
418
+ "Analyze the previous command output and continue.",
419
+ self.last_output,
420
+ on_stream_start=on_stream_start
421
+ )
422
+ except KeyboardInterrupt:
423
+ if not spinner_stopped:
424
+ spinner_stop()
425
+ print(Fore.YELLOW + "\n// 🜂 Request cancelled")
426
+ break
427
+
428
+ if not spinner_stopped:
429
+ spinner_stop()
430
+ spinner_stopped = True
431
+ if not analysis_header_shown:
432
+ cyber("ANALYSIS", color=Style.BRIGHT + Fore.CYAN)
433
+ analysis_header_shown = True
434
+ if not resp.get("__streamed"):
435
+ print(Style.BRIGHT + Fore.CYAN + resp["analysis"] + Style.RESET_ALL)
436
+ break
437
+
438
+ elif choice == "a":
439
+ break_outer = True
440
+ break
441
+
442
+ elif choice == "s":
443
+ break
444
+
445
+ elif choice == "q":
446
+ self.save()
447
+ graceful_exit()
448
+ elif choice == "g":
449
+ self.generate_report()
450
+ elif choice == "v":
451
+ self.view_attack_map()
452
+
453
+ else:
454
+ print("// 🜂 Not a valid input, please type R, S, A, G, V or Q.")
455
+ if break_outer:
456
+ break
457
+ self.save()
utils/__init__.py ADDED
File without changes