cheerleader 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cheerleader/.DS_Store ADDED
Binary file
@@ -0,0 +1,44 @@
1
+ """disasm — binary inspector (Mach-O, ELF)."""
2
+
3
+ import argparse
4
+ import sys
5
+
6
+ from cheerleader.tui.app import APP_TITLE
7
+
8
+
9
+ def main() -> None:
10
+ parser = argparse.ArgumentParser(
11
+ prog=APP_TITLE,
12
+ description="Binary inspector TUI",
13
+ )
14
+ parser.add_argument("binary", help="path to the binary to inspect")
15
+ parser.add_argument(
16
+ "--hex",
17
+ action="store_true",
18
+ default=False,
19
+ help="open in hex editor mode (raw file hex dump only)",
20
+ )
21
+ parser.add_argument(
22
+ "--env",
23
+ default=".env",
24
+ metavar="FILE",
25
+ help="path to .env file for AI agent config (default: .env)",
26
+ )
27
+ args = parser.parse_args()
28
+
29
+ try:
30
+ open(args.binary, "rb").close()
31
+ except OSError as e:
32
+ print(f"Error: {e}")
33
+ sys.exit(1)
34
+
35
+ from cheerleader.formats import detect_format
36
+
37
+ fmt = detect_format(args.binary)
38
+ if fmt == "unknown":
39
+ print("Warning: unrecognised binary format — continuing anyway")
40
+
41
+ from cheerleader.tui import DisasmApp
42
+
43
+ app = DisasmApp(args.binary, env_file=args.env, hex_mode=args.hex)
44
+ app.run()
@@ -0,0 +1,9 @@
1
+ # disassembly agent
2
+
3
+ from .agent import CheerleaderAIAgent
4
+ from .agent_settings import load_environment_variables as load_agent_settings
5
+
6
+ __all__ = [
7
+ "load_agent_settings",
8
+ "CheerleaderAIAgent",
9
+ ]
@@ -0,0 +1,81 @@
1
+ # disassembly agent
2
+
3
+ from typing import Any
4
+ from pathlib import Path
5
+ from deepagents import create_deep_agent
6
+ from langchain.chat_models import init_chat_model, BaseChatModel
7
+ from langchain_core.messages import HumanMessage
8
+ from langgraph.graph.state import CompiledStateGraph
9
+
10
+ # the agent class
11
+ class CheerleaderAIAgent(object):
12
+ def __init__(self, config: dict[str, Any]):
13
+ self.provider: str = config.get("provider")
14
+ self.llm_model: str = config.get("llm_model")
15
+ self.base_url: str = config.get("base_url")
16
+ self.api_key: str = config.get("api_key")
17
+ self.system_prompt: str = config.get("system_prompt")
18
+ self.skills_path: Path = Path(config.get("skills_path"))
19
+
20
+ # at the beginning, connection is empty
21
+ self.llm_connector: BaseChatModel = None
22
+ self.agent: CompiledStateGraph = None
23
+ try:
24
+ self.llm_connector: BaseChatModel = self._create_model_connector()
25
+ self.agent: CompiledStateGraph = self._create_agent()
26
+ except Exception as e:
27
+ raise e from e
28
+
29
+ # create a connection to a custom supported endpoint
30
+ # specify model name and apikey
31
+ def _create_model_connector(self) -> BaseChatModel:
32
+ try:
33
+ return init_chat_model(
34
+ model=self.llm_model,
35
+ model_provider=self.provider,
36
+ api_key=self.api_key if self.api_key != "" else "sk-unprotected",
37
+ base_url=self.base_url,
38
+ )
39
+ except Exception:
40
+ import logging
41
+
42
+ msg = f"Failed to initialize chat model: provider={self.provider}, base_url={self.base_url}, model={self.llm_model}"
43
+
44
+ logging.critical(msg)
45
+ raise Exception(msg)
46
+
47
+ # create reverser agent with langchain deepagent library
48
+ def _create_agent(self) -> CompiledStateGraph:
49
+ try:
50
+ # resolve skills path
51
+ if not self.skills_path.exists():
52
+ raise Exception(f"Skills dir {self.skills_path.absolute()} not found")
53
+
54
+ # check connector
55
+ if self.llm_connector is None:
56
+ raise Exception(f"Connector for model {self.llm_model}, using URL {self.base_url} is not available.")
57
+
58
+ # create a filesystem backend to load skills from
59
+ from deepagents.backends.filesystem import FilesystemBackend
60
+ fs_backend = FilesystemBackend(root_dir=self.skills_path.absolute().parent)
61
+
62
+ # create the agent object
63
+ return create_deep_agent(
64
+ model=self.llm_connector,
65
+ backend=fs_backend,
66
+ system_prompt=self.system_prompt,
67
+ skills=[self.skills_path.name]
68
+ )
69
+ except Exception:
70
+ import logging
71
+ msg = f"Failed to initialize Agent: model={self.llm_model}"
72
+
73
+ logging.critical(msg)
74
+ raise Exception(msg)
75
+
76
+ # main method
77
+ def invoke(self, user_prompt: str):
78
+ payload = {
79
+ "messages": [HumanMessage(content=user_prompt)]
80
+ }
81
+ return self.agent.invoke(payload)
@@ -0,0 +1,46 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Resolved agent configuration: ``.env`` overrides defaults from ``src.config``.
4
+
5
+ Load order: ``python-dotenv`` reads the project ``.env`` into ``os.environ``;
6
+ each setting prefers a non-empty environment value, otherwise the value from
7
+ ``src.config``.
8
+ """
9
+
10
+ import os
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ # import required modules
15
+ try:
16
+ from dotenv import load_dotenv
17
+ from cheerleader.util.utils import _str_setting
18
+ except ImportError:
19
+ raise ImportError("python-dotenv is required to use this module")
20
+
21
+ # load the environment variables
22
+ def load_environment_variables(envfile: str) -> dict[str, Any]:
23
+ e_file = Path(envfile)
24
+ if not e_file.exists():
25
+ raise FileNotFoundError(f"Environment file {e_file} not found")
26
+ load_dotenv(e_file) # load the environment variables
27
+ return _get_settings_from_environment()
28
+
29
+
30
+ # get the settings
31
+ def _get_settings_from_environment():
32
+ settings = {
33
+ "provider": _str_setting("LLM_PROVIDER", "openai"),
34
+ "llm_model": _str_setting("LLM_MODEL", "gpt-4o-mini"),
35
+ "base_url": _str_setting("BASE_URL", "https://api.openai.com/v1"),
36
+ "api_key": _str_setting("API_KEY", "your-api-key-here"),
37
+ "skills_path": _str_setting("SKILLS_PATH", "./skills"),
38
+ "system_prompt": _str_setting("SYSTEM_PROMPT", "you are an expert in reverse engineering"),
39
+ }
40
+ return settings
41
+
42
+
43
+ # export the settings
44
+ __all__ = [
45
+ "load_environment_variables",
46
+ ]
@@ -0,0 +1,45 @@
1
+ """Binary format detection and parser dispatcher."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import struct
6
+
7
+ from cheerleader.libs.types import BinaryInfo
8
+
9
+ _MACHO_MAGICS = frozenset({
10
+ 0xFEEDFACE, 0xCEFAEDFE, # 32-bit LE/BE
11
+ 0xFEEDFACF, 0xCFFAEDFE, # 64-bit LE/BE
12
+ 0xCAFEBABE, 0xBEBAFECA, # fat LE/BE
13
+ })
14
+
15
+
16
+ def detect_format(path: str) -> str:
17
+ """Return "macho", "elf", or "unknown" by reading the first 4 bytes."""
18
+ try:
19
+ with open(path, "rb") as f:
20
+ raw = f.read(4)
21
+ except OSError:
22
+ return "unknown"
23
+ if len(raw) < 4:
24
+ return "unknown"
25
+ if raw[:4] == b'\x7fELF':
26
+ return "elf"
27
+ le_val = struct.unpack_from("<I", raw)[0]
28
+ if le_val in _MACHO_MAGICS:
29
+ return "macho"
30
+ be_val = struct.unpack_from(">I", raw)[0]
31
+ if be_val in _MACHO_MAGICS:
32
+ return "macho"
33
+ return "unknown"
34
+
35
+
36
+ def parse(path: str, **kwargs) -> BinaryInfo:
37
+ """Detect format and dispatch to the appropriate parser."""
38
+ fmt = detect_format(path)
39
+ if fmt == "macho":
40
+ from cheerleader.formats.macho import parse as _macho_parse
41
+ return _macho_parse(path, **kwargs)
42
+ if fmt == "elf":
43
+ from cheerleader.formats.elf import parse as _elf_parse
44
+ return _elf_parse(path, **kwargs)
45
+ return BinaryInfo(path=path, error="Unrecognised file format")
@@ -0,0 +1,11 @@
1
+ """Abstract parser protocol for binary formats."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Protocol
6
+
7
+ from cheerleader.libs.types import BinaryInfo
8
+
9
+
10
+ class FormatParser(Protocol):
11
+ def parse(self, path: str, **kwargs) -> BinaryInfo: ...