|
""" |
|
Allows the inferior to access host's file system. |
|
Reads and writes files in the given GDB_FILE_HOOK_DIR env var. |
|
The paths are sanitized against parent traversal to prevent accidents, |
|
but shouldn't be considered secure. |
|
|
|
This requres support on the guest side, see gdb.cpp. |
|
""" |
|
|
|
import sys |
|
import os |
|
import traceback |
|
import ctypes |
|
import struct |
|
import pathlib |
|
|
|
import gdb |
|
|
|
tmp_dir = os.environ.get('GDB_FILE_HOOK_DIR', None) |
|
print(f"{tmp_dir=}") |
|
|
|
|
|
def sanitize_path(path): |
|
""" |
|
Sanitize a path against directory traversals |
|
""" |
|
# From: https://stackoverflow.com/a/66950540 |
|
# - pretending to chroot to the current directory |
|
# - cancelling all redundant paths (/.. = /) |
|
# - making the path relative |
|
return os.path.relpath(os.path.normpath(os.path.join("/", path)), "/") |
|
|
|
def map_path(path: str): |
|
return os.path.join(tmp_dir, sanitize_path(path)) |
|
|
|
|
|
class BPFileLastModified(gdb.Breakpoint): |
|
def stop (self): |
|
frame = gdb.selected_frame() |
|
path = frame.read_var("path").string() |
|
real_path = map_path(path) |
|
time = gdb.lookup_static_symbol("gdbLastModified").value() |
|
|
|
time.assign(0) |
|
|
|
try: |
|
last_modified = pathlib.Path(real_path).stat().st_mtime |
|
except OSError as e: |
|
print(e) |
|
return False |
|
|
|
print(f"{last_modified=}") |
|
print(f"{time=}") |
|
time.assign(last_modified) |
|
|
|
class BPSaveFile(gdb.Breakpoint): |
|
def stop (self): |
|
frame = gdb.selected_frame() |
|
path = frame.read_var("path").string() |
|
data_addr = str(ctypes.c_uint32(frame.read_var("data")).value) |
|
size = int(frame.read_var("dataSizeBytes")) |
|
wrote_bytes = gdb.lookup_static_symbol("gdbWroteBytes").value() |
|
|
|
wrote_bytes.assign(-1) # Set a return value of -1 in case open() throws |
|
|
|
data = gdb.selected_inferior().read_memory(data_addr, size) |
|
|
|
real_path = map_path(path) |
|
print(f"{path=} {data=}") |
|
print(f"{real_path=}") |
|
|
|
try: |
|
with open(real_path, "wb") as f: |
|
wrote = f.write(data) |
|
except OSError as e: |
|
print(e) |
|
return False |
|
|
|
wrote_bytes.assign(wrote) |
|
print(f"Wrote {len(data)} bytes to {real_path}") |
|
return False |
|
|
|
|
|
class BPReadFile(gdb.Breakpoint): |
|
def stop_impl(self): |
|
remote = gdb.selected_inferior() |
|
frame = gdb.selected_frame() |
|
path = frame.read_var("path").string() |
|
|
|
max_size = int(frame.read_var("maxSize")) |
|
dest = frame.read_var("dest") |
|
dest_addr: str = str(ctypes.c_uint32(dest).value) |
|
|
|
read_bytes = gdb.lookup_static_symbol("gdbReadBytes").value() |
|
read_bytes.assign(-1) # Set a return value of -1 in case open() throws |
|
|
|
real_path = map_path(path) |
|
|
|
try: |
|
with open(real_path, "rb") as f: |
|
raw_data = f.read() |
|
except FileNotFoundError as e: |
|
print(e) |
|
return False |
|
|
|
data = raw_data[:max_size] |
|
remote.write_memory(dest_addr, data) |
|
|
|
read_bytes.assign(len(data)) |
|
|
|
print(f"Read {len(data)} bytes from '{path}' to {hex(ctypes.c_uint32(dest).value)}") |
|
|
|
return False |
|
|
|
|
|
def stop(self): |
|
try: |
|
self.stop_impl() |
|
except Exception: |
|
print(traceback.print_exc()) |
|
|
|
|
|
bp_save = BPSaveFile("GDB::writeFile") |
|
bp_save.silent = True |
|
|
|
bp_read = BPReadFile("GDB::readFile") |
|
bp_read.silent = True |
|
|
|
bp_lm = BPFileLastModified("GDB::fileLastModified") |
|
bp_lm.silent = True |