Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions DemoMultiThreaded.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
##
# Demo.rb
# Created: February 10, 2013
# By: Ron Bowes
#
# A demo of how to use Poracle, that works against RemoteTestServer.
##
#
# coding: utf-8
require 'httparty'
require './Poracle'
require './Utilities'
require 'optparse'
require 'thread/pool'
require 'ostruct'

class Demo
attr_reader :iv, :data, :blocksize,:newlinechars,:url,:starting_block
NAME = "Demo"
# This function should load @data, @iv, and @blocksize appropriately
def initialize()
@data = HTTParty.get("http://localhost:20222/encrypt").parsed_response
# Parse 'data' here
#@data = [@data].pack("H*")
@data = [@data].pack("H*").unpack('C*')
@iv = nil
@blocksize = 16
end

# This should make a decryption attempt and return true/false
def attempt_decrypt(data)
data= data.flatten.pack('C*').unpack("H*")
result = HTTParty.get("http://localhost:20222/decrypt/#{data.join}").parsed_response
# Match 'result' appropriately
return result !~ /Fail/
end

# Optionally define a character set, with the most common characters first
def character_set()
charset=' eationsrlhdcumpfgybw.k:v-/,CT0SA;B#G2xI1PFWE)3(*M\'!LRDHN_"9UO54Vj87q$K6zJY%?Z+=@QX&|[]<>^{}'
return charset.chars.to_a
end
end

#Main Program
options = Utilities.parse(ARGV)
verbose = options.verbose
file = options.file
skip_blocks=[]
sortfile =options.sortfile
threadsize =options.threadsize

if (sortfile)
Utilities.sort_sessionfile(file)
results= Utilities.parse_sessionfile(file)
puts "DECRYPTED: " + results.join

exit
end

# Read already decrypted block from last time and add them to skip_blocks
results= Utilities.parse_sessionfile(file)
if (!results.nil?)
results.each_with_index do |val,i|
if (!val.nil? and val!="\n")
skip_blocks<<i
end
end
end

if (verbose)
puts "Skipping blocks: #{skip_blocks.join(", ")}"
end

mod = Demo.new
if( mod.data.length % mod.blocksize != 0)
puts("Encrypted data isn't a multiple of the blocksize! Is this a block cipher?")
end

blockcount = mod.data.length / mod.blocksize

puts("> Starting poracle decrypter with module #{mod.class::NAME}")
puts(">> Encrypted length: %d" % mod.data.length)
puts(">> Blocksize: %d" % mod.blocksize)
puts(">> %d blocks" % blockcount)
iv = "\x00" * mod.blocksize

if (verbose)
i=0
blocks= mod.data.each_slice(mod.blocksize).to_a
blocks.each do |b|
i = i + 1
puts(">>> Block #{i}: #{b.pack("C*").unpack("H*")}")
end
end

start = Time.now

# Specify pool size
pool = Thread.pool(threadsize)
blockcount=mod.data.length / mod.blocksize
results=Array.new(blockcount)

# Spawn new thread for each block
(blockcount ).step(1,-1) do |i|
if (!skip_blocks.include?(i))
pool.process {
result=Poracle.decrypt(mod, mod.data, iv, verbose, i, file)
results[i]=result.pack('C*').force_encoding('utf-8')
}
end
end
pool.shutdown
puts "DECRYPTED: " + results.join
finish= Time.now
Utilities.sort_sessionfile(file)
puts sprintf("DURATION: %0.02f seconds", (finish - start) % 60)
4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
source 'https://rubygems.org'

gem 'thread', '0.1.3'
gem 'httparty', '0.12.0'
138 changes: 65 additions & 73 deletions Poracle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,42 +44,45 @@ def Poracle.ord(c)
end
return c.unpack('C')[0]
end

def Poracle.generate_set(base_list)
mapping = []
new_list=[]
base_list.each do |i|
new_list<< ord(i)
mapping[ord(i)] = true
end

0.upto(255) do |i|
if(!mapping[i])
base_list << i.chr
new_list <<i
end
end

return base_list
return new_list
end

def Poracle.find_character(mod, character, block, previous, plaintext, character_set, verbose = false)


def Poracle.find_character(mod, character, blocks, index, plaintext, character_set, verbose = false)
# First, generate a good C' (C prime) value, which is what we're going to
# set the previous block to. It's the plaintext we have so far, XORed with
# the expected padding, XORed with the previous block. This is like the
# ketchup in the secret sauce.
blockprime = "\0" * mod.blocksize
STDOUT.flush
block=blocks[index]
previous=blocks[index-1]
blockprime = Array.new(mod.blocksize, 0)
(mod.blocksize - 1).step(character + 1, -1) do |i|
blockprime[i] = (ord(plaintext[i]) ^ (mod.blocksize - character) ^ ord(previous[i])).chr
blockprime[i] = (plaintext[i]) ^ (mod.blocksize - character) ^(previous[i])
end

# Try all possible characters in the set (hopefully the set is exhaustive)
# Try all possible characters in the set (hopefully the set is exhaustive)
character_set.each do |current_guess|
# Calculate the next character of C' based on tghe plaintext character we
# Calculate the next character of C' based on the plaintext character we
# want to guess. This is the mayo in the secret sauce.
blockprime[character] = ((mod.blocksize - character) ^ ord(previous[character]) ^ ord(current_guess)).chr

blockprime[character] = ((mod.blocksize - character) ^ (previous[character]) ^ (current_guess))
# Ask the mod to attempt to decrypt the string. This is the last
# ingredient in the secret sauce - the relish, as it were.
result = mod.attempt_decrypt(blockprime + block)

new_block=Array.new
new_block << blocks[0..index-2] << blockprime << block
result = mod.attempt_decrypt(new_block)
# Increment the number of guesses (for reporting/output purposes)
@@guesses += 1

Expand All @@ -90,35 +93,36 @@ def Poracle.find_character(mod, character, block, previous, plaintext, character
if(character == mod.blocksize - 1)
# Modify the second-last character in any way (we XOR with 1 for
# simplicity)
blockprime[character - 1] = (ord(blockprime[character - 1]) ^ 1).chr
blockprime[character - 1] = (blockprime[character - 1]) ^ 1
# If the decryption fails, we hit a false positive!
if(!mod.attempt_decrypt(blockprime + block))
if(@verbose)
new_block=Array.new
new_block<<blocks[0..index-2] << blockprime << block
if(!mod.attempt_decrypt(new_block))
if(@verbose)
puts("Hit a false positive!")
end
false_positive = true
end
false_positive = true
end
end

# If it's not a false positive, return the character we just found
if(!false_positive)
return current_guess
end
end
end

raise("Couldn't find a valid encoding!")
if (@verbose)
puts("Couldn't find a valid encoding!")
end

def Poracle.do_block(mod, block, previous, has_padding = false, verbose = false)
end
def Poracle.do_block(mod, blocks, i, has_padding = false, verbose = false, file)
# Default result to all question marks - this lets us show it to the user
# in a pretty way
result = "?" * block.length

count=0
block=blocks[i]
previous=blocks[i-1]
# It doesn't matter what we default the plaintext to, as long as it's long
# enough
plaintext = "\0" * mod.blocksize

plaintext = Array.new(mod.blocksize, 0)
# Loop through the string from the end to the beginning
(block.length - 1).step(0, -1) do |character|
# When character is below 0, we've arrived at the beginning of the string
Expand All @@ -144,84 +148,72 @@ def Poracle.do_block(mod, block, previous, has_padding = false, verbose = false)
# the Battlestar Galactica wikia page (yes, I'm serious :) )
set = generate_set(' eationsrlhdcumpfgybw.k:v-/,CT0SA;B#G2xI1PFWE)3(*M\'!LRDHN_"9UO54Vj87q$K6zJY%?Z+=@QX&|[]<>^{}'.chars.to_a)
end

# Break the current character (this is the secret sauce)
c = find_character(mod, character, block, previous, plaintext, set, verbose)
plaintext[character] = c

c = find_character(mod, character, blocks, i, plaintext, set, verbose)
plaintext[character] = c.nil?? 0:c
if (c.nil?)
puts "Skipping block #{i}"
break
end
count+=1
if(verbose)
puts(plaintext)
puts "#{i} --> #{plaintext}"
end
end
if (count==mod.blocksize)
File.open(file, 'a') do |f|
f.puts "#{i},#{plaintext.pack('C*').force_encoding('utf-8')}"
end
end

end
return plaintext
end

# This is the public interface. Call this with the mod, data, and optionally
# the iv, and it'll return the decrypted text or throw an error if it can't.
# If no IV is given, it's assumed to be NULL (all zeroes).
def Poracle.decrypt(mod, data, iv = nil, verbose = false)
def Poracle.decrypt(mod, data, iv = nil, verbose = true, start = data.length / mod.blocksize, file)
# Default to a nil IV
if(iv.nil?)
iv = "\x00" * mod.blocksize

if(!iv.nil?)
iv =iv.unpack('C*')
data = iv + data
end

# Add the IV to the start of the encrypted string (for simplicity)
data = iv + data


blockcount = data.length / mod.blocksize

# Validate the blocksize
if(data.length % mod.blocksize != 0)
puts("Encrypted data isn't a multiple of the blocksize! Is this a block cipher?")
end

# Tell the user what's going on
if(verbose)
puts("> Starting Poracle decrypter with module #{mod.class::NAME}")
puts(">> Encrypted length: %d" % data.length)
puts(">> Blocksize: %d" % mod.blocksize)
puts(">> %d blocks:" % blockcount)
end

# Split the data into blocks - using unpack is kinda weird, but it's the
# best way I could find that isn't Ruby 1.9-specific
blocks = data.unpack("a#{mod.blocksize}" * blockcount)
blocks = data.each_slice(mod.blocksize).to_a
i = 0
blocks.each do |b|
i = i + 1
if(verbose)
puts(">>> Block #{i}: #{b.unpack("H*")}")
end
end

# Decrypt all the blocks - from the last to the first (after the IV).
# This can actually be done in any order.
result = ''
is_last_block = true
(blocks.size - 1).step(1, -1) do |i|
result = Array.new
is_last_block = (start==blockcount-1 ? true:false)
i=start
# Process this block - this is where the magic happens
new_result = do_block(mod, blocks[i], blocks[i - 1], is_last_block, verbose)
new_result = do_block(mod, blocks, i, is_last_block, verbose, file)
if(new_result.nil?)
return nil
end
is_last_block = false
result = new_result + result
if(verbose)
puts(" --> #{result}")
end
end

# Validate and remove the padding
pad_bytes = result[result.length - 1].chr

pad_bytes = result[result.length - 1]
if(result[result.length - ord(pad_bytes), result.length - 1] != pad_bytes * ord(pad_bytes))
puts("Bad padding:")
puts(result.unpack("H*"))
return nil
return result
end

# Remove the padding
result = result[0, result.length - ord(pad_bytes)]

return result
end
end

end
Loading