-
Notifications
You must be signed in to change notification settings - Fork 14
Extending Harbinger Playbook and Output Templating
Related topics: Playbooks and Task Management, File and Output Parsing
Relevant source files
Harbinger's power lies in its ability to automate and structure red team operations through the use of playbooks, job templates, and output parsers. This guide details how to extend Harbinger by creating new playbook and job templates, as well as custom output and file parsers to handle new data formats.
Playbooks in Harbinger are sequences of jobs that can be executed on C2 implants. These are defined using a templating system that allows for reusable and configurable operational workflows.
Job templates are defined using Pydantic schemas, which specify the arguments and structure for a given job. The core Arguments schema in harbinger/src/harbinger/job_templates/schemas.py defines a wide range of possible parameters for C2 jobs.
# harbinger/src/harbinger/job_templates/schemas.py
class Arguments(BaseModel):
command: str = ""
folder: str = ""
path: str = ""
# ... other argumentsSources: harbinger/src/harbinger/job_templates/schemas.py:27-56
When creating a new job template, you will typically define a new schema that inherits from BaseModel and includes the specific arguments required for that job.
Harbinger provides base classes for C2 and proxy jobs in harbinger/src/harbinger/job_templates/c2/base.py and harbinger/src/harbinger/job_templates/proxy/base.py.
The C2Job class is the base for all C2-specific jobs.
# harbinger/src/harbinger/job_templates/c2/base.py
class C2Job(BaseModel):
name: str
description: str
job_type: Literal[schemas.C2JobType.C2Job] = schemas.C2JobType.C2Job
arguments: Arguments | None = None
command: strSources: harbinger/src/harbinger/job_templates/c2/base.py:11-17
To create a new C2 job template, you would create a new class that inherits from C2Job and defines the specific name, description, and command.
New job templates must be registered in the appropriate map to be accessible through the API. For C2 jobs, this is the C2_JOB_BASE_MAP in harbinger/src/harbinger/job_templates/c2/base.py.
# harbinger/src/harbinger/job_templates/c2/base.py
C2_JOB_BASE_MAP = {
"powershell": C2Job(
name="powershell",
description="Run a powershell command",
command="powershell",
arguments=Arguments(powershell=""),
),
# ... other registered jobs
}Sources: harbinger/src/harbinger/job_templates/c2/base.py:19-45
To add a new job, you would add an entry to this dictionary with the job's name as the key and an instance of your new job class as the value.
The Harbinger API exposes endpoints for retrieving job and playbook templates, which are defined in harbinger/src/harbinger/job_templates/router.py.
The /templates/{c2_type}/ endpoint returns a list of available job templates for a given C2 type.
# harbinger/src/harbinger/job_templates/router.py
@router.get(
"/{c2_type}/",
response_model=TemplateList,
tags=["proxy_jobs", "crud"],
)
async def job_templates(
c2_type: schemas.C2Type,
# ...
):
if c2_type == schemas.C2Type.c2:
return dict(templates=[key for key in C2_JOB_BASE_MAP.keys()])
if c2_type == schemas.C2Type.proxy:
return dict(templates=[key for key in PROXY_JOB_BASE_MAP.keys()])Sources: harbinger/src/harbinger/job_templates/router.py:65-72
Once a new template is registered in the appropriate map, it will automatically be available through this endpoint.
Harbinger's parsing framework is designed to be extensible, allowing you to create custom parsers for new types of C2 output and file formats.
Output parsers are designed to analyze the text output from C2 tasks and extract structured information. All output parsers must inherit from the OutputParser abstract base class in harbinger/src/harbinger/worker/output.py.
# harbinger/src/harbinger/worker/output.py
class OutputParser(abc.ABC):
needle: list[str] = []
labels: list[str] = []
@abc.abstractmethod
async def match(self, text: str) -> bool:
raise NotImplementedError
@abc.abstractmethod
async def parse(self, text: str, **kwargs) -> None:
raise NotImplementedErrorSources: harbinger/src/harbinger/worker/output.py:41-55
To create a new output parser:
- Create a new class that inherits from
OutputParser. - Implement the
matchmethod to determine if the parser should process a given text output. You can use theneedleattribute for simple string matching. - Implement the
parsemethod to perform the actual parsing logic, extracting data and saving it to the database.
File parsers are used to process the content of uploaded files. All file parsers must inherit from the BaseFileParser abstract base class in harbinger/src/harbinger/worker/files/parsers.py.
# harbinger/src/harbinger/worker/files/parsers.py
class BaseFileParser(ABC):
@abstractmethod
async def parse(
self,
db: AsyncSession,
graph_db: AsyncNeo4jSession,
tmpdirname: str,
tmpfilename: str,
file: schemas.File,
) -> list[schemas.File]:
return []Sources: harbinger/src/harbinger/worker/files/parsers.py:27-38
To create a new file parser:
- Create a new class that inherits from
BaseFileParser. - Implement the
parsemethod. This method receives the database sessions and information about the file to be parsed. Thebase_parsemethod in the base class can be used to handle downloading the file to a temporary location. - Your
parsemethod should open and read the file, extract the relevant information, and save it to the database.
New parsers need to be registered with the system to be used in workflows. This is typically done by adding the new parser to a list or dictionary that is iterated over when processing new data.
For example, in harbinger/src/harbinger/worker/output.py, you would add your new output parser to the OUTPUT_PARSERS list.
# Example of registering a new output parser
# (This is a conceptual example, the actual implementation may vary)
# in harbinger/src/harbinger/worker/output.py
from .parsers import NewCustomOutputParser
OUTPUT_PARSERS = [
# ... existing parsers
NewCustomOutputParser,
]Extending Harbinger with custom playbook templates and parsers is a powerful way to tailor the framework to your specific operational needs. By following the established patterns of using Pydantic schemas for templates and inheriting from the base parser classes, you can seamlessly integrate new capabilities into the Harbinger system. This modular approach ensures that Harbinger can evolve to support new C2 frameworks, tools, and data formats, making it a versatile platform for red team operations.