fix: DrHider — CSV in ZIP, double-encode fix, multipart parser
Deploy contracts-flask / validate (push) Successful in 0s
Deploy contracts-flask / validate (push) Successful in 0s
This commit is contained in:
+47
-11
@@ -217,28 +217,64 @@ class Handler(BaseHTTPRequestHandler):
|
|||||||
def _handle_drhider(self):
|
def _handle_drhider(self):
|
||||||
"""Обфускация: multipart files → LLM-NER → ZIP."""
|
"""Обфускация: multipart files → LLM-NER → ZIP."""
|
||||||
from services.drhider import obfuscate_files
|
from services.drhider import obfuscate_files
|
||||||
from services.llm_client import HttpxLLMClient
|
|
||||||
from services.upload import parse_multipart
|
|
||||||
|
|
||||||
length = int(self.headers.get("Content-Length", 0))
|
length = int(self.headers.get("Content-Length", 0))
|
||||||
raw = self.rfile.read(length)
|
raw = self.rfile.read(length)
|
||||||
ctype = self.headers.get("Content-Type", "")
|
ctype = self.headers.get("Content-Type", "")
|
||||||
|
|
||||||
files = parse_multipart(raw, ctype)
|
# Парсим multipart вручную
|
||||||
|
import cgi, re
|
||||||
|
_, params = cgi.parse_header(ctype)
|
||||||
|
boundary = params.get("boundary", "").encode()
|
||||||
|
if not boundary:
|
||||||
|
self._json({"ok": False, "error": "No multipart boundary"}, 400)
|
||||||
|
return
|
||||||
|
|
||||||
|
files = []
|
||||||
|
# Разбиваем по boundary
|
||||||
|
parts = raw.split(b"--" + boundary)
|
||||||
|
for part in parts:
|
||||||
|
if b"Content-Disposition" not in part:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
header_end = part.index(b"\r\n\r\n")
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
headers_raw = part[:header_end].decode("latin-1", errors="replace")
|
||||||
|
content = part[header_end + 4:]
|
||||||
|
if content.endswith(b"\r\n"):
|
||||||
|
content = content[:-2]
|
||||||
|
# Извлечь filename
|
||||||
|
m = re.search(r'filename="([^"]*)"', headers_raw)
|
||||||
|
if not m:
|
||||||
|
continue
|
||||||
|
fname = m.group(1)
|
||||||
|
if not fname:
|
||||||
|
continue
|
||||||
|
files.append((fname, content, ""))
|
||||||
|
|
||||||
if not files:
|
if not files:
|
||||||
self._json({"ok": False, "error": "Нет файлов"}, 400)
|
self._json({"ok": False, "error": "Нет файлов"}, 400)
|
||||||
return
|
return
|
||||||
|
|
||||||
file_list = [(f.filename, f.content, f.content_type) for f in files]
|
# LLM-клиент (совместимый с drhider.LLMClient протоколом: complete(prompt)->str)
|
||||||
|
class _VMLLM:
|
||||||
|
def complete(self, prompt):
|
||||||
|
import httpx
|
||||||
|
r = httpx.post(
|
||||||
|
os.environ.get("LLM_URL", "https://api.aillm.ru/v1/chat/completions"),
|
||||||
|
json={"model": os.environ.get("LLM_MODEL", "gpt-oss-120b"),
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
"max_tokens": 8000, "temperature": 0.1},
|
||||||
|
headers={"Authorization": f"Bearer {os.environ.get('LLM_KEY', '')}",
|
||||||
|
"Content-Type": "application/json"},
|
||||||
|
timeout=120
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.json()["choices"][0]["message"]["content"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
llm = HttpxLLMClient(
|
zip_data, _csv = obfuscate_files(files, llm_client=_VMLLM())
|
||||||
os.environ.get("LLM_API_URL", "https://api.aillm.ru/v1/chat/completions"),
|
|
||||||
os.environ.get("LLM_API_KEY", ""),
|
|
||||||
os.environ.get("LLM_MODEL", "gpt-oss-120b")
|
|
||||||
)
|
|
||||||
zip_data, _csv = obfuscate_files(file_list, llm_client=llm)
|
|
||||||
|
|
||||||
self.send_response(200)
|
self.send_response(200)
|
||||||
self.send_header("Content-Type", "application/zip")
|
self.send_header("Content-Type", "application/zip")
|
||||||
self.send_header("Content-Disposition", 'attachment; filename="drhider_output.zip"')
|
self.send_header("Content-Disposition", 'attachment; filename="drhider_output.zip"')
|
||||||
|
|||||||
@@ -218,12 +218,13 @@ class TwoPassObfuscator:
|
|||||||
else:
|
else:
|
||||||
# .doc или другой — замена по тексту
|
# .doc или другой — замена по тексту
|
||||||
txt = all_texts.get(fname, '')
|
txt = all_texts.get(fname, '')
|
||||||
obf_content = self._replace_in_text(txt, fname).encode('utf-8')
|
obf_content = self._replace_in_text(txt, fname)
|
||||||
|
|
||||||
results.append((fname, obf_content))
|
results.append((fname, obf_content))
|
||||||
|
|
||||||
# Собираем ZIP + CSV
|
# Собираем ZIP + CSV
|
||||||
return self._build_zip(results), self._build_mapping_csv()
|
csv_str = self._build_mapping_csv()
|
||||||
|
return self._build_zip(results, csv_str), csv_str
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(self._workdir, ignore_errors=True)
|
shutil.rmtree(self._workdir, ignore_errors=True)
|
||||||
@@ -362,12 +363,14 @@ class TwoPassObfuscator:
|
|||||||
|
|
||||||
# --- Сборка выдачи ---
|
# --- Сборка выдачи ---
|
||||||
|
|
||||||
def _build_zip(self, files: List[Tuple[str, bytes]]) -> bytes:
|
def _build_zip(self, files: List[Tuple[str, bytes]], mapping_csv: str = "") -> bytes:
|
||||||
"""Собрать ZIP с обфусцированными файлами."""
|
"""Собрать ZIP с обфусцированными файлами + mapping.csv."""
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
for fname, content in files:
|
for fname, content in files:
|
||||||
zf.writestr(fname, content)
|
zf.writestr(fname, content)
|
||||||
|
if mapping_csv:
|
||||||
|
zf.writestr("mapping.csv", mapping_csv.encode('utf-8'))
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
def _build_mapping_csv(self) -> str:
|
def _build_mapping_csv(self) -> str:
|
||||||
|
|||||||
@@ -218,12 +218,13 @@ class TwoPassObfuscator:
|
|||||||
else:
|
else:
|
||||||
# .doc или другой — замена по тексту
|
# .doc или другой — замена по тексту
|
||||||
txt = all_texts.get(fname, '')
|
txt = all_texts.get(fname, '')
|
||||||
obf_content = self._replace_in_text(txt, fname).encode('utf-8')
|
obf_content = self._replace_in_text(txt, fname)
|
||||||
|
|
||||||
results.append((fname, obf_content))
|
results.append((fname, obf_content))
|
||||||
|
|
||||||
# Собираем ZIP + CSV
|
# Собираем ZIP + CSV
|
||||||
return self._build_zip(results), self._build_mapping_csv()
|
csv_str = self._build_mapping_csv()
|
||||||
|
return self._build_zip(results, csv_str), csv_str
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(self._workdir, ignore_errors=True)
|
shutil.rmtree(self._workdir, ignore_errors=True)
|
||||||
@@ -362,12 +363,14 @@ class TwoPassObfuscator:
|
|||||||
|
|
||||||
# --- Сборка выдачи ---
|
# --- Сборка выдачи ---
|
||||||
|
|
||||||
def _build_zip(self, files: List[Tuple[str, bytes]]) -> bytes:
|
def _build_zip(self, files: List[Tuple[str, bytes]], mapping_csv: str = "") -> bytes:
|
||||||
"""Собрать ZIP с обфусцированными файлами."""
|
"""Собрать ZIP с обфусцированными файлами + mapping.csv."""
|
||||||
buf = io.BytesIO()
|
buf = io.BytesIO()
|
||||||
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
with zipfile.ZipFile(buf, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||||
for fname, content in files:
|
for fname, content in files:
|
||||||
zf.writestr(fname, content)
|
zf.writestr(fname, content)
|
||||||
|
if mapping_csv:
|
||||||
|
zf.writestr("mapping.csv", mapping_csv.encode('utf-8'))
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
def _build_mapping_csv(self) -> str:
|
def _build_mapping_csv(self) -> str:
|
||||||
|
|||||||
Reference in New Issue
Block a user