scanner.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. from __future__ import annotations
  2. import hashlib
  3. from datetime import datetime
  4. from typing import Any
  5. import psutil
  6. from .database import get_db
  7. def now_iso() -> str:
  8. return datetime.now().astimezone().isoformat(timespec="seconds")
  9. def process_identity(name: str, exe_path: str | None, cmdline: str | None) -> str:
  10. raw = f"{name.lower()}|{(exe_path or cmdline or '').lower()}"
  11. return hashlib.sha256(raw.encode("utf-8", errors="ignore")).hexdigest()
  12. def collect_services() -> list[dict[str, Any]]:
  13. if not hasattr(psutil, "win_service_iter"):
  14. return []
  15. services: list[dict[str, Any]] = []
  16. for service in psutil.win_service_iter():
  17. try:
  18. info = service.as_dict()
  19. except (psutil.Error, OSError):
  20. continue
  21. services.append(
  22. {
  23. "identity_key": info.get("name") or service.name(),
  24. "name": info.get("name") or service.name(),
  25. "display_name": info.get("display_name"),
  26. "status": info.get("status"),
  27. "start_type": info.get("start_type"),
  28. "username": info.get("username"),
  29. "binary_path": info.get("binpath"),
  30. "description": info.get("description"),
  31. }
  32. )
  33. return services
  34. def collect_processes() -> list[dict[str, Any]]:
  35. processes: list[dict[str, Any]] = []
  36. attrs = ["pid", "name", "exe", "cmdline", "username", "status", "cwd", "ppid", "create_time"]
  37. for proc in psutil.process_iter(attrs=attrs):
  38. try:
  39. info = proc.info
  40. name = info.get("name") or f"pid-{info.get('pid')}"
  41. cmdline_list = info.get("cmdline") or []
  42. cmdline = " ".join(str(part) for part in cmdline_list)
  43. create_time = info.get("create_time")
  44. processes.append(
  45. {
  46. "identity_key": process_identity(name, info.get("exe"), cmdline),
  47. "name": name,
  48. "exe_path": info.get("exe"),
  49. "cmdline": cmdline,
  50. "username": info.get("username"),
  51. "status": info.get("status"),
  52. "cwd": info.get("cwd"),
  53. "last_pid": info.get("pid"),
  54. "parent_pid": info.get("ppid"),
  55. "create_time": datetime.fromtimestamp(create_time).astimezone().isoformat(timespec="seconds")
  56. if create_time
  57. else None,
  58. }
  59. )
  60. except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess, OSError):
  61. continue
  62. return processes
  63. def run_full_scan() -> dict[str, Any]:
  64. started_at = now_iso()
  65. with get_db() as conn:
  66. cursor = conn.execute(
  67. "INSERT INTO scan_records (started_at, status) VALUES (?, 'RUNNING')",
  68. (started_at,),
  69. )
  70. scan_id = cursor.lastrowid
  71. try:
  72. services = collect_services()
  73. processes = collect_processes()
  74. scanned_at = now_iso()
  75. new_services = 0
  76. new_processes = 0
  77. with get_db() as conn:
  78. conn.execute("UPDATE windows_services SET is_present_now = 0")
  79. conn.execute("UPDATE windows_processes SET is_present_now = 0")
  80. for item in services:
  81. existing = conn.execute(
  82. "SELECT id FROM windows_services WHERE identity_key = ?",
  83. (item["identity_key"],),
  84. ).fetchone()
  85. if existing:
  86. conn.execute(
  87. """
  88. UPDATE windows_services
  89. SET display_name = ?, status = ?, start_type = ?, username = ?,
  90. binary_path = ?, description = ?, is_present_now = 1,
  91. last_seen_at = ?, updated_at = ?
  92. WHERE identity_key = ?
  93. """,
  94. (
  95. item["display_name"],
  96. item["status"],
  97. item["start_type"],
  98. item["username"],
  99. item["binary_path"],
  100. item["description"],
  101. scanned_at,
  102. scanned_at,
  103. item["identity_key"],
  104. ),
  105. )
  106. else:
  107. new_services += 1
  108. conn.execute(
  109. """
  110. INSERT INTO windows_services (
  111. identity_key, name, display_name, status, start_type, username,
  112. binary_path, description, is_present_now, first_seen_at,
  113. last_seen_at, confirm_status, updated_at
  114. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, 'PENDING', ?)
  115. """,
  116. (
  117. item["identity_key"],
  118. item["name"],
  119. item["display_name"],
  120. item["status"],
  121. item["start_type"],
  122. item["username"],
  123. item["binary_path"],
  124. item["description"],
  125. scanned_at,
  126. scanned_at,
  127. scanned_at,
  128. ),
  129. )
  130. for item in processes:
  131. existing = conn.execute(
  132. "SELECT id FROM windows_processes WHERE identity_key = ?",
  133. (item["identity_key"],),
  134. ).fetchone()
  135. if existing:
  136. conn.execute(
  137. """
  138. UPDATE windows_processes
  139. SET exe_path = ?, cmdline = ?, username = ?, status = ?, cwd = ?,
  140. last_pid = ?, parent_pid = ?, create_time = ?,
  141. is_present_now = 1, last_seen_at = ?, updated_at = ?
  142. WHERE identity_key = ?
  143. """,
  144. (
  145. item["exe_path"],
  146. item["cmdline"],
  147. item["username"],
  148. item["status"],
  149. item["cwd"],
  150. item["last_pid"],
  151. item["parent_pid"],
  152. item["create_time"],
  153. scanned_at,
  154. scanned_at,
  155. item["identity_key"],
  156. ),
  157. )
  158. else:
  159. new_processes += 1
  160. conn.execute(
  161. """
  162. INSERT INTO windows_processes (
  163. identity_key, name, exe_path, cmdline, username, status, cwd,
  164. last_pid, parent_pid, create_time, is_present_now,
  165. first_seen_at, last_seen_at, confirm_status, updated_at
  166. ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, 'PENDING', ?)
  167. """,
  168. (
  169. item["identity_key"],
  170. item["name"],
  171. item["exe_path"],
  172. item["cmdline"],
  173. item["username"],
  174. item["status"],
  175. item["cwd"],
  176. item["last_pid"],
  177. item["parent_pid"],
  178. item["create_time"],
  179. scanned_at,
  180. scanned_at,
  181. scanned_at,
  182. ),
  183. )
  184. conn.execute(
  185. """
  186. UPDATE scan_records
  187. SET finished_at = ?, status = 'SUCCESS', services_found = ?,
  188. processes_found = ?, new_services = ?, new_processes = ?
  189. WHERE id = ?
  190. """,
  191. (now_iso(), len(services), len(processes), new_services, new_processes, scan_id),
  192. )
  193. return {
  194. "scan_id": scan_id,
  195. "status": "SUCCESS",
  196. "services_found": len(services),
  197. "processes_found": len(processes),
  198. "new_services": new_services,
  199. "new_processes": new_processes,
  200. }
  201. except Exception as exc:
  202. with get_db() as conn:
  203. conn.execute(
  204. "UPDATE scan_records SET finished_at = ?, status = 'FAILED', error_message = ? WHERE id = ?",
  205. (now_iso(), str(exc), scan_id),
  206. )
  207. raise