-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor time. add colors and execution from package name. change re…
…covery_tool class to hound.
- Loading branch information
Showing
21 changed files
with
962 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
compiled_output.txt | ||
drivehound.egg-info | ||
DriveHound.egg-info | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
Oops, something went wrong.