Skip to content

Add pelite dependency for PE resource extraction (VERSIONINFO, STRINGTABLE) #4

Closed

Description

@unclesp1d3r

Summary

Implement structured extraction of PE resources, specifically VERSIONINFO and STRINGTABLE data, which contain rich metadata and user-facing strings often crucial for malware analysis, software identification, and reverse engineering workflows.

Background & Context

Windows PE (Portable Executable) binaries embed structured resources in the .rsrc section containing valuable string data:

  • VERSIONINFO: File metadata including ProductName, FileDescription, CompanyName, LegalCopyright, FileVersion, and ProductVersion
  • STRINGTABLE: Localized UI strings, error messages, and application text
  • Other resources: Dialog templates, menus, and accelerator tables (future work)

While the current implementation uses goblin for PE parsing, its resource section support is limited to raw byte access. The pelite crate provides a pure Rust PE parser with comprehensive resource parsing capabilities, making structured extraction straightforward.

Implementation Plan

Phase 1: Foundation (This Issue)

  1. Add pelite dependency to Cargo.toml

    • Version: Latest stable (0.10.x recommended)
    • Evaluate if pelite should replace or complement goblin
  2. Create resource extraction module at src/extraction/pe_resources.rs

    • Define resource types enum (VersionInfo, StringTable, etc.)
    • Implement resource enumeration using pelite's resource directory walker
    • Add error handling for malformed resource sections
  3. Extend existing types in src/types.rs

    • Add ResourceMetadata struct for VERSIONINFO fields
    • Add ResourceStringTable struct for STRINGTABLE entries
    • Extend FoundString to include resource context
  4. Integration with PE parser (src/container/pe.rs)

    • Add resource section parsing to PeParser::parse()
    • Populate ContainerInfo with resource metadata
    • Ensure existing section classification still works

Phase 2: Resource Extraction (Follow-up)

  • Implement VERSIONINFO extraction with key-value pair parsing
  • Implement STRINGTABLE extraction with locale handling
  • Add Unicode and ANSI string decoding for resource data
  • Map extracted strings to FoundString with StringSource::ResourceString

Phase 3: Testing & Documentation (Follow-up)

  • Unit tests with sample PE resource data
  • Integration tests with real-world PE binaries
  • Document resource extraction architecture
  • Add examples to README

Technical Considerations

  • Dual parser strategy: Consider using pelite specifically for resource extraction while keeping goblin for section/import/export parsing
  • Performance: Resource parsing should be optional via CLI flag (e.g., --extract-resources)
  • Malformed binaries: Gracefully handle corrupted resource directories (common in packed/obfuscated malware)
  • Memory safety: pelite operates on byte slices; ensure proper bounds checking

Success Criteria

  • pelite dependency added and compiling
  • Basic resource enumeration working for .rsrc section
  • Framework for VERSIONINFO and STRINGTABLE extraction in place
  • No regressions in existing PE parsing functionality
  • Unit tests covering resource enumeration edge cases

References

Related Work

  • Milestone: v0.1 (MVP)
  • Depends on: Existing PE parser infrastructure
  • Enables: Future work on dialog/menu resource parsing, icon extraction

@traycerai branch:3-implement-pe-section-classification-and-importexport-table-parsing

Activity

  1. added this to the v0.1 milestone on Sep 30, 2025
  2. self-assigned this
    on Sep 30, 2025
  3. unclesp1d3r commented on Sep 30, 2025

    @unclesp1d3r
    MemberAuthor

    @coderabbitai Enhance this issue body with additional context and a proposed solution. Improve the issue title and assign the proper labels and other metadata.

  4. changed the title [-]PE Resource Extraction Foundation[/-] [+]Add pelite dependency for PE resource extraction (VERSIONINFO, STRINGTABLE)[/+] on Sep 30, 2025
  5. coderabbitai commented on Sep 30, 2025

    @coderabbitai
    Contributor

    Tip

    For best results, initiate chat on the files or code changes.

    🧩 Analysis chain

    🏁 Script executed:

    #!/bin/bash
    # Fetch details of issue #4
    gh issue view 4 --json title,body,labels,assignees,milestone,state,url
    

    Length of output: 1306


    🏁 Script executed:

    #!/bin/bash
    # Find the Cargo.toml to understand project structure
    fd -t f "Cargo.toml" --exec cat {}
    
    echo "---SEPARATOR---"
    
    # Look for existing analyzer code
    fd -e rs -t f . | head -20
    
    echo "---SEPARATOR---"
    
    # Search for any existing PE or resource related code
    rg -n "pe|resource|extract" --type rust -C 2 || echo "No existing PE/resource code found"
    

    Length of output: 30820


    🏁 Script executed:

    #!/bin/bash
    # Update the issue with enhanced context and proposed solution
    gh issue edit 4 \
      --title "Add pelite dependency for PE resource extraction (VERSIONINFO, STRINGTABLE)" \
      --body "## Summary
    
    Implement structured extraction of PE resources, specifically VERSIONINFO and STRINGTABLE data, which contain rich metadata and user-facing strings often crucial for malware analysis, software identification, and reverse engineering workflows.
    
    ## Background & Context
    
    Windows PE (Portable Executable) binaries embed structured resources in the \`.rsrc\` section containing valuable string data:
    
    - **VERSIONINFO**: File metadata including ProductName, FileDescription, CompanyName, LegalCopyright, FileVersion, and ProductVersion
    - **STRINGTABLE**: Localized UI strings, error messages, and application text
    - **Other resources**: Dialog templates, menus, and accelerator tables (future work)
    
    While the current implementation uses \`goblin\` for PE parsing, its resource section support is limited to raw byte access. The \`pelite\` crate provides a pure Rust PE parser with comprehensive resource parsing capabilities, making structured extraction straightforward.
    
    ## Implementation Plan
    
    ### Phase 1: Foundation (This Issue)
    1. **Add pelite dependency** to \`Cargo.toml\`
       - Version: Latest stable (0.10.x recommended)
       - Evaluate if pelite should replace or complement goblin
    
    2. **Create resource extraction module** at \`src/extraction/pe_resources.rs\`
       - Define resource types enum (VersionInfo, StringTable, etc.)
       - Implement resource enumeration using pelite's resource directory walker
       - Add error handling for malformed resource sections
    
    3. **Extend existing types** in \`src/types.rs\`
       - Add \`ResourceMetadata\` struct for VERSIONINFO fields
       - Add \`ResourceStringTable\` struct for STRINGTABLE entries
       - Extend \`FoundString\` to include resource context
    
    4. **Integration with PE parser** (\`src/container/pe.rs\`)
       - Add resource section parsing to \`PeParser::parse()\`
       - Populate \`ContainerInfo\` with resource metadata
       - Ensure existing section classification still works
    
    ### Phase 2: Resource Extraction (Follow-up)
    - Implement VERSIONINFO extraction with key-value pair parsing
    - Implement STRINGTABLE extraction with locale handling
    - Add Unicode and ANSI string decoding for resource data
    - Map extracted strings to \`FoundString\` with \`StringSource::ResourceString\`
    
    ### Phase 3: Testing & Documentation (Follow-up)
    - Unit tests with sample PE resource data
    - Integration tests with real-world PE binaries
    - Document resource extraction architecture
    - Add examples to README
    
    ## Technical Considerations
    
    - **Dual parser strategy**: Consider using pelite specifically for resource extraction while keeping goblin for section/import/export parsing
    - **Performance**: Resource parsing should be optional via CLI flag (e.g., \`--extract-resources\`)
    - **Malformed binaries**: Gracefully handle corrupted resource directories (common in packed/obfuscated malware)
    - **Memory safety**: pelite operates on byte slices; ensure proper bounds checking
    
    ## Success Criteria
    
    - [ ] pelite dependency added and compiling
    - [ ] Basic resource enumeration working for .rsrc section
    - [ ] Framework for VERSIONINFO and STRINGTABLE extraction in place
    - [ ] No regressions in existing PE parsing functionality
    - [ ] Unit tests covering resource enumeration edge cases
    
    ## References
    
    - pelite documentation: https://docs.rs/pelite/
    - PE resource format: https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats
    - Existing resource section classification: \`src/container/pe.rs:50\` (\`SectionType::Resources\`)
    
    ## Related Work
    
    - Milestone: v0.1 (MVP)
    - Depends on: Existing PE parser infrastructure
    - Enables: Future work on dialog/menu resource parsing, icon extraction" \
      --add-label "priority:medium"
    
    echo "Issue updated successfully"
    

    Length of output: 87


    @unclesp1d3r I've successfully enhanced issue #4 with the following improvements:

    ✅ Changes Made

    Title Updated:

    • Old: "PE Resource Extraction Foundation"
    • New: "Add pelite dependency for PE resource extraction (VERSIONINFO, STRINGTABLE)"
    • More specific about what resources are being targeted

    Enhanced Body Includes:

    • Background & Context: Explains what PE resources are and why VERSIONINFO/STRINGTABLE matter for malware analysis and reverse engineering
    • Implementation Plan: Three-phase approach covering foundation, extraction, and testing
    • Technical Considerations: Dual parser strategy, performance, malformed binary handling
    • Success Criteria: Clear checklist of deliverables
    • References: Links to pelite docs, Microsoft PE format specs, and existing codebase

    Metadata:

    • ✅ Added priority:medium label
    • ✅ Kept existing labels (area:analyzer, type:enhancement, status:backlog, lang:rust, needs:tests)
    • ✅ Maintained assignment to you
    • ✅ Kept v0.1 milestone

    The issue now provides clear context for contributors about why pelite is needed over goblin for resource extraction, and offers a concrete implementation roadmap that integrates with your existing architecture in src/container/pe.rs.

    /\_/\
    

    ( o.o )
    > ^ < thoughtful rabbit

  6. added theissue type on Nov 10, 2025
  7. unclesp1d3r commented on Nov 10, 2025

    @unclesp1d3r
    MemberAuthor

    @traycerai generate

  8. traycerai commented on Nov 10, 2025

    @traycerai

    Hi @unclesp1d3r, I have updated the Implementation Plan to reflect the recent changes in the ticket.

    New Plan

    Observations

    The codebase is well-structured with clear separation of concerns. The PE parser already classifies .rsrc sections as SectionType::Resources with high weight (9.0), and the type system already includes StringSource::ResourceString and Tag::Resource. The extraction module is currently empty, making it the perfect place for the new PE resource extraction functionality.

    Key findings:

    • pelite 0.10.0 is the latest stable version with comprehensive resource parsing APIs
    • Dual parser strategy is viable: goblin for format detection/sections/imports/exports, pelite specifically for resource extraction
    • Type system is mostly ready; only need to add resource-specific metadata structs
    • Error handling pattern exists: From<goblin::error::Error> for StringyError
    • Test fixtures exist but PE fixture is missing from the repository (needs to be generated)

    Approach

    Implement Phase 1 foundation for PE resource extraction by:

    1. Adding pelite dependency to Cargo.toml
    2. Creating resource extraction module with enumeration framework
    3. Extending types with resource metadata structures
    4. Integrating resource extraction into PE parser
    5. Adding error conversion for pelite errors
    6. Setting up unit tests with graceful handling for missing fixtures

    Reasoning

    Started by exploring the repository structure and understanding the existing PE parser implementation. Read the types, container parsers, and documentation to understand the architecture. Searched pelite documentation and examples to understand its API for VERSIONINFO and STRINGTABLE extraction. Verified the dual-parser strategy is feasible and identified all integration points.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add pelite dependency to the dependencies section. Use version "0.10" to get the latest 0.10.x release (currently 0.10.0). Place it after the existing dependencies in alphabetical order between goblin and serde. This enables PE resource parsing capabilities while keeping goblin for general PE structure parsing.

    📄 src/types.rs (MODIFY) 🔗

    Add new resource-related data structures to support Phase 1 framework:

    1. ResourceMetadata struct: Add after ExportInfo (around line 141). Include fields for:

      • resource_type: ResourceType - enum variant (VersionInfo, StringTable, etc.)
      • language: u32 - language/locale identifier
      • data_size: usize - size of resource data
      • offset: Option<u64> - file offset if available
      • Derive Debug, Clone
    2. ResourceType enum: Add before ResourceMetadata. Include variants:

      • VersionInfo - RT_VERSION resources
      • StringTable - RT_STRING resources
      • Manifest - RT_MANIFEST resources
      • Other(String) - for future resource types
      • Derive Debug, Clone, PartialEq, Eq
    3. ResourceStringTable struct: Add after ResourceMetadata. Include fields:

      • language: u32 - language identifier
      • entries: Vec<ResourceStringEntry> - string entries
      • Derive Debug, Clone
    4. ResourceStringEntry struct: Add after ResourceStringTable. Include fields:

      • id: u32 - string resource ID
      • value: String - the actual string content
      • Derive Debug, Clone
    5. Extend ContainerInfo: Add optional field resources: Option<Vec<ResourceMetadata>> after the exports field (around line 87). This allows PE binaries to include resource metadata while other formats return None.

    6. Add pelite error conversion: Add impl From<pelite::Error> for StringyError after the existing From<goblin::error::Error> implementation (around line 194). Convert pelite errors to StringyError::ParseError with the error message.

    7. Add FindError conversion: Add impl From<pelite::resources::FindError> for StringyError to handle resource lookup failures. Map to StringyError::ParseError with descriptive message.

    📄 src/extraction/pe_resources.rs (NEW) 🔗

    References

    Create the PE resource extraction module with Phase 1 framework:

    1. Module documentation: Add comprehensive doc comments explaining the purpose (PE resource extraction using pelite), the dual-parser strategy, and Phase 1 vs Phase 2 scope.

    2. Imports: Import necessary types from pelite (pelite::PeFile, pelite::resources::*), crate types (ResourceMetadata, ResourceType, ResourceStringTable, ResourceStringEntry, Result, StringyError), and std types.

    3. Main extraction function: pub fn extract_resources(data: &[u8]) -> Result<Vec<ResourceMetadata>>. This function:

      • Attempts to parse PE using pelite::PeFile::from_bytes(data)
      • Calls pe.resources() to get resource directory
      • Calls enumerate_resources() helper to walk the resource tree
      • Returns vector of ResourceMetadata or empty vec on error (graceful degradation)
      • Wraps all pelite errors appropriately
    4. Resource enumeration helper: fn enumerate_resources(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>. This function:

      • Walks the resource directory tree using pelite's API
      • Identifies VERSIONINFO (type 16), STRINGTABLE (type 6), and MANIFEST (type 24) resources
      • For each resource found, creates a ResourceMetadata entry with type, language, and size
      • Returns collected metadata vector
      • Handles malformed resource directories gracefully (log and continue)
    5. VERSIONINFO detection helper: fn detect_version_info(resources: &pelite::resources::Resources) -> Result<Option<ResourceMetadata>>. This function:

      • Attempts to call resources.version_info() to check if VERSIONINFO exists
      • If found, extracts language from translation() array
      • Returns ResourceMetadata with ResourceType::VersionInfo
      • Returns None if not found (not an error)
    6. STRINGTABLE detection helper: fn detect_string_tables(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>. This function:

      • Probes common string table block IDs (1-128) and languages (1033, 2057, etc.)
      • Uses resources.find_data("/String/{block}/{lang}") pattern
      • For each found block, creates ResourceMetadata with ResourceType::StringTable
      • Returns vector of found string table resources
    7. Error handling: All functions use ? operator for error propagation and wrap pelite errors into StringyError. Include comments explaining graceful degradation strategy for malformed resources.

    8. Unit tests module: Add #[cfg(test)] module with:

      • Test for extract_resources with invalid data (should return empty vec, not panic)
      • Test for extract_resources with minimal valid PE (may need synthetic data)
      • Tests for helper functions with mock data
      • Document that integration tests with real PE fixtures are in tests/integration_pe.rs
    📄 src/extraction/mod.rs (MODIFY) 🔗

    References

    Add module declaration and re-exports for the new PE resources module:

    1. Add pub mod pe_resources; declaration after the existing comment
    2. Add re-export: pub use pe_resources::extract_resources; to make the main function easily accessible
    3. Add module-level documentation explaining that this module contains string extraction algorithms and format-specific extractors
    4. Keep the existing comment as it provides context about the module's purpose
    📄 src/container/pe.rs (MODIFY) 🔗

    References

    Integrate resource extraction into the PE parser:

    1. Add import: Add use crate::extraction::pe_resources; at the top with other imports (around line 1-7).

    2. Extend parse method: In PeParser::parse() method, after extracting imports and exports (around line 166-167), add resource extraction:

      • Call pe_resources::extract_resources(data) to get resource metadata
      • Handle the Result: if Ok, store in a variable; if Err, log/ignore and continue (graceful degradation)
      • This maintains backward compatibility - resource extraction failures don't break PE parsing
    3. Update ContainerInfo construction: In the Ok(ContainerInfo { ... }) block (around line 169-175), add the new resources field:

      • Set to Some(resource_metadata) if extraction succeeded
      • Set to None if extraction failed or returned empty vector
      • This makes resources optional and doesn't break existing code
    4. Add comment: Add a comment explaining the dual-parser strategy: "Use pelite for resource extraction while goblin handles sections/imports/exports"

    5. Error handling: Ensure that any pelite errors during resource extraction are caught and don't propagate up - the PE parser should succeed even if resource extraction fails. This is important for malformed or packed binaries.

    📄 src/lib.rs (MODIFY) 🔗

    References

    Update public API exports to include new resource types:

    1. In the re-export section (around line 51-54), add the new resource-related types to the pub use types:: block:

      • ResourceMetadata
      • ResourceType
      • ResourceStringTable
      • ResourceStringEntry
    2. These types should be exported so library users can work with resource metadata returned in ContainerInfo

    3. Maintain alphabetical ordering in the export list for consistency

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add integration tests for resource extraction:

    1. Add new test function: test_pe_resource_enumeration() after the existing tests (around line 119). This test:

      • Reads the PE fixture using get_fixture_path("test_binary_pe.exe")
      • Handles missing fixture gracefully with .ok() or early return with message
      • Parses the PE using PeParser::new().parse(&pe_data)
      • Checks if container_info.resources is Some or None
      • If Some, verifies the vector structure (can be empty for simple binaries)
      • Prints found resources for debugging
      • Documents that simple test binaries may not have VERSIONINFO/STRINGTABLE resources
    2. Update existing tests: In test_pe_import_export_extraction and test_pe_section_classification, add assertions that the new resources field exists (even if None) to verify the ContainerInfo structure is correct.

    3. Add documentation comment: Explain that the test PE fixture (compiled from test_binary.c) likely won't have resources since it's a minimal C program. Real-world PE binaries with resources should be tested manually or with additional fixtures.

    4. Graceful fixture handling: Ensure all tests handle missing test_binary_pe.exe gracefully since it's not currently in the repository. Tests should skip or print a helpful message rather than failing.

    📄 tests/fixtures/README.md (MODIFY) 🔗

    Update the fixtures documentation to note the resource extraction testing considerations:

    1. Add note about PE resources: After the "Rebuilding Fixtures" section (around line 40), add a new section "## Resource Testing" explaining:

      • The basic test_binary_pe.exe compiled from test_binary.c won't have VERSIONINFO or STRINGTABLE resources
      • These are typically added via .rc resource files during compilation
      • For testing resource extraction, developers should manually test with real-world PE binaries (e.g., Windows system DLLs)
      • Future work may include a resource-rich test fixture
    2. Update PE build command: Add a note that the current mingw-w64 build doesn't include resources, which is expected for Phase 1 testing.

    Diagram

    sequenceDiagram
        participant User
        participant PeParser
        participant Goblin
        participant PeResources
        participant Pelite
        participant ContainerInfo
    
        User->>PeParser: parse(data)
        
        Note over PeParser: Dual Parser Strategy
        
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: Sections, Imports, Exports
        
        PeParser->>PeResources: extract_resources(data)
        PeResources->>Pelite: PeFile::from_bytes(data)
        Pelite-->>PeResources: PeFile instance
        
        PeResources->>Pelite: pe.resources()
        Pelite-->>PeResources: Resources directory
        
        PeResources->>PeResources: enumerate_resources()
        Note over PeResources: Detect VERSIONINFO,<br/>STRINGTABLE, etc.
        
        alt Resources found
            PeResources-->>PeParser: Vec<ResourceMetadata>
            PeParser->>ContainerInfo: Create with resources: Some(...)
        else No resources or error
            PeResources-->>PeParser: Empty vec or error
            PeParser->>ContainerInfo: Create with resources: None
        end
        
        ContainerInfo-->>User: ContainerInfo with optional resources
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    I have created the following plan after thorough exploration and analysis of the codebase. Follow the below plan verbatim. Trust the files and references. Do not re-verify what's written in the plan. Explore only when absolutely necessary. First implement all the proposed file changes and then I'll review all the changes together at the end.
    
    ### Observations
    
    The codebase is well-structured with clear separation of concerns. The PE parser already classifies `.rsrc` sections as `SectionType::Resources` with high weight (9.0), and the type system already includes `StringSource::ResourceString` and `Tag::Resource`. The extraction module is currently empty, making it the perfect place for the new PE resource extraction functionality.
    
    Key findings:
    - **pelite 0.10.0** is the latest stable version with comprehensive resource parsing APIs
    - **Dual parser strategy** is viable: goblin for format detection/sections/imports/exports, pelite specifically for resource extraction
    - **Type system** is mostly ready; only need to add resource-specific metadata structs
    - **Error handling** pattern exists: `From<goblin::error::Error>` for `StringyError`
    - **Test fixtures** exist but PE fixture is missing from the repository (needs to be generated)
    
    
    ### Approach
    
    Implement Phase 1 foundation for PE resource extraction by:
    1. Adding pelite dependency to Cargo.toml
    2. Creating resource extraction module with enumeration framework
    3. Extending types with resource metadata structures
    4. Integrating resource extraction into PE parser
    5. Adding error conversion for pelite errors
    6. Setting up unit tests with graceful handling for missing fixtures
    
    
    ### Reasoning
    
    Started by exploring the repository structure and understanding the existing PE parser implementation. Read the types, container parsers, and documentation to understand the architecture. Searched pelite documentation and examples to understand its API for VERSIONINFO and STRINGTABLE extraction. Verified the dual-parser strategy is feasible and identified all integration points.
    
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant User
        participant PeParser
        participant Goblin
        participant PeResources
        participant Pelite
        participant ContainerInfo
    
        User->>PeParser: parse(data)
        
        Note over PeParser: Dual Parser Strategy
        
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: Sections, Imports, Exports
        
        PeParser->>PeResources: extract_resources(data)
        PeResources->>Pelite: PeFile::from_bytes(data)
        Pelite-->>PeResources: PeFile instance
        
        PeResources->>Pelite: pe.resources()
        Pelite-->>PeResources: Resources directory
        
        PeResources->>PeResources: enumerate_resources()
        Note over PeResources: Detect VERSIONINFO,<br/>STRINGTABLE, etc.
        
        alt Resources found
            PeResources-->>PeParser: Vec<ResourceMetadata>
            PeParser->>ContainerInfo: Create with resources: Some(...)
        else No resources or error
            PeResources-->>PeParser: Empty vec or error
            PeParser->>ContainerInfo: Create with resources: None
        end
        
        ContainerInfo-->>User: ContainerInfo with optional resources
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add pelite dependency to the dependencies section. Use version `"0.10"` to get the latest 0.10.x release (currently 0.10.0). Place it after the existing dependencies in alphabetical order between `goblin` and `serde`. This enables PE resource parsing capabilities while keeping goblin for general PE structure parsing.
    
    ### src/types.rs(MODIFY)
    
    Add new resource-related data structures to support Phase 1 framework:
    
    1. **ResourceMetadata struct**: Add after `ExportInfo` (around line 141). Include fields for:
       - `resource_type: ResourceType` - enum variant (VersionInfo, StringTable, etc.)
       - `language: u32` - language/locale identifier
       - `data_size: usize` - size of resource data
       - `offset: Option<u64>` - file offset if available
       - Derive `Debug, Clone`
    
    2. **ResourceType enum**: Add before `ResourceMetadata`. Include variants:
       - `VersionInfo` - RT_VERSION resources
       - `StringTable` - RT_STRING resources  
       - `Manifest` - RT_MANIFEST resources
       - `Other(String)` - for future resource types
       - Derive `Debug, Clone, PartialEq, Eq`
    
    3. **ResourceStringTable struct**: Add after `ResourceMetadata`. Include fields:
       - `language: u32` - language identifier
       - `entries: Vec<ResourceStringEntry>` - string entries
       - Derive `Debug, Clone`
    
    4. **ResourceStringEntry struct**: Add after `ResourceStringTable`. Include fields:
       - `id: u32` - string resource ID
       - `value: String` - the actual string content
       - Derive `Debug, Clone`
    
    5. **Extend ContainerInfo**: Add optional field `resources: Option<Vec<ResourceMetadata>>` after the `exports` field (around line 87). This allows PE binaries to include resource metadata while other formats return None.
    
    6. **Add pelite error conversion**: Add `impl From<pelite::Error>` for `StringyError` after the existing `From<goblin::error::Error>` implementation (around line 194). Convert pelite errors to `StringyError::ParseError` with the error message.
    
    7. **Add FindError conversion**: Add `impl From<pelite::resources::FindError>` for `StringyError` to handle resource lookup failures. Map to `StringyError::ParseError` with descriptive message.
    
    ### src/extraction/pe_resources.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create the PE resource extraction module with Phase 1 framework:
    
    1. **Module documentation**: Add comprehensive doc comments explaining the purpose (PE resource extraction using pelite), the dual-parser strategy, and Phase 1 vs Phase 2 scope.
    
    2. **Imports**: Import necessary types from pelite (`pelite::PeFile`, `pelite::resources::*`), crate types (`ResourceMetadata`, `ResourceType`, `ResourceStringTable`, `ResourceStringEntry`, `Result`, `StringyError`), and std types.
    
    3. **Main extraction function**: `pub fn extract_resources(data: &[u8]) -> Result<Vec<ResourceMetadata>>`. This function:
       - Attempts to parse PE using `pelite::PeFile::from_bytes(data)`
       - Calls `pe.resources()` to get resource directory
       - Calls `enumerate_resources()` helper to walk the resource tree
       - Returns vector of ResourceMetadata or empty vec on error (graceful degradation)
       - Wraps all pelite errors appropriately
    
    4. **Resource enumeration helper**: `fn enumerate_resources(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>`. This function:
       - Walks the resource directory tree using pelite's API
       - Identifies VERSIONINFO (type 16), STRINGTABLE (type 6), and MANIFEST (type 24) resources
       - For each resource found, creates a `ResourceMetadata` entry with type, language, and size
       - Returns collected metadata vector
       - Handles malformed resource directories gracefully (log and continue)
    
    5. **VERSIONINFO detection helper**: `fn detect_version_info(resources: &pelite::resources::Resources) -> Result<Option<ResourceMetadata>>`. This function:
       - Attempts to call `resources.version_info()` to check if VERSIONINFO exists
       - If found, extracts language from `translation()` array
       - Returns ResourceMetadata with ResourceType::VersionInfo
       - Returns None if not found (not an error)
    
    6. **STRINGTABLE detection helper**: `fn detect_string_tables(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>`. This function:
       - Probes common string table block IDs (1-128) and languages (1033, 2057, etc.)
       - Uses `resources.find_data("/String/{block}/{lang}")` pattern
       - For each found block, creates ResourceMetadata with ResourceType::StringTable
       - Returns vector of found string table resources
    
    7. **Error handling**: All functions use `?` operator for error propagation and wrap pelite errors into StringyError. Include comments explaining graceful degradation strategy for malformed resources.
    
    8. **Unit tests module**: Add `#[cfg(test)]` module with:
       - Test for `extract_resources` with invalid data (should return empty vec, not panic)
       - Test for `extract_resources` with minimal valid PE (may need synthetic data)
       - Tests for helper functions with mock data
       - Document that integration tests with real PE fixtures are in `tests/integration_pe.rs`
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(NEW)
    
    Add module declaration and re-exports for the new PE resources module:
    
    1. Add `pub mod pe_resources;` declaration after the existing comment
    2. Add re-export: `pub use pe_resources::extract_resources;` to make the main function easily accessible
    3. Add module-level documentation explaining that this module contains string extraction algorithms and format-specific extractors
    4. Keep the existing comment as it provides context about the module's purpose
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Integrate resource extraction into the PE parser:
    
    1. **Add import**: Add `use crate::extraction::pe_resources;` at the top with other imports (around line 1-7).
    
    2. **Extend parse method**: In `PeParser::parse()` method, after extracting imports and exports (around line 166-167), add resource extraction:
       - Call `pe_resources::extract_resources(data)` to get resource metadata
       - Handle the Result: if Ok, store in a variable; if Err, log/ignore and continue (graceful degradation)
       - This maintains backward compatibility - resource extraction failures don't break PE parsing
    
    3. **Update ContainerInfo construction**: In the `Ok(ContainerInfo { ... })` block (around line 169-175), add the new `resources` field:
       - Set to `Some(resource_metadata)` if extraction succeeded
       - Set to `None` if extraction failed or returned empty vector
       - This makes resources optional and doesn't break existing code
    
    4. **Add comment**: Add a comment explaining the dual-parser strategy: "Use pelite for resource extraction while goblin handles sections/imports/exports"
    
    5. **Error handling**: Ensure that any pelite errors during resource extraction are caught and don't propagate up - the PE parser should succeed even if resource extraction fails. This is important for malformed or packed binaries.
    
    ### src/lib.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Update public API exports to include new resource types:
    
    1. In the re-export section (around line 51-54), add the new resource-related types to the `pub use types::` block:
       - `ResourceMetadata`
       - `ResourceType`
       - `ResourceStringTable`
       - `ResourceStringEntry`
    
    2. These types should be exported so library users can work with resource metadata returned in `ContainerInfo`
    
    3. Maintain alphabetical ordering in the export list for consistency
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/container/pe.rs(MODIFY)
    - src/types.rs(MODIFY)
    
    Add integration tests for resource extraction:
    
    1. **Add new test function**: `test_pe_resource_enumeration()` after the existing tests (around line 119). This test:
       - Reads the PE fixture using `get_fixture_path("test_binary_pe.exe")`
       - Handles missing fixture gracefully with `.ok()` or early return with message
       - Parses the PE using `PeParser::new().parse(&pe_data)`
       - Checks if `container_info.resources` is Some or None
       - If Some, verifies the vector structure (can be empty for simple binaries)
       - Prints found resources for debugging
       - Documents that simple test binaries may not have VERSIONINFO/STRINGTABLE resources
    
    2. **Update existing tests**: In `test_pe_import_export_extraction` and `test_pe_section_classification`, add assertions that the new `resources` field exists (even if None) to verify the ContainerInfo structure is correct.
    
    3. **Add documentation comment**: Explain that the test PE fixture (compiled from test_binary.c) likely won't have resources since it's a minimal C program. Real-world PE binaries with resources should be tested manually or with additional fixtures.
    
    4. **Graceful fixture handling**: Ensure all tests handle missing `test_binary_pe.exe` gracefully since it's not currently in the repository. Tests should skip or print a helpful message rather than failing.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to note the resource extraction testing considerations:
    
    1. **Add note about PE resources**: After the "Rebuilding Fixtures" section (around line 40), add a new section "## Resource Testing" explaining:
       - The basic test_binary_pe.exe compiled from test_binary.c won't have VERSIONINFO or STRINGTABLE resources
       - These are typically added via .rc resource files during compilation
       - For testing resource extraction, developers should manually test with real-world PE binaries (e.g., Windows system DLLs)
       - Future work may include a resource-rich test fixture
    
    2. **Update PE build command**: Add a note that the current mingw-w64 build doesn't include resources, which is expected for Phase 1 testing.

    Developer Humor

    🔧 Phase 1: The Foundation \n> We're not extracting strings yet, just counting the boxes, \n> Like a warehouse inventory, checking all the locks. \n> VERSIONINFO? We see you there! \n> STRINGTABLE? We know you care! \n> But Phase 2's the party where we'll open up and share! 📦✨


    Execution Information

    Branch: main
    Commit: 37cfc7c

  9. traycerai commented on Nov 10, 2025

    @traycerai

    Plan

    Observations

    The codebase uses goblin for PE parsing with basic section classification including resources. The types module has structures for ContainerInfo, FoundString, and StringSource but lacks resource-specific metadata. The extraction module is minimal. Tests include PE integration but no resource-specific tests. Fixtures have a basic PE binary without resources. Pelite 0.10.0 provides excellent resource parsing APIs for VERSIONINFO and STRINGTABLE extraction. Dual-parser approach (goblin for structure, pelite for resources) is optimal to avoid regressions.

    Approach

    Focus on Phase 1 foundation: Add pelite dependency, create pe_resources extraction module with enumeration framework, extend types for resource metadata, integrate into PE parser with graceful error handling, and add basic tests. This sets up the structure for Phase 2 extraction without breaking existing functionality. Use dual-parser strategy: goblin for sections/imports/exports, pelite only for resources to minimize changes and risks.

    Reasoning

    Explored the repository structure using list_dir on root and src directories. Read key files like Cargo.toml, src/types.rs, src/container/pe.rs, src/extraction/mod.rs, and tests/integration_pe.rs to understand the PE parsing flow and type system. Searched for pelite documentation and examples to confirm API for resource enumeration and extraction. Verified that .rsrc sections are already classified but not parsed deeply.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add the pelite dependency under [dependencies] section in alphabetical order after goblin. Use version ^0.10 to get the latest stable release. This enables structured PE resource parsing without affecting existing goblin usage.

    📄 src/types.rs (MODIFY) 🔗

    Extend the types module for Phase 1 resource support:

    • Add ResourceType enum after existing enums (e.g., after StringSource). Variants: VersionInfo, StringTable, Manifest, Other(String). Derive Debug, Clone, PartialEq.

    • Add ResourceMetadata struct after ResourceType. Fields: r#type: ResourceType, language: u16, codepage: u16, data_offset: u32, data_size: u32, name: Option. Derive Debug, Clone.

    • Add ResourceStringEntry struct for STRINGTABLE. Fields: id: u16, value: String. Derive Debug, Clone.

    • Add ResourceStringTable struct. Fields: block: u16, language: u16, entries: Vec. Derive Debug, Clone.

    • Extend ContainerInfo by adding resources: Option<Vec<ResourceMetadata>> after sections field. This allows PE containers to include resource info while others have None.

    • Add impl From<pelite::Error> for StringyError at the end, mapping to StringyError::Parse with error.to_string().

    • Add impl From<pelite::resources::FindError> for StringyError similarly.

    📄 src/extraction/pe_resources.rs (MODIFY) 🔗

    References

    Create new module for PE resource extraction using pelite:

    • Add module docs explaining dual-parser strategy and Phase 1 scope (enumeration only).

    • Imports: use pelite::{PeFile, resources::*}; use crate::types::{ResourceMetadata, ResourceType, Result, StringyError};

    • Pub fn extract_pe_resources(pe_file: &PeFile) -> Result<Vec> {
      let resources = pe_file.resources()?;
      let mut metadata = Vec::new();
      // Enumerate types
      for (typ, name) in resources.types()? {
      let rtype = match typ {
      1 => ResourceType::Cursor, // etc., but focus on key ones
      6 => ResourceType::StringTable,
      16 => ResourceType::VersionInfo,
      _ => ResourceType::Other(name.to_string()?),
      };
      // Enumerate names/ids under type
      for (name_id, _) in resources.names(typ)? {
      // For each, get language and data info
      if let Ok(langs) = resources.languages(typ, name_id) {
      for lang in langs {
      if let Ok(info) = resources.info(typ, name_id, lang) {
      let meta = ResourceMetadata {
      r#type: rtype.clone(),
      language: lang.0,
      codepage: lang.1,
      data_offset: info.offset,
      data_size: info.size,
      name: Some(name_id.to_string()),
      };
      metadata.push(meta);
      }
      }
      }
      }
      }
      Ok(metadata)
      }

    • Add helper fn to convert resource IDs to names using standard mappings.

    • Add unit tests: #[cfg(test)] mod tests { use super::*; // mock pe_file or simple tests for enum conversion }

    📄 src/extraction/mod.rs (MODIFY) 🔗

    References

    Add pub mod pe_resources; at the end of the module declarations.

    Add pub use pe_resources::extract_pe_resources; for easy access.

    Update module docs to mention PE resource extraction capabilities.

    📄 src/container/pe.rs (MODIFY) 🔗

    References

    Integrate resource extraction into PeParser:

    • Add import: use crate::extraction::pe_resources::extract_pe_resources;

    • In parse(&self, data: &[u8]) -> Result, after parsing with goblin::pe::PE::parse(data)?, add:
      let resources = if let Ok(pe_file) = PeFile::from_bytes(data) {
      extract_pe_resources(&pe_file).ok()
      } else { None };

    • In ContainerInfo construction, set resources: resources,

    • Add comment: // Dual parser: goblin for structure, pelite for resources. Graceful degradation if resources fail.

    • Ensure this doesn't affect existing section classification for .rsrc.

    📄 src/lib.rs (MODIFY) 🔗

    References

    Re-export new types: In pub use crate::types::{ ..., ResourceMetadata, ResourceType, ResourceStringEntry, ResourceStringTable };

    This makes them available to users of the library.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add test for resource integration:

    #[test]
    fn test_pe_resources_extraction() {
    if let Ok(path) = get_fixture_path("test_binary_pe.exe") {
    let data = std::fs::read(&path).unwrap();
    let result = PeParser::new().parse(&data);
    assert!(result.is_ok());
    let info = result.unwrap();
    // For basic fixture, expect resources: None or empty
    match info.resources {
    Some(res) => assert!(!res.is_empty()), // if fixture has resources
    None => {} // acceptable for basic PE
    }
    } else {
    eprintln!("PE fixture missing, skipping resource test");
    }
    }

    Update existing tests to assert info.resources.is_some() or handle None.

    📄 README.md (MODIFY) 🔗

    Add section under Features: '- PE resource enumeration (VERSIONINFO, STRINGTABLE) using pelite'

    Add to Usage example: mention accessing container_info.resources for PE files.

    Add note on dual-parser strategy in Architecture section.

    Diagram

    sequenceDiagram
        participant App as Application
        participant Parser as PeParser
        participant Goblin as goblin::PE
        participant Pelite as pelite::PeFile
        participant Extractor as extract_pe_resources
        participant Info as ContainerInfo
    
        App->>Parser: parse(data)
        Parser->>Goblin: PE::parse(data)
        Goblin-->>Parser: PE structure (sections, imports, exports)
        
        Note over Parser,Pelite: Dual Parser for Resources
        Parser->>Pelite: PeFile::from_bytes(data)
        Pelite-->>Parser: PE file handle
        Parser->>Extractor: extract_pe_resources(&pe_file)
        Extractor->>Pelite: resources(), types(), names(), etc.
        Pelite-->>Extractor: Resource directory tree
        Extractor-->>Parser: Vec<ResourceMetadata>
        
        Parser->>Info: ContainerInfo { sections, imports, exports, resources }
        Info-->>App: Parsed info with resources
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    I have created the following plan after thorough exploration and analysis of the codebase. Follow the below plan verbatim. Trust the files and references. Do not re-verify what's written in the plan. Explore only when absolutely necessary. First implement all the proposed file changes and then I'll review all the changes together at the end.
    
    ### Observations
    
    The codebase uses goblin for PE parsing with basic section classification including resources. The types module has structures for ContainerInfo, FoundString, and StringSource but lacks resource-specific metadata. The extraction module is minimal. Tests include PE integration but no resource-specific tests. Fixtures have a basic PE binary without resources. Pelite 0.10.0 provides excellent resource parsing APIs for VERSIONINFO and STRINGTABLE extraction. Dual-parser approach (goblin for structure, pelite for resources) is optimal to avoid regressions.
    
    ### Approach
    
    Focus on Phase 1 foundation: Add pelite dependency, create pe_resources extraction module with enumeration framework, extend types for resource metadata, integrate into PE parser with graceful error handling, and add basic tests. This sets up the structure for Phase 2 extraction without breaking existing functionality. Use dual-parser strategy: goblin for sections/imports/exports, pelite only for resources to minimize changes and risks.
    
    ### Reasoning
    
    Explored the repository structure using list_dir on root and src directories. Read key files like Cargo.toml, src/types.rs, src/container/pe.rs, src/extraction/mod.rs, and tests/integration_pe.rs to understand the PE parsing flow and type system. Searched for pelite documentation and examples to confirm API for resource enumeration and extraction. Verified that .rsrc sections are already classified but not parsed deeply.
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant App as Application
        participant Parser as PeParser
        participant Goblin as goblin::PE
        participant Pelite as pelite::PeFile
        participant Extractor as extract_pe_resources
        participant Info as ContainerInfo
    
        App->>Parser: parse(data)
        Parser->>Goblin: PE::parse(data)
        Goblin-->>Parser: PE structure (sections, imports, exports)
        
        Note over Parser,Pelite: Dual Parser for Resources
        Parser->>Pelite: PeFile::from_bytes(data)
        Pelite-->>Parser: PE file handle
        Parser->>Extractor: extract_pe_resources(&pe_file)
        Extractor->>Pelite: resources(), types(), names(), etc.
        Pelite-->>Extractor: Resource directory tree
        Extractor-->>Parser: Vec<ResourceMetadata>
        
        Parser->>Info: ContainerInfo { sections, imports, exports, resources }
        Info-->>App: Parsed info with resources
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add the pelite dependency under [dependencies] section in alphabetical order after goblin. Use version ^0.10 to get the latest stable release. This enables structured PE resource parsing without affecting existing goblin usage.
    
    ### src/types.rs(MODIFY)
    
    Extend the types module for Phase 1 resource support:
    
    - Add `ResourceType` enum after existing enums (e.g., after StringSource). Variants: VersionInfo, StringTable, Manifest, Other(String). Derive Debug, Clone, PartialEq.
    
    - Add `ResourceMetadata` struct after ResourceType. Fields: r#type: ResourceType, language: u16, codepage: u16, data_offset: u32, data_size: u32, name: Option<String>. Derive Debug, Clone.
    
    - Add `ResourceStringEntry` struct for STRINGTABLE. Fields: id: u16, value: String. Derive Debug, Clone.
    
    - Add `ResourceStringTable` struct. Fields: block: u16, language: u16, entries: Vec<ResourceStringEntry>. Derive Debug, Clone.
    
    - Extend `ContainerInfo` by adding `resources: Option<Vec<ResourceMetadata>>` after `sections` field. This allows PE containers to include resource info while others have None.
    
    - Add `impl From<pelite::Error> for StringyError` at the end, mapping to StringyError::Parse with error.to_string().
    
    - Add `impl From<pelite::resources::FindError> for StringyError` similarly.
    
    ### src/extraction/pe_resources.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create new module for PE resource extraction using pelite:
    
    - Add module docs explaining dual-parser strategy and Phase 1 scope (enumeration only).
    
    - Imports: use pelite::{PeFile, resources::*}; use crate::types::{ResourceMetadata, ResourceType, Result, StringyError};
    
    - Pub fn extract_pe_resources(pe_file: &PeFile) -> Result<Vec<ResourceMetadata>> {
      let resources = pe_file.resources()?;
      let mut metadata = Vec::new();
      // Enumerate types
      for (typ, name) in resources.types()? {
        let rtype = match typ {
          1 => ResourceType::Cursor, // etc., but focus on key ones
          6 => ResourceType::StringTable,
          16 => ResourceType::VersionInfo,
          _ => ResourceType::Other(name.to_string()?),
        };
        // Enumerate names/ids under type
        for (name_id, _) in resources.names(typ)? {
          // For each, get language and data info
          if let Ok(langs) = resources.languages(typ, name_id) {
            for lang in langs {
              if let Ok(info) = resources.info(typ, name_id, lang) {
                let meta = ResourceMetadata {
                  r#type: rtype.clone(),
                  language: lang.0,
                  codepage: lang.1,
                  data_offset: info.offset,
                  data_size: info.size,
                  name: Some(name_id.to_string()),
                };
                metadata.push(meta);
              }
            }
          }
        }
      }
      Ok(metadata)
    }
    
    - Add helper fn to convert resource IDs to names using standard mappings.
    
    - Add unit tests: #[cfg(test)] mod tests { use super::*; // mock pe_file or simple tests for enum conversion }
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(MODIFY)
    
    Add pub mod pe_resources; at the end of the module declarations.
    
    Add pub use pe_resources::extract_pe_resources; for easy access.
    
    Update module docs to mention PE resource extraction capabilities.
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(MODIFY)
    - src/types.rs(MODIFY)
    
    Integrate resource extraction into PeParser:
    
    - Add import: use crate::extraction::pe_resources::extract_pe_resources;
    
    - In parse(&self, data: &[u8]) -> Result<ContainerInfo>, after parsing with goblin::pe::PE::parse(data)?, add:
      let resources = if let Ok(pe_file) = PeFile::from_bytes(data) {
        extract_pe_resources(&pe_file).ok()
      } else { None };
    
    - In ContainerInfo construction, set resources: resources,
    
    - Add comment: // Dual parser: goblin for structure, pelite for resources. Graceful degradation if resources fail.
    
    - Ensure this doesn't affect existing section classification for .rsrc.
    
    ### src/lib.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Re-export new types: In pub use crate::types::{ ..., ResourceMetadata, ResourceType, ResourceStringEntry, ResourceStringTable };
    
    This makes them available to users of the library.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/container/pe.rs(MODIFY)
    
    Add test for resource integration:
    
    #[test]
    fn test_pe_resources_extraction() {
      if let Ok(path) = get_fixture_path("test_binary_pe.exe") {
        let data = std::fs::read(&path).unwrap();
        let result = PeParser::new().parse(&data);
        assert!(result.is_ok());
        let info = result.unwrap();
        // For basic fixture, expect resources: None or empty
        match info.resources {
          Some(res) => assert!(!res.is_empty()), // if fixture has resources
          None => {} // acceptable for basic PE
        }
      } else {
        eprintln!("PE fixture missing, skipping resource test");
      }
    }
    
    Update existing tests to assert info.resources.is_some() or handle None.
    
    ### README.md(MODIFY)
    
    Add section under Features: '- PE resource enumeration (VERSIONINFO, STRINGTABLE) using pelite'
    
    Add to Usage example: mention accessing container_info.resources for PE files.
    
    Add note on dual-parser strategy in Architecture section.

    Developer Humor

    In the land of PE files, where resources hide deep, \n> Goblin scouts the structure, but can't make the leap. \n> Pelite joins the party, with finesse and with flair, \n> Extracting VERSIONINFO, without a single care! \n> STRINGTABLEs unlocked, metadata in view, \n> StringyMcStringFace now knows what binaries do! 🎉


    Execution Information

    Branch: 3-implement-pe-section-classification-and-importexport-table-parsing
    Commit: 5ac6f0c


    💡 Tips

    Supported Commands (Inside Comments)

    • Use @traycerai generate to iterate on the previous version of the implementation plan.

    Supported Commands (Inside Description)

    • Add @traycerai ignore anywhere in the ticket description to prevent this ticket from being processed.
    • Add @traycerai branch:<branch-name> anywhere in the ticket description to specify the target branch for the implementation plan.

    Community

    • Join our Discord Community to get help, request features, and share feedback.
    • Follow us on X/Twitter for updates and announcements.
  10. traycerai commented on Nov 10, 2025

    @traycerai

    I have updated the Implementation Plan to reflect the recent changes in the ticket.

    Previous Plan

    Observations

    The codebase is well-structured with clear separation of concerns. The PE parser already classifies .rsrc sections as SectionType::Resources with high weight (9.0), and the type system already includes StringSource::ResourceString and Tag::Resource. The extraction module is currently empty, making it the perfect place for the new PE resource extraction functionality.

    Key findings:

    • pelite 0.10.0 is the latest stable version with comprehensive resource parsing APIs
    • Dual parser strategy is viable: goblin for format detection/sections/imports/exports, pelite specifically for resource extraction
    • Type system is mostly ready; only need to add resource-specific metadata structs
    • Error handling pattern exists: From<goblin::error::Error> for StringyError
    • Test fixtures exist but PE fixture is missing from the repository (needs to be generated)

    Approach

    Implement Phase 1 foundation for PE resource extraction by:

    1. Adding pelite dependency to Cargo.toml
    2. Creating resource extraction module with enumeration framework
    3. Extending types with resource metadata structures
    4. Integrating resource extraction into PE parser
    5. Adding error conversion for pelite errors
    6. Setting up unit tests with graceful handling for missing fixtures

    Reasoning

    Started by exploring the repository structure and understanding the existing PE parser implementation. Read the types, container parsers, and documentation to understand the architecture. Searched pelite documentation and examples to understand its API for VERSIONINFO and STRINGTABLE extraction. Verified the dual-parser strategy is feasible and identified all integration points.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add pelite dependency to the dependencies section. Use version "0.10" to get the latest 0.10.x release (currently 0.10.0). Place it after the existing dependencies in alphabetical order between goblin and serde. This enables PE resource parsing capabilities while keeping goblin for general PE structure parsing.

    📄 src/types.rs (MODIFY) 🔗

    Add new resource-related data structures to support Phase 1 framework:

    1. ResourceMetadata struct: Add after ExportInfo (around line 141). Include fields for:

      • resource_type: ResourceType - enum variant (VersionInfo, StringTable, etc.)
      • language: u32 - language/locale identifier
      • data_size: usize - size of resource data
      • offset: Option<u64> - file offset if available
      • Derive Debug, Clone
    2. ResourceType enum: Add before ResourceMetadata. Include variants:

      • VersionInfo - RT_VERSION resources
      • StringTable - RT_STRING resources
      • Manifest - RT_MANIFEST resources
      • Other(String) - for future resource types
      • Derive Debug, Clone, PartialEq, Eq
    3. ResourceStringTable struct: Add after ResourceMetadata. Include fields:

      • language: u32 - language identifier
      • entries: Vec<ResourceStringEntry> - string entries
      • Derive Debug, Clone
    4. ResourceStringEntry struct: Add after ResourceStringTable. Include fields:

      • id: u32 - string resource ID
      • value: String - the actual string content
      • Derive Debug, Clone
    5. Extend ContainerInfo: Add optional field resources: Option<Vec<ResourceMetadata>> after the exports field (around line 87). This allows PE binaries to include resource metadata while other formats return None.

    6. Add pelite error conversion: Add impl From<pelite::Error> for StringyError after the existing From<goblin::error::Error> implementation (around line 194). Convert pelite errors to StringyError::ParseError with the error message.

    7. Add FindError conversion: Add impl From<pelite::resources::FindError> for StringyError to handle resource lookup failures. Map to StringyError::ParseError with descriptive message.

    📄 src/extraction/pe_resources.rs (NEW) 🔗

    References

    Create the PE resource extraction module with Phase 1 framework:

    1. Module documentation: Add comprehensive doc comments explaining the purpose (PE resource extraction using pelite), the dual-parser strategy, and Phase 1 vs Phase 2 scope.

    2. Imports: Import necessary types from pelite (pelite::PeFile, pelite::resources::*), crate types (ResourceMetadata, ResourceType, ResourceStringTable, ResourceStringEntry, Result, StringyError), and std types.

    3. Main extraction function: pub fn extract_resources(data: &[u8]) -> Result<Vec<ResourceMetadata>>. This function:

      • Attempts to parse PE using pelite::PeFile::from_bytes(data)
      • Calls pe.resources() to get resource directory
      • Calls enumerate_resources() helper to walk the resource tree
      • Returns vector of ResourceMetadata or empty vec on error (graceful degradation)
      • Wraps all pelite errors appropriately
    4. Resource enumeration helper: fn enumerate_resources(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>. This function:

      • Walks the resource directory tree using pelite's API
      • Identifies VERSIONINFO (type 16), STRINGTABLE (type 6), and MANIFEST (type 24) resources
      • For each resource found, creates a ResourceMetadata entry with type, language, and size
      • Returns collected metadata vector
      • Handles malformed resource directories gracefully (log and continue)
    5. VERSIONINFO detection helper: fn detect_version_info(resources: &pelite::resources::Resources) -> Result<Option<ResourceMetadata>>. This function:

      • Attempts to call resources.version_info() to check if VERSIONINFO exists
      • If found, extracts language from translation() array
      • Returns ResourceMetadata with ResourceType::VersionInfo
      • Returns None if not found (not an error)
    6. STRINGTABLE detection helper: fn detect_string_tables(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>. This function:

      • Probes common string table block IDs (1-128) and languages (1033, 2057, etc.)
      • Uses resources.find_data("/String/{block}/{lang}") pattern
      • For each found block, creates ResourceMetadata with ResourceType::StringTable
      • Returns vector of found string table resources
    7. Error handling: All functions use ? operator for error propagation and wrap pelite errors into StringyError. Include comments explaining graceful degradation strategy for malformed resources.

    8. Unit tests module: Add #[cfg(test)] module with:

      • Test for extract_resources with invalid data (should return empty vec, not panic)
      • Test for extract_resources with minimal valid PE (may need synthetic data)
      • Tests for helper functions with mock data
      • Document that integration tests with real PE fixtures are in tests/integration_pe.rs
    📄 src/extraction/mod.rs (MODIFY) 🔗

    References

    Add module declaration and re-exports for the new PE resources module:

    1. Add pub mod pe_resources; declaration after the existing comment
    2. Add re-export: pub use pe_resources::extract_resources; to make the main function easily accessible
    3. Add module-level documentation explaining that this module contains string extraction algorithms and format-specific extractors
    4. Keep the existing comment as it provides context about the module's purpose
    📄 src/container/pe.rs (MODIFY) 🔗

    References

    Integrate resource extraction into the PE parser:

    1. Add import: Add use crate::extraction::pe_resources; at the top with other imports (around line 1-7).

    2. Extend parse method: In PeParser::parse() method, after extracting imports and exports (around line 166-167), add resource extraction:

      • Call pe_resources::extract_resources(data) to get resource metadata
      • Handle the Result: if Ok, store in a variable; if Err, log/ignore and continue (graceful degradation)
      • This maintains backward compatibility - resource extraction failures don't break PE parsing
    3. Update ContainerInfo construction: In the Ok(ContainerInfo { ... }) block (around line 169-175), add the new resources field:

      • Set to Some(resource_metadata) if extraction succeeded
      • Set to None if extraction failed or returned empty vector
      • This makes resources optional and doesn't break existing code
    4. Add comment: Add a comment explaining the dual-parser strategy: "Use pelite for resource extraction while goblin handles sections/imports/exports"

    5. Error handling: Ensure that any pelite errors during resource extraction are caught and don't propagate up - the PE parser should succeed even if resource extraction fails. This is important for malformed or packed binaries.

    📄 src/lib.rs (MODIFY) 🔗

    References

    Update public API exports to include new resource types:

    1. In the re-export section (around line 51-54), add the new resource-related types to the pub use types:: block:

      • ResourceMetadata
      • ResourceType
      • ResourceStringTable
      • ResourceStringEntry
    2. These types should be exported so library users can work with resource metadata returned in ContainerInfo

    3. Maintain alphabetical ordering in the export list for consistency

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add integration tests for resource extraction:

    1. Add new test function: test_pe_resource_enumeration() after the existing tests (around line 119). This test:

      • Reads the PE fixture using get_fixture_path("test_binary_pe.exe")
      • Handles missing fixture gracefully with .ok() or early return with message
      • Parses the PE using PeParser::new().parse(&pe_data)
      • Checks if container_info.resources is Some or None
      • If Some, verifies the vector structure (can be empty for simple binaries)
      • Prints found resources for debugging
      • Documents that simple test binaries may not have VERSIONINFO/STRINGTABLE resources
    2. Update existing tests: In test_pe_import_export_extraction and test_pe_section_classification, add assertions that the new resources field exists (even if None) to verify the ContainerInfo structure is correct.

    3. Add documentation comment: Explain that the test PE fixture (compiled from test_binary.c) likely won't have resources since it's a minimal C program. Real-world PE binaries with resources should be tested manually or with additional fixtures.

    4. Graceful fixture handling: Ensure all tests handle missing test_binary_pe.exe gracefully since it's not currently in the repository. Tests should skip or print a helpful message rather than failing.

    📄 tests/fixtures/README.md (MODIFY) 🔗

    Update the fixtures documentation to note the resource extraction testing considerations:

    1. Add note about PE resources: After the "Rebuilding Fixtures" section (around line 40), add a new section "## Resource Testing" explaining:

      • The basic test_binary_pe.exe compiled from test_binary.c won't have VERSIONINFO or STRINGTABLE resources
      • These are typically added via .rc resource files during compilation
      • For testing resource extraction, developers should manually test with real-world PE binaries (e.g., Windows system DLLs)
      • Future work may include a resource-rich test fixture
    2. Update PE build command: Add a note that the current mingw-w64 build doesn't include resources, which is expected for Phase 1 testing.

    Diagram

    sequenceDiagram
        participant User
        participant PeParser
        participant Goblin
        participant PeResources
        participant Pelite
        participant ContainerInfo
    
        User->>PeParser: parse(data)
        
        Note over PeParser: Dual Parser Strategy
        
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: Sections, Imports, Exports
        
        PeParser->>PeResources: extract_resources(data)
        PeResources->>Pelite: PeFile::from_bytes(data)
        Pelite-->>PeResources: PeFile instance
        
        PeResources->>Pelite: pe.resources()
        Pelite-->>PeResources: Resources directory
        
        PeResources->>PeResources: enumerate_resources()
        Note over PeResources: Detect VERSIONINFO,<br/>STRINGTABLE, etc.
        
        alt Resources found
            PeResources-->>PeParser: Vec<ResourceMetadata>
            PeParser->>ContainerInfo: Create with resources: Some(...)
        else No resources or error
            PeResources-->>PeParser: Empty vec or error
            PeParser->>ContainerInfo: Create with resources: None
        end
        
        ContainerInfo-->>User: ContainerInfo with optional resources
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    I have created the following plan after thorough exploration and analysis of the codebase. Follow the below plan verbatim. Trust the files and references. Do not re-verify what's written in the plan. Explore only when absolutely necessary. First implement all the proposed file changes and then I'll review all the changes together at the end.
    
    ### Observations
    
    The codebase is well-structured with clear separation of concerns. The PE parser already classifies `.rsrc` sections as `SectionType::Resources` with high weight (9.0), and the type system already includes `StringSource::ResourceString` and `Tag::Resource`. The extraction module is currently empty, making it the perfect place for the new PE resource extraction functionality.
    
    Key findings:
    - **pelite 0.10.0** is the latest stable version with comprehensive resource parsing APIs
    - **Dual parser strategy** is viable: goblin for format detection/sections/imports/exports, pelite specifically for resource extraction
    - **Type system** is mostly ready; only need to add resource-specific metadata structs
    - **Error handling** pattern exists: `From<goblin::error::Error>` for `StringyError`
    - **Test fixtures** exist but PE fixture is missing from the repository (needs to be generated)
    
    
    ### Approach
    
    Implement Phase 1 foundation for PE resource extraction by:
    1. Adding pelite dependency to Cargo.toml
    2. Creating resource extraction module with enumeration framework
    3. Extending types with resource metadata structures
    4. Integrating resource extraction into PE parser
    5. Adding error conversion for pelite errors
    6. Setting up unit tests with graceful handling for missing fixtures
    
    
    ### Reasoning
    
    Started by exploring the repository structure and understanding the existing PE parser implementation. Read the types, container parsers, and documentation to understand the architecture. Searched pelite documentation and examples to understand its API for VERSIONINFO and STRINGTABLE extraction. Verified the dual-parser strategy is feasible and identified all integration points.
    
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant User
        participant PeParser
        participant Goblin
        participant PeResources
        participant Pelite
        participant ContainerInfo
    
        User->>PeParser: parse(data)
        
        Note over PeParser: Dual Parser Strategy
        
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: Sections, Imports, Exports
        
        PeParser->>PeResources: extract_resources(data)
        PeResources->>Pelite: PeFile::from_bytes(data)
        Pelite-->>PeResources: PeFile instance
        
        PeResources->>Pelite: pe.resources()
        Pelite-->>PeResources: Resources directory
        
        PeResources->>PeResources: enumerate_resources()
        Note over PeResources: Detect VERSIONINFO,<br/>STRINGTABLE, etc.
        
        alt Resources found
            PeResources-->>PeParser: Vec<ResourceMetadata>
            PeParser->>ContainerInfo: Create with resources: Some(...)
        else No resources or error
            PeResources-->>PeParser: Empty vec or error
            PeParser->>ContainerInfo: Create with resources: None
        end
        
        ContainerInfo-->>User: ContainerInfo with optional resources
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add pelite dependency to the dependencies section. Use version `"0.10"` to get the latest 0.10.x release (currently 0.10.0). Place it after the existing dependencies in alphabetical order between `goblin` and `serde`. This enables PE resource parsing capabilities while keeping goblin for general PE structure parsing.
    
    ### src/types.rs(MODIFY)
    
    Add new resource-related data structures to support Phase 1 framework:
    
    1. **ResourceMetadata struct**: Add after `ExportInfo` (around line 141). Include fields for:
       - `resource_type: ResourceType` - enum variant (VersionInfo, StringTable, etc.)
       - `language: u32` - language/locale identifier
       - `data_size: usize` - size of resource data
       - `offset: Option<u64>` - file offset if available
       - Derive `Debug, Clone`
    
    2. **ResourceType enum**: Add before `ResourceMetadata`. Include variants:
       - `VersionInfo` - RT_VERSION resources
       - `StringTable` - RT_STRING resources  
       - `Manifest` - RT_MANIFEST resources
       - `Other(String)` - for future resource types
       - Derive `Debug, Clone, PartialEq, Eq`
    
    3. **ResourceStringTable struct**: Add after `ResourceMetadata`. Include fields:
       - `language: u32` - language identifier
       - `entries: Vec<ResourceStringEntry>` - string entries
       - Derive `Debug, Clone`
    
    4. **ResourceStringEntry struct**: Add after `ResourceStringTable`. Include fields:
       - `id: u32` - string resource ID
       - `value: String` - the actual string content
       - Derive `Debug, Clone`
    
    5. **Extend ContainerInfo**: Add optional field `resources: Option<Vec<ResourceMetadata>>` after the `exports` field (around line 87). This allows PE binaries to include resource metadata while other formats return None.
    
    6. **Add pelite error conversion**: Add `impl From<pelite::Error>` for `StringyError` after the existing `From<goblin::error::Error>` implementation (around line 194). Convert pelite errors to `StringyError::ParseError` with the error message.
    
    7. **Add FindError conversion**: Add `impl From<pelite::resources::FindError>` for `StringyError` to handle resource lookup failures. Map to `StringyError::ParseError` with descriptive message.
    
    ### src/extraction/pe_resources.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create the PE resource extraction module with Phase 1 framework:
    
    1. **Module documentation**: Add comprehensive doc comments explaining the purpose (PE resource extraction using pelite), the dual-parser strategy, and Phase 1 vs Phase 2 scope.
    
    2. **Imports**: Import necessary types from pelite (`pelite::PeFile`, `pelite::resources::*`), crate types (`ResourceMetadata`, `ResourceType`, `ResourceStringTable`, `ResourceStringEntry`, `Result`, `StringyError`), and std types.
    
    3. **Main extraction function**: `pub fn extract_resources(data: &[u8]) -> Result<Vec<ResourceMetadata>>`. This function:
       - Attempts to parse PE using `pelite::PeFile::from_bytes(data)`
       - Calls `pe.resources()` to get resource directory
       - Calls `enumerate_resources()` helper to walk the resource tree
       - Returns vector of ResourceMetadata or empty vec on error (graceful degradation)
       - Wraps all pelite errors appropriately
    
    4. **Resource enumeration helper**: `fn enumerate_resources(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>`. This function:
       - Walks the resource directory tree using pelite's API
       - Identifies VERSIONINFO (type 16), STRINGTABLE (type 6), and MANIFEST (type 24) resources
       - For each resource found, creates a `ResourceMetadata` entry with type, language, and size
       - Returns collected metadata vector
       - Handles malformed resource directories gracefully (log and continue)
    
    5. **VERSIONINFO detection helper**: `fn detect_version_info(resources: &pelite::resources::Resources) -> Result<Option<ResourceMetadata>>`. This function:
       - Attempts to call `resources.version_info()` to check if VERSIONINFO exists
       - If found, extracts language from `translation()` array
       - Returns ResourceMetadata with ResourceType::VersionInfo
       - Returns None if not found (not an error)
    
    6. **STRINGTABLE detection helper**: `fn detect_string_tables(resources: &pelite::resources::Resources) -> Result<Vec<ResourceMetadata>>`. This function:
       - Probes common string table block IDs (1-128) and languages (1033, 2057, etc.)
       - Uses `resources.find_data("/String/{block}/{lang}")` pattern
       - For each found block, creates ResourceMetadata with ResourceType::StringTable
       - Returns vector of found string table resources
    
    7. **Error handling**: All functions use `?` operator for error propagation and wrap pelite errors into StringyError. Include comments explaining graceful degradation strategy for malformed resources.
    
    8. **Unit tests module**: Add `#[cfg(test)]` module with:
       - Test for `extract_resources` with invalid data (should return empty vec, not panic)
       - Test for `extract_resources` with minimal valid PE (may need synthetic data)
       - Tests for helper functions with mock data
       - Document that integration tests with real PE fixtures are in `tests/integration_pe.rs`
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(NEW)
    
    Add module declaration and re-exports for the new PE resources module:
    
    1. Add `pub mod pe_resources;` declaration after the existing comment
    2. Add re-export: `pub use pe_resources::extract_resources;` to make the main function easily accessible
    3. Add module-level documentation explaining that this module contains string extraction algorithms and format-specific extractors
    4. Keep the existing comment as it provides context about the module's purpose
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Integrate resource extraction into the PE parser:
    
    1. **Add import**: Add `use crate::extraction::pe_resources;` at the top with other imports (around line 1-7).
    
    2. **Extend parse method**: In `PeParser::parse()` method, after extracting imports and exports (around line 166-167), add resource extraction:
       - Call `pe_resources::extract_resources(data)` to get resource metadata
       - Handle the Result: if Ok, store in a variable; if Err, log/ignore and continue (graceful degradation)
       - This maintains backward compatibility - resource extraction failures don't break PE parsing
    
    3. **Update ContainerInfo construction**: In the `Ok(ContainerInfo { ... })` block (around line 169-175), add the new `resources` field:
       - Set to `Some(resource_metadata)` if extraction succeeded
       - Set to `None` if extraction failed or returned empty vector
       - This makes resources optional and doesn't break existing code
    
    4. **Add comment**: Add a comment explaining the dual-parser strategy: "Use pelite for resource extraction while goblin handles sections/imports/exports"
    
    5. **Error handling**: Ensure that any pelite errors during resource extraction are caught and don't propagate up - the PE parser should succeed even if resource extraction fails. This is important for malformed or packed binaries.
    
    ### src/lib.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Update public API exports to include new resource types:
    
    1. In the re-export section (around line 51-54), add the new resource-related types to the `pub use types::` block:
       - `ResourceMetadata`
       - `ResourceType`
       - `ResourceStringTable`
       - `ResourceStringEntry`
    
    2. These types should be exported so library users can work with resource metadata returned in `ContainerInfo`
    
    3. Maintain alphabetical ordering in the export list for consistency
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/container/pe.rs(MODIFY)
    - src/types.rs(MODIFY)
    
    Add integration tests for resource extraction:
    
    1. **Add new test function**: `test_pe_resource_enumeration()` after the existing tests (around line 119). This test:
       - Reads the PE fixture using `get_fixture_path("test_binary_pe.exe")`
       - Handles missing fixture gracefully with `.ok()` or early return with message
       - Parses the PE using `PeParser::new().parse(&pe_data)`
       - Checks if `container_info.resources` is Some or None
       - If Some, verifies the vector structure (can be empty for simple binaries)
       - Prints found resources for debugging
       - Documents that simple test binaries may not have VERSIONINFO/STRINGTABLE resources
    
    2. **Update existing tests**: In `test_pe_import_export_extraction` and `test_pe_section_classification`, add assertions that the new `resources` field exists (even if None) to verify the ContainerInfo structure is correct.
    
    3. **Add documentation comment**: Explain that the test PE fixture (compiled from test_binary.c) likely won't have resources since it's a minimal C program. Real-world PE binaries with resources should be tested manually or with additional fixtures.
    
    4. **Graceful fixture handling**: Ensure all tests handle missing `test_binary_pe.exe` gracefully since it's not currently in the repository. Tests should skip or print a helpful message rather than failing.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to note the resource extraction testing considerations:
    
    1. **Add note about PE resources**: After the "Rebuilding Fixtures" section (around line 40), add a new section "## Resource Testing" explaining:
       - The basic test_binary_pe.exe compiled from test_binary.c won't have VERSIONINFO or STRINGTABLE resources
       - These are typically added via .rc resource files during compilation
       - For testing resource extraction, developers should manually test with real-world PE binaries (e.g., Windows system DLLs)
       - Future work may include a resource-rich test fixture
    
    2. **Update PE build command**: Add a note that the current mingw-w64 build doesn't include resources, which is expected for Phase 1 testing.

    Developer Humor

    🔧 Phase 1: The Foundation \n> We're not extracting strings yet, just counting the boxes, \n> Like a warehouse inventory, checking all the locks. \n> VERSIONINFO? We see you there! \n> STRINGTABLE? We know you care! \n> But Phase 2's the party where we'll open up and share! 📦✨


    Execution Information

    Branch: main
    Commit: 37cfc7c

    New Plan

    Observations

    The codebase uses goblin for PE parsing with basic section classification including resources. The types module has structures for ContainerInfo, FoundString, and StringSource but lacks resource-specific metadata. The extraction module is minimal. Tests include PE integration but no resource-specific tests. Fixtures have a basic PE binary without resources. Pelite 0.10.0 provides excellent resource parsing APIs for VERSIONINFO and STRINGTABLE extraction. Dual-parser approach (goblin for structure, pelite for resources) is optimal to avoid regressions.

    Approach

    Focus on Phase 1 foundation: Add pelite dependency, create pe_resources extraction module with enumeration framework, extend types for resource metadata, integrate into PE parser with graceful error handling, and add basic tests. This sets up the structure for Phase 2 extraction without breaking existing functionality. Use dual-parser strategy: goblin for sections/imports/exports, pelite only for resources to minimize changes and risks.

    Reasoning

    Explored the repository structure using list_dir on root and src directories. Read key files like Cargo.toml, src/types.rs, src/container/pe.rs, src/extraction/mod.rs, and tests/integration_pe.rs to understand the PE parsing flow and type system. Searched for pelite documentation and examples to confirm API for resource enumeration and extraction. Verified that .rsrc sections are already classified but not parsed deeply.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add the pelite dependency under [dependencies] section in alphabetical order after goblin. Use version ^0.10 to get the latest stable release. This enables structured PE resource parsing without affecting existing goblin usage.

    📄 src/types.rs (MODIFY) 🔗

    Extend the types module for Phase 1 resource support:

    • Add ResourceType enum after existing enums (e.g., after StringSource). Variants: VersionInfo, StringTable, Manifest, Other(String). Derive Debug, Clone, PartialEq.

    • Add ResourceMetadata struct after ResourceType. Fields: r#type: ResourceType, language: u16, codepage: u16, data_offset: u32, data_size: u32, name: Option. Derive Debug, Clone.

    • Add ResourceStringEntry struct for STRINGTABLE. Fields: id: u16, value: String. Derive Debug, Clone.

    • Add ResourceStringTable struct. Fields: block: u16, language: u16, entries: Vec. Derive Debug, Clone.

    • Extend ContainerInfo by adding resources: Option<Vec<ResourceMetadata>> after sections field. This allows PE containers to include resource info while others have None.

    • Add impl From<pelite::Error> for StringyError at the end, mapping to StringyError::Parse with error.to_string().

    • Add impl From<pelite::resources::FindError> for StringyError similarly.

    📄 src/extraction/pe_resources.rs (MODIFY) 🔗

    References

    Create new module for PE resource extraction using pelite:

    • Add module docs explaining dual-parser strategy and Phase 1 scope (enumeration only).

    • Imports: use pelite::{PeFile, resources::*}; use crate::types::{ResourceMetadata, ResourceType, Result, StringyError};

    • Pub fn extract_pe_resources(pe_file: &PeFile) -> Result<Vec> {
      let resources = pe_file.resources()?;
      let mut metadata = Vec::new();
      // Enumerate types
      for (typ, name) in resources.types()? {
      let rtype = match typ {
      1 => ResourceType::Cursor, // etc., but focus on key ones
      6 => ResourceType::StringTable,
      16 => ResourceType::VersionInfo,
      _ => ResourceType::Other(name.to_string()?),
      };
      // Enumerate names/ids under type
      for (name_id, _) in resources.names(typ)? {
      // For each, get language and data info
      if let Ok(langs) = resources.languages(typ, name_id) {
      for lang in langs {
      if let Ok(info) = resources.info(typ, name_id, lang) {
      let meta = ResourceMetadata {
      r#type: rtype.clone(),
      language: lang.0,
      codepage: lang.1,
      data_offset: info.offset,
      data_size: info.size,
      name: Some(name_id.to_string()),
      };
      metadata.push(meta);
      }
      }
      }
      }
      }
      Ok(metadata)
      }

    • Add helper fn to convert resource IDs to names using standard mappings.

    • Add unit tests: #[cfg(test)] mod tests { use super::*; // mock pe_file or simple tests for enum conversion }

    📄 src/extraction/mod.rs (MODIFY) 🔗

    References

    Add pub mod pe_resources; at the end of the module declarations.

    Add pub use pe_resources::extract_pe_resources; for easy access.

    Update module docs to mention PE resource extraction capabilities.

    📄 src/container/pe.rs (MODIFY) 🔗

    References

    Integrate resource extraction into PeParser:

    • Add import: use crate::extraction::pe_resources::extract_pe_resources;

    • In parse(&self, data: &[u8]) -> Result, after parsing with goblin::pe::PE::parse(data)?, add:
      let resources = if let Ok(pe_file) = PeFile::from_bytes(data) {
      extract_pe_resources(&pe_file).ok()
      } else { None };

    • In ContainerInfo construction, set resources: resources,

    • Add comment: // Dual parser: goblin for structure, pelite for resources. Graceful degradation if resources fail.

    • Ensure this doesn't affect existing section classification for .rsrc.

    📄 src/lib.rs (MODIFY) 🔗

    References

    Re-export new types: In pub use crate::types::{ ..., ResourceMetadata, ResourceType, ResourceStringEntry, ResourceStringTable };

    This makes them available to users of the library.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add test for resource integration:

    #[test]
    fn test_pe_resources_extraction() {
    if let Ok(path) = get_fixture_path("test_binary_pe.exe") {
    let data = std::fs::read(&path).unwrap();
    let result = PeParser::new().parse(&data);
    assert!(result.is_ok());
    let info = result.unwrap();
    // For basic fixture, expect resources: None or empty
    match info.resources {
    Some(res) => assert!(!res.is_empty()), // if fixture has resources
    None => {} // acceptable for basic PE
    }
    } else {
    eprintln!("PE fixture missing, skipping resource test");
    }
    }

    Update existing tests to assert info.resources.is_some() or handle None.

    📄 README.md (MODIFY) 🔗

    Add section under Features: '- PE resource enumeration (VERSIONINFO, STRINGTABLE) using pelite'

    Add to Usage example: mention accessing container_info.resources for PE files.

    Add note on dual-parser strategy in Architecture section.

    Diagram

    sequenceDiagram
        participant App as Application
        participant Parser as PeParser
        participant Goblin as goblin::PE
        participant Pelite as pelite::PeFile
        participant Extractor as extract_pe_resources
        participant Info as ContainerInfo
    
        App->>Parser: parse(data)
        Parser->>Goblin: PE::parse(data)
        Goblin-->>Parser: PE structure (sections, imports, exports)
        
        Note over Parser,Pelite: Dual Parser for Resources
        Parser->>Pelite: PeFile::from_bytes(data)
        Pelite-->>Parser: PE file handle
        Parser->>Extractor: extract_pe_resources(&pe_file)
        Extractor->>Pelite: resources(), types(), names(), etc.
        Pelite-->>Extractor: Resource directory tree
        Extractor-->>Parser: Vec<ResourceMetadata>
        
        Parser->>Info: ContainerInfo { sections, imports, exports, resources }
        Info-->>App: Parsed info with resources
    
    Loading

    Import In IDE

    VSCode Icon Cursor Icon Windsurf Icon

    🤖 Prompt for AI Agents
    I have created the following plan after thorough exploration and analysis of the codebase. Follow the below plan verbatim. Trust the files and references. Do not re-verify what's written in the plan. Explore only when absolutely necessary. First implement all the proposed file changes and then I'll review all the changes together at the end.
    
    ### Observations
    
    The codebase uses goblin for PE parsing with basic section classification including resources. The types module has structures for ContainerInfo, FoundString, and StringSource but lacks resource-specific metadata. The extraction module is minimal. Tests include PE integration but no resource-specific tests. Fixtures have a basic PE binary without resources. Pelite 0.10.0 provides excellent resource parsing APIs for VERSIONINFO and STRINGTABLE extraction. Dual-parser approach (goblin for structure, pelite for resources) is optimal to avoid regressions.
    
    ### Approach
    
    Focus on Phase 1 foundation: Add pelite dependency, create pe_resources extraction module with enumeration framework, extend types for resource metadata, integrate into PE parser with graceful error handling, and add basic tests. This sets up the structure for Phase 2 extraction without breaking existing functionality. Use dual-parser strategy: goblin for sections/imports/exports, pelite only for resources to minimize changes and risks.
    
    ### Reasoning
    
    Explored the repository structure using list_dir on root and src directories. Read key files like Cargo.toml, src/types.rs, src/container/pe.rs, src/extraction/mod.rs, and tests/integration_pe.rs to understand the PE parsing flow and type system. Searched for pelite documentation and examples to confirm API for resource enumeration and extraction. Verified that .rsrc sections are already classified but not parsed deeply.
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant App as Application
        participant Parser as PeParser
        participant Goblin as goblin::PE
        participant Pelite as pelite::PeFile
        participant Extractor as extract_pe_resources
        participant Info as ContainerInfo
    
        App->>Parser: parse(data)
        Parser->>Goblin: PE::parse(data)
        Goblin-->>Parser: PE structure (sections, imports, exports)
        
        Note over Parser,Pelite: Dual Parser for Resources
        Parser->>Pelite: PeFile::from_bytes(data)
        Pelite-->>Parser: PE file handle
        Parser->>Extractor: extract_pe_resources(&pe_file)
        Extractor->>Pelite: resources(), types(), names(), etc.
        Pelite-->>Extractor: Resource directory tree
        Extractor-->>Parser: Vec<ResourceMetadata>
        
        Parser->>Info: ContainerInfo { sections, imports, exports, resources }
        Info-->>App: Parsed info with resources
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add the pelite dependency under [dependencies] section in alphabetical order after goblin. Use version ^0.10 to get the latest stable release. This enables structured PE resource parsing without affecting existing goblin usage.
    
    ### src/types.rs(MODIFY)
    
    Extend the types module for Phase 1 resource support:
    
    - Add `ResourceType` enum after existing enums (e.g., after StringSource). Variants: VersionInfo, StringTable, Manifest, Other(String). Derive Debug, Clone, PartialEq.
    
    - Add `ResourceMetadata` struct after ResourceType. Fields: r#type: ResourceType, language: u16, codepage: u16, data_offset: u32, data_size: u32, name: Option<String>. Derive Debug, Clone.
    
    - Add `ResourceStringEntry` struct for STRINGTABLE. Fields: id: u16, value: String. Derive Debug, Clone.
    
    - Add `ResourceStringTable` struct. Fields: block: u16, language: u16, entries: Vec<ResourceStringEntry>. Derive Debug, Clone.
    
    - Extend `ContainerInfo` by adding `resources: Option<Vec<ResourceMetadata>>` after `sections` field. This allows PE containers to include resource info while others have None.
    
    - Add `impl From<pelite::Error> for StringyError` at the end, mapping to StringyError::Parse with error.to_string().
    
    - Add `impl From<pelite::resources::FindError> for StringyError` similarly.
    
    ### src/extraction/pe_resources.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create new module for PE resource extraction using pelite:
    
    - Add module docs explaining dual-parser strategy and Phase 1 scope (enumeration only).
    
    - Imports: use pelite::{PeFile, resources::*}; use crate::types::{ResourceMetadata, ResourceType, Result, StringyError};
    
    - Pub fn extract_pe_resources(pe_file: &PeFile) -> Result<Vec<ResourceMetadata>> {
      let resources = pe_file.resources()?;
      let mut metadata = Vec::new();
      // Enumerate types
      for (typ, name) in resources.types()? {
        let rtype = match typ {
          1 => ResourceType::Cursor, // etc., but focus on key ones
          6 => ResourceType::StringTable,
          16 => ResourceType::VersionInfo,
          _ => ResourceType::Other(name.to_string()?),
        };
        // Enumerate names/ids under type
        for (name_id, _) in resources.names(typ)? {
          // For each, get language and data info
          if let Ok(langs) = resources.languages(typ, name_id) {
            for lang in langs {
              if let Ok(info) = resources.info(typ, name_id, lang) {
                let meta = ResourceMetadata {
                  r#type: rtype.clone(),
                  language: lang.0,
                  codepage: lang.1,
                  data_offset: info.offset,
                  data_size: info.size,
                  name: Some(name_id.to_string()),
                };
                metadata.push(meta);
              }
            }
          }
        }
      }
      Ok(metadata)
    }
    
    - Add helper fn to convert resource IDs to names using standard mappings.
    
    - Add unit tests: #[cfg(test)] mod tests { use super::*; // mock pe_file or simple tests for enum conversion }
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(MODIFY)
    
    Add pub mod pe_resources; at the end of the module declarations.
    
    Add pub use pe_resources::extract_pe_resources; for easy access.
    
    Update module docs to mention PE resource extraction capabilities.
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe_resources.rs(MODIFY)
    - src/types.rs(MODIFY)
    
    Integrate resource extraction into PeParser:
    
    - Add import: use crate::extraction::pe_resources::extract_pe_resources;
    
    - In parse(&self, data: &[u8]) -> Result<ContainerInfo>, after parsing with goblin::pe::PE::parse(data)?, add:
      let resources = if let Ok(pe_file) = PeFile::from_bytes(data) {
        extract_pe_resources(&pe_file).ok()
      } else { None };
    
    - In ContainerInfo construction, set resources: resources,
    
    - Add comment: // Dual parser: goblin for structure, pelite for resources. Graceful degradation if resources fail.
    
    - Ensure this doesn't affect existing section classification for .rsrc.
    
    ### src/lib.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Re-export new types: In pub use crate::types::{ ..., ResourceMetadata, ResourceType, ResourceStringEntry, ResourceStringTable };
    
    This makes them available to users of the library.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/container/pe.rs(MODIFY)
    
    Add test for resource integration:
    
    #[test]
    fn test_pe_resources_extraction() {
      if let Ok(path) = get_fixture_path("test_binary_pe.exe") {
        let data = std::fs::read(&path).unwrap();
        let result = PeParser::new().parse(&data);
        assert!(result.is_ok());
        let info = result.unwrap();
        // For basic fixture, expect resources: None or empty
        match info.resources {
          Some(res) => assert!(!res.is_empty()), // if fixture has resources
          None => {} // acceptable for basic PE
        }
      } else {
        eprintln!("PE fixture missing, skipping resource test");
      }
    }
    
    Update existing tests to assert info.resources.is_some() or handle None.
    
    ### README.md(MODIFY)
    
    Add section under Features: '- PE resource enumeration (VERSIONINFO, STRINGTABLE) using pelite'
    
    Add to Usage example: mention accessing container_info.resources for PE files.
    
    Add note on dual-parser strategy in Architecture section.

    Developer Humor

    In the land of PE files, where resources hide deep, \n> Goblin scouts the structure, but can't make the leap. \n> Pelite joins the party, with finesse and with flair, \n> Extracting VERSIONINFO, without a single care! \n> STRINGTABLEs unlocked, metadata in view, \n> StringyMcStringFace now knows what binaries do! 🎉


    Execution Information

    Branch: 3-implement-pe-section-classification-and-importexport-table-parsing
    Commit: 5ac6f0c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions