class Analyzers::PaddingOracle::Analyzer

Attributes

result[R]

Public Class Methods

new(oracle = CryptoToolbox::Oracles::PaddingOracle::TcpOracle.new) click to toggle source
# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 10
def initialize(oracle = CryptoToolbox::Oracles::PaddingOracle::TcpOracle.new)
  @result      = [ ]
  @oracle      = oracle
end

Public Instance Methods

analyze(cipher) click to toggle source

start with the second to last block to manipulate the final block ( cbc xor behaviour ) from there on we move to the left until we have used the first block (iv) to decrypt the second blick ( first plain text block )

we have to manipulate the block before the one we want to change xxxxxxxxx xxxxxxxxx xxxxxxxxxx changing this byte ^- will change ^- this byte at decryption

# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 22
def analyze(cipher)
  blocks = CryptBuffer.from_hex(cipher).chunks_of(16)

  # ranges cant be from high to low
  (1..(blocks.length() -1)).reverse_each do |block_index|
    result.unshift analyse_block(blocks,block_index)
  end

  plaintext = CryptBuffer(result.flatten)
  report_result(plaintext)
  plaintext.strip_padding
end

Private Instance Methods

analyse_block(blocks,block_index) click to toggle source
# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 37
def analyse_block(blocks,block_index)
  block_result = []

  # manipulate each byte of the 16 byte block
  1.upto(blocks[block_index -1].length) do |pad_index|
    with_oracle_connection do
      jot("processing byte #{pad_index} in block: #{block_index -1} => #{block_index}",debug: true)
      byte = read_byte(pad_index,block_result,blocks,block_index)
      block_result.unshift byte
    end
  end
  block_result
end
apply_found_bytes(buf,cur_result,pad_index) click to toggle source
# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 63
def apply_found_bytes(buf,cur_result,pad_index)
  # first we have to apply all the already found bytes

  # NOTE: to easily xor all already found byte and the current padding value
  # We build up a byte-array with all the known values and "left-pad" them with zeros
  other = ([0] * ( buf.length - cur_result.length)) + cur_result.map{|x| x ^ pad_index }
  # => [0,0,0,...,cur[n] ^ pad_index,... ]
  buf.xor(other)
end
assemble_oracle_input(buffer,blocks,block_index,pad_index,guess) click to toggle source

Create a subset to only send the blocks we still need to decrypt. manipulate the byte with a padding-index and a guess map the crypt buffer array to a flat array of integers ( representing bytes )

# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 103
def assemble_oracle_input(buffer,blocks,block_index,pad_index,guess)
  # the bytes from the subset we will send to the padding oracle
  subset = blocks[0,block_index+1]
  subset[block_index -1 ] = buffer.xor_at([guess,pad_index], -1 * pad_index)
  subset.map(&:bytes).flatten
end
block_amount(index) click to toggle source

include the block after the index, since this is the one effected by our manipulation. ( due to cbc mode )

# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 96
def block_amount(index)
  index +1 
end
read_byte(pad_index,cur_result,blocks,block_index) click to toggle source

the blocks are: xxxxxxxx xxxxxxxx xxxxxxxx [..] ^- IV ^- first ^- second …

# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 76
def read_byte(pad_index,cur_result,blocks,block_index)
  jot(cur_result.inspect,debug: true)
  
  # apply all the current-result bytes to the block corresponding to <block_index>
  # and store the result in a buffer we will mess with
  forge_buf = apply_found_bytes(blocks[block_index - 1],cur_result,pad_index)
  
  1.upto 255 do |guess|
    input = assemble_oracle_input(forge_buf,blocks,block_index,pad_index,guess)
    
    next if skip?(pad_index,block_index,guess,cur_result)
    return guess if @oracle.valid_padding?(input,block_amount(block_index))
  end

  raise FailedAnalysis, "No padding found... this should never happen..."
end
report_result(result) click to toggle source
# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 51
def report_result(result)
  jot(result.chars.inspect,debug: true)
  jot("stripping padding!",debug: true)
  jot(result.strip_padding.str,debug: true)
end
skip?(pad_index,block_index,guess,block_result) click to toggle source

In case of the first iteration there is a special case to skip: 1) No other blocks have been decrypted yet ( result.empty? ) 2) No bytes of the current block have been processed yet ( block_result_empty? ) 3) guess xor pad-index does not modify anything ( eq zero )

> This would leed to the original ciphertext without any modification beeing sent

# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 115
def skip?(pad_index,block_index,guess,block_result)
  result.empty? && block_result.empty? && (guess ^ pad_index).zero?
end
with_oracle_connection() { || ... } click to toggle source
# File lib/crypto-toolbox/analyzers/padding_oracle/analyzer.rb, line 57
def with_oracle_connection
  @oracle.connect
  yield
  @oracle.disconnect
end