Files
AWatch-rus/aw-server/hayabusa/aw-hayabusa-from-windows.py
T

112 lines
4.7 KiB
Python

#!/usr/bin/env python3
import argparse
import json
import pathlib
import re
import subprocess
import sys
OPS_ROOT = pathlib.Path('/opt/activitywatch/aw-rus-ops')
DEFAULT_INVENTORY = OPS_ROOT / 'ansible' / 'inventory.ini'
DEFAULT_DROP = OPS_ROOT / 'drop'
DEFAULT_ANSIBLE = OPS_ROOT / 'venv' / 'bin' / 'ansible'
DEFAULT_WRAPPER = pathlib.Path('/usr/local/bin/aw-hayabusa')
DEFAULT_LINKER = pathlib.Path('/usr/local/bin/aw-hayabusa-link-case')
WINDOWS_EXPORT_CMD = r"powershell.exe -ExecutionPolicy Bypass -File C:\ProgramData\AWatch-rus\export-evtx-for-hayabusa.ps1 -DaysBack {days_back} | ConvertTo-Json -Depth 8 -Compress"
WINDOWS_LATEST_ZIP_CMD = r"Get-ChildItem 'C:\ProgramData\AWatch-rus\forensics\evtx-exports' -File -Filter '*.zip' | Sort-Object LastWriteTime -Descending | Select-Object -First 1 FullName,Length,LastWriteTime | ConvertTo-Json -Compress"
def run(cmd):
proc = subprocess.run(cmd, text=True, capture_output=True)
if proc.returncode != 0:
sys.stderr.write(proc.stdout)
sys.stderr.write(proc.stderr)
raise SystemExit(proc.returncode)
return proc.stdout
def extract_json_blob(text):
matches = re.findall(r'(\{.*\}|\[.*\])', text, re.S)
for candidate in reversed(matches):
try:
return json.loads(candidate)
except Exception:
continue
raise SystemExit('cannot parse JSON from ansible output:\n' + text)
def main():
p = argparse.ArgumentParser(description='Run Windows EVTX export and Hayabusa intake directly from aw-server, without the laptop')
p.add_argument('--inventory', default=str(DEFAULT_INVENTORY))
p.add_argument('--ansible-bin', default=str(DEFAULT_ANSIBLE))
p.add_argument('--drop-dir', default=str(DEFAULT_DROP))
p.add_argument('--days-back', type=int, default=1)
p.add_argument('--mode', default='incident', choices=['quick', 'incident', 'full'])
p.add_argument('--case-id', type=int)
p.add_argument('--link-source', default='aw-rus-ops-from-windows')
p.add_argument('--windows-group', default='aw_windows')
p.add_argument('--wrapper', default=str(DEFAULT_WRAPPER))
p.add_argument('--linker', default=str(DEFAULT_LINKER))
args = p.parse_args()
inventory = pathlib.Path(args.inventory)
ansible_bin = pathlib.Path(args.ansible_bin)
drop_dir = pathlib.Path(args.drop_dir)
wrapper = pathlib.Path(args.wrapper)
linker = pathlib.Path(args.linker)
if not inventory.is_file():
raise SystemExit(f'inventory not found: {inventory}')
if not ansible_bin.is_file():
raise SystemExit(f'ansible binary not found: {ansible_bin}')
if not wrapper.is_file():
raise SystemExit(f'wrapper not found: {wrapper}')
drop_dir.mkdir(parents=True, exist_ok=True)
export_cmd = [str(ansible_bin), args.windows_group, '-i', str(inventory), '-m', 'win_shell', '-a', WINDOWS_EXPORT_CMD.format(days_back=args.days_back)]
print('RUN_EXPORT', ' '.join(export_cmd))
export_out = run(export_cmd)
export_json = extract_json_blob(export_out)
list_cmd = [str(ansible_bin), args.windows_group, '-i', str(inventory), '-m', 'win_shell', '-a', WINDOWS_LATEST_ZIP_CMD]
print('RUN_LIST', ' '.join(list_cmd))
latest_out = run(list_cmd)
latest = extract_json_blob(latest_out)
if isinstance(latest, list):
latest = latest[0]
remote_zip = latest['FullName']
filename = pathlib.PureWindowsPath(remote_zip).name
local_zip = drop_dir / filename
remote_zip_posix = remote_zip.replace('\\', '/')
fetch_cmd = [str(ansible_bin), args.windows_group, '-i', str(inventory), '-m', 'fetch', '-a', f'src={remote_zip_posix} dest={drop_dir}/ flat=yes']
print('RUN_FETCH', ' '.join(fetch_cmd))
run(fetch_cmd)
if not local_zip.is_file():
raise SystemExit(f'fetched zip not found: {local_zip}')
accept_cmd = [str(wrapper), 'accept', '--package', str(local_zip)]
host = export_json.get('hostname') or pathlib.Path(filename).stem.split('-')[0]
if host:
accept_cmd += ['--host', str(host)]
print('RUN_ACCEPT', ' '.join(accept_cmd))
subprocess.run(accept_cmd, check=True)
process_cmd = [str(wrapper), 'process-inbox', '--mode', args.mode, '--limit', '1']
print('RUN_PROCESS', ' '.join(process_cmd))
subprocess.run(process_cmd, check=True)
if args.case_id is not None:
if not linker.is_file():
raise SystemExit(f'linker not found: {linker}')
link_cmd = [str(linker), '--case-id', str(args.case_id), '--mode', args.mode, '--link-source', args.link_source]
print('RUN_LINK', ' '.join(link_cmd))
subprocess.run(link_cmd, check=True)
latest_intake = pathlib.Path('/opt/hayabusa/state/latest-intake.json')
print('LATEST_INTAKE')
print(latest_intake.read_text(encoding='utf-8'))
if __name__ == '__main__':
main()