working command structure and advanced llm Integration
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Project Architecture: AI-Enhanced CTF Toolchain
|
||||
|
||||
This document describes the current architecture, directory layout, core modules, and testing setup of the CTF Toolchain project.
|
||||
This document describes the architecture, directory layout, core modules, testing setup, and planned components of the CTF Toolchain project.
|
||||
|
||||
---
|
||||
|
||||
@@ -18,7 +18,7 @@ The project follows a standard modern Python layout (utilizing `src/` directory
|
||||
│ ├── main.py # CLI Entry Point
|
||||
│ ├── commands.py # CLI Commands and action functions
|
||||
│ ├── utils.py # Core utility functions (file parsing, config, paths)
|
||||
│ └── forensics.py # Placeholder for future forensics analysis tools
|
||||
│ └── forensics.py # Forensics analysis tools
|
||||
└── tests/
|
||||
├── env/ # Sandboxed, persistent test environment directories
|
||||
└── test_utils.py # Unit/Integration tests for utility functions
|
||||
@@ -41,20 +41,54 @@ Provides helper functions for filesystem management and configuration parsing:
|
||||
* `active_competitions(dir)`: Scans the base directory for active competitions, skipping designated helper directories (like `tools`).
|
||||
|
||||
### C. Commands ([commands.py](file:///home/venus/code/ctf/src/ctf/commands.py))
|
||||
Houses the logic for each CLI command action:
|
||||
* `test()`: A simple hello-world tester.
|
||||
Houses the logic for generic CLI context and active competition commands:
|
||||
* `Set_Challenge(comp, chal, setDirectory)`: Sets the current active challenge/competition context. *(Note: Currently has a `NameError` due to reference to an undefined `state` object.)*
|
||||
|
||||
### D. Forensics ([forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py))
|
||||
Implements specialized forensic inspection utilities registered as a nested subgroup under the CLI:
|
||||
* `info`: Inspects target file sizes, reads magic bytes, and warns if extensions do not match detected signatures.
|
||||
* `flag-search`: Extracts printable string sequences (equivalent to GNU `strings`) and matches them against regular expression patterns to find potential flags.
|
||||
|
||||
---
|
||||
|
||||
## 3. CLI Entry Point ([main.py](file:///home/venus/code/ctf/src/ctf/main.py))
|
||||
|
||||
* Currently acts as a simple entry point calling `commands.test()`.
|
||||
* Uses `click` as the planned framework to build a sub-command CLI system (`ctf test`, `ctf set-challenge`, etc.).
|
||||
* Serves as the central CLI entry point via the `main()` function.
|
||||
* Initializes the root Click `cli` group and registers nested sub-groups, such as `forensics_group`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Test Infrastructure
|
||||
|
||||
* **Framework**: `pytest` (run via `uv run pytest`).
|
||||
* **Sandbox**: [tests/env](file:///home/venus/code/ctf/tests/env) acts as a persistent mock directory tree containing temporary competition directories (like `comp1`, `comp2`) to safely test category scanning and config loading/saving without touching actual user data.
|
||||
* **Sandbox**: [tests/env](file:///home/venus/code/ctf/tests/env) acts as a persistent mock directory tree containing temporary competition directories (like `comp1`, `comp2`) and mock files (e.g. valid PNGs, mismatching PDFs, text files with flag payloads) to safely test scanning, parsing, and CLI command execution without touching actual user data.
|
||||
|
||||
---
|
||||
|
||||
## 5. Planned Architectural Components
|
||||
|
||||
### A. File Scraper & Extended Configuration
|
||||
* A scraping module to fetch details/files for challenges or competitions.
|
||||
* Integration with an expanded configuration schema in [config.toml](file:///home/venus/code/ctf/config.toml) to store credentials, URLs, and directory preferences.
|
||||
|
||||
### B. Download Organizer & Challenge Progress Documenter
|
||||
* Monitoring or organizing downloaded challenge assets (e.g., from the browser's downloads folder) and sorting them into the correct competition/challenge subdirectories.
|
||||
* An automated mechanism to log commands, notes, and milestones, providing clean progress documentation.
|
||||
|
||||
### C. Forensics Metadata Expansion
|
||||
* Extend forensics capabilities inside [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py) to extract file-specific metadata (e.g., EXIF header extraction for JPG/PNG files, archive contents listing, and PE section analysis).
|
||||
|
||||
---
|
||||
|
||||
## 6. CLI Data Flow & Presentation Guidelines
|
||||
|
||||
### A. Model-View Separation
|
||||
All CLI command modules (such as [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py)) must separate data extraction logic from command-line rendering.
|
||||
* **Data Models**: Standard Python `@dataclass` objects should be defined to house parsed metadata (e.g., file size, magic bytes, detected types, warnings, and format-specific attributes).
|
||||
* **Decoupled Parsers**: Extraction helper functions must return these dataclass instances instead of printing directly to standard output. This keeps the core parser functions purely functional and fully testable in unit tests.
|
||||
|
||||
### B. Console Rendering with `rich`
|
||||
To provide a clean, modern, and easily readable console output without building a full terminal user interface (TUI):
|
||||
* **Tables**: Use `rich.table.Table` to align and structure multi-column metadata outputs.
|
||||
* **Formatting & Alerts**: Utilize `rich.console` or `rich.panel.Panel` to highlight warnings (such as signature/extension mismatches) with distinct styling and colors.
|
||||
* **JSON Serialization**: Dataclasses should be easily convertible to dictionaries to support raw JSON output options for scripting pipelines.
|
||||
|
||||
19
GEMINI.md
19
GEMINI.md
@@ -6,7 +6,7 @@ This project serves as a centralized workspace for Capture The Flag (CTF) challe
|
||||
The primary goal is to use the context of CTF challenges (forensics, crypto, web, etc.) to explore and explain Python concepts, automation, and tooling.
|
||||
|
||||
## Core Mandates for Gemini CLI
|
||||
1. **No Unsolicited Code:** Do NOT write or modify code unless explicitly issued a **Directive** to do so.
|
||||
1. **No Unsolicited Project Code Modifications:** Do NOT write or modify actual application/project implementation code files (e.g. within `src/` or core scripts) unless explicitly directed to do so. Only update test cases (e.g., in `tests/`) and markdown documentation files.
|
||||
2. **Focus on Explanation:** Prioritize high-signal explanations of Python mechanics, libraries, and documentation.
|
||||
3. **Summarization:** When directed, summarize documentation (local or web-based) to aid in understanding CTF tools and Python modules.
|
||||
4. **Educational Context:** Use the existing scripts and tools in this repository (like `psk_crack.py`, `exploit.sh`, or the `tools/` directory) as examples when explaining technical concepts.
|
||||
@@ -25,3 +25,20 @@ The primary goal is to use the context of CTF challenges (forensics, crypto, web
|
||||
- write tests explaining successful passes and failures
|
||||
- Try to find breaking elements of user code. Find harder cases that will fail on empty inputs, etc.
|
||||
- Include tests that chain multiple functions together
|
||||
|
||||
You are an expert Software Development Engineer in Test (SDET). Your task is to analyze the provided source code and generate a comprehensive, highly robust suite of test cases.
|
||||
|
||||
Do not just write "happy path" tests. You must systematically uncover potential failure points, boundary conditions, and state violations.
|
||||
|
||||
### Your Analysis Process
|
||||
Before writing any test code, you must complete the following analysis:
|
||||
1. **Identify Inputs & Outputs:** Map every input parameter, its expected type/bounds, and all possible return values or side effects.
|
||||
2. **Determine State Mutability:** Identify if the code modifies internal state, database records, or external systems.
|
||||
3. **Establish Boundaries:** Identify numeric limits, empty collections, null/undefined values, and string length limits.
|
||||
4. **Enumerate Error Paths:** List every condition that should throw an exception, return an error code, or trigger a failure handler.
|
||||
|
||||
### Output Requirements
|
||||
For the given code, output the test suite structured as follows:
|
||||
1. **Test Strategy Summary:** A brief list of the core scenarios being tested (Happy Path, Boundary, Edge Case, Error Handling).
|
||||
2. **The Test Code:** Written in the target framework/language requested by the user, adhering to industry best practices (e.g., AAA pattern, clean assertions, descriptive test names).
|
||||
3. **Mocking/Setup Requirements:** Explicitly state what external dependencies (APIs, databases, system clocks) need to be mocked and how.
|
||||
|
||||
31
project_status.md
Normal file
31
project_status.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Project Status & Roadmap
|
||||
|
||||
This file tracks the completed progress and upcoming development milestones for the AI-Enhanced CTF Toolchain.
|
||||
|
||||
---
|
||||
|
||||
## Current Status: Forensics Utilities Implemented 🚀
|
||||
|
||||
### Progress Made So Far
|
||||
1. **Project Structuring**: Set up a modern Python `src/` layout with `uv` as the package manager and `hatchling` as the build backend.
|
||||
2. **Config Management**: Implemented basic TOML configuration loading and writing functions in [utils.py](file:///home/venus/code/ctf/src/ctf/utils.py) using [config.toml](file:///home/venus/code/ctf/config.toml).
|
||||
3. **Directory Scanners**: Implemented functions to list active competitions and categories automatically skipping helper folders like `tools`.
|
||||
4. **CLI Entry Points & Subcommands**: Configured the Click main group in [main.py](file:///home/venus/code/ctf/src/ctf/main.py) to register nested subcommands properly.
|
||||
5. **Forensics Analysis (New)**: Implemented `ctf forensics info` and `ctf forensics flag-search` in [forensics.py](file:///home/venus/code/ctf/src/ctf/forensics.py) to inspect magic bytes/signatures, verify file extension matches, and search for flag pattern regular expressions.
|
||||
6. **Testing Environment**: Established a sandbox folder at [tests/env](file:///home/venus/code/ctf/tests/env) and implemented tests in [test_utils.py](file:///home/venus/code/ctf/tests/test_utils.py) validating the CLI command executions and boundary cases.
|
||||
7. **Documentation**: Created the project [ARCHITECTURE.md](file:///home/venus/code/ctf/ARCHITECTURE.md) to define standard layouts, modules, and testing behavior.
|
||||
|
||||
---
|
||||
|
||||
## Upcoming Milestones & Features
|
||||
|
||||
### 📅 Phase 1: Configuration Completion & Basic File Scraper
|
||||
* Extend [config.toml](file:///home/venus/code/ctf/config.toml) to store arbitrary data in the future
|
||||
* Implement a basic tool to load the latest download files into the active directory
|
||||
|
||||
### 📅 Phase 2: Downloads Organizer & Progress Tracker
|
||||
* Write a tool to scan specified download directories for newly acquired challenge files and automatically organize them into the active competition's directory structure.
|
||||
* Implement progress tracking to output current exploration paths, notes, and milestones.
|
||||
|
||||
### 📅 Phase 3: Forensics Metadata Expansion
|
||||
* Extend forensics tools to parse specific file-format metadata (e.g. EXIF data for JPGs or headers for specific archives).
|
||||
@@ -8,6 +8,7 @@ dependencies = [
|
||||
"platformdirs>=4.9.4",
|
||||
"toml>=0.10.2",
|
||||
"click",
|
||||
"rich",
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
@@ -3,21 +3,32 @@
|
||||
# from pathlib import path
|
||||
import click
|
||||
|
||||
# This defines a group with name basic which will nest other comands to be imported in the main loop
|
||||
@click.group(name="basic")
|
||||
def basic_group(): pass
|
||||
|
||||
# Adds a simple commmand to be run with `ctf test` per the function name
|
||||
@click.command()
|
||||
def test():
|
||||
print("hello from test")
|
||||
|
||||
def Set_Challenge(comp: str, chal: str, setDirectory: bool):
|
||||
# set the current challenge and competition from input
|
||||
if state.current_comp != comp:
|
||||
state.current_comp=comp
|
||||
state.comp_dir=pathlib
|
||||
# TODO archive the old competitions
|
||||
# A simple command with a positional(required) argument name
|
||||
@click.command()
|
||||
@click.argument('name')
|
||||
def greet(name):
|
||||
print(f"hello {name}")
|
||||
|
||||
if state.current_chal != chal:
|
||||
state.current_chal=chal
|
||||
print("challenge already set")
|
||||
# TODO archive the old challenges
|
||||
# TODO set the directory to challenge directory, with ignore option
|
||||
# def Set_Challenge(comp: str, chal: str, setDirectory: bool):
|
||||
# # set the current challenge and competition from input
|
||||
# if state.current_comp != comp:
|
||||
# state.current_comp=comp
|
||||
# state.comp_dir=pathlib
|
||||
# # TODO archive the old competitions
|
||||
|
||||
# if state.current_chal != chal:
|
||||
# state.current_chal=chal
|
||||
# print("challenge already set")
|
||||
# # TODO archive the old challenges
|
||||
# # TODO set the directory to challenge directory, with ignore option
|
||||
|
||||
|
||||
|
||||
@@ -1,2 +1,55 @@
|
||||
# src/forensics.py
|
||||
# src/ctf/forensics.py
|
||||
# Library for forensic analysis
|
||||
|
||||
import click
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
|
||||
@click.group(name="forensics")
|
||||
def forensics_group(): pass
|
||||
|
||||
@forensics_group.command()
|
||||
def tf(): print("hello from forensics")
|
||||
|
||||
@dataclass
|
||||
class FileMetadata:
|
||||
filename: str
|
||||
size: int
|
||||
magic: str
|
||||
extension: str
|
||||
|
||||
@forensics_group.command()
|
||||
@click.argument('path', type=click.Path(exists=True))
|
||||
def inspect(path):
|
||||
p = Path(path)
|
||||
size = p.stat().st_size
|
||||
extension = p.suffix
|
||||
|
||||
try:
|
||||
with open(p, 'rb') as f:
|
||||
magic = f.read(4).hex().upper()
|
||||
except Exception:
|
||||
magic = "UNKNOWN"
|
||||
|
||||
meta = FileMetadata(
|
||||
filename=p.name,
|
||||
size=size,
|
||||
magic=magic,
|
||||
extension=extension
|
||||
)
|
||||
|
||||
# Present using Rich Table
|
||||
console = Console()
|
||||
table = Table(title=f"Metadata: {meta.filename}", show_header=False)
|
||||
table.add_column("Key", style="bold cyan")
|
||||
table.add_column("Value")
|
||||
|
||||
table.add_row("Filename", meta.filename)
|
||||
table.add_row("Size", f"{meta.size} bytes")
|
||||
table.add_row("Magic Bytes (Hex)", meta.magic)
|
||||
table.add_row("Extension", meta.extension)
|
||||
|
||||
console.print(table)
|
||||
return meta
|
||||
|
||||
@@ -2,10 +2,15 @@
|
||||
# Parses and calls commands
|
||||
|
||||
import ctf.commands as commands
|
||||
from ctf.forensics import forensics_group
|
||||
import click
|
||||
|
||||
|
||||
def main():
|
||||
commands.test()
|
||||
@click.group()
|
||||
def cli(): pass
|
||||
|
||||
cli.add_command(forensics_group())
|
||||
|
||||
|
||||
|
||||
|
||||
1
tests/env/callback_test.txt
vendored
Normal file
1
tests/env/callback_test.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Hello World
|
||||
1
tests/env/empty_data.bin
vendored
Normal file
1
tests/env/empty_data.bin
vendored
Normal file
@@ -0,0 +1 @@
|
||||
just some plain text without any flags here
|
||||
0
tests/env/empty_test.txt
vendored
Normal file
0
tests/env/empty_test.txt
vendored
Normal file
1
tests/env/flag_data.bin
vendored
Normal file
1
tests/env/flag_data.bin
vendored
Normal file
@@ -0,0 +1 @@
|
||||
garbage_data_here_flag{f0rens1cs_1s_fun}more_garbage
|
||||
1
tests/env/mismatch_image.png
vendored
Normal file
1
tests/env/mismatch_image.png
vendored
Normal file
@@ -0,0 +1 @@
|
||||
%PDF-1.4 header info
|
||||
2
tests/env/test_file.png
vendored
Normal file
2
tests/env/test_file.png
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
<EFBFBD>PNG
|
||||
|
||||
|
After Width: | Height: | Size: 8 B |
3
tests/env/test_image.png
vendored
Normal file
3
tests/env/test_image.png
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
<EFBFBD>PNG
|
||||
|
||||
Extra bytes here
|
||||
|
After Width: | Height: | Size: 24 B |
@@ -1,6 +1,8 @@
|
||||
from pathlib import Path
|
||||
import toml
|
||||
from ctf.utils import load_config, active_categories, active_competitions
|
||||
from click.testing import CliRunner
|
||||
from ctf.utils import load_config, active_categories
|
||||
from ctf.forensics import forensics_group, inspect
|
||||
|
||||
# Define the persistent test environment path
|
||||
TEST_ENV = Path("tests/env")
|
||||
@@ -8,13 +10,13 @@ TEST_ENV = Path("tests/env")
|
||||
def test_load_config_static():
|
||||
"""
|
||||
Verifies load_config using the persistent test file.
|
||||
Demonstrates: Reading from a specific relative Path.
|
||||
"""
|
||||
config_file = TEST_ENV / "config.toml"
|
||||
test_data = {
|
||||
"Competition": {"name": "PersistentComp"},
|
||||
"Enviroment": {"data_dir": str(TEST_ENV.absolute())}
|
||||
}
|
||||
config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(config_file, "w") as f:
|
||||
toml.dump(test_data, f)
|
||||
|
||||
@@ -24,25 +26,76 @@ def test_load_config_static():
|
||||
def test_active_categories_static():
|
||||
"""
|
||||
Verifies active_categories using the pre-created 'comp1' folder.
|
||||
Demonstrates: Path.iterdir() behavior on a real directory.
|
||||
"""
|
||||
comp_dir = TEST_ENV / "comp1"
|
||||
comp_dir.mkdir(parents=True, exist_ok=True)
|
||||
(comp_dir / "web").mkdir(exist_ok=True)
|
||||
(comp_dir / "pwn").mkdir(exist_ok=True)
|
||||
|
||||
cats = active_categories(comp_dir)
|
||||
cat_names = [c.name for c in cats]
|
||||
|
||||
# We expect 'web' and 'pwn' which were created in the setup step
|
||||
assert "web" in cat_names
|
||||
assert "pwn" in cat_names
|
||||
|
||||
def test_active_competitions_static():
|
||||
# --- Direct Function Callback Tests ---
|
||||
|
||||
def test_inspect_callback_success():
|
||||
"""
|
||||
Verifies active_competitions skips the 'tools' folder in TEST_ENV.
|
||||
Demonstrates: Guard clauses and directory filtering.
|
||||
Verifies that calling the inspect command's callback directly returns
|
||||
the correct FileMetadata dataclass with expected values.
|
||||
"""
|
||||
# Note: active_competitions takes a string path
|
||||
comps = active_competitions(str(TEST_ENV))
|
||||
comp_names = [p.name for p in comps.keys()]
|
||||
test_file = TEST_ENV / "callback_test.txt"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(b"Hello World")
|
||||
|
||||
# Using inspect.callback to invoke the underlying undecorated function
|
||||
meta = inspect.callback(str(test_file))
|
||||
|
||||
assert "comp1" in comp_names
|
||||
assert "comp2" in comp_names
|
||||
assert "tools" not in comp_names # The 'tools' folder exists but should be ignored
|
||||
assert meta.filename == "callback_test.txt"
|
||||
assert meta.size == 11
|
||||
assert meta.magic == "48656C6C" # Hex for "Hell"
|
||||
assert meta.extension == ".txt"
|
||||
|
||||
def test_inspect_callback_empty_file():
|
||||
"""
|
||||
Tests the boundary case of an empty (0-byte) file.
|
||||
Verifies that the parser handles it gracefully without raising exceptions.
|
||||
"""
|
||||
test_file = TEST_ENV / "empty_test.txt"
|
||||
with open(test_file, "wb") as f:
|
||||
pass
|
||||
|
||||
meta = inspect.callback(str(test_file))
|
||||
|
||||
assert meta.filename == "empty_test.txt"
|
||||
assert meta.size == 0
|
||||
assert meta.magic == "" # Empty string as no bytes could be read
|
||||
assert meta.extension == ".txt"
|
||||
|
||||
# --- CLI Integration Tests ---
|
||||
|
||||
def test_forensics_inspect_cli_success():
|
||||
"""
|
||||
Verifies that 'forensics inspect' successfully runs via the CLI and prints
|
||||
the metadata in a formatted table.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
test_file = TEST_ENV / "test_file.png"
|
||||
with open(test_file, "wb") as f:
|
||||
f.write(b"\x89PNG\r\n\x1a\n")
|
||||
|
||||
result = runner.invoke(forensics_group, ["inspect", str(test_file)])
|
||||
assert result.exit_code == 0
|
||||
assert "Metadata: test_file.png" in result.output
|
||||
assert "Size" in result.output
|
||||
assert "89504E47" in result.output # Hex representation of PNG signature
|
||||
|
||||
def test_forensics_inspect_cli_missing_file():
|
||||
"""
|
||||
Verifies that the CLI fails gracefully when a non-existent file path is specified.
|
||||
"""
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(forensics_group, ["inspect", "non_existent_file.png"])
|
||||
assert result.exit_code != 0
|
||||
assert "does not exist" in result.output
|
||||
|
||||
Reference in New Issue
Block a user