Skip to content

Implement PE Resource String Extraction (VERSIONINFO, STRINGTABLE, Manifests) #5

Closed

Description

@unclesp1d3r

Summary

Extract strings from PE (Portable Executable) resource sections including VERSIONINFO, STRINGTABLE, and manifest resources. This feature will significantly improve string extraction quality from Windows executables by tapping into the rich metadata and localized string resources that PE files contain.

Background

PE resources (stored in the .rsrc section) are a hierarchical data structure containing various resource types that are rich sources of meaningful strings:

  • VERSIONINFO (RT_VERSION): Product names, file descriptions, company names, copyright information, version strings
  • STRINGTABLE (RT_STRING): Localized UI strings, error messages, labels organized by language/locale
  • RT_MANIFEST: Application manifests containing assembly information, compatibility settings, DPI awareness, and UAC requirements
  • Dialog Resources (RT_DIALOG): Future enhancement - control captions and labels

PE Resource Structure

PE resources are organized as a three-level tree:

  1. Type: Resource type (RT_VERSION, RT_STRING, RT_MANIFEST, etc.)
  2. Name/ID: Resource identifier (numeric ID or string name)
  3. Language: Language/locale identifier (LCID)

Each level uses a resource directory structure with entries pointing to either subdirectories or data descriptors containing the actual resource data.

Why This Matters

  • Windows APIs heavily favor UTF-16 encoding; resources are a primary source of UTF-16 strings
  • Version information provides high-confidence metadata (company names, product names)
  • String tables contain localized content, useful for malware analysis and application fingerprinting
  • Manifests reveal application capabilities, dependencies, and security settings
  • These strings are typically high-value and should receive strong ranking scores

Current Implementation Status

✅ Completed:

  • Basic PE parsing via goblin crate
  • Section classification that identifies .rsrc sections
  • SectionType::Resources enumeration
  • StringSource::ResourceString type defined

❌ Missing:

  • Resource directory tree parsing
  • VERSIONINFO structure parsing and string extraction
  • STRINGTABLE entry enumeration and text extraction
  • Manifest XML parsing
  • UTF-16LE decoding for resource strings

Proposed Solution

Implementation Approach

Phase 1: Resource Directory Parsing

  1. Locate .rsrc section in PE file
  2. Parse resource directory header and entry tables
  3. Traverse three-level tree (Type → Name → Language)
  4. Collect data descriptors for target resource types

Phase 2: VERSIONINFO Extraction

  1. Identify RT_VERSION (type 16) resources
  2. Parse VS_VERSIONINFO structure
  3. Extract StringFileInfo blocks:
    • FileDescription
    • ProductName
    • CompanyName
    • LegalCopyright
    • FileVersion
    • ProductVersion
    • InternalName
    • OriginalFilename
  4. Handle multiple language blocks if present

Phase 3: STRINGTABLE Extraction

  1. Identify RT_STRING (type 6) resources
  2. Enumerate string table blocks (grouped by ID)
  3. Extract individual strings (each block contains up to 16 strings)
  4. Associate strings with language IDs for locale awareness

Phase 4: Manifest Parsing

  1. Identify RT_MANIFEST (type 24) resources
  2. Extract XML content (typically UTF-8 or UTF-16)
  3. Parse key XML elements:
    • Assembly identity (name, version, architecture)
    • Dependency information
    • Compatibility sections
    • Security settings (requestedExecutionLevel)

Phase 5: Integration

  1. Add extracted strings to results with StringSource::ResourceString
  2. Tag appropriately (version_info, string_table, manifest)
  3. Apply high ranking scores (resources are high-confidence data)
  4. Preserve resource type, ID, and language metadata

Key Technical Considerations

  • Encoding: Resources use UTF-16LE primarily; manifest may be UTF-8
  • RVA Translation: Resource data descriptors use RVAs that must be translated to file offsets
  • Alignment: Resource data is aligned; respect padding
  • Malformed Data: Handle corrupted or malicious resource structures gracefully
  • goblin Limitations: May need to manually parse resource structures if goblin's PE resource support is limited

Dependencies

  • Blocked by: PE Resource Extraction Foundation (assumed to provide resource directory traversal utilities)
  • goblin crate: Already integrated for PE parsing
  • Potential additions:
    • quick-xml or similar for manifest parsing
    • Custom resource structure parsers if goblin insufficient

Acceptance Criteria

  • Extract all VERSIONINFO string fields from PE files
  • Enumerate and extract strings from STRINGTABLE resources
  • Parse and extract meaningful strings from RT_MANIFEST resources
  • Handle multiple language/locale variants
  • Correctly decode UTF-16LE resource strings
  • Tag extracted strings with resource type and metadata
  • Apply appropriate ranking scores (resources should score high)
  • Handle malformed/corrupted resource structures without crashing
  • Add unit tests with sample PE resource data
  • Add integration tests with real PE binaries (e.g., notepad.exe, sample malware)

Testing Strategy

  1. Unit Tests:

    • Resource directory parsing with crafted binary blobs
    • VERSIONINFO structure parsing
    • STRINGTABLE entry extraction
    • Manifest XML parsing
    • UTF-16LE decoding edge cases
  2. Integration Tests:

    • Real PE binaries (Windows system utilities)
    • Known malware samples (if available in test corpus)
    • PE files with multiple languages
    • PE files with missing or corrupted resources
  3. Regression Tests:

    • Ensure non-resource string extraction still works
    • Verify performance doesn't degrade significantly

Implementation Plan

  1. Research goblin's PE resource support capabilities
  2. Implement resource directory traversal utility
  3. Implement VERSIONINFO parser
  4. Implement STRINGTABLE parser
  5. Implement manifest parser
  6. Integrate with main extraction pipeline
  7. Add comprehensive tests
  8. Update documentation

Related Issues

  • Requirement version: 1.2
  • Task-ID: stringy-analyzer/pe-resource-string-extraction

References

@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 String Extraction[/-] [+]Implement PE Resource String Extraction (VERSIONINFO, STRINGTABLE, Manifests)[/+] 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 #5
    gh issue view 5 --repo EvilBit-Labs/StringyMcStringFace --json title,body,labels,assignees,state,author,url
    

    Length of output: 1291


    🏁 Script executed:

    #!/bin/bash
    # Get repository structure to understand the project
    fd -t f -e rb -e md | head -20
    

    Length of output: 404


    🏁 Script executed:

    #!/bin/bash
    # Search for relevant code files in the project
    ls -la
    cat README.md 2>/dev/null || echo "No README found"
    

    Length of output: 7563


    🏁 Script executed:

    #!/bin/bash
    # Search for existing PE-related code
    rg -n "PE|pe_parser|resource" --type rust -C 2 | head -100
    

    Length of output: 3959


    🏁 Script executed:

    #!/bin/bash
    # Look for PE format handling code
    fd -e rs -x grep -l "PE\|Portable Executable\|goblin.*pe" {} \;
    

    Length of output: 59


    🏁 Script executed:

    #!/bin/bash
    # Check project structure for PE-related modules
    fd -t f -e rs | grep -i pe
    find src -type f -name "*.rs" 2>/dev/null | head -20
    

    Length of output: 219


    🏁 Script executed:

    #!/bin/bash
    # Read the PE parser implementation
    cat src/container/pe.rs
    

    Length of output: 7448


    🏁 Script executed:

    #!/bin/bash
    # Check if there's any specification or documentation about PE resources
    cat docs/src/binary-formats.md 2>/dev/null | grep -A 20 -i "PE\|portable executable" || echo "No specific PE docs found"
    

    Length of output: 6192


    🏁 Script executed:

    #!/bin/bash
    # Look for any existing resource-related code or TODOs
    rg -n "TODO|FIXME|resource" --type rust src/container/pe.rs
    

    Length of output: 149


    🏁 Script executed:

    #!/bin/bash
    # Update issue #5 with enhanced content
    gh issue edit 5 --repo EvilBit-Labs/StringyMcStringFace \
      --title "Implement PE Resource String Extraction (VERSIONINFO, STRINGTABLE, Manifests)" \
      --body "## Summary
    
    Extract strings from PE (Portable Executable) resource sections including VERSIONINFO, STRINGTABLE, and manifest resources. This feature will significantly improve string extraction quality from Windows executables by tapping into the rich metadata and localized string resources that PE files contain.
    
    ## Background
    
    PE resources (stored in the \`.rsrc\` section) are a hierarchical data structure containing various resource types that are rich sources of meaningful strings:
    
    - **VERSIONINFO (RT_VERSION)**: Product names, file descriptions, company names, copyright information, version strings
    - **STRINGTABLE (RT_STRING)**: Localized UI strings, error messages, labels organized by language/locale
    - **RT_MANIFEST**: Application manifests containing assembly information, compatibility settings, DPI awareness, and UAC requirements
    - **Dialog Resources (RT_DIALOG)**: Future enhancement - control captions and labels
    
    ### PE Resource Structure
    
    PE resources are organized as a three-level tree:
    1. **Type**: Resource type (RT_VERSION, RT_STRING, RT_MANIFEST, etc.)
    2. **Name/ID**: Resource identifier (numeric ID or string name)
    3. **Language**: Language/locale identifier (LCID)
    
    Each level uses a resource directory structure with entries pointing to either subdirectories or data descriptors containing the actual resource data.
    
    ### Why This Matters
    
    - Windows APIs heavily favor UTF-16 encoding; resources are a primary source of UTF-16 strings
    - Version information provides high-confidence metadata (company names, product names)
    - String tables contain localized content, useful for malware analysis and application fingerprinting
    - Manifests reveal application capabilities, dependencies, and security settings
    - These strings are typically high-value and should receive strong ranking scores
    
    ## Current Implementation Status
    
    ✅ **Completed**:
    - Basic PE parsing via \`goblin\` crate
    - Section classification that identifies \`.rsrc\` sections
    - \`SectionType::Resources\` enumeration
    - \`StringSource::ResourceString\` type defined
    
    ❌ **Missing**:
    - Resource directory tree parsing
    - VERSIONINFO structure parsing and string extraction
    - STRINGTABLE entry enumeration and text extraction
    - Manifest XML parsing
    - UTF-16LE decoding for resource strings
    
    ## Proposed Solution
    
    ### Implementation Approach
    
    **Phase 1: Resource Directory Parsing**
    1. Locate \`.rsrc\` section in PE file
    2. Parse resource directory header and entry tables
    3. Traverse three-level tree (Type → Name → Language)
    4. Collect data descriptors for target resource types
    
    **Phase 2: VERSIONINFO Extraction**
    1. Identify RT_VERSION (type 16) resources
    2. Parse VS_VERSIONINFO structure
    3. Extract StringFileInfo blocks:
       - FileDescription
       - ProductName
       - CompanyName
       - LegalCopyright
       - FileVersion
       - ProductVersion
       - InternalName
       - OriginalFilename
    4. Handle multiple language blocks if present
    
    **Phase 3: STRINGTABLE Extraction**
    1. Identify RT_STRING (type 6) resources
    2. Enumerate string table blocks (grouped by ID)
    3. Extract individual strings (each block contains up to 16 strings)
    4. Associate strings with language IDs for locale awareness
    
    **Phase 4: Manifest Parsing**
    1. Identify RT_MANIFEST (type 24) resources
    2. Extract XML content (typically UTF-8 or UTF-16)
    3. Parse key XML elements:
       - Assembly identity (name, version, architecture)
       - Dependency information
       - Compatibility sections
       - Security settings (requestedExecutionLevel)
    
    **Phase 5: Integration**
    1. Add extracted strings to results with \`StringSource::ResourceString\`
    2. Tag appropriately (version_info, string_table, manifest)
    3. Apply high ranking scores (resources are high-confidence data)
    4. Preserve resource type, ID, and language metadata
    
    ### Key Technical Considerations
    
    - **Encoding**: Resources use UTF-16LE primarily; manifest may be UTF-8
    - **RVA Translation**: Resource data descriptors use RVAs that must be translated to file offsets
    - **Alignment**: Resource data is aligned; respect padding
    - **Malformed Data**: Handle corrupted or malicious resource structures gracefully
    - **goblin Limitations**: May need to manually parse resource structures if goblin's PE resource support is limited
    
    ### Dependencies
    
    - **Blocked by**: PE Resource Extraction Foundation (assumed to provide resource directory traversal utilities)
    - **goblin crate**: Already integrated for PE parsing
    - **Potential additions**: 
      - \`quick-xml\` or similar for manifest parsing
      - Custom resource structure parsers if goblin insufficient
    
    ## Acceptance Criteria
    
    - [ ] Extract all VERSIONINFO string fields from PE files
    - [ ] Enumerate and extract strings from STRINGTABLE resources
    - [ ] Parse and extract meaningful strings from RT_MANIFEST resources
    - [ ] Handle multiple language/locale variants
    - [ ] Correctly decode UTF-16LE resource strings
    - [ ] Tag extracted strings with resource type and metadata
    - [ ] Apply appropriate ranking scores (resources should score high)
    - [ ] Handle malformed/corrupted resource structures without crashing
    - [ ] Add unit tests with sample PE resource data
    - [ ] Add integration tests with real PE binaries (e.g., \`notepad.exe\`, sample malware)
    
    ## Testing Strategy
    
    1. **Unit Tests**:
       - Resource directory parsing with crafted binary blobs
       - VERSIONINFO structure parsing
       - STRINGTABLE entry extraction
       - Manifest XML parsing
       - UTF-16LE decoding edge cases
    
    2. **Integration Tests**:
       - Real PE binaries (Windows system utilities)
       - Known malware samples (if available in test corpus)
       - PE files with multiple languages
       - PE files with missing or corrupted resources
    
    3. **Regression Tests**:
       - Ensure non-resource string extraction still works
       - Verify performance doesn't degrade significantly
    
    ## Implementation Plan
    
    1. Research goblin's PE resource support capabilities
    2. Implement resource directory traversal utility
    3. Implement VERSIONINFO parser
    4. Implement STRINGTABLE parser
    5. Implement manifest parser
    6. Integrate with main extraction pipeline
    7. Add comprehensive tests
    8. Update documentation
    
    ## Related Issues
    
    - Requirement version: 1.2
    - Task-ID: stringy-analyzer/pe-resource-string-extraction
    
    ## References
    
    - [PE Format Specification - Microsoft Learn](https://learn.microsoft.com/en-us/windows/win32/debug/pe-format)
    - [Resource File Formats](https://learn.microsoft.com/en-us/windows/win32/menurc/resource-file-formats)
    - [VERSIONINFO Structure](https://learn.microsoft.com/en-us/windows/win32/menurc/vs-versioninfo)
    - [Application Manifests](https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests)"
    

    Length of output: 60


    🏁 Script executed:

    #!/bin/bash
    # Verify the issue was updated correctly
    gh issue view 5 --repo EvilBit-Labs/StringyMcStringFace --json title,labels,assignees,url
    

    Length of output: 818


    @unclesp1d3r I've successfully enhanced issue #5 with comprehensive technical context and implementation guidance. Here's what I've updated:

    Changes Made

    ✅ Improved Title

    Changed from "PE Resource String Extraction" to:
    "Implement PE Resource String Extraction (VERSIONINFO, STRINGTABLE, Manifests)"

    This makes the scope immediately clear to anyone viewing the issue list.

    ✅ Enhanced Issue Body

    Added Technical Context:

    • Detailed explanation of PE resource structure (three-level tree: Type → Name → Language)
    • Background on why these resources are valuable for string extraction
    • Current implementation status showing what's already in place vs. what's missing

    Proposed Solution with 5 Phases:

    1. Resource Directory Parsing (tree traversal)
    2. VERSIONINFO Extraction (product metadata)
    3. STRINGTABLE Extraction (localized UI strings)
    4. Manifest Parsing (XML application metadata)
    5. Integration (tagging, ranking, output)

    Added Comprehensive Sections:

    • Key technical considerations (UTF-16LE encoding, RVA translation, alignment, error handling)
    • Detailed acceptance criteria with checkboxes
    • Testing strategy (unit, integration, regression tests)
    • Step-by-step implementation plan
    • References to official Microsoft documentation

    ✅ Metadata Review

    The existing labels are already appropriate and well-chosen:

    • area:analyzer - Correct functional area
    • type:enhancement - Appropriate issue type
    • status:backlog - Current status
    • lang:rust - Implementation language
    • needs:tests - Reminder about test coverage

    No label changes were needed. The issue remains assigned to you as the maintainer.

    View the Enhanced Issue

    👉 Issue #5: Implement PE Resource String Extraction

    The enhanced issue now provides a clear roadmap for implementation with technical depth that will help during development. It includes proper context about PE resource structures, phased implementation approach, and comprehensive acceptance criteria.

  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

    Key Findings:

    1. Goblin 0.10.3 has built-in PE resource support - The project already uses goblin 0.10.3 which includes ImageResourceDirectory, ResourceData, VersionInfo, and resource parsing utilities added in v0.10.1+
    2. Infrastructure is ready - Types like StringSource::ResourceString, SectionType::Resources, and tags (Version, Manifest, Resource) are already defined
    3. Extraction module is empty - src/extraction/mod.rs is a placeholder, perfect for adding the resource extraction logic
    4. No UTF-16 decoder exists - Need to implement UTF-16LE decoding for resource strings
    5. No XML parser - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)
    6. PE fixtures exist but lack resources - Current test_binary_pe.exe is minimal; need to add resource-rich test binaries
    7. Documentation describes the architecture - Clear separation between container parsing (metadata) and extraction (string extraction)
    8. Ranking system is documented - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags

    Approach

    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.

    Reasoning

    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in src/container/pe.rs which identifies .rsrc sections but doesn't parse them. Reviewed the type definitions in src/types.rs confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including ImageResourceDirectory, ResourceData, and VersionInfo. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add quick-xml dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.

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

    References

    Transform this placeholder into the extraction module's main interface. Define the StringExtractor trait with a method extract_strings that takes binary data and ContainerInfo and returns a vector of FoundString. Create a public function extract_all_strings that dispatches to format-specific extractors. Add module declarations for submodules: pub mod pe; and pub mod utf16; (for shared UTF-16 decoding utilities).

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

    References

    Create a UTF-16LE decoding utility module. Implement decode_utf16le function that takes a byte slice and returns Result<String>, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add decode_utf16le_null_terminated variant that stops at the first null terminator (0x00 0x00). Include helper functions like is_valid_utf16le_char for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).

    📄 src/extraction/pe/mod.rs (NEW) 🔗

    References

    Create the PE-specific extraction module. Define a PeExtractor struct that implements the StringExtractor trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: pub mod resources; for resource-specific extraction logic. The extractor should use the ContainerInfo to identify the .rsrc section and delegate to the resources module for parsing.

    📄 src/extraction/pe/resources.rs (NEW) 🔗

    References

    Implement the core PE resource extraction logic. Create a ResourceExtractor struct with methods:

    1. extract_resources - Main entry point that takes PE bytes, uses goblin's ResourceData::parse to get the resource directory, and orchestrates extraction of all resource types

    2. extract_version_info - Use goblin's VersionInfo::parse to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using translation(). Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from crate::extraction::utf16. Create FoundString instances with StringSource::ResourceString, tag with Tag::Version, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")

    3. extract_string_tables - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create FoundString instances tagged with Tag::Resource, preserving string IDs and language information

    4. extract_manifests - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's Reader. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create FoundString instances tagged with Tag::Manifest

    5. Helper methods: rva_to_offset for translating resource RVAs to file offsets using section information, get_resource_data to extract raw bytes for a resource entry, error handling for malformed resources

    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).

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

    References

    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method get_resource_section that returns the .rsrc section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the ContainerInfo returned includes the .rsrc section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.

    📄 src/types.rs (MODIFY) 🔗

    Add a new error variant to StringyError enum: ResourceParseError(String) for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.

    📄 src/lib.rs (MODIFY) 🔗

    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add comprehensive integration tests for PE resource extraction:

    1. test_pe_version_info_extraction - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with Tag::Version and have StringSource::ResourceString.

    2. test_pe_string_table_extraction - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.

    3. test_pe_manifest_extraction - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with Tag::Manifest.

    4. test_pe_multiple_languages - Test handling of resources with multiple language variants, ensuring all languages are processed.

    5. test_pe_malformed_resources - Test graceful handling of corrupted or malformed resource structures without panicking.

    Note: These tests will require adding new PE fixtures with actual resource data. The current test_binary_pe.exe is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.

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

    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.

    📄 tests/unit_utf16.rs (NEW) 🔗

    References

    Create comprehensive unit tests for UTF-16LE decoding:

    1. test_decode_simple_ascii - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)

    2. test_decode_unicode_chars - Test decoding of non-ASCII Unicode characters

    3. test_decode_null_terminated - Test null-terminated string handling

    4. test_decode_empty_input - Test handling of empty byte slices

    5. test_decode_odd_length - Test error handling for odd-length input (invalid UTF-16LE)

    6. test_decode_invalid_surrogate - Test handling of invalid surrogate pairs

    7. test_decode_with_bom - Test handling of UTF-16LE BOM (0xFF 0xFE)

    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.

    📄 tests/unit_pe_resources.rs (NEW) 🔗

    References

    Create unit tests for PE resource extraction with crafted binary data:

    1. test_version_info_parsing - Test parsing of a minimal VERSIONINFO structure with known string values

    2. test_string_table_parsing - Test parsing of RT_STRING blocks with known string IDs

    3. test_manifest_xml_parsing - Test extraction of strings from a minimal XML manifest

    4. test_resource_rva_translation - Test RVA to file offset translation for resource data

    5. test_malformed_version_info - Test graceful handling of corrupted VERSIONINFO structures

    6. test_empty_string_table - Test handling of empty string table blocks

    7. test_manifest_encoding_detection - Test detection of UTF-8 vs UTF-16 manifest encoding

    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.

    📄 docs/src/string-extraction.md (MODIFY) 🔗

    References

    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.

    📄 .kiro/specs/stringy-binary-analyzer/tasks.md (MODIFY) 🔗

    Mark tasks 4.1 and 4.2 as completed:

    • Change [ ] 4.1 Add PE resource extraction foundation to [x] 4.1 Add PE resource extraction foundation
    • Change [ ] 4.2 Implement PE resource string extraction to [x] 4.2 Implement PE resource string extraction

    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Diagram

    sequenceDiagram
        participant User
        participant PeParser as PE Parser<br/>(Container)
        participant PeExtractor as PE Extractor<br/>(Extraction)
        participant ResourceExtractor as Resource Extractor
        participant Goblin as Goblin Library
        participant UTF16 as UTF-16 Decoder
        participant QuickXML as Quick-XML
    
        User->>PeParser: parse(pe_bytes)
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: PE metadata
        PeParser->>PeParser: Identify .rsrc section
        PeParser-->>User: ContainerInfo with sections
    
        User->>PeExtractor: extract_strings(pe_bytes, ContainerInfo)
        PeExtractor->>ResourceExtractor: extract_resources(pe_bytes)
        
        ResourceExtractor->>Goblin: ResourceData::parse()
        Goblin-->>ResourceExtractor: Resource directory tree
        
        par VERSIONINFO Extraction
            ResourceExtractor->>Goblin: VersionInfo::parse()
            Goblin-->>ResourceExtractor: Version strings (UTF-16LE)
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Version
        and STRINGTABLE Extraction
            ResourceExtractor->>Goblin: Locate RT_STRING resources
            Goblin-->>ResourceExtractor: String table blocks
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Resource
        and MANIFEST Extraction
            ResourceExtractor->>Goblin: Locate RT_MANIFEST
            Goblin-->>ResourceExtractor: Manifest XML bytes
            ResourceExtractor->>QuickXML: Parse XML
            QuickXML-->>ResourceExtractor: XML elements
            ResourceExtractor->>ResourceExtractor: Extract text, tag with Manifest
        end
        
        ResourceExtractor-->>PeExtractor: Vec<FoundString>
        PeExtractor-->>User: All extracted strings
    
    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
    
    **Key Findings:**
    
    1. **Goblin 0.10.3 has built-in PE resource support** - The project already uses goblin 0.10.3 which includes `ImageResourceDirectory`, `ResourceData`, `VersionInfo`, and resource parsing utilities added in v0.10.1+
    2. **Infrastructure is ready** - Types like `StringSource::ResourceString`, `SectionType::Resources`, and tags (`Version`, `Manifest`, `Resource`) are already defined
    3. **Extraction module is empty** - `src/extraction/mod.rs` is a placeholder, perfect for adding the resource extraction logic
    4. **No UTF-16 decoder exists** - Need to implement UTF-16LE decoding for resource strings
    5. **No XML parser** - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)
    6. **PE fixtures exist but lack resources** - Current `test_binary_pe.exe` is minimal; need to add resource-rich test binaries
    7. **Documentation describes the architecture** - Clear separation between container parsing (metadata) and extraction (string extraction)
    8. **Ranking system is documented** - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags
    
    ### Approach
    
    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.
    
    ### Reasoning
    
    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in `src/container/pe.rs` which identifies `.rsrc` sections but doesn't parse them. Reviewed the type definitions in `src/types.rs` confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including `ImageResourceDirectory`, `ResourceData`, and `VersionInfo`. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant User
        participant PeParser as PE Parser<br/>(Container)
        participant PeExtractor as PE Extractor<br/>(Extraction)
        participant ResourceExtractor as Resource Extractor
        participant Goblin as Goblin Library
        participant UTF16 as UTF-16 Decoder
        participant QuickXML as Quick-XML
    
        User->>PeParser: parse(pe_bytes)
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: PE metadata
        PeParser->>PeParser: Identify .rsrc section
        PeParser-->>User: ContainerInfo with sections
    
        User->>PeExtractor: extract_strings(pe_bytes, ContainerInfo)
        PeExtractor->>ResourceExtractor: extract_resources(pe_bytes)
        
        ResourceExtractor->>Goblin: ResourceData::parse()
        Goblin-->>ResourceExtractor: Resource directory tree
        
        par VERSIONINFO Extraction
            ResourceExtractor->>Goblin: VersionInfo::parse()
            Goblin-->>ResourceExtractor: Version strings (UTF-16LE)
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Version
        and STRINGTABLE Extraction
            ResourceExtractor->>Goblin: Locate RT_STRING resources
            Goblin-->>ResourceExtractor: String table blocks
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Resource
        and MANIFEST Extraction
            ResourceExtractor->>Goblin: Locate RT_MANIFEST
            Goblin-->>ResourceExtractor: Manifest XML bytes
            ResourceExtractor->>QuickXML: Parse XML
            QuickXML-->>ResourceExtractor: XML elements
            ResourceExtractor->>ResourceExtractor: Extract text, tag with Manifest
        end
        
        ResourceExtractor-->>PeExtractor: Vec<FoundString>
        PeExtractor-->>User: All extracted strings
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add `quick-xml` dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Transform this placeholder into the extraction module's main interface. Define the `StringExtractor` trait with a method `extract_strings` that takes binary data and `ContainerInfo` and returns a vector of `FoundString`. Create a public function `extract_all_strings` that dispatches to format-specific extractors. Add module declarations for submodules: `pub mod pe;` and `pub mod utf16;` (for shared UTF-16 decoding utilities).
    
    ### src/extraction/utf16.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create a UTF-16LE decoding utility module. Implement `decode_utf16le` function that takes a byte slice and returns `Result<String>`, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add `decode_utf16le_null_terminated` variant that stops at the first null terminator (0x00 0x00). Include helper functions like `is_valid_utf16le_char` for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).
    
    ### src/extraction/pe/mod.rs(NEW)
    
    References: 
    
    - src/extraction/mod.rs(MODIFY)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Create the PE-specific extraction module. Define a `PeExtractor` struct that implements the `StringExtractor` trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: `pub mod resources;` for resource-specific extraction logic. The extractor should use the `ContainerInfo` to identify the `.rsrc` section and delegate to the resources module for parsing.
    
    ### src/extraction/pe/resources.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Implement the core PE resource extraction logic. Create a `ResourceExtractor` struct with methods:
    
    1. `extract_resources` - Main entry point that takes PE bytes, uses goblin's `ResourceData::parse` to get the resource directory, and orchestrates extraction of all resource types
    
    2. `extract_version_info` - Use goblin's `VersionInfo::parse` to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using `translation()`. Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from `crate::extraction::utf16`. Create `FoundString` instances with `StringSource::ResourceString`, tag with `Tag::Version`, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")
    
    3. `extract_string_tables` - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create `FoundString` instances tagged with `Tag::Resource`, preserving string IDs and language information
    
    4. `extract_manifests` - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's `Reader`. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create `FoundString` instances tagged with `Tag::Manifest`
    
    5. Helper methods: `rva_to_offset` for translating resource RVAs to file offsets using section information, `get_resource_data` to extract raw bytes for a resource entry, error handling for malformed resources
    
    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method `get_resource_section` that returns the `.rsrc` section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the `ContainerInfo` returned includes the `.rsrc` section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.
    
    ### src/types.rs(MODIFY)
    
    Add a new error variant to `StringyError` enum: `ResourceParseError(String)` for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.
    
    ### src/lib.rs(MODIFY)
    
    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Add comprehensive integration tests for PE resource extraction:
    
    1. `test_pe_version_info_extraction` - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with `Tag::Version` and have `StringSource::ResourceString`.
    
    2. `test_pe_string_table_extraction` - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.
    
    3. `test_pe_manifest_extraction` - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with `Tag::Manifest`.
    
    4. `test_pe_multiple_languages` - Test handling of resources with multiple language variants, ensuring all languages are processed.
    
    5. `test_pe_malformed_resources` - Test graceful handling of corrupted or malformed resource structures without panicking.
    
    Note: These tests will require adding new PE fixtures with actual resource data. The current `test_binary_pe.exe` is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.
    
    ### tests/unit_utf16.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    
    Create comprehensive unit tests for UTF-16LE decoding:
    
    1. `test_decode_simple_ascii` - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)
    
    2. `test_decode_unicode_chars` - Test decoding of non-ASCII Unicode characters
    
    3. `test_decode_null_terminated` - Test null-terminated string handling
    
    4. `test_decode_empty_input` - Test handling of empty byte slices
    
    5. `test_decode_odd_length` - Test error handling for odd-length input (invalid UTF-16LE)
    
    6. `test_decode_invalid_surrogate` - Test handling of invalid surrogate pairs
    
    7. `test_decode_with_bom` - Test handling of UTF-16LE BOM (0xFF 0xFE)
    
    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.
    
    ### tests/unit_pe_resources.rs(NEW)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/extraction/utf16.rs(NEW)
    
    Create unit tests for PE resource extraction with crafted binary data:
    
    1. `test_version_info_parsing` - Test parsing of a minimal VERSIONINFO structure with known string values
    
    2. `test_string_table_parsing` - Test parsing of RT_STRING blocks with known string IDs
    
    3. `test_manifest_xml_parsing` - Test extraction of strings from a minimal XML manifest
    
    4. `test_resource_rva_translation` - Test RVA to file offset translation for resource data
    
    5. `test_malformed_version_info` - Test graceful handling of corrupted VERSIONINFO structures
    
    6. `test_empty_string_table` - Test handling of empty string table blocks
    
    7. `test_manifest_encoding_detection` - Test detection of UTF-8 vs UTF-16 manifest encoding
    
    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.
    
    ### docs/src/string-extraction.md(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    
    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.
    
    ### .kiro/specs/stringy-binary-analyzer/tasks.md(MODIFY)
    
    Mark tasks 4.1 and 4.2 as completed:
    - Change `[ ] 4.1 Add PE resource extraction foundation` to `[x] 4.1 Add PE resource extraction foundation`
    - Change `[ ] 4.2 Implement PE resource string extraction` to `[x] 4.2 Implement PE resource string extraction`
    
    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Developer Humor

    🎯 Resource Extraction Haiku

    Deep in PE's .rsrc,
    Version strings hide in UTF-16—
    Goblin finds them all!

    📦 VERSIONINFO speaks,
    STRINGTABLE whispers secrets,
    Manifests confess.

    🔍 No pelite needed,
    Goblin 0.10.3 delivers—
    Quick-XML helps too!


    Execution Information

    Branch: main
    Commit: 37cfc7c

  9. traycerai commented on Nov 10, 2025

    @traycerai

    Plan

    Observations

    Key Findings:\n\n1. Goblin 0.10.3 has built-in PE resource support - The project already uses goblin 0.10.3 which includes ImageResourceDirectory, ResourceData, VersionInfo, and resource parsing utilities added in v0.10.1+\n2. Infrastructure is ready - Types like StringSource::ResourceString, SectionType::Resources, and tags (Version, Manifest, Resource) are already defined\n3. Extraction module is empty - src/extraction/mod.rs is a placeholder, perfect for adding the resource extraction logic\n4. No UTF-16 decoder exists - Need to implement UTF-16LE decoding for resource strings\n5. No XML parser - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)\n6. PE fixtures exist but lack resources - Current test_binary_pe.exe is minimal; need to add resource-rich test binaries\n7. Documentation describes the architecture - Clear separation between container parsing (metadata) and extraction (string extraction)\n8. Ranking system is documented - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags

    Approach

    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.

    Reasoning

    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in src/container/pe.rs which identifies .rsrc sections but doesn't parse them. Reviewed the type definitions in src/types.rs confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including ImageResourceDirectory, ResourceData, and VersionInfo. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add quick-xml dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.

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

    References

    Transform this placeholder into the extraction module's main interface. Define the StringExtractor trait with a method extract_strings that takes binary data and ContainerInfo and returns a vector of FoundString. Create a public function extract_all_strings that dispatches to format-specific extractors. Add module declarations for submodules: pub mod pe; and pub mod utf16; (for shared UTF-16 decoding utilities).

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

    References

    Create a UTF-16LE decoding utility module. Implement decode_utf16le function that takes a byte slice and returns Result<String>, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add decode_utf16le_null_terminated variant that stops at the first null terminator (0x00 0x00). Include helper functions like is_valid_utf16le_char for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).

    📄 src/extraction/pe/mod.rs (NEW) 🔗

    References

    Create the PE-specific extraction module. Define a PeExtractor struct that implements the StringExtractor trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: pub mod resources; for resource-specific extraction logic. The extractor should use the ContainerInfo to identify the .rsrc section and delegate to the resources module for parsing.

    📄 src/extraction/pe/resources.rs (NEW) 🔗

    References

    Implement the core PE resource extraction logic. Create a ResourceExtractor struct with methods:

    1. extract_resources - Main entry point that takes PE bytes, uses goblin's ResourceData::parse to get the resource directory, and orchestrates extraction of all resource types

    2. extract_version_info - Use goblin's VersionInfo::parse to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using translation(). Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from crate::extraction::utf16. Create FoundString instances with StringSource::ResourceString, tag with Tag::Version, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")

    3. extract_string_tables - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create FoundString instances tagged with Tag::Resource, preserving string IDs and language information

    4. extract_manifests - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's Reader. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create FoundString instances tagged with Tag::Manifest

    5. Helper methods: rva_to_offset for translating resource RVAs to file offsets using section information, get_resource_data to extract raw bytes for a resource entry, error handling for malformed resources

    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).

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

    References

    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method get_resource_section that returns the .rsrc section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the ContainerInfo returned includes the .rsrc section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.

    📄 src/types.rs (MODIFY) 🔗

    Add a new error variant to StringyError enum: ResourceParseError(String) for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.

    📄 src/lib.rs (MODIFY) 🔗

    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add comprehensive integration tests for PE resource extraction:

    1. test_pe_version_info_extraction - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with Tag::Version and have StringSource::ResourceString.

    2. test_pe_string_table_extraction - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.

    3. test_pe_manifest_extraction - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with Tag::Manifest.

    4. test_pe_multiple_languages - Test handling of resources with multiple language variants, ensuring all languages are processed.

    5. test_pe_malformed_resources - Test graceful handling of corrupted or malformed resource structures without panicking.

    Note: These tests will require adding new PE fixtures with actual resource data. The current test_binary_pe.exe is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.

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

    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.

    📄 tests/unit_utf16.rs (NEW) 🔗

    References

    Create comprehensive unit tests for UTF-16LE decoding:

    1. test_decode_simple_ascii - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)

    2. test_decode_unicode_chars - Test decoding of non-ASCII Unicode characters

    3. test_decode_null_terminated - Test null-terminated string handling

    4. test_decode_empty_input - Test handling of empty byte slices

    5. test_decode_odd_length - Test error handling for odd-length input (invalid UTF-16LE)

    6. test_decode_invalid_surrogate - Test handling of invalid surrogate pairs

    7. test_decode_with_bom - Test handling of UTF-16LE BOM (0xFF 0xFE)

    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.

    📄 tests/unit_pe_resources.rs (NEW) 🔗

    References

    Create unit tests for PE resource extraction with crafted binary data:

    1. test_version_info_parsing - Test parsing of a minimal VERSIONINFO structure with known string values

    2. test_string_table_parsing - Test parsing of RT_STRING blocks with known string IDs

    3. test_manifest_xml_parsing - Test extraction of strings from a minimal XML manifest

    4. test_resource_rva_translation - Test RVA to file offset translation for resource data

    5. test_malformed_version_info - Test graceful handling of corrupted VERSIONINFO structures

    6. test_empty_string_table - Test handling of empty string table blocks

    7. test_manifest_encoding_detection - Test detection of UTF-8 vs UTF-16 manifest encoding

    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.

    📄 docs/src/string-extraction.md (MODIFY) 🔗

    References

    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.

    📄 .kiro/specs/stringy-binary-analyzer/tasks.md (MODIFY) 🔗

    Mark tasks 4.1 and 4.2 as completed:

    • Change [ ] 4.1 Add PE resource extraction foundation to [x] 4.1 Add PE resource extraction foundation
    • Change [ ] 4.2 Implement PE resource string extraction to [x] 4.2 Implement PE resource string extraction

    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    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
    
    **Key Findings:**\n\n1. **Goblin 0.10.3 has built-in PE resource support** - The project already uses goblin 0.10.3 which includes `ImageResourceDirectory`, `ResourceData`, `VersionInfo`, and resource parsing utilities added in v0.10.1+\n2. **Infrastructure is ready** - Types like `StringSource::ResourceString`, `SectionType::Resources`, and tags (`Version`, `Manifest`, `Resource`) are already defined\n3. **Extraction module is empty** - `src/extraction/mod.rs` is a placeholder, perfect for adding the resource extraction logic\n4. **No UTF-16 decoder exists** - Need to implement UTF-16LE decoding for resource strings\n5. **No XML parser** - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)\n6. **PE fixtures exist but lack resources** - Current `test_binary_pe.exe` is minimal; need to add resource-rich test binaries\n7. **Documentation describes the architecture** - Clear separation between container parsing (metadata) and extraction (string extraction)\n8. **Ranking system is documented** - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags
    
    ### Approach
    
    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.
    
    ### Reasoning
    
    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in `src/container/pe.rs` which identifies `.rsrc` sections but doesn't parse them. Reviewed the type definitions in `src/types.rs` confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including `ImageResourceDirectory`, `ResourceData`, and `VersionInfo`. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add `quick-xml` dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Transform this placeholder into the extraction module's main interface. Define the `StringExtractor` trait with a method `extract_strings` that takes binary data and `ContainerInfo` and returns a vector of `FoundString`. Create a public function `extract_all_strings` that dispatches to format-specific extractors. Add module declarations for submodules: `pub mod pe;` and `pub mod utf16;` (for shared UTF-16 decoding utilities).
    
    ### src/extraction/utf16.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create a UTF-16LE decoding utility module. Implement `decode_utf16le` function that takes a byte slice and returns `Result<String>`, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add `decode_utf16le_null_terminated` variant that stops at the first null terminator (0x00 0x00). Include helper functions like `is_valid_utf16le_char` for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).
    
    ### src/extraction/pe/mod.rs(NEW)
    
    References: 
    
    - src/extraction/mod.rs(MODIFY)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Create the PE-specific extraction module. Define a `PeExtractor` struct that implements the `StringExtractor` trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: `pub mod resources;` for resource-specific extraction logic. The extractor should use the `ContainerInfo` to identify the `.rsrc` section and delegate to the resources module for parsing.
    
    ### src/extraction/pe/resources.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Implement the core PE resource extraction logic. Create a `ResourceExtractor` struct with methods:
    
    1. `extract_resources` - Main entry point that takes PE bytes, uses goblin's `ResourceData::parse` to get the resource directory, and orchestrates extraction of all resource types
    
    2. `extract_version_info` - Use goblin's `VersionInfo::parse` to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using `translation()`. Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from `crate::extraction::utf16`. Create `FoundString` instances with `StringSource::ResourceString`, tag with `Tag::Version`, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")
    
    3. `extract_string_tables` - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create `FoundString` instances tagged with `Tag::Resource`, preserving string IDs and language information
    
    4. `extract_manifests` - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's `Reader`. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create `FoundString` instances tagged with `Tag::Manifest`
    
    5. Helper methods: `rva_to_offset` for translating resource RVAs to file offsets using section information, `get_resource_data` to extract raw bytes for a resource entry, error handling for malformed resources
    
    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method `get_resource_section` that returns the `.rsrc` section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the `ContainerInfo` returned includes the `.rsrc` section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.
    
    ### src/types.rs(MODIFY)
    
    Add a new error variant to `StringyError` enum: `ResourceParseError(String)` for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.
    
    ### src/lib.rs(MODIFY)
    
    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Add comprehensive integration tests for PE resource extraction:
    
    1. `test_pe_version_info_extraction` - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with `Tag::Version` and have `StringSource::ResourceString`.
    
    2. `test_pe_string_table_extraction` - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.
    
    3. `test_pe_manifest_extraction` - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with `Tag::Manifest`.
    
    4. `test_pe_multiple_languages` - Test handling of resources with multiple language variants, ensuring all languages are processed.
    
    5. `test_pe_malformed_resources` - Test graceful handling of corrupted or malformed resource structures without panicking.
    
    Note: These tests will require adding new PE fixtures with actual resource data. The current `test_binary_pe.exe` is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.
    
    ### tests/unit_utf16.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    
    Create comprehensive unit tests for UTF-16LE decoding:
    
    1. `test_decode_simple_ascii` - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)
    
    2. `test_decode_unicode_chars` - Test decoding of non-ASCII Unicode characters
    
    3. `test_decode_null_terminated` - Test null-terminated string handling
    
    4. `test_decode_empty_input` - Test handling of empty byte slices
    
    5. `test_decode_odd_length` - Test error handling for odd-length input (invalid UTF-16LE)
    
    6. `test_decode_invalid_surrogate` - Test handling of invalid surrogate pairs
    
    7. `test_decode_with_bom` - Test handling of UTF-16LE BOM (0xFF 0xFE)
    
    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.
    
    ### tests/unit_pe_resources.rs(NEW)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/extraction/utf16.rs(NEW)
    
    Create unit tests for PE resource extraction with crafted binary data:
    
    1. `test_version_info_parsing` - Test parsing of a minimal VERSIONINFO structure with known string values
    
    2. `test_string_table_parsing` - Test parsing of RT_STRING blocks with known string IDs
    
    3. `test_manifest_xml_parsing` - Test extraction of strings from a minimal XML manifest
    
    4. `test_resource_rva_translation` - Test RVA to file offset translation for resource data
    
    5. `test_malformed_version_info` - Test graceful handling of corrupted VERSIONINFO structures
    
    6. `test_empty_string_table` - Test handling of empty string table blocks
    
    7. `test_manifest_encoding_detection` - Test detection of UTF-8 vs UTF-16 manifest encoding
    
    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.
    
    ### docs/src/string-extraction.md(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    
    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.
    
    ### .kiro/specs/stringy-binary-analyzer/tasks.md(MODIFY)
    
    Mark tasks 4.1 and 4.2 as completed:
    - Change `[ ] 4.1 Add PE resource extraction foundation` to `[x] 4.1 Add PE resource extraction foundation`
    - Change `[ ] 4.2 Implement PE resource string extraction` to `[x] 4.2 Implement PE resource string extraction`
    
    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Developer Humor

    🎯 Resource Extraction Haiku\n> \n> Deep in PE's .rsrc,\n> Version strings hide in UTF-16—\n> Goblin finds them all!\n> \n> 📦 VERSIONINFO speaks,\n> STRINGTABLE whispers secrets,\n> Manifests confess.\n> \n> 🔍 No pelite needed,\n> Goblin 0.10.3 delivers—\n> Quick-XML helps too!


    Execution Information

    Branch: main
    Commit: 37cfc7c


    💡 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

    Key Findings:

    1. Goblin 0.10.3 has built-in PE resource support - The project already uses goblin 0.10.3 which includes ImageResourceDirectory, ResourceData, VersionInfo, and resource parsing utilities added in v0.10.1+
    2. Infrastructure is ready - Types like StringSource::ResourceString, SectionType::Resources, and tags (Version, Manifest, Resource) are already defined
    3. Extraction module is empty - src/extraction/mod.rs is a placeholder, perfect for adding the resource extraction logic
    4. No UTF-16 decoder exists - Need to implement UTF-16LE decoding for resource strings
    5. No XML parser - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)
    6. PE fixtures exist but lack resources - Current test_binary_pe.exe is minimal; need to add resource-rich test binaries
    7. Documentation describes the architecture - Clear separation between container parsing (metadata) and extraction (string extraction)
    8. Ranking system is documented - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags

    Approach

    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.

    Reasoning

    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in src/container/pe.rs which identifies .rsrc sections but doesn't parse them. Reviewed the type definitions in src/types.rs confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including ImageResourceDirectory, ResourceData, and VersionInfo. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add quick-xml dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.

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

    References

    Transform this placeholder into the extraction module's main interface. Define the StringExtractor trait with a method extract_strings that takes binary data and ContainerInfo and returns a vector of FoundString. Create a public function extract_all_strings that dispatches to format-specific extractors. Add module declarations for submodules: pub mod pe; and pub mod utf16; (for shared UTF-16 decoding utilities).

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

    References

    Create a UTF-16LE decoding utility module. Implement decode_utf16le function that takes a byte slice and returns Result<String>, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add decode_utf16le_null_terminated variant that stops at the first null terminator (0x00 0x00). Include helper functions like is_valid_utf16le_char for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).

    📄 src/extraction/pe/mod.rs (NEW) 🔗

    References

    Create the PE-specific extraction module. Define a PeExtractor struct that implements the StringExtractor trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: pub mod resources; for resource-specific extraction logic. The extractor should use the ContainerInfo to identify the .rsrc section and delegate to the resources module for parsing.

    📄 src/extraction/pe/resources.rs (NEW) 🔗

    References

    Implement the core PE resource extraction logic. Create a ResourceExtractor struct with methods:

    1. extract_resources - Main entry point that takes PE bytes, uses goblin's ResourceData::parse to get the resource directory, and orchestrates extraction of all resource types

    2. extract_version_info - Use goblin's VersionInfo::parse to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using translation(). Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from crate::extraction::utf16. Create FoundString instances with StringSource::ResourceString, tag with Tag::Version, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")

    3. extract_string_tables - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create FoundString instances tagged with Tag::Resource, preserving string IDs and language information

    4. extract_manifests - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's Reader. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create FoundString instances tagged with Tag::Manifest

    5. Helper methods: rva_to_offset for translating resource RVAs to file offsets using section information, get_resource_data to extract raw bytes for a resource entry, error handling for malformed resources

    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).

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

    References

    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method get_resource_section that returns the .rsrc section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the ContainerInfo returned includes the .rsrc section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.

    📄 src/types.rs (MODIFY) 🔗

    Add a new error variant to StringyError enum: ResourceParseError(String) for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.

    📄 src/lib.rs (MODIFY) 🔗

    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add comprehensive integration tests for PE resource extraction:

    1. test_pe_version_info_extraction - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with Tag::Version and have StringSource::ResourceString.

    2. test_pe_string_table_extraction - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.

    3. test_pe_manifest_extraction - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with Tag::Manifest.

    4. test_pe_multiple_languages - Test handling of resources with multiple language variants, ensuring all languages are processed.

    5. test_pe_malformed_resources - Test graceful handling of corrupted or malformed resource structures without panicking.

    Note: These tests will require adding new PE fixtures with actual resource data. The current test_binary_pe.exe is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.

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

    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.

    📄 tests/unit_utf16.rs (NEW) 🔗

    References

    Create comprehensive unit tests for UTF-16LE decoding:

    1. test_decode_simple_ascii - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)

    2. test_decode_unicode_chars - Test decoding of non-ASCII Unicode characters

    3. test_decode_null_terminated - Test null-terminated string handling

    4. test_decode_empty_input - Test handling of empty byte slices

    5. test_decode_odd_length - Test error handling for odd-length input (invalid UTF-16LE)

    6. test_decode_invalid_surrogate - Test handling of invalid surrogate pairs

    7. test_decode_with_bom - Test handling of UTF-16LE BOM (0xFF 0xFE)

    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.

    📄 tests/unit_pe_resources.rs (NEW) 🔗

    References

    Create unit tests for PE resource extraction with crafted binary data:

    1. test_version_info_parsing - Test parsing of a minimal VERSIONINFO structure with known string values

    2. test_string_table_parsing - Test parsing of RT_STRING blocks with known string IDs

    3. test_manifest_xml_parsing - Test extraction of strings from a minimal XML manifest

    4. test_resource_rva_translation - Test RVA to file offset translation for resource data

    5. test_malformed_version_info - Test graceful handling of corrupted VERSIONINFO structures

    6. test_empty_string_table - Test handling of empty string table blocks

    7. test_manifest_encoding_detection - Test detection of UTF-8 vs UTF-16 manifest encoding

    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.

    📄 docs/src/string-extraction.md (MODIFY) 🔗

    References

    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.

    📄 .kiro/specs/stringy-binary-analyzer/tasks.md (MODIFY) 🔗

    Mark tasks 4.1 and 4.2 as completed:

    • Change [ ] 4.1 Add PE resource extraction foundation to [x] 4.1 Add PE resource extraction foundation
    • Change [ ] 4.2 Implement PE resource string extraction to [x] 4.2 Implement PE resource string extraction

    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Diagram

    sequenceDiagram
        participant User
        participant PeParser as PE Parser<br/>(Container)
        participant PeExtractor as PE Extractor<br/>(Extraction)
        participant ResourceExtractor as Resource Extractor
        participant Goblin as Goblin Library
        participant UTF16 as UTF-16 Decoder
        participant QuickXML as Quick-XML
    
        User->>PeParser: parse(pe_bytes)
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: PE metadata
        PeParser->>PeParser: Identify .rsrc section
        PeParser-->>User: ContainerInfo with sections
    
        User->>PeExtractor: extract_strings(pe_bytes, ContainerInfo)
        PeExtractor->>ResourceExtractor: extract_resources(pe_bytes)
        
        ResourceExtractor->>Goblin: ResourceData::parse()
        Goblin-->>ResourceExtractor: Resource directory tree
        
        par VERSIONINFO Extraction
            ResourceExtractor->>Goblin: VersionInfo::parse()
            Goblin-->>ResourceExtractor: Version strings (UTF-16LE)
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Version
        and STRINGTABLE Extraction
            ResourceExtractor->>Goblin: Locate RT_STRING resources
            Goblin-->>ResourceExtractor: String table blocks
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Resource
        and MANIFEST Extraction
            ResourceExtractor->>Goblin: Locate RT_MANIFEST
            Goblin-->>ResourceExtractor: Manifest XML bytes
            ResourceExtractor->>QuickXML: Parse XML
            QuickXML-->>ResourceExtractor: XML elements
            ResourceExtractor->>ResourceExtractor: Extract text, tag with Manifest
        end
        
        ResourceExtractor-->>PeExtractor: Vec<FoundString>
        PeExtractor-->>User: All extracted strings
    
    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
    
    **Key Findings:**
    
    1. **Goblin 0.10.3 has built-in PE resource support** - The project already uses goblin 0.10.3 which includes `ImageResourceDirectory`, `ResourceData`, `VersionInfo`, and resource parsing utilities added in v0.10.1+
    2. **Infrastructure is ready** - Types like `StringSource::ResourceString`, `SectionType::Resources`, and tags (`Version`, `Manifest`, `Resource`) are already defined
    3. **Extraction module is empty** - `src/extraction/mod.rs` is a placeholder, perfect for adding the resource extraction logic
    4. **No UTF-16 decoder exists** - Need to implement UTF-16LE decoding for resource strings
    5. **No XML parser** - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)
    6. **PE fixtures exist but lack resources** - Current `test_binary_pe.exe` is minimal; need to add resource-rich test binaries
    7. **Documentation describes the architecture** - Clear separation between container parsing (metadata) and extraction (string extraction)
    8. **Ranking system is documented** - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags
    
    ### Approach
    
    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.
    
    ### Reasoning
    
    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in `src/container/pe.rs` which identifies `.rsrc` sections but doesn't parse them. Reviewed the type definitions in `src/types.rs` confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including `ImageResourceDirectory`, `ResourceData`, and `VersionInfo`. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.
    
    ## Mermaid Diagram
    
    sequenceDiagram
        participant User
        participant PeParser as PE Parser<br/>(Container)
        participant PeExtractor as PE Extractor<br/>(Extraction)
        participant ResourceExtractor as Resource Extractor
        participant Goblin as Goblin Library
        participant UTF16 as UTF-16 Decoder
        participant QuickXML as Quick-XML
    
        User->>PeParser: parse(pe_bytes)
        PeParser->>Goblin: Parse PE structure
        Goblin-->>PeParser: PE metadata
        PeParser->>PeParser: Identify .rsrc section
        PeParser-->>User: ContainerInfo with sections
    
        User->>PeExtractor: extract_strings(pe_bytes, ContainerInfo)
        PeExtractor->>ResourceExtractor: extract_resources(pe_bytes)
        
        ResourceExtractor->>Goblin: ResourceData::parse()
        Goblin-->>ResourceExtractor: Resource directory tree
        
        par VERSIONINFO Extraction
            ResourceExtractor->>Goblin: VersionInfo::parse()
            Goblin-->>ResourceExtractor: Version strings (UTF-16LE)
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Version
        and STRINGTABLE Extraction
            ResourceExtractor->>Goblin: Locate RT_STRING resources
            Goblin-->>ResourceExtractor: String table blocks
            ResourceExtractor->>UTF16: decode_utf16le()
            UTF16-->>ResourceExtractor: Decoded strings
            ResourceExtractor->>ResourceExtractor: Tag with Resource
        and MANIFEST Extraction
            ResourceExtractor->>Goblin: Locate RT_MANIFEST
            Goblin-->>ResourceExtractor: Manifest XML bytes
            ResourceExtractor->>QuickXML: Parse XML
            QuickXML-->>ResourceExtractor: XML elements
            ResourceExtractor->>ResourceExtractor: Extract text, tag with Manifest
        end
        
        ResourceExtractor-->>PeExtractor: Vec<FoundString>
        PeExtractor-->>User: All extracted strings
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add `quick-xml` dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Transform this placeholder into the extraction module's main interface. Define the `StringExtractor` trait with a method `extract_strings` that takes binary data and `ContainerInfo` and returns a vector of `FoundString`. Create a public function `extract_all_strings` that dispatches to format-specific extractors. Add module declarations for submodules: `pub mod pe;` and `pub mod utf16;` (for shared UTF-16 decoding utilities).
    
    ### src/extraction/utf16.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create a UTF-16LE decoding utility module. Implement `decode_utf16le` function that takes a byte slice and returns `Result<String>`, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add `decode_utf16le_null_terminated` variant that stops at the first null terminator (0x00 0x00). Include helper functions like `is_valid_utf16le_char` for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).
    
    ### src/extraction/pe/mod.rs(NEW)
    
    References: 
    
    - src/extraction/mod.rs(MODIFY)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Create the PE-specific extraction module. Define a `PeExtractor` struct that implements the `StringExtractor` trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: `pub mod resources;` for resource-specific extraction logic. The extractor should use the `ContainerInfo` to identify the `.rsrc` section and delegate to the resources module for parsing.
    
    ### src/extraction/pe/resources.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Implement the core PE resource extraction logic. Create a `ResourceExtractor` struct with methods:
    
    1. `extract_resources` - Main entry point that takes PE bytes, uses goblin's `ResourceData::parse` to get the resource directory, and orchestrates extraction of all resource types
    
    2. `extract_version_info` - Use goblin's `VersionInfo::parse` to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using `translation()`. Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from `crate::extraction::utf16`. Create `FoundString` instances with `StringSource::ResourceString`, tag with `Tag::Version`, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")
    
    3. `extract_string_tables` - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create `FoundString` instances tagged with `Tag::Resource`, preserving string IDs and language information
    
    4. `extract_manifests` - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's `Reader`. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create `FoundString` instances tagged with `Tag::Manifest`
    
    5. Helper methods: `rva_to_offset` for translating resource RVAs to file offsets using section information, `get_resource_data` to extract raw bytes for a resource entry, error handling for malformed resources
    
    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method `get_resource_section` that returns the `.rsrc` section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the `ContainerInfo` returned includes the `.rsrc` section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.
    
    ### src/types.rs(MODIFY)
    
    Add a new error variant to `StringyError` enum: `ResourceParseError(String)` for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.
    
    ### src/lib.rs(MODIFY)
    
    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Add comprehensive integration tests for PE resource extraction:
    
    1. `test_pe_version_info_extraction` - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with `Tag::Version` and have `StringSource::ResourceString`.
    
    2. `test_pe_string_table_extraction` - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.
    
    3. `test_pe_manifest_extraction` - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with `Tag::Manifest`.
    
    4. `test_pe_multiple_languages` - Test handling of resources with multiple language variants, ensuring all languages are processed.
    
    5. `test_pe_malformed_resources` - Test graceful handling of corrupted or malformed resource structures without panicking.
    
    Note: These tests will require adding new PE fixtures with actual resource data. The current `test_binary_pe.exe` is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.
    
    ### tests/unit_utf16.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    
    Create comprehensive unit tests for UTF-16LE decoding:
    
    1. `test_decode_simple_ascii` - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)
    
    2. `test_decode_unicode_chars` - Test decoding of non-ASCII Unicode characters
    
    3. `test_decode_null_terminated` - Test null-terminated string handling
    
    4. `test_decode_empty_input` - Test handling of empty byte slices
    
    5. `test_decode_odd_length` - Test error handling for odd-length input (invalid UTF-16LE)
    
    6. `test_decode_invalid_surrogate` - Test handling of invalid surrogate pairs
    
    7. `test_decode_with_bom` - Test handling of UTF-16LE BOM (0xFF 0xFE)
    
    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.
    
    ### tests/unit_pe_resources.rs(NEW)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/extraction/utf16.rs(NEW)
    
    Create unit tests for PE resource extraction with crafted binary data:
    
    1. `test_version_info_parsing` - Test parsing of a minimal VERSIONINFO structure with known string values
    
    2. `test_string_table_parsing` - Test parsing of RT_STRING blocks with known string IDs
    
    3. `test_manifest_xml_parsing` - Test extraction of strings from a minimal XML manifest
    
    4. `test_resource_rva_translation` - Test RVA to file offset translation for resource data
    
    5. `test_malformed_version_info` - Test graceful handling of corrupted VERSIONINFO structures
    
    6. `test_empty_string_table` - Test handling of empty string table blocks
    
    7. `test_manifest_encoding_detection` - Test detection of UTF-8 vs UTF-16 manifest encoding
    
    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.
    
    ### docs/src/string-extraction.md(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    
    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.
    
    ### .kiro/specs/stringy-binary-analyzer/tasks.md(MODIFY)
    
    Mark tasks 4.1 and 4.2 as completed:
    - Change `[ ] 4.1 Add PE resource extraction foundation` to `[x] 4.1 Add PE resource extraction foundation`
    - Change `[ ] 4.2 Implement PE resource string extraction` to `[x] 4.2 Implement PE resource string extraction`
    
    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Developer Humor

    🎯 Resource Extraction Haiku

    Deep in PE's .rsrc,
    Version strings hide in UTF-16—
    Goblin finds them all!

    📦 VERSIONINFO speaks,
    STRINGTABLE whispers secrets,
    Manifests confess.

    🔍 No pelite needed,
    Goblin 0.10.3 delivers—
    Quick-XML helps too!


    Execution Information

    Branch: main
    Commit: 37cfc7c

    New Plan

    Observations

    Key Findings:\n\n1. Goblin 0.10.3 has built-in PE resource support - The project already uses goblin 0.10.3 which includes ImageResourceDirectory, ResourceData, VersionInfo, and resource parsing utilities added in v0.10.1+\n2. Infrastructure is ready - Types like StringSource::ResourceString, SectionType::Resources, and tags (Version, Manifest, Resource) are already defined\n3. Extraction module is empty - src/extraction/mod.rs is a placeholder, perfect for adding the resource extraction logic\n4. No UTF-16 decoder exists - Need to implement UTF-16LE decoding for resource strings\n5. No XML parser - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)\n6. PE fixtures exist but lack resources - Current test_binary_pe.exe is minimal; need to add resource-rich test binaries\n7. Documentation describes the architecture - Clear separation between container parsing (metadata) and extraction (string extraction)\n8. Ranking system is documented - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags

    Approach

    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.

    Reasoning

    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in src/container/pe.rs which identifies .rsrc sections but doesn't parse them. Reviewed the type definitions in src/types.rs confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including ImageResourceDirectory, ResourceData, and VersionInfo. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.

    Proposed File Changes

    📄 Cargo.toml (MODIFY) 🔗

    Add quick-xml dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.

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

    References

    Transform this placeholder into the extraction module's main interface. Define the StringExtractor trait with a method extract_strings that takes binary data and ContainerInfo and returns a vector of FoundString. Create a public function extract_all_strings that dispatches to format-specific extractors. Add module declarations for submodules: pub mod pe; and pub mod utf16; (for shared UTF-16 decoding utilities).

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

    References

    Create a UTF-16LE decoding utility module. Implement decode_utf16le function that takes a byte slice and returns Result<String>, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add decode_utf16le_null_terminated variant that stops at the first null terminator (0x00 0x00). Include helper functions like is_valid_utf16le_char for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).

    📄 src/extraction/pe/mod.rs (NEW) 🔗

    References

    Create the PE-specific extraction module. Define a PeExtractor struct that implements the StringExtractor trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: pub mod resources; for resource-specific extraction logic. The extractor should use the ContainerInfo to identify the .rsrc section and delegate to the resources module for parsing.

    📄 src/extraction/pe/resources.rs (NEW) 🔗

    References

    Implement the core PE resource extraction logic. Create a ResourceExtractor struct with methods:

    1. extract_resources - Main entry point that takes PE bytes, uses goblin's ResourceData::parse to get the resource directory, and orchestrates extraction of all resource types

    2. extract_version_info - Use goblin's VersionInfo::parse to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using translation(). Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from crate::extraction::utf16. Create FoundString instances with StringSource::ResourceString, tag with Tag::Version, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")

    3. extract_string_tables - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create FoundString instances tagged with Tag::Resource, preserving string IDs and language information

    4. extract_manifests - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's Reader. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create FoundString instances tagged with Tag::Manifest

    5. Helper methods: rva_to_offset for translating resource RVAs to file offsets using section information, get_resource_data to extract raw bytes for a resource entry, error handling for malformed resources

    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).

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

    References

    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method get_resource_section that returns the .rsrc section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the ContainerInfo returned includes the .rsrc section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.

    📄 src/types.rs (MODIFY) 🔗

    Add a new error variant to StringyError enum: ResourceParseError(String) for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.

    📄 src/lib.rs (MODIFY) 🔗

    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.

    📄 tests/integration_pe.rs (MODIFY) 🔗

    References

    Add comprehensive integration tests for PE resource extraction:

    1. test_pe_version_info_extraction - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with Tag::Version and have StringSource::ResourceString.

    2. test_pe_string_table_extraction - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.

    3. test_pe_manifest_extraction - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with Tag::Manifest.

    4. test_pe_multiple_languages - Test handling of resources with multiple language variants, ensuring all languages are processed.

    5. test_pe_malformed_resources - Test graceful handling of corrupted or malformed resource structures without panicking.

    Note: These tests will require adding new PE fixtures with actual resource data. The current test_binary_pe.exe is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.

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

    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.

    📄 tests/unit_utf16.rs (NEW) 🔗

    References

    Create comprehensive unit tests for UTF-16LE decoding:

    1. test_decode_simple_ascii - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)

    2. test_decode_unicode_chars - Test decoding of non-ASCII Unicode characters

    3. test_decode_null_terminated - Test null-terminated string handling

    4. test_decode_empty_input - Test handling of empty byte slices

    5. test_decode_odd_length - Test error handling for odd-length input (invalid UTF-16LE)

    6. test_decode_invalid_surrogate - Test handling of invalid surrogate pairs

    7. test_decode_with_bom - Test handling of UTF-16LE BOM (0xFF 0xFE)

    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.

    📄 tests/unit_pe_resources.rs (NEW) 🔗

    References

    Create unit tests for PE resource extraction with crafted binary data:

    1. test_version_info_parsing - Test parsing of a minimal VERSIONINFO structure with known string values

    2. test_string_table_parsing - Test parsing of RT_STRING blocks with known string IDs

    3. test_manifest_xml_parsing - Test extraction of strings from a minimal XML manifest

    4. test_resource_rva_translation - Test RVA to file offset translation for resource data

    5. test_malformed_version_info - Test graceful handling of corrupted VERSIONINFO structures

    6. test_empty_string_table - Test handling of empty string table blocks

    7. test_manifest_encoding_detection - Test detection of UTF-8 vs UTF-16 manifest encoding

    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.

    📄 docs/src/string-extraction.md (MODIFY) 🔗

    References

    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.

    📄 .kiro/specs/stringy-binary-analyzer/tasks.md (MODIFY) 🔗

    Mark tasks 4.1 and 4.2 as completed:

    • Change [ ] 4.1 Add PE resource extraction foundation to [x] 4.1 Add PE resource extraction foundation
    • Change [ ] 4.2 Implement PE resource string extraction to [x] 4.2 Implement PE resource string extraction

    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    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
    
    **Key Findings:**\n\n1. **Goblin 0.10.3 has built-in PE resource support** - The project already uses goblin 0.10.3 which includes `ImageResourceDirectory`, `ResourceData`, `VersionInfo`, and resource parsing utilities added in v0.10.1+\n2. **Infrastructure is ready** - Types like `StringSource::ResourceString`, `SectionType::Resources`, and tags (`Version`, `Manifest`, `Resource`) are already defined\n3. **Extraction module is empty** - `src/extraction/mod.rs` is a placeholder, perfect for adding the resource extraction logic\n4. **No UTF-16 decoder exists** - Need to implement UTF-16LE decoding for resource strings\n5. **No XML parser** - Need to add quick-xml for manifest parsing (better for streaming than roxmltree)\n6. **PE fixtures exist but lack resources** - Current `test_binary_pe.exe` is minimal; need to add resource-rich test binaries\n7. **Documentation describes the architecture** - Clear separation between container parsing (metadata) and extraction (string extraction)\n8. **Ranking system is documented** - Resources should score 35 points (section weight) + semantic boosts for version/manifest tags
    
    ### Approach
    
    Implement PE resource string extraction by leveraging goblin 0.10.3's built-in resource parser to extract VERSIONINFO, STRINGTABLE, and RT_MANIFEST resources. The implementation will add a new resource extraction module within the extraction layer, integrate UTF-16LE decoding utilities, add quick-xml for manifest parsing, and wire everything into the existing container/extraction pipeline with appropriate tagging and scoring.
    
    ### Reasoning
    
    Started by exploring the repository structure and reading the GitHub issue. Examined the current PE parser implementation in `src/container/pe.rs` which identifies `.rsrc` sections but doesn't parse them. Reviewed the type definitions in `src/types.rs` confirming all necessary enums exist. Checked Cargo.toml and discovered goblin 0.10.3 is already in use. Searched the web and confirmed goblin 0.10.3 has comprehensive PE resource parsing capabilities including `ImageResourceDirectory`, `ResourceData`, and `VersionInfo`. Reviewed the design document and documentation to understand the extraction pipeline architecture. Examined test fixtures and found they lack PE resources. Researched XML parsing options and determined quick-xml is the best fit for manifest parsing.
    
    ## Proposed File Changes
    
    ### Cargo.toml(MODIFY)
    
    Add `quick-xml` dependency (version ~0.38) to the dependencies section for parsing RT_MANIFEST XML content. This is a lightweight, fast XML parser suitable for extracting strings from manifest resources without building a full DOM tree.
    
    ### src/extraction/mod.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Transform this placeholder into the extraction module's main interface. Define the `StringExtractor` trait with a method `extract_strings` that takes binary data and `ContainerInfo` and returns a vector of `FoundString`. Create a public function `extract_all_strings` that dispatches to format-specific extractors. Add module declarations for submodules: `pub mod pe;` and `pub mod utf16;` (for shared UTF-16 decoding utilities).
    
    ### src/extraction/utf16.rs(NEW)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Create a UTF-16LE decoding utility module. Implement `decode_utf16le` function that takes a byte slice and returns `Result<String>`, handling proper conversion from UTF-16LE bytes to Rust String. Include validation to detect invalid UTF-16 sequences and return appropriate errors. Add `decode_utf16le_null_terminated` variant that stops at the first null terminator (0x00 0x00). Include helper functions like `is_valid_utf16le_char` for validation. Add comprehensive unit tests covering valid strings, null termination, invalid sequences, and edge cases (empty input, odd-length input, surrogate pairs).
    
    ### src/extraction/pe/mod.rs(NEW)
    
    References: 
    
    - src/extraction/mod.rs(MODIFY)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Create the PE-specific extraction module. Define a `PeExtractor` struct that implements the `StringExtractor` trait. Implement the main extraction orchestration that calls resource extraction, section data extraction (future), and combines results. Add module declarations: `pub mod resources;` for resource-specific extraction logic. The extractor should use the `ContainerInfo` to identify the `.rsrc` section and delegate to the resources module for parsing.
    
    ### src/extraction/pe/resources.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    - src/types.rs(MODIFY)
    - src/container/pe.rs(MODIFY)
    
    Implement the core PE resource extraction logic. Create a `ResourceExtractor` struct with methods:
    
    1. `extract_resources` - Main entry point that takes PE bytes, uses goblin's `ResourceData::parse` to get the resource directory, and orchestrates extraction of all resource types
    
    2. `extract_version_info` - Use goblin's `VersionInfo::parse` to extract VERSIONINFO resources (RT_VERSION/type 16). Iterate through all language variants using `translation()`. Extract key fields: FileDescription, ProductName, CompanyName, LegalCopyright, FileVersion, ProductVersion, InternalName, OriginalFilename. Use the UTF-16 decoder from `crate::extraction::utf16`. Create `FoundString` instances with `StringSource::ResourceString`, tag with `Tag::Version`, and include resource metadata (type, language ID) in the section name (e.g., "RT_VERSION[1033]")
    
    3. `extract_string_tables` - Locate RT_STRING resources (type 6). String tables are organized in blocks of 16 strings each, indexed by resource ID. Iterate through all string table blocks and language variants. Decode UTF-16LE strings using the shared decoder. Create `FoundString` instances tagged with `Tag::Resource`, preserving string IDs and language information
    
    4. `extract_manifests` - Locate RT_MANIFEST resources (type 24). Extract the raw manifest data (typically UTF-8 or UTF-16). Detect encoding by checking for UTF-16 BOM (0xFF 0xFE) or UTF-8 BOM. Parse XML using quick-xml's `Reader`. Extract meaningful strings from key XML elements: assembly identity (name, version), dependency names, compatibility sections, requestedExecutionLevel. Create `FoundString` instances tagged with `Tag::Manifest`
    
    5. Helper methods: `rva_to_offset` for translating resource RVAs to file offsets using section information, `get_resource_data` to extract raw bytes for a resource entry, error handling for malformed resources
    
    All extracted strings should include proper offset calculation, RVA when available, and appropriate scoring hints (resources are high-confidence, should score ~35 base + semantic boosts).
    
    ### src/container/pe.rs(MODIFY)
    
    References: 
    
    - src/types.rs(MODIFY)
    
    Enhance the PE parser to expose resource directory information that the extraction layer will need. Add a helper method `get_resource_section` that returns the `.rsrc` section's offset and size. Consider adding a method to expose the PE's data directories (specifically the resource table directory) so the extraction layer can access it without re-parsing. Ensure the `ContainerInfo` returned includes the `.rsrc` section with proper RVA information. No changes to the core parsing logic are needed since goblin already handles this, but we may want to add convenience methods for the extraction layer.
    
    ### src/types.rs(MODIFY)
    
    Add a new error variant to `StringyError` enum: `ResourceParseError(String)` for resource-specific parsing failures. This allows graceful handling of malformed or corrupted resource structures without crashing. Update the error message to be descriptive for debugging purposes.
    
    ### src/lib.rs(MODIFY)
    
    Update the module documentation to reflect that PE resource extraction is now implemented. Update the status indicators in the doc comments: change extraction module status from "🚧 Framework ready" to "✅ PE Resources Complete" or similar. Ensure the extraction module is properly exposed in the public API if needed.
    
    ### tests/integration_pe.rs(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/types.rs(MODIFY)
    
    Add comprehensive integration tests for PE resource extraction:
    
    1. `test_pe_version_info_extraction` - Test extraction of VERSIONINFO strings from a PE binary with version resources. Verify that common fields like FileDescription, ProductName, CompanyName are extracted. Check that strings are tagged with `Tag::Version` and have `StringSource::ResourceString`.
    
    2. `test_pe_string_table_extraction` - Test extraction of STRINGTABLE resources. Verify that localized strings are extracted with proper language IDs.
    
    3. `test_pe_manifest_extraction` - Test extraction of RT_MANIFEST resources. Verify that assembly names, dependency information, and execution level strings are extracted and tagged with `Tag::Manifest`.
    
    4. `test_pe_multiple_languages` - Test handling of resources with multiple language variants, ensuring all languages are processed.
    
    5. `test_pe_malformed_resources` - Test graceful handling of corrupted or malformed resource structures without panicking.
    
    Note: These tests will require adding new PE fixtures with actual resource data. The current `test_binary_pe.exe` is minimal and likely lacks resources. Consider using a Windows system binary (like notepad.exe) as a test fixture or creating a custom PE with embedded resources using a resource compiler.
    
    ### tests/fixtures/README.md(MODIFY)
    
    Update the fixtures documentation to describe the new PE resource test fixtures. Add a section explaining that resource-rich PE binaries are needed for testing. Document how to create PE binaries with embedded resources using resource compilers (rc.exe on Windows or windres with MinGW). Alternatively, document that certain Windows system binaries (like notepad.exe, calc.exe) can be used as test fixtures for resource extraction validation. Include instructions for adding VERSIONINFO, STRINGTABLE, and manifest resources to test binaries.
    
    ### tests/unit_utf16.rs(NEW)
    
    References: 
    
    - src/extraction/utf16.rs(NEW)
    
    Create comprehensive unit tests for UTF-16LE decoding:
    
    1. `test_decode_simple_ascii` - Test decoding of simple ASCII strings in UTF-16LE format (e.g., "Hello" as 48 00 65 00 6C 00 6C 00 6F 00)
    
    2. `test_decode_unicode_chars` - Test decoding of non-ASCII Unicode characters
    
    3. `test_decode_null_terminated` - Test null-terminated string handling
    
    4. `test_decode_empty_input` - Test handling of empty byte slices
    
    5. `test_decode_odd_length` - Test error handling for odd-length input (invalid UTF-16LE)
    
    6. `test_decode_invalid_surrogate` - Test handling of invalid surrogate pairs
    
    7. `test_decode_with_bom` - Test handling of UTF-16LE BOM (0xFF 0xFE)
    
    These tests ensure the UTF-16 decoder is robust and handles all edge cases properly.
    
    ### tests/unit_pe_resources.rs(NEW)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    - src/extraction/utf16.rs(NEW)
    
    Create unit tests for PE resource extraction with crafted binary data:
    
    1. `test_version_info_parsing` - Test parsing of a minimal VERSIONINFO structure with known string values
    
    2. `test_string_table_parsing` - Test parsing of RT_STRING blocks with known string IDs
    
    3. `test_manifest_xml_parsing` - Test extraction of strings from a minimal XML manifest
    
    4. `test_resource_rva_translation` - Test RVA to file offset translation for resource data
    
    5. `test_malformed_version_info` - Test graceful handling of corrupted VERSIONINFO structures
    
    6. `test_empty_string_table` - Test handling of empty string table blocks
    
    7. `test_manifest_encoding_detection` - Test detection of UTF-8 vs UTF-16 manifest encoding
    
    These tests use crafted binary blobs to test the parsing logic in isolation without requiring full PE binaries.
    
    ### docs/src/string-extraction.md(MODIFY)
    
    References: 
    
    - src/extraction/pe/resources.rs(NEW)
    
    Update the "Resource Sections" subsection under "Section-Aware Extraction" to reflect the completed implementation. Replace the placeholder code example with actual implementation details. Document the three resource types extracted (VERSIONINFO, STRINGTABLE, RT_MANIFEST), the UTF-16LE decoding process, language/locale handling, and the tagging strategy. Add examples of typical strings extracted from each resource type. Include notes about scoring (resources receive high base scores of 35 + semantic boosts). Document error handling for malformed resources.
    
    ### .kiro/specs/stringy-binary-analyzer/tasks.md(MODIFY)
    
    Mark tasks 4.1 and 4.2 as completed:
    - Change `[ ] 4.1 Add PE resource extraction foundation` to `[x] 4.1 Add PE resource extraction foundation`
    - Change `[ ] 4.2 Implement PE resource string extraction` to `[x] 4.2 Implement PE resource string extraction`
    
    Update the task descriptions to reflect that goblin's built-in resource parser was used instead of pelite, and that quick-xml was added for manifest parsing. Note that the implementation covers VERSIONINFO, STRINGTABLE, and RT_MANIFEST as specified in the requirements.

    Developer Humor

    🎯 Resource Extraction Haiku\n> \n> Deep in PE's .rsrc,\n> Version strings hide in UTF-16—\n> Goblin finds them all!\n> \n> 📦 VERSIONINFO speaks,\n> STRINGTABLE whispers secrets,\n> Manifests confess.\n> \n> 🔍 No pelite needed,\n> Goblin 0.10.3 delivers—\n> Quick-XML helps too!


    Execution Information

    Branch: main
    Commit: 37cfc7c

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