forensic-cli 1.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.
Files changed (44) hide show
  1. cli/__init__.py +0 -0
  2. cli/commands/__init__.py +0 -0
  3. cli/commands/browser.py +220 -0
  4. cli/commands/describe.py +54 -0
  5. cli/commands/email.py +3 -0
  6. cli/commands/network.py +126 -0
  7. cli/commands/utils.py +3 -0
  8. cli/main.py +18 -0
  9. core/__init__.py +0 -0
  10. core/browser/browser_history.py +156 -0
  11. core/browser/common_words.py +60 -0
  12. core/browser/downloads_history.py +129 -0
  13. core/browser/fav_screen.py +70 -0
  14. core/browser/logins.py +108 -0
  15. core/browser/unusual_patterns.py +111 -0
  16. core/data/__init__.py +0 -0
  17. core/data/data_recovery.py +157 -0
  18. core/db/db.py +12 -0
  19. core/email/email_parser.py +87 -0
  20. core/email/header_analysis.py +87 -0
  21. core/models/orm.py +48 -0
  22. core/models/schemas.py +76 -0
  23. core/network/__init__.py +0 -0
  24. core/network/arp_scan.py +16 -0
  25. core/network/dns_recon.py +90 -0
  26. core/network/fingerprinting.py +117 -0
  27. core/network/ip_info.py +29 -0
  28. core/network/network_map.py +120 -0
  29. core/network/ping_sweep.py +53 -0
  30. core/network/port_scanner.py +93 -0
  31. core/network/smb_scan.py +32 -0
  32. core/network/snmp_scan.py +43 -0
  33. core/network/traceroute.py +40 -0
  34. core/network/utils.py +27 -0
  35. core/registry.py +25 -0
  36. core/utils/__init__.py +0 -0
  37. core/utils/gerar_amostras.py +33 -0
  38. forensic_cli-1.2.5.dist-info/METADATA +293 -0
  39. forensic_cli-1.2.5.dist-info/RECORD +44 -0
  40. forensic_cli-1.2.5.dist-info/WHEEL +5 -0
  41. forensic_cli-1.2.5.dist-info/entry_points.txt +2 -0
  42. forensic_cli-1.2.5.dist-info/licenses/LICENSE +21 -0
  43. forensic_cli-1.2.5.dist-info/licenses/LICENSE-GPL +675 -0
  44. forensic_cli-1.2.5.dist-info/top_level.txt +2 -0
@@ -0,0 +1,129 @@
1
+ import os
2
+ import sqlite3
3
+ import time
4
+ import json
5
+ import shutil
6
+ from pathlib import Path
7
+
8
+ def timestamp_chrome(microsegundos):
9
+ if microsegundos:
10
+ return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(microsegundos / 1000000 - 11644473600))
11
+ return "N/A"
12
+
13
+ def timestamp_firefox(microsegundos):
14
+ if microsegundos:
15
+ return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(microsegundos / 1000000))
16
+ return "N/A"
17
+
18
+ def salvar_em_json(dados, navegador, usuario, output_dir: Path):
19
+ output_dir.mkdir(parents=True, exist_ok=True)
20
+ nome_arquivo = output_dir / f"Downloads_{navegador}_{usuario}.json"
21
+ with open(nome_arquivo, "w", encoding="utf-8") as f:
22
+ json.dump(dados, f, indent=4, ensure_ascii=False)
23
+ print(f"[✓] Arquivo salvo: {nome_arquivo}")
24
+
25
+ def copiar_banco(origem):
26
+ destino = f"temp_{os.path.basename(origem)}"
27
+ shutil.copy2(origem, destino)
28
+ return destino
29
+
30
+ def extrair_downloads_chrome_edge(caminho_banco, navegador, usuario, output_dir: Path):
31
+ caminho_temp = copiar_banco(caminho_banco)
32
+ try:
33
+ conn = sqlite3.connect(caminho_temp)
34
+ cursor = conn.cursor()
35
+
36
+ cursor.execute("""
37
+ SELECT d.target_path, d.tab_url, d.start_time, d.received_bytes, d.state, u.url
38
+ FROM downloads d
39
+ LEFT JOIN downloads_url_chains u ON d.id = u.id
40
+ ORDER BY d.start_time DESC
41
+ LIMIT 10;
42
+ """)
43
+ rows = cursor.fetchall()
44
+
45
+ downloads = []
46
+ for row in rows:
47
+ path, tab_url, start_time, bytes_received, state, source_url = row
48
+ downloads.append({
49
+ "arquivo": path,
50
+ "url_origem": source_url,
51
+ "url_abas": tab_url,
52
+ "inicio_download": timestamp_chrome(start_time),
53
+ "bytes_recebidos": bytes_received,
54
+ "estado": state
55
+ })
56
+
57
+ salvar_em_json(downloads, navegador, usuario, output_dir)
58
+
59
+ except Exception as e:
60
+ print(f"[!] Erro ({navegador}): {e}")
61
+ finally:
62
+ conn.close()
63
+ os.remove(caminho_temp)
64
+
65
+ def extrair_downloads_firefox(caminho_banco, usuario, output_dir: Path):
66
+ caminho_temp = copiar_banco(caminho_banco)
67
+ try:
68
+ conn = sqlite3.connect(caminho_temp)
69
+ cursor = conn.cursor()
70
+
71
+ cursor.execute("""
72
+ SELECT source, target, startTime, endTime, state
73
+ FROM moz_downloads
74
+ ORDER BY startTime DESC
75
+ LIMIT 10;
76
+ """)
77
+ rows = cursor.fetchall()
78
+
79
+ downloads = []
80
+ for row in rows:
81
+ source, target, startTime, endTime, state = row
82
+ downloads.append({
83
+ "arquivo": target,
84
+ "url_origem": source,
85
+ "inicio_download": timestamp_firefox(startTime),
86
+ "estado": state
87
+ })
88
+
89
+ salvar_em_json(downloads, "Firefox", usuario, output_dir)
90
+
91
+ except Exception as e:
92
+ print(f"[!] Erro (Firefox): {e}")
93
+ finally:
94
+ conn.close()
95
+ os.remove(caminho_temp)
96
+
97
+ def extract_downloads_history(output_dir: Path, chrome=True, edge=True, firefox=True):
98
+ usuario = os.getlogin()
99
+ home = Path.home()
100
+
101
+ if chrome:
102
+ caminho_chrome = home / "AppData/Local/Google/Chrome/User Data/Default/History"
103
+ if caminho_chrome.exists():
104
+ print("[*] Extraindo downloads do Chrome...")
105
+ extrair_downloads_chrome_edge(caminho_chrome, "Chrome", usuario, output_dir)
106
+ else:
107
+ print("[!] Chrome não encontrado.")
108
+
109
+ if edge:
110
+ caminho_edge = home / "AppData/Local/Microsoft/Edge/User Data/Default/History"
111
+ if caminho_edge.exists():
112
+ print("[*] Extraindo downloads do Edge...")
113
+ extrair_downloads_chrome_edge(caminho_edge, "Edge", usuario, output_dir)
114
+ else:
115
+ print("[!] Edge não encontrado.")
116
+
117
+ if firefox:
118
+ caminho_firefox_perfis = home / "AppData/Roaming/Mozilla/Firefox/Profiles"
119
+ if caminho_firefox_perfis.exists():
120
+ for perfil in caminho_firefox_perfis.iterdir():
121
+ caminho_downloads_sqlite = perfil / "downloads.sqlite"
122
+ if caminho_downloads_sqlite.exists():
123
+ print(f"[*] Extraindo downloads do Firefox ({perfil.name})...")
124
+ extrair_downloads_firefox(caminho_downloads_sqlite, usuario, output_dir)
125
+ break
126
+ else:
127
+ print("[!] downloads.sqlite do Firefox não encontrado.")
128
+ else:
129
+ print("[!] Perfis do Firefox não encontrados.")
@@ -0,0 +1,70 @@
1
+ from pathlib import Path
2
+ from selenium import webdriver
3
+ from selenium.webdriver.chrome.options import Options
4
+ from urllib.parse import urlparse
5
+ import hashlib
6
+ import requests
7
+ import json
8
+ import time
9
+
10
+ def carregar_json(caminho):
11
+ with open(caminho, 'r', encoding='utf-8') as f:
12
+ return json.load(f)
13
+
14
+ def extrair_urls_validas(dados_json):
15
+ urls = []
16
+
17
+ for chave in ["historico_ultimas_urls", "ultimas_10_urls", "historico_completo"]:
18
+ entradas = dados_json.get(chave, [])
19
+ for entrada in entradas:
20
+ url = entrada.get("url")
21
+ if url and url.startswith("http"):
22
+ urls.append(url)
23
+
24
+ return urls
25
+
26
+ def baixar_favicon(url, pasta_destino: Path):
27
+ try:
28
+ pasta_destino.mkdir(parents=True, exist_ok=True)
29
+ dominio = urlparse(url).netloc
30
+ favicon_url = f"https://{dominio}/favicon.ico"
31
+ resposta = requests.get(favicon_url, timeout=5)
32
+ if resposta.status_code == 200:
33
+ nome_arquivo = hashlib.md5(dominio.encode()).hexdigest() + ".ico"
34
+ caminho = pasta_destino / nome_arquivo
35
+ with open(caminho, "wb") as f:
36
+ f.write(resposta.content)
37
+ print(f"[FAVICON] Baixado: {dominio}")
38
+ else:
39
+ print(f"[FAVICON] Não encontrado para {dominio}")
40
+ except Exception as e:
41
+ print(f"[FAVICON] Erro ao baixar de {url}: {e}")
42
+
43
+ def capturar_print(url, pasta_destino: Path):
44
+ try:
45
+ pasta_destino.mkdir(parents=True, exist_ok=True)
46
+ nome_arquivo = hashlib.md5(url.encode()).hexdigest() + ".png"
47
+ caminho = pasta_destino / nome_arquivo
48
+
49
+ chrome_options = Options()
50
+ chrome_options.add_argument("--headless=new")
51
+ chrome_options.add_argument("--disable-gpu")
52
+ chrome_options.add_argument("--window-size=1280,1024")
53
+
54
+ driver = webdriver.Chrome(options=chrome_options)
55
+ driver.set_page_load_timeout(10)
56
+ driver.get(url)
57
+ time.sleep(3)
58
+ driver.save_screenshot(str(caminho))
59
+ driver.quit()
60
+ print(f"[PRINT] Capturado: {url}")
61
+ except Exception as e:
62
+ print(f"[PRINT] Erro ao capturar print de {url}: {e}")
63
+
64
+ def processar_urls(urls, output_dir: Path):
65
+ fav_dir = output_dir / "favicons"
66
+ print_dir = output_dir / "prints"
67
+ for url in urls:
68
+ print(f"\n[PROCESSANDO] {url}")
69
+ baixar_favicon(url, fav_dir)
70
+ capturar_print(url, print_dir)
core/browser/logins.py ADDED
@@ -0,0 +1,108 @@
1
+ import os
2
+ import json
3
+ import base64
4
+ import shutil
5
+ import sqlite3
6
+ from Crypto.Cipher import AES
7
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
8
+ import win32crypt
9
+
10
+ def find_chrome_profiles() -> list[tuple[str, str]]:
11
+ base = os.path.join(os.environ["USERPROFILE"], "AppData", "Local", "Google", "Chrome", "User Data")
12
+ profiles = []
13
+
14
+ for name in os.listdir(base):
15
+ if name == "Default" or name.startswith("Profile"):
16
+ db = os.path.join(base, name, "Login Data")
17
+ if os.path.isfile(db):
18
+ profiles.append((name, db))
19
+
20
+ return profiles
21
+
22
+ def collect_chrome_logins() -> dict:
23
+ result = []
24
+ profiles = find_chrome_profiles()
25
+ for profile_name, db_path in profiles:
26
+ tmp_db = os.path.join(os.getenv("TEMP"), f"{profile_name}_LoginData.db")
27
+ shutil.copy2(db_path, tmp_db)
28
+
29
+ conn = sqlite3.connect(tmp_db)
30
+ cursor = conn.cursor()
31
+ cursor.execute("SELECT origin_url, username_value, password_value FROM logins")
32
+ rows = cursor.fetchall()
33
+
34
+ for origin_url, username, pwd_blob in rows:
35
+ pwd_b64 = base64.b64encode(pwd_blob).decode('ascii')
36
+ result.append({
37
+ "profile": profile_name,
38
+ "site": origin_url,
39
+ "user": username,
40
+ "password_encrypted": pwd_b64
41
+ })
42
+
43
+ cursor.close()
44
+ conn.close()
45
+ os.remove(tmp_db)
46
+
47
+ return {"logins": result}
48
+
49
+ def get_edge_user_data_path():
50
+ user = os.getenv("USERNAME")
51
+
52
+ return f"C:/Users/{user}/AppData/Local/Microsoft/Edge/User Data/Default"
53
+
54
+ def get_edge_encryption_key():
55
+ user = os.getenv("USERNAME")
56
+ local_state_path = f"C:/Users/{user}/AppData/Local/Microsoft/Edge/User Data/Local State"
57
+
58
+ with open(local_state_path, 'r', encoding='utf-8') as file:
59
+ local_state = json.load(file)
60
+
61
+ encrypted_key = base64.b64decode(local_state["os_crypt"]["encrypted_key"])
62
+ encrypted_key = encrypted_key[5:]
63
+ key = win32crypt.CryptUnprotectData(encrypted_key, None, None, None, 0)[1]
64
+
65
+ return key
66
+
67
+ def decrypt_edge_password(password_encrypted, key):
68
+ try:
69
+ if password_encrypted.startswith(b'v10') or password_encrypted.startswith(b'v11'):
70
+ password_encrypted = password_encrypted[3:]
71
+ iv = password_encrypted[:12]
72
+ payload = password_encrypted[12:]
73
+ aesgcm = AESGCM(key)
74
+
75
+ return aesgcm.decrypt(iv, payload, None).decode()
76
+ else:
77
+ return win32crypt.CryptUnprotectData(password_encrypted, None, None, None, 0)[1].decode()
78
+ except Exception:
79
+ return None
80
+
81
+ def collect_edge_logins() -> dict:
82
+ user_data_path = get_edge_user_data_path()
83
+ login_data_path = os.path.join(user_data_path, "Login Data")
84
+ tmp_db = os.path.join(os.getenv("TEMP"), "Edge_LoginData.db")
85
+ shutil.copy2(login_data_path, tmp_db)
86
+
87
+ key = get_edge_encryption_key()
88
+ result = []
89
+
90
+ conn = sqlite3.connect(tmp_db)
91
+ cursor = conn.cursor()
92
+ try:
93
+ cursor.execute("SELECT origin_url, username_value, password_value FROM logins")
94
+ for origin_url, username, pwd_encrypted in cursor.fetchall():
95
+ if not pwd_encrypted:
96
+ continue
97
+ pwd = decrypt_edge_password(pwd_encrypted, key)
98
+ result.append({
99
+ "site": origin_url,
100
+ "user": username,
101
+ "password": pwd
102
+ })
103
+ finally:
104
+ cursor.close()
105
+ conn.close()
106
+ os.remove(tmp_db)
107
+
108
+ return {"logins": result}
@@ -0,0 +1,111 @@
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import matplotlib.pyplot as plt
5
+ from datetime import datetime
6
+
7
+ def carregar_json(caminho):
8
+ with open(caminho, 'r', encoding='utf-8') as f:
9
+ return json.load(f)
10
+
11
+ def tratar_data_acesso(data_str):
12
+ try:
13
+ if data_str == "N/A" or not data_str:
14
+ return None
15
+ return pd.to_datetime(data_str, format="%Y-%m-%d %H:%M:%S", errors='raise')
16
+ except Exception as e:
17
+ print(f"[ALERTA] Erro ao processar data: {data_str}. Erro: {e}")
18
+ return None
19
+
20
+ def detectar_acessos_horarios_diferentes(df, output_dir=None, arquivo_prefixo=""):
21
+ df['ultimo_acesso'] = df['ultimo_acesso'].apply(tratar_data_acesso)
22
+ df = df.dropna(subset=['ultimo_acesso']).copy()
23
+ df.loc[:, 'hora'] = df['ultimo_acesso'].dt.hour
24
+
25
+ acessos_madrugada = df[df['hora'].between(0, 6)]
26
+ acessos_manha = df[df['hora'].between(7, 11)]
27
+ acessos_tarde = df[df['hora'].between(12, 17)]
28
+ acessos_noite = df[df['hora'].between(18, 23)]
29
+
30
+ plt.figure(figsize=(10, 6))
31
+ acessos_por_hora = df['hora'].value_counts().sort_index()
32
+ acessos_por_hora.plot(kind='bar', color='skyblue')
33
+ plt.title('Acessos por Hora do Dia')
34
+ plt.xlabel('Hora')
35
+ plt.ylabel('Número de Acessos')
36
+ plt.xticks(rotation=0)
37
+ plt.tight_layout()
38
+
39
+ if output_dir:
40
+ caminho_grafico = os.path.join(output_dir, f"{arquivo_prefixo}acessos_por_hora.png")
41
+ plt.savefig(caminho_grafico)
42
+ plt.close()
43
+ print(f"[INFO] Gráfico salvo em: {caminho_grafico}")
44
+ else:
45
+ plt.show()
46
+
47
+ acessos_por_periodo = {
48
+ "Madrugada (00h-06h)": len(acessos_madrugada),
49
+ "Manhã (07h-11h)": len(acessos_manha),
50
+ "Tarde (12h-17h)": len(acessos_tarde),
51
+ "Noite (18h-23h)": len(acessos_noite),
52
+ }
53
+ print("\nAcessos por período:")
54
+ print(pd.DataFrame(list(acessos_por_periodo.items()), columns=["Período", "Quantidade de Acessos"]))
55
+
56
+ acessos_incomuns = pd.concat([acessos_madrugada, acessos_noite])
57
+ if not acessos_incomuns.empty:
58
+ print(f"\nAcessos em horários incomuns (madrugada/noite):")
59
+ print(acessos_incomuns[['url', 'ultimo_acesso']].head())
60
+
61
+ def detectar_acessos_repetidos(df, intervalo_maximo=5):
62
+ df['ultimo_acesso'] = pd.to_datetime(df['ultimo_acesso'])
63
+ df['diferenca'] = df['ultimo_acesso'].diff().fillna(pd.Timedelta(seconds=0))
64
+
65
+ acessos_repetidos = df[df['diferenca'] < pd.Timedelta(minutes=intervalo_maximo)]
66
+
67
+ if not acessos_repetidos.empty:
68
+ print(f"\nAcessos repetidos detectados em intervalos menores que {intervalo_maximo} minutos:")
69
+ print(acessos_repetidos[['url', 'ultimo_acesso', 'diferenca']].head())
70
+
71
+ def processar_historico_da_pasta(pasta, output_dir=None):
72
+ arquivos_json = [f for f in os.listdir(pasta) if f.endswith('.json')]
73
+
74
+ if not arquivos_json:
75
+ print("[ALERTA] Nenhum arquivo JSON encontrado na pasta especificada.")
76
+ return
77
+
78
+ if output_dir:
79
+ os.makedirs(output_dir, exist_ok=True)
80
+
81
+ for arquivo in arquivos_json:
82
+ caminho_arquivo = os.path.join(pasta, arquivo)
83
+ try:
84
+ print(f"\n[INFO] Processando arquivo: {caminho_arquivo}")
85
+
86
+ dados = carregar_json(caminho_arquivo)
87
+
88
+ if "historico_completo" in dados:
89
+ lista_acessos = dados["historico_completo"]
90
+ elif "ultimas_10_urls" in dados:
91
+ lista_acessos = dados["ultimas_10_urls"]
92
+ else:
93
+ print(f"[AVISO] Nenhum histórico válido encontrado em {arquivo}")
94
+ continue
95
+
96
+ df = pd.DataFrame(lista_acessos).copy()
97
+
98
+ prefixo = os.path.splitext(arquivo)[0] + "_"
99
+ detectar_acessos_horarios_diferentes(df, output_dir=output_dir, arquivo_prefixo=prefixo)
100
+ detectar_acessos_repetidos(df, intervalo_maximo=5)
101
+
102
+ except Exception as erro:
103
+ print(f"\n❌ Erro ao processar o arquivo {arquivo}: {erro}")
104
+
105
+ if __name__ == "__main__":
106
+ try:
107
+ pasta_json = "artefatos/historico"
108
+ output_dir = "artefatos/patterns_output"
109
+ processar_historico_da_pasta(pasta_json, output_dir=output_dir)
110
+ except Exception as erro:
111
+ print(f"\n❌ Erro geral: {erro}")
core/data/__init__.py ADDED
File without changes
@@ -0,0 +1,157 @@
1
+ import os
2
+ import json
3
+ import csv
4
+ import platform
5
+ from datetime import datetime
6
+ import shutil
7
+
8
+ IS_WINDOWS = platform.system() == "Windows"
9
+ RECOVERY_DIR = os.path.join(os.getcwd(), "recovered_files")
10
+ os.makedirs(RECOVERY_DIR, exist_ok=True)
11
+
12
+ FILE_FILTERS = {
13
+ "imagens": [".jpg", ".jpeg", ".png", ".gif", ".bmp", ".tiff"],
14
+ "documentos": [".txt", ".doc", ".docx", ".odt", ".json"],
15
+ "pdfs": [".pdf"],
16
+ "planilhas": [".xls", ".xlsx", ".ods", ".csv"]
17
+ }
18
+
19
+ ACTIVE_FILTERS = FILE_FILTERS["imagens"] + FILE_FILTERS["documentos"] + FILE_FILTERS["pdfs"] + FILE_FILTERS["planilhas"]
20
+
21
+ if IS_WINDOWS:
22
+ try:
23
+ import winshell
24
+ WIN_AVAILABLE = True
25
+ except ImportError:
26
+ WIN_AVAILABLE = False
27
+ print("⚠️ winshell não disponível. Módulo de recuperação Windows desativado.")
28
+ else:
29
+ WIN_AVAILABLE = False
30
+
31
+ def filter_file(filename):
32
+ if not ACTIVE_FILTERS:
33
+ return True
34
+ _, ext = os.path.splitext(filename.lower())
35
+ return ext in ACTIVE_FILTERS
36
+
37
+ def list_deleted_files_windows():
38
+ deleted_files = []
39
+ if not WIN_AVAILABLE:
40
+ return deleted_files
41
+ try:
42
+ for item in winshell.recycle_bin():
43
+ try:
44
+ original_path = item.original_filename()
45
+ name = os.path.basename(original_path)
46
+ if not filter_file(name):
47
+ continue
48
+
49
+ deleted_files.append({
50
+ "original_path": original_path,
51
+ "recycle_path": item.filename(),
52
+ "name": name,
53
+ "deleted_time": item.recycle_date().isoformat(),
54
+ "hidden": False
55
+ })
56
+ except Exception as e:
57
+ print(f"⚠️ Falha ao processar item da Lixeira: {e}")
58
+ print(f"[INFO] {len(deleted_files)} arquivo(s) encontrados na Lixeira com filtro aplicado.")
59
+ except Exception as e:
60
+ print(f"⚠️ Erro ao acessar a Lixeira: {e}")
61
+ return deleted_files
62
+
63
+ def recover_file_windows(file_info):
64
+ try:
65
+ src = file_info["recycle_path"]
66
+ dst = os.path.join(RECOVERY_DIR, file_info["name"])
67
+ shutil.copy2(src, dst)
68
+ print(f"[OK] Arquivo recuperado: {file_info['name']}")
69
+ return True
70
+ except Exception as e:
71
+ print(f"[ERRO] Falha ao recuperar {file_info['name']}: {e}")
72
+ return False
73
+
74
+ try:
75
+ import pytsk3
76
+ PYTSK3_AVAILABLE = True
77
+ except ImportError:
78
+ PYTSK3_AVAILABLE = False
79
+ if not IS_WINDOWS:
80
+ print("⚠️ pytsk3 não instalado. Recuperação Linux desativada.")
81
+
82
+ def list_deleted_files_linux(partition="/"):
83
+ deleted_files = []
84
+ if not PYTSK3_AVAILABLE:
85
+ return deleted_files
86
+ try:
87
+ img = pytsk3.Img_Info(partition)
88
+ fs = pytsk3.FS_Info(img)
89
+ directory = fs.open_dir(path="/")
90
+ for entry in directory:
91
+ try:
92
+ if entry.info.meta and entry.info.meta.flags & pytsk3.TSK_FS_META_FLAG_UNALLOC:
93
+ name = entry.info.name.name.decode("utf-8")
94
+ if not filter_file(name):
95
+ continue
96
+ deleted_files.append({
97
+ "name": name,
98
+ "inode": entry.info.meta.addr,
99
+ "size": entry.info.meta.size,
100
+ "deleted_time": datetime.fromtimestamp(entry.info.meta.mtime).isoformat()
101
+ })
102
+ except:
103
+ continue
104
+ print(f"[INFO] {len(deleted_files)} arquivo(s) deletados encontrados na partição {partition} com filtro aplicado.")
105
+ except Exception as e:
106
+ print(f"[ERRO] Não foi possível acessar a partição {partition}: {e}")
107
+ return deleted_files
108
+
109
+ def recover_file_linux(file_info, partition="/"):
110
+ dst = os.path.join(RECOVERY_DIR, file_info["name"])
111
+ try:
112
+ with open(dst, "w") as f:
113
+ f.write("Recuperação simulada para Linux")
114
+ print(f"[OK] Arquivo simulado recuperado: {file_info['name']}")
115
+ return True
116
+ except Exception as e:
117
+ print(f"[ERRO] Falha ao recuperar {file_info['name']}: {e}")
118
+ return False
119
+
120
+ def save_recovery_results(results, prefix="data_recovery"):
121
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
122
+ json_file = f"{prefix}_{timestamp}.json"
123
+ csv_file = f"{prefix}_{timestamp}.csv"
124
+
125
+ if not results:
126
+ results.append({"info": "Nenhum arquivo encontrado"})
127
+
128
+ with open(json_file, "w", encoding="utf-8") as f:
129
+ json.dump(results, f, indent=4, ensure_ascii=False)
130
+
131
+ headers = results[0].keys()
132
+ with open(csv_file, "w", newline="", encoding="utf-8") as f:
133
+ writer = csv.DictWriter(f, fieldnames=headers)
134
+ writer.writeheader()
135
+ for r in results:
136
+ writer.writerow(r)
137
+
138
+ print(f"\n✅ Resultados salvos em JSON: {json_file} e CSV: {csv_file}")
139
+
140
+ if __name__ == "__main__":
141
+ recovered_results = []
142
+
143
+ if IS_WINDOWS:
144
+ files = list_deleted_files_windows()
145
+ for f in files:
146
+ status = recover_file_windows(f)
147
+ f["recovered"] = "Sim" if status else "Falha"
148
+ recovered_results.append(f)
149
+ else:
150
+ partition = input("Digite a partição ou device para recuperar arquivos (ex: /dev/sda1): ").strip()
151
+ files = list_deleted_files_linux(partition)
152
+ for f in files:
153
+ status = recover_file_linux(f, partition)
154
+ f["recovered"] = "Sim" if status else "Falha"
155
+ recovered_results.append(f)
156
+
157
+ save_recovery_results(recovered_results)
core/db/db.py ADDED
@@ -0,0 +1,12 @@
1
+ from sqlalchemy import create_engine
2
+ from sqlalchemy.orm import sessionmaker, declarative_base
3
+
4
+ SQLALCHEMY_DATABASE_URL = "sqlite:///./devkit.db"
5
+
6
+ engine = create_engine(
7
+ SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
8
+ )
9
+
10
+ SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
11
+
12
+ Base = declarative_base()
@@ -0,0 +1,87 @@
1
+ import os
2
+ import json
3
+ import csv
4
+ import email
5
+ from email import policy
6
+ from email.parser import BytesParser
7
+ from datetime import datetime
8
+ import hashlib
9
+
10
+ BASE_DIR = os.getcwd()
11
+ ATTACHMENT_DIR = os.path.join(BASE_DIR, "attachments")
12
+ os.makedirs(ATTACHMENT_DIR, exist_ok=True)
13
+
14
+ def hash_file(filepath, method="md5"):
15
+ h = hashlib.md5() if method == "md5" else hashlib.sha256()
16
+ with open(filepath, "rb") as f:
17
+ for chunk in iter(lambda: f.read(4096), b""):
18
+ h.update(chunk)
19
+ return h.hexdigest()
20
+
21
+ def parse_eml_file(filepath):
22
+ with open(filepath, "rb") as f:
23
+ msg = BytesParser(policy=policy.default).parse(f)
24
+
25
+ email_data = {
26
+ "subject": msg.get("subject"),
27
+ "from": msg.get("from"),
28
+ "to": msg.get("to"),
29
+ "cc": msg.get("cc"),
30
+ "bcc": msg.get("bcc"),
31
+ "date": msg.get("date"),
32
+ "message_id": msg.get("message-id"),
33
+ "attachments": []
34
+ }
35
+
36
+ for part in msg.iter_attachments():
37
+ filename = part.get_filename()
38
+ if filename:
39
+ safe_filename = filename.replace("/", "_").replace("\\", "_")
40
+ filepath = os.path.join(ATTACHMENT_DIR, safe_filename)
41
+ with open(filepath, "wb") as af:
42
+ af.write(part.get_payload(decode=True))
43
+ email_data["attachments"].append({
44
+ "name": safe_filename,
45
+ "size": os.path.getsize(filepath),
46
+ "hash_md5": hash_file(filepath)
47
+ })
48
+
49
+ return email_data
50
+
51
+ def parse_eml_folder(folder_path):
52
+ results = []
53
+ for root, _, files in os.walk(folder_path):
54
+ for file in files:
55
+ if file.lower().endswith(".eml"):
56
+ filepath = os.path.join(root, file)
57
+ try:
58
+ data = parse_eml_file(filepath)
59
+ results.append(data)
60
+ print(f"[OK] E-mail processado: {file}")
61
+ except Exception as e:
62
+ print(f"[ERRO] Falha ao processar {file}: {e}")
63
+ return results
64
+
65
+ def export_results(results, prefix="email_analysis"):
66
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
67
+ json_file = f"{prefix}_{timestamp}.json"
68
+ csv_file = f"{prefix}_{timestamp}.csv"
69
+
70
+ with open(json_file, "w", encoding="utf-8") as f:
71
+ json.dump(results, f, indent=4, ensure_ascii=False)
72
+
73
+ headers = ["subject", "from", "to", "cc", "bcc", "date", "message_id", "attachments"]
74
+ with open(csv_file, "w", newline="", encoding="utf-8") as f:
75
+ writer = csv.DictWriter(f, fieldnames=headers)
76
+ writer.writeheader()
77
+ for r in results:
78
+ r_copy = r.copy()
79
+ r_copy["attachments"] = ", ".join([a["name"] for a in r["attachments"]])
80
+ writer.writerow(r_copy)
81
+
82
+ print(f"\n✅ Resultados salvos em JSON: {json_file} e CSV: {csv_file}")
83
+
84
+ if __name__ == "__main__":
85
+ folder = input("Digite o caminho da pasta com arquivos .eml: ").strip()
86
+ results = parse_eml_folder(folder)
87
+ export_results(results)