1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| """HackThisSite Forensic 1 - recover the deleted password file from the NTFS image.
Chain: fls -r -d lists deleted inodes -> icat extracts them -> the termination letter leaks the phone number -> that number is the RAR password -> the recovered .docx holds the mission password.
Requires: sleuthkit (fls, icat) and 7z on PATH. """ import html import os import re import subprocess import sys import zipfile
IMAGE = "image.dd" OUT = "recovered" os.chdir(os.path.dirname(os.path.abspath(__file__)))
def run(*args, **kw): return subprocess.run(args, capture_output=True, **kw)
def deleted_inodes(image): """Return {basename: inode} for every deleted inode in the image.""" out = run("fls", "-r", "-d", "-p", image).stdout.decode("utf-8", "replace") found = {} for line in out.splitlines(): m = re.match(r"^[-rd]/[rd-] \* (\d+)-\d+-\d+:\t(.*)$", line) if not m: continue inode, path = m.group(1), m.group(2) found[os.path.basename(path)] = int(inode) return found
def icat(image, inode, dest): """Extract the default $DATA stream of an inode to dest.""" with open(dest, "wb") as fh: subprocess.run(["icat", image, str(inode)], stdout=fh, check=True) return os.path.getsize(dest)
def docx_text(path): """Return the plain text of a .docx (word/document.xml, tags stripped).""" with zipfile.ZipFile(path) as z: xml = z.read("word/document.xml").decode("utf-8", "replace") xml = xml.replace("</w:p>", "\n") text = re.sub(r"<[^>]+>", "", xml) return html.unescape(text)
def main(): os.makedirs(OUT, exist_ok=True) inodes = deleted_inodes(IMAGE) print("[*] deleted inodes: %d" % len(inodes)) for name in ("Termination - Allen Smith.docx", "Your new password is.rar", "Voicemail 1.wav", "logins.txt"): if name in inodes: print(" %-34s inode %d" % (name, inodes[name]))
letter = os.path.join(OUT, "Termination.docx") icat(IMAGE, inodes["Termination - Allen Smith.docx"], letter) text = docx_text(letter) phone = re.search(r"(\d{3})[-.\s]?(\d{3})[-.\s]?(\d{4})", text) if not phone: sys.exit("no phone number in the termination letter") rar_pw = "".join(phone.groups()) print("[*] phone number : %s" % phone.group(0)) print("[*] rar password : %s" % rar_pw)
logins = os.path.join(OUT, "logins.txt") icat(IMAGE, inodes["logins.txt"], logins) print("[*] logins.txt : %s" % open(logins, encoding="utf-8", errors="replace").read().strip())
wav = os.path.join(OUT, "Voicemail 1.wav") size = icat(IMAGE, inodes["Voicemail 1.wav"], wav) import wave with wave.open(wav) as w: dur = round(w.getnframes() / w.getframerate(), 2) print("[*] voicemail : %d bytes, %s s PCM" % (size, dur))
rar = os.path.join(OUT, "Your new password is.rar") icat(IMAGE, inodes["Your new password is.rar"], rar) subprocess.run(["7z", "x", "-y", "-p" + rar_pw, "-o" + OUT, rar], check=True, stdout=subprocess.DEVNULL) inner = os.path.join(OUT, "Your new password is.docx") match = re.search(r"'([^']+)'", docx_text(inner)) print("[*] mission password: %s" % match.group(1))
if __name__ == "__main__": main()
|