diff --git a/.claude/skills/ghidra-cli/SKILL.md b/.claude/skills/ghidra-cli/SKILL.md index 59c3490..fc36b32 100644 --- a/.claude/skills/ghidra-cli/SKILL.md +++ b/.claude/skills/ghidra-cli/SKILL.md @@ -14,7 +14,31 @@ description: > # ghidra-cli -Use ghidra-cli for reverse engineering tasks: binary analysis, decompilation, function inspection, cross-reference analysis, pattern discovery, and binary patching. +A high-performance Rust CLI for automating Ghidra reverse engineering tasks. Designed for both direct usage and AI agent integration. + +## Architecture Overview + +ghidra-cli uses a **daemon-only architecture**: + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ CLI Command │────▶│ Daemon (IPC) │────▶│ GhidraBridge │ +│ ghidra ... │ │ Unix socket │ │ TCP to Ghidra │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ bridge.py │ + │ (Ghidra Script)│ + └─────────────────┘ +``` + +**Key concepts:** +- **Daemon**: Background process managing IPC and Ghidra bridge +- **Bridge**: Python script running inside Ghidra, executing commands +- **Auto-start**: Daemon starts automatically when needed (import, analyze, quick) +- **One daemon per project**: Each project gets its own daemon instance +- **One program per daemon**: Daemon loads a single program for queries ## When to Use @@ -27,154 +51,236 @@ Activate when the user requests: - Binary patching or modification - Ghidra project management -## Workflow +## Quick Start -### Pre-flight Check - -Before running queries, verify the environment: - -```bash -# Check if daemon is running for fast queries -ghidra daemon status --project - -# If not running, start it -ghidra daemon start --project --program -``` - -### Quick Start (New Binary) - -For one-off analysis, use quick mode: +### Fastest Path (Auto-Start) ```bash +# Import and analyze - daemon starts automatically ghidra quick ./binary -ghidra daemon start --project quick-analysis --program binary + +# Daemon is now running, queries are fast +ghidra function list +ghidra decompile main ``` ### Full Project Setup -For sustained analysis: +```bash +# Create project structure +ghidra project create myproject + +# Import binary (auto-starts daemon) +ghidra import ./binary --project myproject --program mybinary + +# Analyze (uses running daemon) +ghidra analyze --project myproject --program mybinary + +# All subsequent queries use daemon +ghidra function list +ghidra decompile main +ghidra find string "password" +``` + +### Manual Daemon Control ```bash -ghidra project create myproject -ghidra import ./binary --project myproject -ghidra analyze --project myproject --program binary -ghidra daemon start --project myproject --program binary +# Start daemon explicitly +ghidra daemon start --project myproject --program mybinary + +# Check status +ghidra daemon status --project myproject + +# Stop daemon +ghidra daemon stop --project myproject + +# Restart with different program +ghidra daemon restart --project myproject --program other_binary ``` ## Command Reference -### Querying Functions +### Project Management + +| Command | Description | +|---------|-------------| +| `ghidra project create ` | Create new project | +| `ghidra project list` | List all projects | +| `ghidra project info ` | Show project details | +| `ghidra project delete ` | Delete project and all programs | + +### Import & Analysis + +| Command | Description | +|---------|-------------| +| `ghidra import --project

` | Import binary (auto-starts daemon) | +| `ghidra analyze --project

--program ` | Run Ghidra analysis | +| `ghidra quick ` | Import + analyze in one step | + +### Function Operations ```bash -# List all functions -ghidra function list --project

--program - -# Filter functions by size or name -ghidra function list --filter "size > 500" +# List functions +ghidra function list +ghidra function list --limit 50 +ghidra function list --filter "size > 100" ghidra function list --filter "name contains 'crypt'" # Get function details ghidra function get main +ghidra function get 0x401000 -# Decompile to pseudocode -ghidra function decompile main +# Decompile to C-like pseudocode +ghidra decompile main +ghidra decompile 0x401000 # Disassemble -ghidra function disasm main +ghidra disasm main +ghidra disasm 0x401000 --count 50 -# Cross-references -ghidra function xrefs main -ghidra function calls main +# Rename function +ghidra function rename sub_401000 decrypt_key ``` ### Search Operations ```bash -# Find functions by pattern +# Find functions by pattern (glob) ghidra find function "*crypt*" +ghidra find function "str*" # Find strings ghidra find string "password" +ghidra find string "error" --case-insensitive -# Find byte patterns (hex) +# Find byte patterns (hex, spaces optional) ghidra find bytes "4883ec08" +ghidra find bytes "48 83 ec 08" -# Find crypto constants +# Find function calls +ghidra find calls malloc + +# Find crypto constants (AES, DES, RSA, etc.) ghidra find crypto -# Find suspicious patterns (anti-analysis, obfuscation) +# Find suspicious patterns (anti-debug, obfuscation, etc.) ghidra find interesting ``` ### Cross-References ```bash -# References TO an address +# References TO an address (who calls/reads this) ghidra x-ref to 0x401000 +ghidra x-ref to main -# References FROM an address +# References FROM an address (what this calls/reads) ghidra x-ref from 0x401000 +ghidra x-ref from main ``` ### Call Graphs ```bash -# Full call graph -ghidra graph calls +# Full call graph from function +ghidra graph calls main -# Who calls this function (callers) +# Callers only (who calls this function) ghidra graph callers main --depth 3 -# What does this function call (callees) +# Callees only (what this function calls) ghidra graph callees main --depth 3 -# Export as DOT format -ghidra graph export dot +# Export to DOT format for visualization +ghidra graph export dot --output callgraph.dot ``` -### Symbols and Strings +### Symbols ```bash -# List symbols +# List all symbols ghidra symbol list +ghidra symbol list --limit 100 -# List strings -ghidra strings list --limit 100 +# Get symbol at address +ghidra symbol get 0x401000 -# References to a string -ghidra strings refs "error" +# Create new symbol +ghidra symbol create my_func 0x401000 + +# Rename symbol +ghidra symbol rename old_name new_name + +# Delete symbol +ghidra symbol delete my_func ``` -### Memory and Types +### Strings ```bash -# Memory map -ghidra memory map +# List strings +ghidra strings list +ghidra strings list --limit 100 +ghidra strings list --filter "length > 20" -# Read memory at address -ghidra memory read 0x401000 64 +# Find string references +ghidra strings refs "error message" +``` +### Data Types + +```bash # List data types ghidra type list +ghidra type list --filter "name contains 'struct'" + +# Get type details +ghidra type get "MyStruct" + +# Create type +ghidra type create "typedef int HANDLE" # Apply type to address ghidra type apply 0x402000 "char[32]" ``` -### Modifications +### Memory ```bash -# Rename function -ghidra function rename sub_401000 decrypt_password +# Show memory map +ghidra memory map -# Add comment -ghidra comment set 0x401000 "Key derivation starts here" +# Read bytes at address +ghidra memory read 0x401000 64 -# Patch bytes +# Dump section +ghidra dump section .text +``` + +### Comments + +```bash +# Get comment at address +ghidra comment get 0x401000 + +# Set comment +ghidra comment set 0x401000 "Entry point for decryption" + +# List all comments +ghidra comment list + +# Delete comment +ghidra comment delete 0x401000 +``` + +### Binary Patching + +```bash +# Patch bytes at address ghidra patch bytes 0x401000 "90909090" -# NOP instructions -ghidra patch nop 0x401010 --count 5 +# NOP out instructions +ghidra patch nop 0x401000 --count 5 # Export patched binary ghidra patch export --output patched.bin @@ -183,77 +289,249 @@ ghidra patch export --output patched.bin ### Scripting ```bash +# List available scripts +ghidra script list + # Run Python script ghidra script run analysis.py -# Inline Python +# Run with arguments +ghidra script run myscript.py --args "arg1 arg2" + +# Inline Python (access currentProgram, state, etc.) ghidra script python "print(currentProgram.getName())" -# Batch commands from file -ghidra batch commands.txt +# Inline Java +ghidra script java "println(currentProgram.getName());" ``` -## Output Handling - -ghidra-cli outputs JSON by default. Parse the structured data: +### Batch Operations ```bash -# JSON output (default) +# Run commands from file +ghidra batch commands.txt + +# Commands file format (one per line): +# function list +# decompile main +# find string "password" +``` + +### Statistics + +```bash +# Program statistics +ghidra stats + +# Program summary +ghidra summary +``` + +### Daemon Management + +```bash +# Start daemon for project +ghidra daemon start --project myproject --program mybinary + +# Start in foreground (for debugging) +ghidra daemon start --project myproject --program mybinary --foreground + +# Check if daemon is running +ghidra daemon status --project myproject + +# Ping daemon (health check) +ghidra daemon ping --project myproject + +# Clear result cache +ghidra daemon clear-cache --project myproject + +# Stop daemon +ghidra daemon stop --project myproject + +# Restart with new program +ghidra daemon restart --project myproject --program newbinary +``` + +## Output Formats + +```bash +# Human-readable (default for terminal) ghidra function list -# Table format for display -ghidra function list --format table +# JSON output +ghidra function list --json + +# Pretty JSON +ghidra function list --pretty + +# Select specific fields +ghidra function list --fields "name,address,size" # Count only ghidra function list --format count ``` -When processing results, extract relevant fields from JSON rather than displaying raw output. +## Filtering -## Common Patterns - -### Investigate a Function +Use expressions to filter results: ```bash -ghidra function get # Overview -ghidra function decompile # Pseudocode -ghidra function calls # What it calls -ghidra function xrefs # Who calls it -ghidra graph callers --depth 2 +# Numeric comparisons +ghidra function list --filter "size > 100" +ghidra function list --filter "size >= 50" +ghidra function list --filter "size < 1000" + +# String matching +ghidra function list --filter "name contains 'crypt'" +ghidra function list --filter "name starts_with 'sub_'" +ghidra function list --filter "name ends_with '_init'" + +# Combine with limit +ghidra function list --filter "size > 100" --limit 20 ``` -### Find Interesting Code +## Common Analysis Patterns + +### Investigate a Suspicious Function ```bash -ghidra find crypto # Crypto constants -ghidra find interesting # Suspicious patterns -ghidra find function "*alloc*" # Memory functions -ghidra strings list --filter "length > 50" +# Get overview +ghidra function get suspicious_func + +# See the code +ghidra decompile suspicious_func + +# What does it call? +ghidra graph callees suspicious_func --depth 2 + +# Who calls it? +ghidra graph callers suspicious_func --depth 3 + +# Check cross-references +ghidra x-ref to suspicious_func +``` + +### Find Crypto or Sensitive Code + +```bash +# Find crypto constants +ghidra find crypto + +# Find password-related strings +ghidra find string "password" +ghidra find string "key" +ghidra find string "secret" + +# Find crypto function names +ghidra find function "*crypt*" +ghidra find function "*aes*" +ghidra find function "*sha*" ``` ### Trace Data Flow ```bash -ghidra x-ref to

# Who writes here -ghidra x-ref from
# What this references -ghidra graph callees --depth 3 +# Find where data is written +ghidra x-ref to 0x404000 + +# Find where data is read +ghidra x-ref from 0x404000 + +# Trace through call graph +ghidra graph callees source_func --depth 5 +``` + +### Analyze Anti-Analysis Techniques + +```bash +# Find interesting/suspicious patterns +ghidra find interesting + +# Look for timing checks, debugger detection +ghidra find string "IsDebuggerPresent" +ghidra find function "*debug*" + +# Find self-modifying code indicators +ghidra find bytes "e8 00 00 00 00" # call $+5 pattern +``` + +### Patch and Export + +```bash +# Identify patch location +ghidra disasm 0x401000 --count 10 + +# Apply patch +ghidra patch nop 0x401000 --count 2 + +# Verify +ghidra disasm 0x401000 --count 10 + +# Export +ghidra patch export --output patched.exe ``` ## Error Recovery -| Situation | Resolution | -| ------------------ | ------------------------------------------------------------- | -| Daemon not running | `ghidra daemon start --project

--program ` | -| No project exists | `ghidra project create ` or use `ghidra quick ` | -| Function not found | Use `ghidra find function "*pattern*"` to search | -| Address format | Use hex with 0x prefix: `0x401000` | -| Slow queries | Start daemon for sub-second response times | +| Situation | Resolution | +|-----------|------------| +| Daemon not running | Commands auto-start daemon; or `ghidra daemon start --project

--program ` | +| No project exists | `ghidra project create ` or use `ghidra quick ` | +| Function not found | Use `ghidra find function "*pattern*"` to search | +| Address format | Use hex with 0x prefix: `0x401000` | +| Slow queries | Daemon should be running; check with `ghidra daemon status` | +| Wrong program loaded | `ghidra daemon restart --project

--program ` | +| Daemon crashed | `ghidra daemon start --project

--program ` | ## Global Options All commands accept: -- `--project ` - Target project -- `--program ` - Target program within project -- `--format json|table|count` - Output format -- `--filter ` - Filter expression -- `--limit ` - Max results + +| Option | Description | +|--------|-------------| +| `--project ` | Target project (auto-detected if daemon running) | +| `--program ` | Target program within project | +| `--json` | JSON output | +| `--pretty` | Pretty-printed JSON output | +| `--filter ` | Filter expression | +| `--limit ` | Maximum results to return | +| `--fields ` | Comma-separated fields to include | + +## Environment Variables + +| Variable | Description | +|----------|-------------| +| `GHIDRA_INSTALL_DIR` | Path to Ghidra installation | +| `GHIDRA_PROJECT_DIR` | Default project directory | + +## Troubleshooting + +### Check Installation + +```bash +ghidra doctor +``` + +### View Daemon Logs + +```bash +# Logs are at ~/.local/share/ghidra-cli/daemon.log +tail -f ~/.local/share/ghidra-cli/daemon.log +``` + +### Debug Mode + +```bash +# Run daemon in foreground to see output +ghidra daemon start --project myproject --program mybinary --foreground +``` + +### Reset State + +```bash +# Stop all daemons +ghidra daemon stop --project myproject + +# Remove lock files if needed +rm ~/.local/share/ghidra-cli/daemon-*.lock +``` diff --git a/AGENTS.md b/AGENTS.md index ac5f710..4341812 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,3 +1,49 @@ -NEVER SKIP TESTS! IF GHIDRA IS NOT INSTALLED, THE TESTS MUST FAIL. +# Agent Instructions -DEFAULT OUTPUT FORMAT SHOULD BE HUMAN AND AGENT READABLE, NOT JSON (--json and --pretty for json/pretty json) \ No newline at end of file +## Critical Rules + +1. **NEVER SKIP TESTS!** If Ghidra is not installed, the tests MUST fail. +2. **DEFAULT OUTPUT FORMAT** should be human and agent readable, NOT JSON. Use `--json` and `--pretty` for JSON output. + +## Architecture + +ghidra-cli uses a **daemon-only architecture**: +- All commands route through a daemon process +- Daemon manages a persistent Ghidra bridge connection +- Import/Analyze/Quick commands auto-start the daemon +- One daemon per project, one program per daemon + +## Key Patterns + +### Starting Analysis +```bash +# Quickest path - auto-starts daemon +ghidra quick ./binary + +# Or explicit steps (daemon auto-starts on import) +ghidra import ./binary --project myproj --program prog +ghidra analyze --project myproj --program prog +``` + +### Daemon is Always Running +After import/analyze/quick, the daemon is running. All query commands use it automatically: +```bash +ghidra function list # Uses daemon +ghidra decompile main # Uses daemon +ghidra find crypto # Uses daemon +``` + +### Manual Daemon Control +```bash +ghidra daemon status # Check if running +ghidra daemon stop # Stop daemon +ghidra daemon restart --project p --program new_prog # Switch program +``` + +## Code Organization + +- `src/main.rs` - CLI entry point, command routing +- `src/daemon/` - Daemon process, IPC server, command handlers +- `src/ghidra/bridge.rs` - Ghidra bridge connection management +- `src/ghidra/scripts/bridge.py` - Python script running inside Ghidra +- `src/ipc/` - IPC protocol and client diff --git a/README.md b/README.md index cf5a755..1938ab6 100644 --- a/README.md +++ b/README.md @@ -4,16 +4,38 @@ A high-performance Rust CLI for automating Ghidra reverse engineering tasks, des ## Features -- **Fast daemon mode** - Keeps Ghidra loaded in memory for sub-second response times +- **Daemon-only architecture** - All operations route through a persistent daemon for consistency +- **Auto-start daemon** - Import/analyze commands automatically start the daemon +- **Fast queries** - Sub-second response times with Ghidra kept in memory - **Comprehensive analysis** - Functions, symbols, types, strings, cross-references - **Binary patching** - Modify bytes, NOP instructions, export patches - **Call graphs** - Generate caller/callee graphs, export to DOT format - **Search capabilities** - Find strings, bytes, functions, crypto patterns - **Script execution** - Run Python/Java scripts, inline or from files - **Batch operations** - Execute multiple commands from a file -- **Flexible output** - JSON, table, or count formats with field selection +- **Flexible output** - Human-readable, JSON, or pretty JSON formats - **Filtering** - Powerful expression-based filtering (e.g., `size > 100`) +## Architecture + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ CLI Command │────▶│ Daemon (IPC) │────▶│ GhidraBridge │ +│ ghidra ... │ │ Unix socket │ │ TCP to Ghidra │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ bridge.py │ + │ (Ghidra Script)│ + └─────────────────┘ +``` + +All commands go through the daemon, which maintains a persistent connection to Ghidra via the bridge script. This provides: +- **Consistent state** - Single Ghidra process for all operations +- **Fast queries** - No JVM startup overhead per command +- **Auto-start** - Daemon starts automatically when needed + ## Installation ### From Source @@ -43,14 +65,14 @@ ghidra config set ghidra_path /path/to/ghidra # Check installation ghidra doctor -# Import and analyze a binary +# Import and analyze a binary (daemon auto-starts) +ghidra quick ./binary + +# Or step by step: ghidra import ./binary --project myproject --program mybinary ghidra analyze --project myproject --program mybinary -# Start the daemon for fast repeated queries -ghidra daemon start --project myproject --program mybinary - -# List functions +# Query functions (uses running daemon) ghidra function list # Decompile a function @@ -73,8 +95,9 @@ ghidra graph calls main --depth 3 ghidra project create # Create project ghidra project list # List projects ghidra project delete # Delete project -ghidra import --project

# Import binary +ghidra import --project

# Import binary (auto-starts daemon) ghidra analyze --project

# Run analysis +ghidra quick # Import + analyze in one step ``` ### Function Analysis @@ -149,36 +172,39 @@ ghidra stats # Program statistics ghidra summary # Program summary ``` -## Daemon Mode +## Daemon Management -The daemon keeps Ghidra loaded in memory for fast queries: +The daemon keeps Ghidra loaded in memory. It starts automatically when needed, but you can also control it manually: ```bash # Start daemon with a program loaded ghidra daemon start --project myproject --program mybinary -# All subsequent commands use the daemon automatically -ghidra function list # Fast! -ghidra decompile main # Fast! - # Check daemon status ghidra daemon status +# All commands use the daemon automatically +ghidra function list # Fast! +ghidra decompile main # Fast! + # Stop daemon ghidra daemon stop + +# Restart with different program +ghidra daemon restart --project myproject --program otherbinary ``` ## Output Formats ```bash -# JSON output (default) -ghidra function list --format json +# Human-readable (default) +ghidra function list -# Table format -ghidra function list --format table +# JSON output +ghidra function list --json -# Count only -ghidra function list --format count +# Pretty JSON +ghidra function list --pretty # Select specific fields ghidra function list --fields "name,address,size" @@ -196,15 +222,15 @@ ghidra strings list --filter "length > 20" ## AI Agent Integration -Ghidra CLI is designed to work seamlessly with AI coding assistants like Claude Code. The structured JSON output and comprehensive command set make it ideal for automated reverse engineering workflows. +Ghidra CLI is designed to work seamlessly with AI coding assistants like Claude Code. The structured output and comprehensive command set make it ideal for automated reverse engineering workflows. Example workflow with an AI agent: -1. `ghidra import suspicious.exe --project analysis --program suspicious` -2. `ghidra analyze --project analysis --program suspicious` -3. `ghidra daemon start --project analysis --program suspicious` -4. `ghidra find interesting` - AI analyzes suspicious patterns -5. `ghidra decompile ` - AI examines specific functions -6. `ghidra x-ref to ` - AI traces data flow +1. `ghidra quick suspicious.exe` - Import, analyze, start daemon +2. `ghidra find interesting` - AI analyzes suspicious patterns +3. `ghidra decompile ` - AI examines specific functions +4. `ghidra x-ref to ` - AI traces data flow +5. `ghidra patch nop ` - AI patches anti-debug code +6. `ghidra patch export` - Export patched binary ## Contributing diff --git a/SKILL.md b/SKILL.md index 37e37c2..fc36b32 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,5 +1,5 @@ --- -name: ghidra-cli-skill +name: ghidra-cli description: > Use ghidra-cli for reverse engineering tasks: binary analysis, decompilation, function inspection, cross-reference analysis, pattern discovery, and binary patching. Activate when the user requests: @@ -14,7 +14,31 @@ description: > # ghidra-cli -Use ghidra-cli for reverse engineering tasks: binary analysis, decompilation, function inspection, cross-reference analysis, pattern discovery, and binary patching. +A high-performance Rust CLI for automating Ghidra reverse engineering tasks. Designed for both direct usage and AI agent integration. + +## Architecture Overview + +ghidra-cli uses a **daemon-only architecture**: + +``` +┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐ +│ CLI Command │────▶│ Daemon (IPC) │────▶│ GhidraBridge │ +│ ghidra ... │ │ Unix socket │ │ TCP to Ghidra │ +└─────────────────┘ └──────────────────┘ └─────────────────┘ + │ + ▼ + ┌─────────────────┐ + │ bridge.py │ + │ (Ghidra Script)│ + └─────────────────┘ +``` + +**Key concepts:** +- **Daemon**: Background process managing IPC and Ghidra bridge +- **Bridge**: Python script running inside Ghidra, executing commands +- **Auto-start**: Daemon starts automatically when needed (import, analyze, quick) +- **One daemon per project**: Each project gets its own daemon instance +- **One program per daemon**: Daemon loads a single program for queries ## When to Use @@ -27,154 +51,236 @@ Activate when the user requests: - Binary patching or modification - Ghidra project management -## Workflow +## Quick Start -### Pre-flight Check - -Before running queries, verify the environment: - -```bash -# Check if daemon is running for fast queries -ghidra daemon status --project - -# If not running, start it -ghidra daemon start --project --program -``` - -### Quick Start (New Binary) - -For one-off analysis, use quick mode: +### Fastest Path (Auto-Start) ```bash +# Import and analyze - daemon starts automatically ghidra quick ./binary -ghidra daemon start --project quick-analysis --program binary + +# Daemon is now running, queries are fast +ghidra function list +ghidra decompile main ``` ### Full Project Setup -For sustained analysis: +```bash +# Create project structure +ghidra project create myproject + +# Import binary (auto-starts daemon) +ghidra import ./binary --project myproject --program mybinary + +# Analyze (uses running daemon) +ghidra analyze --project myproject --program mybinary + +# All subsequent queries use daemon +ghidra function list +ghidra decompile main +ghidra find string "password" +``` + +### Manual Daemon Control ```bash -ghidra project create myproject -ghidra import ./binary --project myproject -ghidra analyze --project myproject --program binary -ghidra daemon start --project myproject --program binary +# Start daemon explicitly +ghidra daemon start --project myproject --program mybinary + +# Check status +ghidra daemon status --project myproject + +# Stop daemon +ghidra daemon stop --project myproject + +# Restart with different program +ghidra daemon restart --project myproject --program other_binary ``` ## Command Reference -### Querying Functions +### Project Management + +| Command | Description | +|---------|-------------| +| `ghidra project create ` | Create new project | +| `ghidra project list` | List all projects | +| `ghidra project info ` | Show project details | +| `ghidra project delete ` | Delete project and all programs | + +### Import & Analysis + +| Command | Description | +|---------|-------------| +| `ghidra import --project

` | Import binary (auto-starts daemon) | +| `ghidra analyze --project

--program ` | Run Ghidra analysis | +| `ghidra quick ` | Import + analyze in one step | + +### Function Operations ```bash -# List all functions -ghidra function list --project

--program - -# Filter functions by size or name -ghidra function list --filter "size > 500" +# List functions +ghidra function list +ghidra function list --limit 50 +ghidra function list --filter "size > 100" ghidra function list --filter "name contains 'crypt'" # Get function details ghidra function get main +ghidra function get 0x401000 -# Decompile to pseudocode -ghidra function decompile main +# Decompile to C-like pseudocode +ghidra decompile main +ghidra decompile 0x401000 # Disassemble -ghidra function disasm main +ghidra disasm main +ghidra disasm 0x401000 --count 50 -# Cross-references -ghidra function xrefs main -ghidra function calls main +# Rename function +ghidra function rename sub_401000 decrypt_key ``` ### Search Operations ```bash -# Find functions by pattern +# Find functions by pattern (glob) ghidra find function "*crypt*" +ghidra find function "str*" # Find strings ghidra find string "password" +ghidra find string "error" --case-insensitive -# Find byte patterns (hex) +# Find byte patterns (hex, spaces optional) ghidra find bytes "4883ec08" +ghidra find bytes "48 83 ec 08" -# Find crypto constants +# Find function calls +ghidra find calls malloc + +# Find crypto constants (AES, DES, RSA, etc.) ghidra find crypto -# Find suspicious patterns (anti-analysis, obfuscation) +# Find suspicious patterns (anti-debug, obfuscation, etc.) ghidra find interesting ``` ### Cross-References ```bash -# References TO an address +# References TO an address (who calls/reads this) ghidra x-ref to 0x401000 +ghidra x-ref to main -# References FROM an address +# References FROM an address (what this calls/reads) ghidra x-ref from 0x401000 +ghidra x-ref from main ``` ### Call Graphs ```bash -# Full call graph -ghidra graph calls +# Full call graph from function +ghidra graph calls main -# Who calls this function (callers) +# Callers only (who calls this function) ghidra graph callers main --depth 3 -# What does this function call (callees) +# Callees only (what this function calls) ghidra graph callees main --depth 3 -# Export as DOT format -ghidra graph export dot +# Export to DOT format for visualization +ghidra graph export dot --output callgraph.dot ``` -### Symbols and Strings +### Symbols ```bash -# List symbols +# List all symbols ghidra symbol list +ghidra symbol list --limit 100 -# List strings -ghidra strings list --limit 100 +# Get symbol at address +ghidra symbol get 0x401000 -# References to a string -ghidra strings refs "error" +# Create new symbol +ghidra symbol create my_func 0x401000 + +# Rename symbol +ghidra symbol rename old_name new_name + +# Delete symbol +ghidra symbol delete my_func ``` -### Memory and Types +### Strings ```bash -# Memory map -ghidra memory map +# List strings +ghidra strings list +ghidra strings list --limit 100 +ghidra strings list --filter "length > 20" -# Read memory at address -ghidra memory read 0x401000 64 +# Find string references +ghidra strings refs "error message" +``` +### Data Types + +```bash # List data types ghidra type list +ghidra type list --filter "name contains 'struct'" + +# Get type details +ghidra type get "MyStruct" + +# Create type +ghidra type create "typedef int HANDLE" # Apply type to address ghidra type apply 0x402000 "char[32]" ``` -### Modifications +### Memory ```bash -# Rename function -ghidra function rename sub_401000 decrypt_password +# Show memory map +ghidra memory map -# Add comment -ghidra comment set 0x401000 "Key derivation starts here" +# Read bytes at address +ghidra memory read 0x401000 64 -# Patch bytes +# Dump section +ghidra dump section .text +``` + +### Comments + +```bash +# Get comment at address +ghidra comment get 0x401000 + +# Set comment +ghidra comment set 0x401000 "Entry point for decryption" + +# List all comments +ghidra comment list + +# Delete comment +ghidra comment delete 0x401000 +``` + +### Binary Patching + +```bash +# Patch bytes at address ghidra patch bytes 0x401000 "90909090" -# NOP instructions -ghidra patch nop 0x401010 --count 5 +# NOP out instructions +ghidra patch nop 0x401000 --count 5 # Export patched binary ghidra patch export --output patched.bin @@ -183,77 +289,249 @@ ghidra patch export --output patched.bin ### Scripting ```bash +# List available scripts +ghidra script list + # Run Python script ghidra script run analysis.py -# Inline Python +# Run with arguments +ghidra script run myscript.py --args "arg1 arg2" + +# Inline Python (access currentProgram, state, etc.) ghidra script python "print(currentProgram.getName())" -# Batch commands from file -ghidra batch commands.txt +# Inline Java +ghidra script java "println(currentProgram.getName());" ``` -## Output Handling - -ghidra-cli outputs JSON by default. Parse the structured data: +### Batch Operations ```bash -# JSON output (default) +# Run commands from file +ghidra batch commands.txt + +# Commands file format (one per line): +# function list +# decompile main +# find string "password" +``` + +### Statistics + +```bash +# Program statistics +ghidra stats + +# Program summary +ghidra summary +``` + +### Daemon Management + +```bash +# Start daemon for project +ghidra daemon start --project myproject --program mybinary + +# Start in foreground (for debugging) +ghidra daemon start --project myproject --program mybinary --foreground + +# Check if daemon is running +ghidra daemon status --project myproject + +# Ping daemon (health check) +ghidra daemon ping --project myproject + +# Clear result cache +ghidra daemon clear-cache --project myproject + +# Stop daemon +ghidra daemon stop --project myproject + +# Restart with new program +ghidra daemon restart --project myproject --program newbinary +``` + +## Output Formats + +```bash +# Human-readable (default for terminal) ghidra function list -# Table format for display -ghidra function list --format table +# JSON output +ghidra function list --json + +# Pretty JSON +ghidra function list --pretty + +# Select specific fields +ghidra function list --fields "name,address,size" # Count only ghidra function list --format count ``` -When processing results, extract relevant fields from JSON rather than displaying raw output. +## Filtering -## Common Patterns - -### Investigate a Function +Use expressions to filter results: ```bash -ghidra function get # Overview -ghidra function decompile # Pseudocode -ghidra function calls # What it calls -ghidra function xrefs # Who calls it -ghidra graph callers --depth 2 +# Numeric comparisons +ghidra function list --filter "size > 100" +ghidra function list --filter "size >= 50" +ghidra function list --filter "size < 1000" + +# String matching +ghidra function list --filter "name contains 'crypt'" +ghidra function list --filter "name starts_with 'sub_'" +ghidra function list --filter "name ends_with '_init'" + +# Combine with limit +ghidra function list --filter "size > 100" --limit 20 ``` -### Find Interesting Code +## Common Analysis Patterns + +### Investigate a Suspicious Function ```bash -ghidra find crypto # Crypto constants -ghidra find interesting # Suspicious patterns -ghidra find function "*alloc*" # Memory functions -ghidra strings list --filter "length > 50" +# Get overview +ghidra function get suspicious_func + +# See the code +ghidra decompile suspicious_func + +# What does it call? +ghidra graph callees suspicious_func --depth 2 + +# Who calls it? +ghidra graph callers suspicious_func --depth 3 + +# Check cross-references +ghidra x-ref to suspicious_func +``` + +### Find Crypto or Sensitive Code + +```bash +# Find crypto constants +ghidra find crypto + +# Find password-related strings +ghidra find string "password" +ghidra find string "key" +ghidra find string "secret" + +# Find crypto function names +ghidra find function "*crypt*" +ghidra find function "*aes*" +ghidra find function "*sha*" ``` ### Trace Data Flow ```bash -ghidra x-ref to

# Who writes here -ghidra x-ref from
# What this references -ghidra graph callees --depth 3 +# Find where data is written +ghidra x-ref to 0x404000 + +# Find where data is read +ghidra x-ref from 0x404000 + +# Trace through call graph +ghidra graph callees source_func --depth 5 +``` + +### Analyze Anti-Analysis Techniques + +```bash +# Find interesting/suspicious patterns +ghidra find interesting + +# Look for timing checks, debugger detection +ghidra find string "IsDebuggerPresent" +ghidra find function "*debug*" + +# Find self-modifying code indicators +ghidra find bytes "e8 00 00 00 00" # call $+5 pattern +``` + +### Patch and Export + +```bash +# Identify patch location +ghidra disasm 0x401000 --count 10 + +# Apply patch +ghidra patch nop 0x401000 --count 2 + +# Verify +ghidra disasm 0x401000 --count 10 + +# Export +ghidra patch export --output patched.exe ``` ## Error Recovery -| Situation | Resolution | -| ------------------ | ------------------------------------------------------------- | -| Daemon not running | `ghidra daemon start --project

--program ` | -| No project exists | `ghidra project create ` or use `ghidra quick ` | -| Function not found | Use `ghidra find function "*pattern*"` to search | -| Address format | Use hex with 0x prefix: `0x401000` | -| Slow queries | Start daemon for sub-second response times | +| Situation | Resolution | +|-----------|------------| +| Daemon not running | Commands auto-start daemon; or `ghidra daemon start --project

--program ` | +| No project exists | `ghidra project create ` or use `ghidra quick ` | +| Function not found | Use `ghidra find function "*pattern*"` to search | +| Address format | Use hex with 0x prefix: `0x401000` | +| Slow queries | Daemon should be running; check with `ghidra daemon status` | +| Wrong program loaded | `ghidra daemon restart --project

--program ` | +| Daemon crashed | `ghidra daemon start --project

--program ` | ## Global Options All commands accept: -- `--project ` - Target project -- `--program ` - Target program within project -- `--format json|table|count` - Output format -- `--filter ` - Filter expression -- `--limit ` - Max results + +| Option | Description | +|--------|-------------| +| `--project ` | Target project (auto-detected if daemon running) | +| `--program ` | Target program within project | +| `--json` | JSON output | +| `--pretty` | Pretty-printed JSON output | +| `--filter ` | Filter expression | +| `--limit ` | Maximum results to return | +| `--fields ` | Comma-separated fields to include | + +## Environment Variables + +| Variable | Description | +|----------|-------------| +| `GHIDRA_INSTALL_DIR` | Path to Ghidra installation | +| `GHIDRA_PROJECT_DIR` | Default project directory | + +## Troubleshooting + +### Check Installation + +```bash +ghidra doctor +``` + +### View Daemon Logs + +```bash +# Logs are at ~/.local/share/ghidra-cli/daemon.log +tail -f ~/.local/share/ghidra-cli/daemon.log +``` + +### Debug Mode + +```bash +# Run daemon in foreground to see output +ghidra daemon start --project myproject --program mybinary --foreground +``` + +### Reset State + +```bash +# Stop all daemons +ghidra daemon stop --project myproject + +# Remove lock files if needed +rm ~/.local/share/ghidra-cli/daemon-*.lock +``` diff --git a/src/daemon/README.md b/src/daemon/README.md new file mode 100644 index 0000000..c166c24 --- /dev/null +++ b/src/daemon/README.md @@ -0,0 +1,88 @@ +# Daemon Module + +The daemon is the central execution authority for ghidra-cli. All commands route through the daemon, which maintains a persistent connection to Ghidra via the bridge. + +## Architecture + +``` +┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ +│ CLI Client │────▶│ IPC Server │────▶│ Handler │────▶│ GhidraBridge│ +│ (DaemonCli) │ │ (Unix sock) │ │ (Routing) │ │ (TCP→Ghidra)│ +└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ + │ + ▼ + ┌─────────────┐ + │ bridge.py │ + │ (In Ghidra) │ + └─────────────┘ +``` + +## Key Components + +| File | Purpose | +|------|---------| +| `mod.rs` | Daemon main loop, startup, shutdown | +| `ipc_server.rs` | Unix socket server, accepts client connections | +| `handler.rs` | Routes IPC commands to bridge or specialized handlers | +| `process.rs` | Daemon lifecycle, lock files, process management | +| `queue.rs` | Command queue execution | +| `cache.rs` | Result caching | +| `state.rs` | Daemon state management | +| `handlers/` | Specialized command handlers | + +## Command Flow + +1. **CLI sends command** via IPC (Unix socket) +2. **IPC server** receives request, parses JSON +3. **Handler** routes to appropriate processor: + - Direct bridge commands (decompile, function list, etc.) + - Import/Analyze commands (via bridge.py handlers) + - ExecuteCli for generic CLI command forwarding +4. **Bridge** sends to Ghidra via TCP, receives response +5. **Response** flows back through IPC to CLI + +## Auto-Start Behavior + +Import, Analyze, and Quick commands auto-start the daemon: + +1. CLI checks if daemon is running for project +2. If not, starts daemon in background (`daemonize_unix` / `daemonize_windows`) +3. Waits briefly for daemon to initialize +4. Connects and sends command + +## Lifecycle + +- **One daemon per project** - Lock file prevents duplicates +- **One program per daemon** - Daemon loads a single program +- **Graceful shutdown** - Handles SIGTERM, SIGINT, IPC shutdown command +- **Lock files** - Located at `~/.local/share/ghidra-cli/daemon-{hash}.lock` +- **Logs** - Located at `~/.local/share/ghidra-cli/daemon.log` + +## Handlers + +Specialized handlers in `handlers/` directory: + +| Handler | Commands | +|---------|----------| +| `program.rs` | Program info, memory, imports, exports | +| `symbols.rs` | Symbol operations | +| `types.rs` | Data type operations | +| `comments.rs` | Comment operations | +| `graph.rs` | Call graph operations | +| `find.rs` | Search operations | +| `diff.rs` | Program diff operations | +| `patch.rs` | Binary patching | +| `script.rs` | Script execution | +| `disasm.rs` | Disassembly | +| `stats.rs` | Statistics | +| `batch.rs` | Batch command execution | + +## Bridge Commands + +Commands sent to bridge.py in Ghidra: + +- `import` - Import binary using AutoImporter +- `analyze` - Trigger analysis using AutoAnalysisManager +- `list_functions`, `decompile`, `list_strings`, etc. + +See `src/ghidra/scripts/bridge.py` for the full command reference. diff --git a/src/daemon/handler.rs b/src/daemon/handler.rs index 21fd4af..88f00db 100644 --- a/src/daemon/handler.rs +++ b/src/daemon/handler.rs @@ -97,6 +97,21 @@ async fn handle_command_inner( }))).await } + Command::Import { binary_path, project, program } => { + execute_bridge_command(bridge, "import", Some(json!({ + "binary_path": binary_path, + "project": project, + "program": program, + }))).await + } + + Command::Analyze { project, program } => { + execute_bridge_command(bridge, "analyze", Some(json!({ + "project": project, + "program": program, + }))).await + } + Command::ExecuteCli { command_json } => { // Deserialize and execute CLI command through the queue handlers let cli_command: crate::cli::Commands = serde_json::from_str(&command_json) diff --git a/src/ghidra/mod.rs b/src/ghidra/mod.rs index db71547..f81502b 100644 --- a/src/ghidra/mod.rs +++ b/src/ghidra/mod.rs @@ -6,7 +6,6 @@ pub mod scripts; pub mod setup; use std::path::{Path, PathBuf}; -use std::process::Command; use crate::config::Config; use crate::error::{GhidraError, Result}; @@ -87,86 +86,6 @@ impl GhidraClient { Ok(()) } - pub fn import_binary(&self, project_name: &str, binary_path: &Path, program_name: Option<&str>) -> Result { - if !self.project_exists(project_name) { - self.create_project(project_name)?; - } - - let program_name = program_name.unwrap_or_else(|| { - binary_path.file_name() - .and_then(|n| n.to_str()) - .unwrap_or("program") - }); - - let project_path = self.get_project_path(project_name); - let headless = self.get_headless_script(); - - let output = Command::new(&headless) - .arg(project_path.to_str().unwrap()) - .arg(project_name) - .arg("-import") - .arg(binary_path.to_str().unwrap()) - .arg("-overwrite") - .output()?; - - if !output.status.success() { - return Err(GhidraError::ExecutionFailed( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - - Ok(program_name.to_string()) - } - - pub fn analyze_program(&self, project_name: &str, program_name: &str) -> Result<()> { - let project_path = self.get_project_path(project_name); - let headless = self.get_headless_script(); - - let output = Command::new(&headless) - .arg(project_path.to_str().unwrap()) - .arg(project_name) - .arg("-process") - .arg(program_name) - .output()?; - - if !output.status.success() { - return Err(GhidraError::ExecutionFailed( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - - Ok(()) - } - - pub fn run_script(&self, project_name: &str, program_name: &str, script_path: &Path, args: &[String]) -> Result { - let project_path = self.get_project_path(project_name); - let headless = self.get_headless_script(); - - let mut cmd = Command::new(&headless); - cmd.arg(project_path.to_str().unwrap()) - .arg(project_name) - .arg("-process") - .arg(program_name) - .arg("-scriptPath") - .arg(script_path.parent().unwrap().to_str().unwrap()) - .arg("-postScript") - .arg(script_path.file_name().unwrap().to_str().unwrap()); - - for arg in args { - cmd.arg(arg); - } - - let output = cmd.output()?; - - if !output.status.success() { - return Err(GhidraError::ExecutionFailed( - String::from_utf8_lossy(&output.stderr).to_string() - )); - } - - Ok(String::from_utf8_lossy(&output.stdout).to_string()) - } - fn get_scripts_dir(&self) -> Result { let config_dir = dirs::config_dir() .ok_or_else(|| GhidraError::ConfigError("Could not determine config directory".to_string()))?; diff --git a/src/ghidra/scripts/bridge.py b/src/ghidra/scripts/bridge.py index b56b4bd..8d4fea1 100644 --- a/src/ghidra/scripts/bridge.py +++ b/src/ghidra/scripts/bridge.py @@ -800,8 +800,86 @@ def handle_stats(args): except Exception as e: return {"error": "Failed to get stats: " + str(e)} +# --- Import/Analyze Handlers --- + +def handle_import(args): + """Import a binary into the current project.""" + from ghidra.app.util.importer import AutoImporter + from ghidra.util.task import ConsoleTaskMonitor + from java.io import File + + binary_path = args.get("binary_path") + if not binary_path: + return {"error": "No binary_path provided"} + + program_name = args.get("program") + if not program_name: + binary_file = File(binary_path) + program_name = binary_file.getName() + + project = state.getProject() + if project is None: + return {"error": "No project open"} + + try: + binary_file = File(binary_path) + if not binary_file.exists(): + return {"error": "Binary file not found: " + binary_path} + + monitor = ConsoleTaskMonitor() + project_data = project.getProjectData() + + imported = AutoImporter.importByUsingBestGuess( + binary_file, + None, + project_data.getRootFolder(), + program_name, + monitor + ) + + if imported is None: + return {"error": "Failed to import binary"} + + return {"status": "success", "program": program_name} + + except Exception as e: + return {"error": "Import failed: " + str(e)} + +def handle_analyze(args): + """Trigger auto-analysis on the current program.""" + from ghidra.app.cmd.analysis import AutoAnalysisManager + from ghidra.util.task import ConsoleTaskMonitor + + program_name = args.get("program") + if not program_name: + return {"error": "No program name provided"} + + if currentProgram is None: + return {"error": "No program currently loaded"} + + if currentProgram.getName() != program_name: + return {"error": "Program mismatch: expected " + program_name + " but current is " + currentProgram.getName()} + + try: + monitor = ConsoleTaskMonitor() + auto_mgr = AutoAnalysisManager.getAnalysisManager(currentProgram) + + if auto_mgr is None: + return {"error": "Could not get AutoAnalysisManager"} + + auto_mgr.reAnalyzeAll(None) + auto_mgr.startAnalysis(monitor) + + return {"status": "success", "program": program_name} + + except Exception as e: + return {"error": "Analysis failed: " + str(e)} + COMMANDS = { "ping": handle_ping, + # Import/Analyze commands + "import": handle_import, + "analyze": handle_analyze, "program_info": handle_program_info, "program_close": handle_program_close, "program_delete": handle_program_delete, diff --git a/src/ipc/client.rs b/src/ipc/client.rs index f875920..626f5c0 100644 --- a/src/ipc/client.rs +++ b/src/ipc/client.rs @@ -152,6 +152,34 @@ impl DaemonClient { pub async fn execute_cli_json(&mut self, command_json: String) -> Result { self.send_command(Command::ExecuteCli { command_json }).await } + + /// Import a binary into a project. + pub async fn import_binary( + &mut self, + binary_path: &str, + project: &str, + program: Option<&str>, + ) -> Result { + self.send_command(Command::Import { + binary_path: binary_path.to_string(), + project: project.to_string(), + program: program.map(|s| s.to_string()), + }) + .await + } + + /// Analyze a program in a project. + pub async fn analyze_program( + &mut self, + project: &str, + program: &str, + ) -> Result { + self.send_command(Command::Analyze { + project: project.to_string(), + program: program.to_string(), + }) + .await + } } /// Check if daemon is running (without establishing a full connection). diff --git a/src/ipc/protocol.rs b/src/ipc/protocol.rs index 1ab43d4..3fc8a21 100644 --- a/src/ipc/protocol.rs +++ b/src/ipc/protocol.rs @@ -112,6 +112,18 @@ pub enum Command { /// Get cross-references from an address XRefsFrom { address: String }, + // === Project Management === + /// Import a binary into a project + Import { + binary_path: String, + project: String, + #[serde(skip_serializing_if = "Option::is_none")] + program: Option, + }, + + /// Analyze a program in a project + Analyze { project: String, program: String }, + // === Session Management === /// Health check Ping, diff --git a/src/main.rs b/src/main.rs index c0efb29..712d3ce 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,14 +51,14 @@ fn run(cli: Cli) -> anyhow::Result<()> { Commands::Init => handle_init(), Commands::Doctor => handle_doctor(), Commands::Version => handle_version(), - Commands::Import(args) => handle_import(args), - Commands::Analyze(args) => handle_analyze(args), - Commands::Quick(args) => handle_quick(args), Commands::Config(cmd) => handle_config_command(cmd), Commands::SetDefault(args) => handle_set_default(args), Commands::Project(args) => handle_project_command(args.command), // Commands requiring daemon are handled by run_with_daemon_check - Commands::Query(_) + Commands::Import(_) + | Commands::Analyze(_) + | Commands::Quick(_) + | Commands::Query(_) | Commands::Summary(_) | Commands::Function(_) | Commands::Strings(_) @@ -88,7 +88,10 @@ async fn run_async(cli: Cli) -> anyhow::Result<()> { fn requires_daemon(command: &Commands) -> bool { matches!( command, - Commands::Query(_) + Commands::Import(_) + | Commands::Analyze(_) + | Commands::Quick(_) + | Commands::Query(_) | Commands::Decompile(_) | Commands::Function(_) | Commands::Strings(_) @@ -117,33 +120,147 @@ async fn run_with_daemon_check(cli: Cli) -> anyhow::Result<()> { return run(cli); } - // Query-type commands REQUIRE the daemon + let config = Config::load()?; + let project_path = match &cli.command { + Commands::Import(args) => { + resolve_project_path(&args.project, &config)? + } + Commands::Analyze(args) => { + resolve_project_path(&args.project, &config)? + } + Commands::Quick(args) => { + resolve_project_path(&args.project, &config)? + } + _ => { + resolve_project_path(&None, &config)? + } + }; + + ensure_daemon_running(&project_path).await?; + match ipc::client::DaemonClient::connect().await { Ok(mut client) => { info!("Connected to daemon via IPC"); let output = execute_via_daemon(&mut client, &cli.command).await?; - println!("{}", output); + if !output.is_empty() { + println!("{}", output); + } Ok(()) } - Err(_) => { - eprintln!("Error: This command requires the daemon to be running."); + Err(e) => { + eprintln!("Error: Failed to connect to daemon: {}", e); eprintln!(); - eprintln!("Start the daemon with:"); - eprintln!(" ghidra daemon start --project "); - eprintln!(); - eprintln!("Or run a quick analysis first:"); - eprintln!(" ghidra quick "); + eprintln!("The daemon may still be starting. Try again in a moment."); std::process::exit(1); } } } +/// Ensure daemon is running for the given project path. +async fn ensure_daemon_running(project_path: &PathBuf) -> anyhow::Result<()> { + let data_dir = get_data_dir()?; + + if get_running_daemon_info(&data_dir, project_path)?.is_some() { + return Ok(()); + } + + let config = Config::load()?; + let log_file = data_dir.join("daemon.log"); + + let daemon_config = DaemonConfig { + project_path: project_path.clone(), + ghidra_install_dir: config.ghidra_install_dir.map(PathBuf::from), + log_file, + program_name: config.default_program.clone(), + }; + + #[cfg(unix)] + { + daemonize_unix(daemon_config, None)?; + } + + #[cfg(windows)] + { + daemonize_windows(daemon_config, None)?; + } + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + Ok(()) +} + /// Execute a command via the daemon IPC connection. async fn execute_via_daemon( client: &mut ipc::client::DaemonClient, command: &Commands, ) -> anyhow::Result { let result = match command { + Commands::Import(args) => { + let binary_path = PathBuf::from(&args.binary); + if !binary_path.exists() { + anyhow::bail!("Binary not found: {}", args.binary); + } + + let result = client.import_binary( + &args.binary, + &args.project.as_ref().unwrap_or(&"quick-analysis".to_string()), + args.program.as_deref(), + ).await?; + + if let Some(program_name) = result.as_str() { + println!("Successfully imported as: {}", program_name); + } else if let Some(program_name) = result.get("program").and_then(|p| p.as_str()) { + println!("Successfully imported as: {}", program_name); + } + + return Ok(String::new()); + } + Commands::Analyze(args) => { + let config = Config::load()?; + let program = resolve_program(&args.program, &config)?; + let project = resolve_project(&args.project, &config, &program)?; + + println!("Analyzing {}...", program); + + client.analyze_program(&project, &program).await?; + + println!("Analysis complete!"); + + return Ok(String::new()); + } + Commands::Quick(args) => { + let project = args.project.clone().unwrap_or_else(|| "quick-analysis".to_string()); + let binary_path = PathBuf::from(&args.binary); + + println!("Quick analysis of {}...\n", args.binary); + + println!("[1/3] Importing binary..."); + let result = client.import_binary(&args.binary, &project, None).await?; + + let program_name = if let Some(name) = result.as_str() { + name.to_string() + } else if let Some(name) = result.get("program").and_then(|p| p.as_str()) { + name.to_string() + } else { + binary_path.file_stem() + .and_then(|s| s.to_str()) + .unwrap_or("program") + .to_string() + }; + + println!("[2/3] Running analysis..."); + client.analyze_program(&project, &program_name).await?; + + println!("[3/3] Done!\n"); + println!("Analysis complete. To query the binary, start the daemon:"); + println!(" ghidra daemon start --project {} --program {}", project, program_name); + println!("\nThen run queries like:"); + println!(" ghidra function list"); + println!(" ghidra decompile main"); + println!(" ghidra summary"); + + return Ok(String::new()); + } Commands::Query(args) => { match args.data_type.as_str() { "functions" => client.list_functions(args.limit, args.filter.clone()).await?, @@ -658,70 +775,6 @@ fn handle_version() -> anyhow::Result<()> { Ok(()) } -fn handle_import(args: cli::ImportArgs) -> anyhow::Result<()> { - let config = Config::load()?; - let client = GhidraClient::new(config.clone())?; - - let project = resolve_project(&args.project, &config, &args.program.as_ref().unwrap_or(&"unknown".to_string()))?; - - let binary_path = PathBuf::from(&args.binary); - if !binary_path.exists() { - anyhow::bail!(format!("Binary not found: {}", args.binary)); - } - - println!("Importing {} into project {}...", args.binary, project); - - let program_name = client.import_binary(&project, &binary_path, args.program.as_deref())?; - - println!("Successfully imported as: {}", program_name); - - Ok(()) -} - -fn handle_analyze(args: cli::AnalyzeArgs) -> anyhow::Result<()> { - let config = Config::load()?; - let client = GhidraClient::new(config.clone())?; - - let program = resolve_program(&args.program, &config)?; - let project = resolve_project(&args.project, &config, &program)?; - - println!("Analyzing {}...", program); - - client.analyze_program(&project, &program)?; - - println!("Analysis complete!"); - - Ok(()) -} - -fn handle_quick(args: cli::QuickArgs) -> anyhow::Result<()> { - let config = Config::load()?; - let client = GhidraClient::new(config.clone())?; - - let project = args.project.unwrap_or_else(|| "quick-analysis".to_string()); - let binary_path = PathBuf::from(&args.binary); - - println!("Quick analysis of {}...\n", args.binary); - - // Import - println!("[1/3] Importing binary..."); - let program_name = client.import_binary(&project, &binary_path, None)?; - - // Analyze - println!("[2/3] Running analysis..."); - client.analyze_program(&project, &program_name)?; - - // Done - println!("[3/3] Done!\n"); - println!("Analysis complete. To query the binary, start the daemon:"); - println!(" ghidra daemon start --project {} --program {}", project, program_name); - println!("\nThen run queries like:"); - println!(" ghidra function list"); - println!(" ghidra decompile main"); - println!(" ghidra summary"); - - Ok(()) -} fn handle_config_command(cmd: cli::ConfigCommands) -> anyhow::Result<()> { use cli::ConfigCommands;