Skip to content

Commit

Permalink
refactor time. add colors and execution from package name. change re…
Browse files Browse the repository at this point in the history
…covery_tool class to hound.
  • Loading branch information
mewmix committed Jan 23, 2025
1 parent 5f62670 commit e7ecb9c
Show file tree
Hide file tree
Showing 21 changed files with 962 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
compiled_output.txt
drivehound.egg-info
DriveHound.egg-info
build
23 changes: 23 additions & 0 deletions drivehound/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# drivehound/__init__.py

from .hound import Hound
from .carver import Carver
from .win_drive_tools import open_drive, list_partitions
from .color_utils import (
colored_text,
colored_bg_text,
print_colored,
print_colored_bg
)
from .file_signatures import FILE_SIGNATURES
from .colors import get_color_hex, COLOR_PALETTE
from .ascii_utils import scale_ascii_art
from .logo import LOGO

__all__ = [
'Hound',
'Carver',
'open_drive',
'list_partitions',
'scale_ascii_art',
]
Binary file added drivehound/__pycache__/__init__.cpython-312.pyc
Binary file not shown.
Binary file added drivehound/__pycache__/ascii_utils.cpython-312.pyc
Binary file not shown.
Binary file added drivehound/__pycache__/carver.cpython-312.pyc
Binary file not shown.
Binary file not shown.
Binary file added drivehound/__pycache__/colors.cpython-312.pyc
Binary file not shown.
Binary file not shown.
Binary file added drivehound/__pycache__/hound.cpython-312.pyc
Binary file not shown.
Binary file added drivehound/__pycache__/logo.cpython-312.pyc
Binary file not shown.
Binary file not shown.
21 changes: 21 additions & 0 deletions drivehound/ascii_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ascii_art_utils.py

def scale_ascii_art(art: str, scale: int) -> str:
"""
Scales the given ASCII art by the specified scale factor.
Args:
art (str): The original ASCII art as a multi-line string.
scale (int): The scale factor (e.g., 2 doubles the size).
Returns:
str: The scaled ASCII art.
"""
scaled_art = ""
for line in art.splitlines():
scaled_line = ""
for char in line:
scaled_line += char * scale
for _ in range(scale):
scaled_art += scaled_line + "\n"
return scaled_art
139 changes: 139 additions & 0 deletions drivehound/carver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# carver.py
import os
import logging

# Example usage: Carve a specific file type from a disk image or raw file data.
# This module provides a Carver class that can:
# - Tune into a specific file format signature (start and optional end marker)
# - Search through large raw data (like a disk image or memory dump)
# - Extract all occurrences of files matching that signature
#
# It supports partial searching, offset-based adjustments, and chunked reading for large files.

class Carver:
def __init__(self, signature_key, signatures_dict, sector_size=512, output_dir="carved_output"):
"""
Initialize the Carver with a specific signature key and a dictionary of signatures.
Args:
signature_key (str): The key from the signatures_dict to carve.
signatures_dict (dict): Dictionary of signatures in format:
signature_key: (start_bytes, end_bytes_or_None, extension)
sector_size (int): Sector size to read at a time. Defaults to 512 for disk-like sources.
output_dir (str): Directory to store carved files.
"""
self.signature_key = signature_key
self.signatures = signatures_dict
if signature_key not in self.signatures:
raise ValueError(f"Signature key {signature_key} not found in provided dictionary.")
self.start_sig, self.end_sig, self.extension = self.signatures[signature_key]
self.sector_size = sector_size
self.output_dir = output_dir
os.makedirs(self.output_dir, exist_ok=True)
self._file_counter = 0

def carve_from_file(self, source_path):
"""
Carve files of the specified signature type from the given source file.
Args:
source_path (str): Path to the source file (e.g., disk image, memory dump)
Returns:
int: The number of files carved.
"""
# Open in binary mode
with open(source_path, "rb") as src:
return self.carve_from_stream(src)

def carve_from_stream(self, src):
"""
Carve files of the specified signature type from a binary stream.
Args:
src (file-like): A binary stream with a read() method.
Returns:
int: The number of files carved.
"""
logging.info(f"Starting carving for {self.signature_key} with extension {self.extension}")

# We will read in chunks and search for the start pattern.
# Once found, we will keep reading until the end pattern is located (if end pattern is defined).
total_carved = 0
buffer = b""
chunk_size = self.sector_size * 64 # read bigger chunks for better performance
eof_reached = False
file_in_progress = False
outfile = None

while not eof_reached:
data = src.read(chunk_size)
if not data:
eof_reached = True
else:
buffer += data

# Process the buffer
# If we are not currently extracting a file, look for start_sig
if not file_in_progress:
start_pos = buffer.find(self.start_sig)
if start_pos >= 0:
# Found a start signature
file_in_progress = True
# Create a new output file
out_name = f"{self.signature_key}_{self._file_counter}{self.extension}"
out_path = os.path.join(self.output_dir, out_name)
outfile = open(out_path, "wb")
# Write from start_pos onwards
outfile.write(buffer[start_pos:])
# Trim buffer to only what was beyond start_pos
buffer = b""
self._file_counter += 1
total_carved += 1
else:
# Keep buffer small if we didn't find anything: avoid memory blowup
# Retain last len(start_sig)-1 bytes to not miss a signature crossing chunks
max_retain = len(self.start_sig) - 1 if len(self.start_sig) > 1 else 1
buffer = buffer[-max_retain:]
else:
# We are currently writing to a file. If end_sig is None, we write until EOF.
# If end_sig is defined, search for it
if self.end_sig is not None:
end_pos = buffer.find(self.end_sig)
if end_pos >= 0:
# End found, write up to end signature
outfile.write(buffer[:end_pos + len(self.end_sig)])
outfile.close()
outfile = None
file_in_progress = False
# Discard everything up to end_pos
buffer = buffer[end_pos + len(self.end_sig):]
# After finishing one file, we might want to immediately look if another file start is here
# We'll just continue the loop to handle that in next iteration
else:
# No end signature found, write entire buffer and reset it
outfile.write(buffer)
buffer = b""
else:
# No end signature, we keep writing until EOF
if eof_reached:
# Write whatever left in buffer
outfile.write(buffer)
buffer = b""
outfile.close()
outfile = None
file_in_progress = False
else:
# Just keep writing
outfile.write(buffer)
buffer = b""

# If file_in_progress still True at the end (no end found and not ended):
if file_in_progress and outfile:
outfile.write(buffer)
outfile.close()
file_in_progress = False

logging.info(f"Carving complete. Total files carved: {total_carved}")
return total_carved
128 changes: 128 additions & 0 deletions drivehound/color_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# drivehound/color_utils.py

"""
color_utils.py
Utility functions for handling colored text output in the terminal using ANSI escape codes.
Supports both predefined color palettes and custom hex color codes.
"""

import sys
from .colors import COLOR_PALETTE, get_color_hex

def hex_to_rgb(hex_color: str):
"""
Converts a hex color string to an RGB tuple.
Args:
hex_color (str): Hex color string (e.g., '#FFAABB' or 'FFAABB').
Returns:
tuple: (R, G, B) as integers.
"""
hex_color = hex_color.lstrip('#')
if len(hex_color) != 6:
raise ValueError("Hex color must be in the format RRGGBB.")
r, g, b = tuple(int(hex_color[i:i+2], 16) for i in (0, 2, 4))
return (r, g, b)

def rgb_to_ansi_fg(r: int, g: int, b: int):
"""
Creates an ANSI escape code for the foreground color.
Args:
r (int): Red component (0-255).
g (int): Green component (0-255).
b (int): Blue component (0-255).
Returns:
str: ANSI escape code string.
"""
return f'\033[38;2;{r};{g};{b}m'

def rgb_to_ansi_bg(r: int, g: int, b: int):
"""
Creates an ANSI escape code for the background color.
Args:
r (int): Red component (0-255).
g (int): Green component (0-255).
b (int): Blue component (0-255).
Returns:
str: ANSI escape code string.
"""
return f'\033[48;2;{r};{g};{b}m'

def reset_ansi():
"""
Returns the ANSI escape code to reset colors.
Returns:
str: ANSI reset code.
"""
return '\033[0m'

def colored_text(text: str, color: str):
"""
Wraps the given text with ANSI codes to display it in the specified color.
Supports both predefined color names and custom hex color codes.
Args:
text (str): The text to color.
color (str): Color name (e.g., 'red') or hex color string (e.g., '#FFAABB').
Returns:
str: Colored text with ANSI codes.
"""
try:
# Attempt to get hex code from color name
hex_color = get_color_hex(color)
except ValueError:
# Assume the color is a hex code
hex_color = color
r, g, b = hex_to_rgb(hex_color)
return f"{rgb_to_ansi_fg(r, g, b)}{text}{reset_ansi()}"

def colored_bg_text(text: str, color: str):
"""
Wraps the given text with ANSI codes to display it with the specified background color.
Supports both predefined color names and custom hex color codes.
Args:
text (str): The text to color.
color (str): Color name (e.g., 'blue') or hex color string (e.g., '#0000FF').
Returns:
str: Text with colored background using ANSI codes.
"""
try:
# Attempt to get hex code from color name
hex_color = get_color_hex(color)
except ValueError:
# Assume the color is a hex code
hex_color = color
r, g, b = hex_to_rgb(hex_color)
return f"{rgb_to_ansi_bg(r, g, b)}{text}{reset_ansi()}"

def print_colored(text: str, color: str):
"""
Prints the given text in the specified color.
Args:
text (str): The text to print.
color (str): Color name or hex color string.
"""
colored = colored_text(text, color)
print(colored)

def print_colored_bg(text: str, color: str):
"""
Prints the given text with the specified background color.
Args:
text (str): The text to print.
color (str): Color name or hex color string.
"""
colored = colored_bg_text(text, color)
print(colored)
Loading

0 comments on commit e7ecb9c

Please sign in to comment.