Class: Omnizip::Filters::BCJ

Inherits:
Omnizip::Filter show all
Defined in:
lib/omnizip/filters/bcj.rb

Overview

Unified BCJ (Branch/Call/Jump) filter for multiple architectures

This filter preprocesses executable code by converting relative addresses in branch/call instructions to absolute addresses. The transformation is reversible and improves compression ratio.

Supports x86, ARM, ARM Thumb, ARM64, PowerPC, IA64, SPARC architectures. Automatically returns correct filter ID for 7z or XZ format.

Examples:

Create x86 BCJ filter

bcj = Omnizip::Filters::BCJ.new(architecture: :x86)
bcj.id_for_format(:xz)         # => 0x04
bcj.id_for_format(:seven_zip)  # => 0x03030103

Constant Summary collapse

CONFIG =

Architecture-specific configurations

{
  x86: {
    opcodes: [0xE8, 0xE9],  # CALL, JMP
    address_size: 4,
    instruction_size: 5,
    xz_id: 0x04,
    seven_zip_id: 0x03030103,
  },
  arm: {
    opcodes: [0x0A, 0x0B],  # ARM BL/B conditional
    address_size: 4,
    instruction_size: 4,
    xz_id: 0x07,
    seven_zip_id: 0x03030501,
  },
  armthumb: {
    opcodes: [0xE8, 0xF0, 0xF1], # ARM Thumb BL/B conditional
    address_size: 4,
    instruction_size: 4,
    xz_id: 0x08,
    seven_zip_id: 0x03030701,
  },
  arm64: {
    opcodes: [0x00], # ARM64 BL
    address_size: 4,
    instruction_size: 4,
    xz_id: nil, # Not yet in XZ
    seven_zip_id: 0x03030601,
  },
  powerpc: {
    opcodes: [0x48, 0x18], # PowerPC branch instructions
    address_size: 4,
    instruction_size: 4,
    xz_id: 0x05,
    seven_zip_id: 0x03030205,
  },
  ia64: {
    opcodes: [0x04, 0x05, 0x06, 0x07, 0x08], # IA64 branches
    address_size: 4,
    instruction_size: 4,
    xz_id: 0x06,
    seven_zip_id: 0x03030401,
  },
  sparc: {
    opcodes: [0x04, 0x06, 0x07], # SPARC call/branch
    address_size: 4,
    instruction_size: 4,
    xz_id: 0x09,
    seven_zip_id: 0x03030805,
  },
}.freeze

Instance Attribute Summary collapse

Attributes inherited from Omnizip::Filter

#name

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(architecture:) ⇒ BCJ

Initialize BCJ filter for specific architecture

Parameters:

  • architecture (Symbol)

    Target architecture (:x86, :arm, :armthumb, :arm64, :powerpc, :ia64, :sparc)

Raises:

  • (ArgumentError)

    If architecture is not supported



100
101
102
103
104
105
106
107
108
109
# File 'lib/omnizip/filters/bcj.rb', line 100

def initialize(architecture:)
  unless CONFIG.key?(architecture)
    raise ArgumentError, "Unsupported BCJ architecture: #{architecture}. " \
                         "Supported: #{CONFIG.keys.join(', ')}"
  end

  @architecture = architecture
  @config = CONFIG[architecture]
  super(architecture: architecture, name: "BCJ-#{architecture.to_s.upcase}")
end

Instance Attribute Details

#architectureSymbol (readonly)

Returns Architecture identifier.

Returns:

  • (Symbol)

    Architecture identifier



94
95
96
# File 'lib/omnizip/filters/bcj.rb', line 94

def architecture
  @architecture
end

Class Method Details

.metadataHash

Get metadata about this filter

Returns:

  • (Hash)

    Filter metadata



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/omnizip/filters/bcj.rb', line 215

def 
  {
    name: "BCJ",
    description: "Branch/Call/Jump converter for executable files",
    supported_architectures: CONFIG.keys,
    architectures: {
      x86: "x86/x86-64",
      arm: "ARM 32-bit",
      arm64: "ARM 64-bit",
      powerpc: "PowerPC",
      ia64: "IA-64 (Itanium)",
      sparc: "SPARC",
    },
  }
end

Instance Method Details

#decode(data, position = 0) ⇒ String

Decode (postprocess) data after decompression

Reverses encoding by converting absolute addresses back to relative addresses.

Parameters:

  • data (String)

    Binary executable data

  • position (Integer) (defaults to: 0)

    Current stream position (default: 0)

Returns:

  • (String)

    Decoded binary data



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/omnizip/filters/bcj.rb', line 181

def decode(data, position = 0)
  return data.dup if data.bytesize < @config[:instruction_size]

  result = data.b
  i = 0
  limit = data.bytesize - @config[:instruction_size]

  while i <= limit
    opcode = result.getbyte(i)

    if @config[:opcodes].include?(opcode)
      # Extract absolute address
      absolute = extract_address(result, i + 1)

      # Convert to relative
      address = absolute - (position + i + @config[:instruction_size])

      if valid_relative_address?(address)
        write_address(result, i + 1, address)
      end

      i += @config[:instruction_size]
    else
      i += 1
    end
  end

  result
end

#encode(data, position = 0) ⇒ String

Encode (preprocess) data for compression

Scans for branch/call opcodes and converts relative addresses to absolute addresses.

Parameters:

  • data (String)

    Binary executable data

  • position (Integer) (defaults to: 0)

    Current stream position (default: 0)

Returns:

  • (String)

    Encoded binary data



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/omnizip/filters/bcj.rb', line 143

def encode(data, position = 0)
  return data.dup if data.bytesize < @config[:instruction_size]

  result = data.b
  i = 0
  limit = data.bytesize - @config[:instruction_size]

  while i <= limit
    opcode = result.getbyte(i)

    if @config[:opcodes].include?(opcode)
      # Extract address (little-endian)
      address = extract_address(result, i + 1)

      # Check if valid relative address
      if valid_relative_address?(address)
        # Convert to absolute
        absolute = address + position + i + @config[:instruction_size]
        write_address(result, i + 1, absolute)
      end

      i += @config[:instruction_size]
    else
      i += 1
    end
  end

  result
end

#id_for_format(format) ⇒ Integer

Get filter ID for specific format

Parameters:

  • format (Symbol)

    Format identifier (:seven_zip, :xz)

Returns:

  • (Integer)

    Format-specific filter ID

Raises:

  • (ArgumentError)

    If format is not supported

  • (NotImplementedError)

    If architecture not supported in format



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/omnizip/filters/bcj.rb', line 117

def id_for_format(format)
  case format
  when :seven_zip
    @config[:seven_zip_id]
  when :xz
    id = @config[:xz_id]
    if id.nil?
      raise NotImplementedError,
            "#{@architecture} BCJ not yet supported in XZ format"
    end

    id
  else
    raise ArgumentError,
          "Unknown format: #{format}. Supported: :seven_zip, :xz"
  end
end