Index: /LICENSE
===================================================================
--- /LICENSE	(revision 1)
+++ /LICENSE	(revision 1)
@@ -0,0 +1,165 @@
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
Index: ADME.wiki
===================================================================
--- /README.wiki	(revision 138)
+++ 	(revision )
@@ -1,29 +1,0 @@
-<p>Cryptography is hard.  Most software developers realize this.  Security dogma of "don't invent your own crypto; use standard algorithms" has been a "best practice" for some time.  But what does this mean?  Use AES?  As cryptographers know, and history has shown, this alone is insufficient.  Using otherwise safe cryptographic primitives in custom protocols and message formats requires an understanding of cryptography that most programmers lack.  As a result, many implementations are sorely insecure due to the way cryptographic primitives are combined and applied in real world contexts.
-</p>
-<p>
-As a software penetration tester, your job is to identify security flaws in custom software.  However, when black-box testing an application that misuses cryptographic primitives, how do you identify and exploit these issues efficiently?  The task is fraught with engineering difficulties which frustrate the process and cause you to expend valuable testing time on trivial matters that are specific to the implementation.  Bletchley was created to assist with the detection, analysis, and exploitation of cryptographic flaws and aims to help automate the tedious aspects of this analysis while leaving the security expert in control of the process.
-</p>
-<p>
-Bletchley is currently in the early stages of development and consists of tools which provide:
-<ul>
-<li>Automated token encoding detection (47 encoding variants)</li>
-<li>Passive ciphertext block length and repetition analysis</li>
-<li>Script generator for efficient automation of HTTP requests</li>
-<li>A flexible, multithreaded padding oracle attack library with CBC-R support</li>
-<li>Automated chosen ciphertext probes, particularly useful against stream ciphers (experimental)</li>
-</ul>
-</p>
-<p>
-As the framework matures, the following additional features are currently anticipated:
-<ul>
-<li>Passive binary structure detection</li>
-<li>Tools to analyze and exploit other flaws, such as asymmetric algorithm and hash misuse</li>
-<li>Integration with timing attack tools</li>
-</ul>
-</p>
-<p>
-For more information on the specific tools, see the <a href="https://code.blindspotsecurity.com/trac/bletchley/wiki/Overview">documentation</a>.  Bletchley was created by penetration testers at <a href="http://vsecurity.com/">VSR</a> and is now maintained by <a href="https://blindspotsecurity.com/">Blindspot Security</a>.
-</p>
-<p>
-At the moment, the best way to use Bletchley is to <a href="https://code.blindspotsecurity.com/dav/bletchley">check out the source code</a> and build it from the <code>trunk</code> directory.  For more information, see the <a href="https://code.blindspotsecurity.com/trac/bletchley/browser/trunk/INSTALL">installation instructions</a>.  Note that contributions (patches, testing, <a href="https://code.blindspotsecurity.com/trac/bletchley/newticket">bug reports</a>) are greatly appreciated.
-</p>
Index: /SConstruct
===================================================================
--- /SConstruct	(revision 138)
+++ /SConstruct	(revision 1)
@@ -1,96 +1,91 @@
 import sys
 import os
+sys.dont_write_bytecode = True
+from bletchley_version import BLETCHLEY_VERSION
 
-cflags = '-std=gnu99 -pedantic -Wall -D_FILE_OFFSET_BITS=64'
+cflags = '-std=gnu99 -pedantic -Wall -D_FILE_OFFSET_BITS=64 -fvisibility=hidden'
+cflags += ' -DBLETCHLEY_VERSION=\'"%s"\'' % BLETCHLEY_VERSION
+cflags += ' -ggdb'
+
+#lib_src = ['lib/bletchley.c',
+#           'lib/winsec.c',
+#           'lib/range_list.c',
+#           'lib/lru_cache.c',
+#           'lib/void_stack.c']
+
+cc=os.environ.get('CC', 'gcc')
+env = Environment(ENV=os.environ,
+                  CC=cc,
+                  CFLAGS=cflags,
+                  CPPPATH=['include', '/usr/local/include'],
+                  LIBPATH=['lib', '/usr/local/lib'],
+                  LIBS=[])
+
+# Libraries
+#libbletchley_static = env.Library(lib_src)
+#libbletchley = env.SharedLibrary(lib_src, LIBS=['m','pthread', 'talloc'], 
+#                                 LINKFLAGS="-shared -fPIC -Wl,-soname,libbletchley.so.%s" % BLETCHLEY_VERSION)
 
 
-latest_release='0.0.1'
-source_targets=('src-trunk', 'src-0.0.1',)
-all_targets = source_targets
+# Executable binaries
+bletchley_nextrand = env.Program('bin/bletchley-nextrand', ['src/nextrand.c'])
 
 
-def parse_target(target):
-    chunks = target.split('-')
-    if len(chunks) != 2:
-        return None
-    return chunks
+# Documentation
+#  This only needs to be run during the release/packaging process
+#man_fixup = "|sed 's/.SH DESCRIPTION/\\n.SH DESCRIPTION/'"
+#man_builder = Builder(action='docbook2x-man --to-stdout $SOURCE'
+#                      + man_fixup + '| gzip -9 > $TARGET',
+#                      suffix = '.gz',
+#                      src_suffix = '.docbook')
+#env['BUILDERS']['ManPage'] = man_builder
 
-def version2input(version):
-    if version == 'trunk':
-        return 'trunk/'
-    else:
-        return 'releases/%s/' % version
+#man_bletchley = env.ManPage('doc/bletchley.1.docbook')
+#man_bletchley_recover = env.ManPage('doc/bletchley-recover.1.docbook')
+#man_bletchley_timeline = env.ManPage('doc/bletchley-timeline.1.docbook')
+
+# Installation
+prefix     = os.environ.get('PREFIX','/usr/local')+'/'
+destdir    = os.environ.get('DESTDIR','')
+bindir     = os.environ.get('BINDIR', prefix + 'bin')
+libdir     = os.environ.get('LIBDIR', prefix + 'lib')
+includedir = os.environ.get('INCLUDEDIR', prefix + 'include')
+mandir     = os.environ.get('MANDIR', prefix + 'man')
+
+install_items = [destdir + bindir]
+
+env.Install(destdir+bindir, [bletchley_nextrand, 'bin/bletchley-analyze'])
+#libinstall = env.Install(destdir+libdir, [libbletchley, libbletchley_static])
+#env.Install(destdir+includedir+'/bletchley', Glob('include/*.h'))
+#env.Install(destdir+mandir+'/man1', [man_bletchley, man_bletchley_recover,
+#                                     man_bletchley_timeline])
+#if os.getuid() == 0:
+#   env.AddPostAction(libinstall, 'ldconfig')
+
+if sys.version_info[0] == 2:
+   install_items.append('bletchley-python2.log')
+   env.Command('bletchley-python2.log', Glob('lib/bletchley/*.py')+Glob('lib/bletchley/PaddingOracle/*.py'),
+               "python bletchley-distutils install --root=/%s | tee bletchley-python2.log" % destdir)
+
+python_path = os.popen('which python3').read()
+if python_path != '':
+   install_items.append('bletchley-python3.log')
+   env.Command('bletchley-python3.log', Glob('lib/bletchley/*.py')+Glob('lib/bletchley/PaddingOracle/*.py'),
+               "python3 bletchley-distutils install --root=/%s | tee bletchley-python3.log" % destdir)
+
+# API documentation
+#bletchley_doc = env.Command('doc/devel/bletchley/index.html', 
+#                            Glob('lib/*.c')+Glob('include/*.h')+['doc/devel/Doxyfile.bletchley'],
+#                            'doxygen doc/devel/Doxyfile.bletchley')
+#pybletchley_doc = env.Command('doc/devel/pybletchley/index.html', 
+#                              Glob('python/pybletchley/*.py')+['doc/devel/Doxyfile.pybletchley', bletchley_doc],
+#                              'doxygen doc/devel/Doxyfile.pybletchley')
 
 
-export_cmds='''
-rm -rf .export
-svn export --depth files https://bletchley.googlecode.com/svn/ .export
-svn export https://bletchley.googlecode.com/svn/%(path)s .export/%(path)s
-'''
+# User Friendly Targets
+env.Alias('bin', [bletchley_nextrand])
+#env.Alias('doc', [man_bletchley,man_bletchley_recover,man_bletchley_timeline])
+#env.Alias('doc-devel', [bletchley_doc, pybletchley_doc])
+env.Alias('install', install_items)
 
-version_cmds='''
-echo 'BLETCHLEY_VERSION="%(version)s"' > .export/%(path)s/bletchley_version.py
-'''
-
-svnversion_cmds='''
-svn info https://bletchley.googlecode.com/svn/trunk/\
-  | grep "Last Changed Rev:" | cut -d' ' -f 4 \
-  | sed 's/^/BLETCHLEY_VERSION="%(latest_release)s.svn/' | sed 's/$/"/' > .export/%(path)s/bletchley_version.py
-'''
-
-cleanup_cmds='''
-rm -rf .export
-'''
-
-source_cmds='''
-mv %s .export/%s
-cd .export/%s && scons doc
-cd .export && tar cf %s.tar %s && gzip -9 %s.tar
-mv .export/%s.tar.gz .
-'''+cleanup_cmds
-
-
-def generate_cmds(source, target, env, for_signature):
-    ret_val = ''
-    input_prefix = str(source[0])+'/'
-
-    for t in target:
-        ttype,version = parse_target(str(t))
-        t_base = 'bletchley-%s-%s' % (ttype, version)
-
-        if ttype == 'src':
-            ret_val += source_cmds % (input_prefix, t_base, t_base, t_base,
-                                      t_base, t_base, t_base)
-    return ret_val
-
-
-release_builder = Builder(generator = generate_cmds,
-                          suffix = '',
-                          src_suffix = '',
-                          prefix='')
-
-env = Environment()
-env['BUILDERS']['Release'] = release_builder
-
-if len(COMMAND_LINE_TARGETS) == 0:
-    print('Acceptable targets: %s' % repr(all_targets))
-
-for target in COMMAND_LINE_TARGETS:
-    if target not in all_targets:
-        print('ERROR: cannot build "%s".  Acceptable targets: %s'
-              % (target, repr(all_targets)))
-        sys.exit(1)
-    AlwaysBuild(target)
-    ttype,version = parse_target(target)
-
-    params = {'path':version2input(version), 
-              'version':version,
-              'latest_release':latest_release}
-    env.Execute(export_cmds % params)
-    if version == 'trunk':
-        print env.Execute(svnversion_cmds % params)
-    else:
-        env.Execute(version_cmds % params)
-    env.Release(target, Dir('.export/'+params['path']))
-
-Default(None)
+Default('bin')
Index: /bin/bletchley-analyze
===================================================================
--- /bin/bletchley-analyze	(revision 1)
+++ /bin/bletchley-analyze	(revision 1)
@@ -0,0 +1,133 @@
+#!/usr/bin/env python
+
+# Requires Python 2.7+
+
+'''
+Simple script to leverage some of the blobtools features
+
+Copyright (C) 2011-2012 Virtual Security Research, LLC
+Author: Timothy D. Morgan
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+
+import sys
+import argparse
+import binascii
+from bletchley import blobtools,buffertools
+
+
+parser = argparse.ArgumentParser(description='Analyzes samples of encrypted data in an attempt to decode samples to binary and identify patterns useful in cryptanalysis.')
+parser.add_argument('input_file', nargs='?', default=None,
+                    help='File containing encrypted blobs to analyze, one per line. Omit to read from stdin.')
+parser.add_argument('-p', dest='output_lines', type=int, default=10,
+                    help='Number of lines of input to display in decoded form.')
+parser.add_argument('-c', action='store_const', const=True, default=False,
+                    help='Use color output for decoded highlights.')
+parser.add_argument('-c256', action='store_const', const=True, default=False,
+                    help='Use 256 color output for decoded highlights.')
+options = parser.parse_args()
+
+input_file = sys.stdin
+if options.input_file is not None:
+    input_file = file(options.input_file, 'rb')
+
+
+blobs = input_file.read().rstrip('\n').replace('\r', '').split('\n')
+#print(repr(blobs))
+
+def terminalHighlightedString(code, s):
+    global options
+    if code == None:
+        return s
+
+    if options.c256:
+        fg = code%216+16
+        bg = (code^0xffffffff)%216+16
+        return "\x1b[0;38;05;%d;48;05;%dm%s\x1b[0m" % (fg,bg,s)
+    elif options.c:
+        fg = code%6+31
+        bg = (code^0xffffffff)%6+41
+        return "\x1b[0;%d;%dm%s\x1b[0m" % (fg,bg,s)
+    else:
+        return "\x1b[7m%s\x1b[0m" % s
+
+
+def printColoredHexDump(blobs, group_size=8):    
+    chunk_size = 64
+    group_size *= 2 # two hex digits per byte
+
+    hex_blobs = map(binascii.b2a_hex, blobs)
+    color_map = buffertools.blockWiseColorMap(group_size, hex_blobs)
+
+    for k in range(0,len(hex_blobs)):
+        hex = hex_blobs[k]
+        line = ''
+        
+        for i in range(0,len(hex),chunk_size):
+            line += '%.8X: ' % i
+
+            if len(hex) < chunk_size:
+                hex += (chunk_size-len(hex))*' '
+
+            for j in range(0,len(hex),group_size):
+                group = hex[j:j+group_size]
+                line += terminalHighlightedString(color_map.get(group, None), group) + ' '
+        
+            line += '| '
+            line += repr(blobs[k][i/2:(i+chunk_size)/2])
+            line += '\n'
+        
+        print(line)
+
+
+
+def analyze(blobs):
+    lengths = blobtools.getLengths(blobs)
+    print('Unique Lengths: ' + ','.join(map(str, lengths)))
+
+    max_block_size = blobtools.maxBlockSize(lengths)
+    print('Maximum Possible Block Size: %d' % max_block_size)
+
+    block_sizes = blobtools.checkCommonBlocksizes(lengths)
+    print('Matching Common Block Sizes: %s' % ','.join(map(str, block_sizes)))
+
+    encodings = blobtools.encodingIntersection(blobs)
+    print('Possible Encodings: ' + ','.join(encodings))
+
+    encoding = blobtools.bestEncoding(encodings)
+    print('Best Encoding: %s' % encoding)
+
+    return encoding,block_sizes
+
+
+encoding_chain = []
+encoding = 'dummy'
+while encoding != None:
+    print('='*80)
+    print('Beginning analysis after decoding by chain: %s' % ','.join(encoding_chain))
+    encoding,block_sizes = analyze(blobs)
+    group_size = 4
+    if len(block_sizes) > 0 and min(block_sizes) in (8,16):
+        group_size = min(block_sizes)
+
+    blobs_to_print = blobs[:options.output_lines]
+    print('First %d Values:' % len(blobs_to_print))
+
+    printColoredHexDump(blobs_to_print, group_size)
+
+    if encoding != None:
+        encoding_chain.append(encoding)
+        blobs = blobtools.decodeAll(encoding, blobs)
+
Index: /bletchley-distutils
===================================================================
--- /bletchley-distutils	(revision 1)
+++ /bletchley-distutils	(revision 1)
@@ -0,0 +1,8 @@
+# Called from scons with appropriate python version
+import sys
+from distutils.core import setup
+sys.dont_write_bytecode = True
+from bletchley_version import BLETCHLEY_VERSION
+sys.dont_write_bytecode = False
+
+setup(name='bletchley', version=BLETCHLEY_VERSION, package_dir={'':'lib'}, packages=['bletchley'])
Index: /bletchley_version.py
===================================================================
--- /bletchley_version.py	(revision 1)
+++ /bletchley_version.py	(revision 1)
@@ -0,0 +1,2 @@
+SVN_REV = "$Rev: 103 $"
+BLETCHLEY_VERSION = "0.0.%s" % SVN_REV.split(' ')[1]
Index: /lib/bletchley/PaddingOracle/CBCREncryptionOracle.py
===================================================================
--- /lib/bletchley/PaddingOracle/CBCREncryptionOracle.py	(revision 1)
+++ /lib/bletchley/PaddingOracle/CBCREncryptionOracle.py	(revision 1)
@@ -0,0 +1,61 @@
+'''
+Created on Sep 19, 2010
+
+Copyright (C) 2010 ELOI SANFÈLIX
+@author: Eloi Sanfelix < eloi AT limited-entropy.com >
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from PaddingOracle.InvalidBlockError import InvalidBlockError
+import random
+import struct
+
+class CBCREncryptionOracle:
+    '''
+    This class implements an encryption oracle based on a decryption oracle.
+    The decryption oracle must implement a decrypt_block method, which given an input ciphertext
+    block returns the corresponding plaintext block.
+    
+    The technique used is known as CBC-R and was described by Juliano Rizzo and Thai Duong in 
+    their BlackHat 2010 presentation "Practical Padding Oracle Attacks".
+    '''
+
+    def __init__(self,oracle,blockSize=8):
+        self.oracle = oracle
+        self.blockSize = blockSize
+
+    def encrypt_block(self,input_block, prev_block = None):
+        if (len(input_block) != self.blockSize):
+            print "Received input block of len ",len(input_block)
+            raise InvalidBlockError(self.blockSize,len(input_block))
+        
+        if (prev_block == None):
+            prev_block =  "".join([struct.pack("B",random.getrandbits(8)) for i in range(self.blockSize) ])
+        ctext = self.oracle.decrypt_block(prev_block)
+        iv = self.oracle.xor_strings(ctext,input_block)
+        return iv+prev_block        
+    
+    def encrypt_message(self,message):
+        if (len(message) % self.blockSize != 0):
+            raise InvalidBlockError(self.blockSize,len(message))
+        
+        nblocks = len(message) / self.blockSize
+        # Encrypt last block
+        ctext = self.encrypt_block(message[-self.blockSize:])
+        for i in range(1,nblocks):
+            #Obtain next ctext and IV using previous ciphertext block + current message block
+            next = self.encrypt_block(message[-(i+1)*self.blockSize:-(i)*self.blockSize],ctext[0:self.blockSize])
+            #Add computed previous block to the ciphertext list
+            ctext = next[0:self.blockSize] + ctext
+        return ctext
Index: /lib/bletchley/PaddingOracle/DecryptionOracle.py
===================================================================
--- /lib/bletchley/PaddingOracle/DecryptionOracle.py	(revision 1)
+++ /lib/bletchley/PaddingOracle/DecryptionOracle.py	(revision 1)
@@ -0,0 +1,146 @@
+'''
+Created on Jul 4, 2010
+
+Copyright (C) 2010 ELOI SANFÈLIX
+@author: Eloi Sanfelix < eloi AT limited-entropy.com >
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from PaddingOracle.InvalidBlockError import InvalidBlockError
+import random
+import struct
+
+class DecryptionOracle:
+    '''
+    This class implements a decryption oracle based on a given padding oracle.
+    The attacked padding scheme is the one defined in PKCS#5 and RFC2040, and maybe other places.
+    The attack was first described in the "Security Flaws Induced by CBC Padding. Applications to SSL, IPSEC, WTLS... by Serge Vaudenay"
+    '''
+
+    
+    def __init__(self,oracle,blockSize=8):
+        '''
+        Creates a new DecryptionOracle object. Receives an oracle function which returns True 
+        if the given ciphertext results in a correct padding and False otherwise. A second 
+        parameter defining the cipher block size in bytes is also supported (default is 8). 
+        '''
+        self.oracle = oracle
+        self.blockSize = blockSize
+        
+    def decrypt_last_bytes(self,block):
+        '''
+        Decrypts the last bytes of block using the oracle.
+        '''
+        if(len(block)!=self.blockSize):
+            raise InvalidBlockError(self.blockSize,len(block))
+        
+        #First we get some random bytes
+        rand = [random.getrandbits(8) for i in range(self.blockSize)]
+        
+        for b in range(256):
+            
+            #XOR with current guess
+            rand[-1] = rand[-1]^b
+            #Generate padding string    
+            randStr = "".join([ struct.pack("B",i) for i in rand ] )
+            if(self.oracle(randStr+block)):
+                break
+            else:
+                #Remove current guess
+                rand[-1]=rand[-1]^b
+                
+        #Now we have a correct padding, test how many bytes we got!
+        for i in range(self.blockSize-1):
+            #Modify currently tested byte
+            rand[i] = rand[i]^0x01
+            randStr = "".join([ struct.pack("B",j) for j in rand ] )
+            if(not self.oracle(randStr+block)):
+                #We got a hit! Byte i is also part of the padding
+                paddingLen = self.blockSize-i
+                #Correct random i
+                rand[i] = rand[i]^0x01
+                #Return paddingLen final bytes
+                return "".join([ struct.pack("B",i^paddingLen) for i in rand[-paddingLen:]])
+            
+            #Nothing to do when there is no hit. This byte is useless then.
+
+        #Could only recover 1 byte. Return it.    
+        return "".join(struct.pack("B",rand[-1]^0x01))
+
+    def decrypt_next_byte(self,block,known_bytes):
+        '''
+        Given some known final bytes, decrypts the next byte using the padding oracle. 
+        '''
+        if(len(block)!=self.blockSize):
+            raise InvalidBlockError
+        numKnownBytes = len(known_bytes)
+        
+        if(numKnownBytes >= self.blockSize):
+            return known_bytes
+        
+        # Craft data that will produce xx ... xx <numKnownBytes+1> ... <numKnownBytes+1> after decryption
+        
+        rand = [random.getrandbits(8) for i in range(self.blockSize-numKnownBytes)]
+        for i in known_bytes:
+            rand.append(struct.unpack("B",i)[0]^(numKnownBytes+1))
+        
+        #Now we do same trick again to find next byte.
+        for b in range(256):
+            rand[-(numKnownBytes+1)] =rand[-(numKnownBytes+1)]^b 
+            #Generate padding string    
+            randStr = "".join([ struct.pack("B",i) for i in rand ] )
+
+            if(self.oracle(randStr+block)):
+                break
+            else:
+                rand[-(numKnownBytes+1)] =rand[-(numKnownBytes+1)]^b
+        
+        #  Return previous bytes together with current byte
+        return "".join([struct.pack("B",rand[i]^(numKnownBytes+1)) for i in range(self.blockSize-numKnownBytes-1,self.blockSize)])
+    
+    def decrypt_block(self,block):
+        '''
+        Decrypts the block of ciphertext provided as a parameter.
+        '''
+        bytes = self.decrypt_last_bytes(block)
+        while(len(bytes)!=self.blockSize):
+            bytes = self.decrypt_next_byte(block,bytes)
+        return bytes
+    
+    def decrypt_message(self,ctext, iv = None):
+        '''
+        Decrypts a message using CBC mode. If the IV is not provided, it assumes a null IV.
+        '''
+        #Recover first block
+        result = self.decrypt_block(ctext[0:self.blockSize])
+        
+        #XOR IV if provided, else we assume zero IV.
+        if( iv != None):
+            result = self.xor_strings(result, iv)
+
+        #Recover block by block, XORing with previous ctext block
+        for i in range(self.blockSize,len(ctext),self.blockSize):
+            prev = ctext[i-self.blockSize:i]
+            current = self.decrypt_block(ctext[i:i+self.blockSize])
+            result += self.xor_strings(prev,current)
+        return result
+    
+    def xor_strings(self,s1,s2):
+        result = ""
+        for i in range(len(s1)):
+            result += struct.pack("B",ord(s1[i])^ord(s2[i]))
+        return result
+    
+    def hex_string(self,data):
+        return "".join([ hex(ord(i))+" " for i in data])
Index: /lib/bletchley/PaddingOracle/InvalidBlockError.py
===================================================================
--- /lib/bletchley/PaddingOracle/InvalidBlockError.py	(revision 1)
+++ /lib/bletchley/PaddingOracle/InvalidBlockError.py	(revision 1)
@@ -0,0 +1,31 @@
+'''
+Created on Jul 4, 2010
+
+Copyright (C) 2010 ELOI SANFÈLIX
+@author: Eloi Sanfelix < eloi AT limited-entropy.com >
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+class InvalidBlockError(Exception):
+    '''
+    classdocs
+    '''
+
+    
+
+    def __init__(self, expectedSize, receivedSize):
+        self.expected = expectedSize
+        self.received = receivedSize
+    def __str__(self):
+        return "Invalid block size: "+self.received+" bytes. Block must be "+self.expected+" bytes long."
Index: /lib/bletchley/PaddingOracle/TimingWebPaddingOracle.py
===================================================================
--- /lib/bletchley/PaddingOracle/TimingWebPaddingOracle.py	(revision 1)
+++ /lib/bletchley/PaddingOracle/TimingWebPaddingOracle.py	(revision 1)
@@ -0,0 +1,156 @@
+'''
+Created on Sep 21, 2010
+
+Copyright (C) 2010 ELOI SANFÈLIX
+@author: Eloi Sanfelix < eloi AT limited-entropy.com >
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from time import *
+from urllib import urlencode
+from urllib2 import *
+import struct
+import urllib2
+
+class TimingWebPaddingOracle:
+    
+    def __init__(self, url , encoder = None, decoder = None, requests=100,headers = {}):
+        #Initialize Request with URL and headers
+        #self.req = Request(url,headers)
+        self.url = url
+        self.headers = headers
+        self.data = {}
+        self.oracle_name = ""
+        self.oracle_value = ""
+        self.time_threshold = None
+        self.requests = requests
+        
+        if(encoder== None and decoder!=None) or (encoder!=None and decoder==None):
+            print "ERROR: Encoder and decoder must be both set or not set at all. Disabling both."
+            self.encoder = None
+            self.decoder = None
+        else:
+            self.encoder = encoder
+            self.decoder = decoder
+        
+    def add_variable(self,name,value, oracle=False):
+        if(oracle):
+            #Data is defined as being vulnerable to oracle attack.
+            self.oracle_name = name
+            self.oracle_value = value
+        else:
+            #Add to dictionary with data
+            self.data[name] = value
+    
+    def analyze_normal_request(self):
+        
+        newdict = self.data.copy()
+        newdict[self.oracle_name] = self.oracle_value
+        r = urlencode(newdict)
+        #r = ""
+        #for i in self.data:
+        #    r = r + str(i)+"="+urlencode(self.data[i])+"&"
+        #r = r + str(self.oracle_name) + "=" + urlencode(self.oracle_value)
+        return self.analyze_request(r)
+    
+    def analyze_request(self,data):
+        t = 0
+        for i in range(self.requests):
+            t += self.perform_request(data)
+        return t/self.requests
+    
+    def perform_request(self,r):
+        t = time.time()
+        req = Request(self.url,self.headers)
+        req.add_data(r)
+        f = urllib2.urlopen(req)
+        f.read() # Read result from page
+        t = time.time() - t
+        return t
+        
+    def test_oracle(self):
+        if(self.oracle_name == None or self.oracle_value == None):
+            print "ERROR: Cannot test_oracle if no oracle variable defined"
+            return
+        
+        #Perform 'normal' analysis first
+        time1 = self.analyze_normal_request()            
+        
+        #Decode value if needed
+        if(self.decoder != None):
+            value = self.decoder(self.oracle_value)
+        else:
+            value = self.oracle_value
+        
+        oracle_list = [struct.unpack("B", value[i])[0] for i in range(len(value))]
+        
+        oracle_list[-1] ^= 0xFF #Ensure we always start with a different value
+
+        for b in range(256):
+            # XOR current counter. Last byte is now b^0xFF^i = ~b ^ i
+            oracle_list[-1] ^=b
+            v = "".join([struct.pack("B", i) for i in oracle_list])
+            if (self.encoder != None):
+                v = self.encoder(v)
+            
+            newdict = self.data.copy()
+            newdict[self.oracle_name] = v
+            r = urlencode(newdict)
+            
+            #And return to original value
+            oracle_list[-1] ^=b
+            
+            time2 = self.analyze_request(r)
+            
+            #FIXME most likely this will find a difference
+            if(time1 != time2):
+                print "Found difference for i="+hex(b)
+                print "Original timing: " + str(time1)
+                print "Bad timing: " + str(time2)
+                self.time_threshold = abs(time1 - time2) / 2 + min(time1,time2)
+                if(time1 > time2):
+                    self.oracle_type = 0x01 #Normal timing is higher than threshold
+                else:
+                    self.oracle_type = 0x02 #Normal timing is lower than threshold
+                return True
+        print "ERROR: Could not find a difference."
+        return False
+    
+    def oracle(self,ctext):
+        if(self.time_threshold == None):
+            print "ERROR: Oracle not defined!"
+        else:
+            
+            newdict = self.data.copy()
+            if(self.encoder != None):
+                ctext = self.encoder(ctext) #Encode ciphertext before sending request if needed
+            newdict[self.oracle_name] = ctext
+            
+            r = urlencode(newdict)
+            t = self.analyze_request(r)
+            
+            if (self.oracle_type == 0x01 ):
+                ret = self.time_threshold < t #Padding is correct if time above threshold
+            else:
+                ret = self.time_threshold > t #Padding is incorrect if time above threshold
+            return ret
+        
+    def set_threshold(self,threshold):
+        self.time_threshold = threshold
+    
+    def set_type(self,type):
+        self.oracle_type = type #0x01 means correct padding takes more time    
+    
+    def hex_string(self,data):
+        return "".join([ hex(ord(i))+" " for i in data])
Index: /lib/bletchley/blobtools.py
===================================================================
--- /lib/bletchley/blobtools.py	(revision 1)
+++ /lib/bletchley/blobtools.py	(revision 1)
@@ -0,0 +1,451 @@
+'''
+A collection of tools to assist in analyzing encrypted blobs of data
+
+Copyright (C) 2011-2012 Virtual Security Research, LLC
+Author: Timothy D. Morgan, Jason A. Donenfeld
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import string
+import base64
+import binascii
+import urllib
+import fractions
+import operator
+import functools
+import itertools
+import buffertools
+
+# abstract class
+class DataEncoding(object):
+    charset = frozenset('')
+    dialect = None
+    name = None
+    priority = None
+
+    def __init__(self, dialect=''):
+        self.dialect = dialect
+
+    def isExample(self, blob):
+        sblob = frozenset(blob)
+        return ((self.charset == None or sblob.issubset(self.charset)) and self.extraTests(blob))
+    
+    def extraTests(self, blob):
+        return True
+
+    def decode(self, blob):
+        return None
+
+    def encode(self, blob):
+        return None
+
+
+class base64Encoding(DataEncoding):
+    name = 'base64'
+    def __init__(self, dialect='rfc3548'):
+        super(base64Encoding, self).__init__(dialect)
+        if dialect.startswith('rfc3548'):
+            self.c62 = '+'
+            self.c63 = '/'
+            self.pad = '='
+        elif dialect.startswith('filename'):
+            self.c62 = '+'
+            self.c63 = '-'
+            self.pad = '='
+        elif dialect.startswith('url1'):
+            self.c62 = '-'
+            self.c63 = '_'
+            self.pad = '='
+        elif dialect.startswith('url2'):
+            self.c62 = '-'
+            self.c63 = '_'
+            self.pad = '.'
+        elif dialect.startswith('url3'):
+            self.c62 = '_'
+            self.c63 = '-'
+            self.pad = '.'
+        elif dialect.startswith('url4'):
+            self.c62 = '-'
+            self.c63 = '_'
+            self.pad = '!'
+        elif dialect.startswith('url5'):
+            self.c62 = '+'
+            self.c63 = '/'
+            self.pad = '$'
+        elif dialect.startswith('otkurl'):
+            self.c62 = '-'
+            self.c63 = '_'
+            self.pad = '*'
+        elif dialect.startswith('xmlnmtoken'):
+            self.c62 = '.'
+            self.c63 = '-'
+            self.pad = '='
+        elif dialect.startswith('xmlname'):
+            self.c62 = '_'
+            self.c63 = ':'
+            self.pad = '='
+        
+        self.charset = frozenset('ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+                                 +'abcdefghijklmnopqrstuvwxyz0123456789'
+                                 +self.c62+self.c63+self.pad)
+
+    def _guessPadLength(self, nopad_len):
+        length = ((4 - nopad_len % 4) % 4)
+        if length != 3:
+            return length
+        return None
+
+    def extraTests(self, blob):
+        nopad = blob.rstrip(self.pad)
+        padlen_guess = self._guessPadLength(len(nopad))
+        if padlen_guess == None:
+            return False
+
+        # we don't accept bad pads, only missing pads
+        if self.dialect.endswith('nopad'):
+            return self.pad not in blob
+
+        # pad must not appear in the middle of the 
+        # string and must be the correct length at the end
+        return (self.pad not in nopad) and (len(blob) == len(nopad)+padlen_guess)
+
+    def decode(self, blob):
+        if self.dialect.endswith('nopad'):
+            if self.pad in blob:
+                raise Exception("Unpadded base64 string contains pad character")
+
+            padlen = self._guessPadLength(len(blob))
+            if padlen == None:
+                raise Exception("Invalid length for unpadded base64 string.")
+
+            blob = blob+(self.pad*padlen)
+
+        if not self.dialect.startswith('rfc3548'):
+            table = string.maketrans(self.c62+self.c63+self.pad, '+/=')
+            blob = blob.translate(table)
+
+        return base64.standard_b64decode(blob)
+
+
+    def encode(self, blob):
+        ret_val = base64.standard_b64encode(blob)
+
+        if not self.dialect.startswith('rfc3548'):
+            table = string.maketrans('+/=', self.c62+self.c63+self.pad)
+            ret_val = ret_val.translate(table)
+
+        if ret_val != None and self.dialect.endswith('nopad'):
+            ret_val = ret_val.rstrip(self.pad)
+
+        return ret_val
+
+
+class base32Encoding(DataEncoding):
+    name = 'base32'
+    def __init__(self, dialect='rfc3548upper'):
+        super(base32Encoding, self).__init__(dialect)
+        if dialect.startswith('rfc3548upper'):
+            self.pad = '='
+            self.charset = frozenset('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'+self.pad)
+
+        elif dialect.startswith('rfc3548lower'):
+            self.pad = '='
+            self.charset = frozenset('abcdefghijklmnopqrstuvwxyz234567'+self.pad)
+
+    def _guessPadLength(self, nopad_len):
+        pad_lengths = {0:0, 7:1, 5:3, 4:4, 2:6}
+        return pad_lengths.get(nopad_len%8, None)  
+
+    def extraTests(self, blob):
+        nopad = blob.rstrip(self.pad)
+        padlen_guess = self._guessPadLength(len(nopad))
+        if padlen_guess == None:
+            return False
+
+        # we don't accept bad pads, only missing pads
+        if self.dialect.endswith('nopad'):
+            return self.pad not in blob
+
+        # pad must not appear in the middle of the 
+        # string and must be the correct length at the end
+        return (self.pad not in nopad) and (len(blob) == len(nopad)+padlen_guess)
+
+
+    def decode(self, blob):
+        if self.dialect.endswith('nopad'):
+            if self.pad in blob:
+                raise Exception("Unpadded base64 string contains pad character")
+
+            padlen = self._guessPadLength(len(blob))
+            if padlen == None:
+                raise Exception("Invalid length for unpadded base64 string.")
+
+            blob = blob+(self.pad*padlen)
+
+        return base64.b32decode(blob.upper())
+
+
+    def encode(self, blob):
+        ret_val = base64.b32encode(blob)
+
+        if ret_val != None and self.dialect.endswith('nopad'):
+            ret_val = ret_val.rstrip(self.pad)
+
+        if 'lower' in self.dialect:
+            ret_val = ret_val.lower()
+        else:
+            ret_val = ret_val.upper()
+
+        return ret_val
+
+
+class hexEncoding(DataEncoding):
+    name = 'hex'
+    def __init__(self, dialect='mixed'):
+        super(hexEncoding, self).__init__(dialect)
+        if 'mixed' in dialect:
+            self.charset = frozenset('ABCDEFabcdef0123456789')
+        elif 'upper' in dialect:
+            self.charset = frozenset('ABCDEF0123456789')            
+        elif 'lower' in dialect:
+            self.charset = frozenset('abcdef0123456789')
+
+
+    def extraTests(self, blob):
+        return (len(blob) % 2 == 0)
+
+    def decode(self, blob):
+        return binascii.a2b_hex(blob)
+
+    def encode(self, blob):
+        if 'upper' in self.dialect:
+            return binascii.b2a_hex(blob).upper()
+        if 'lower' in self.dialect:
+            return binascii.b2a_hex(blob).lower()
+        else:
+            return binascii.b2a_hex(blob)
+
+
+class percentEncoding(DataEncoding):
+    name = 'percent'
+    def __init__(self, dialect='mixed'):
+        super(percentEncoding, self).__init__(dialect)
+        self.charset = None
+        if 'mixed' in dialect:
+            self.hexchars = frozenset('ABCDEFabcdef0123456789')
+        elif 'upper' in dialect:
+            self.hexchars = frozenset('ABCDEF0123456789')            
+        elif 'lower' in dialect:
+            self.hexchars = frozenset('abcdef0123456789')
+
+    def extraTests(self, blob):
+        chunks = blob.split('%')
+        if len(chunks) < 2:
+            return False
+        for c in chunks[1:]:
+            if len(c) < 2:
+                return False
+            if (c[0] not in self.hexchars) or (c[1] not in self.hexchars):
+                return False
+        return True
+
+    def decode(self, blob):
+        if 'plus' in self.dialect:
+            return urllib.unquote(blob)
+        else:
+            return urllib.unquote_plus(blob)
+
+    # XXX: should technically produce quoted digits in same upper/lower case
+    def encode(self, blob):
+        if 'plus' in self.dialect:
+            return urllib.quote(blob)
+        else:
+            return urllib.quote_plus(blob)
+
+
+priorities = [
+    (hexEncoding, 'upper', 100),
+    (hexEncoding, 'lower', 101),
+    (hexEncoding, 'mixed', 102),
+    (base32Encoding, 'rfc3548upper', 150),
+    (base32Encoding, 'rfc3548lower', 151),
+    (base32Encoding, 'rfc3548upper-nopad', 160),
+    (base32Encoding, 'rfc3548lower-nopad', 161),
+    (base64Encoding, 'rfc3548', 200),
+    (base64Encoding, 'rfc3548-nopad', 201),
+    (base64Encoding, 'filename', 210),
+    (base64Encoding, 'filename-nopad', 211),
+    (base64Encoding, 'url1', 230),
+    (base64Encoding, 'url1-nopad', 231),
+    (base64Encoding, 'otkurl', 235),
+    (base64Encoding, 'otkurl-nopad', 236),
+    (base64Encoding, 'url2', 240),
+    (base64Encoding, 'url2-nopad', 241),
+    (base64Encoding, 'url3', 250),
+    (base64Encoding, 'url3-nopad', 251),
+    (base64Encoding, 'url4', 260),
+    (base64Encoding, 'url4-nopad', 261),
+    (base64Encoding, 'url5', 265),
+    (base64Encoding, 'url5-nopad', 266),
+    (base64Encoding, 'xmlnmtoken', 270),
+    (base64Encoding, 'xmlnmtoken-nopad', 271),
+    (base64Encoding, 'xmlname', 280),
+    (base64Encoding, 'xmlname-nopad', 281),
+    (percentEncoding, 'upper-plus', 400),
+    (percentEncoding, 'upper', 401),
+    (percentEncoding, 'lower-plus', 410),
+    (percentEncoding, 'lower', 411),
+    (percentEncoding, 'mixed-plus', 420),
+    (percentEncoding, 'mixed', 421),
+    ]
+
+encodings = {}
+for enc,d,p in priorities:
+    e = enc(d)
+    e.priority = p
+    encodings["%s/%s" % (enc.name, d)] = e
+
+
+def possibleEncodings(blob):
+    ret_val = set()
+    for name,encoding in encodings.items():
+        if encoding.isExample(blob):
+            ret_val.add(name)
+    return ret_val
+
+
+def encodingIntersection(blobs):
+    ret_val = set(encodings.keys())
+    for b in blobs:
+        ret_val &= possibleEncodings(b)
+
+    return ret_val
+
+
+def bestEncoding(encs):
+    priority = 999999999
+    best = None
+    for e in encs:
+        if encodings[e].priority < priority:
+            best = e
+            priority = encodings[e].priority
+    return best
+
+
+def decode(encoding, blob):
+    return encodings[encoding].decode(blob)
+
+def encode(encoding, blob):
+    return encodings[encoding].encode(blob)
+
+def decodeAll(encoding, blobs):
+    return map(encodings[encoding].decode, blobs)
+
+def encodeAll(encoding, blobs):
+    return map(encodings[encoding].encode, blobs)
+
+def decodeChain(decoding_chain, blob):
+    for decoding in decoding_chain:
+        blob = decode(decoding, blob)
+    return blob
+
+def encodeChain(encoding_chain, blob):
+    for encoding in encoding_chain:
+        blob = encode(encoding, blob)
+    return blob
+
+def getLengths(s):
+    lengths = set()
+    for bin in s:
+        lengths.add(len(bin))
+    lengths = list(lengths)
+    lengths.sort()
+    return lengths
+
+
+def maxBlockSize(blob_lengths):
+    divisor = 0
+    for bl in blob_lengths:
+        divisor = fractions.gcd(divisor, bl)
+
+    return divisor
+
+
+allTrue = functools.partial(reduce, (lambda x,y: x and y))
+
+def checkCommonBlocksizes(lengths):
+    common_block_sizes = (8,16,20)
+    ret_val = []
+    for cbs in common_block_sizes:
+        gcdIsCBS = (lambda x: fractions.gcd(x,cbs)==cbs)
+        if allTrue(map(gcdIsCBS, lengths)):
+            ret_val.append(cbs)
+    return ret_val
+
+
+def int2binary(x, bits=8):
+        """
+        Integer to binary
+        Count is number of bits
+        """
+        return "".join(map(lambda y:str((x>>y)&1), range(bits-1, -1, -1)))
+
+
+def smartPermutateBlobs(blobs, block_size=8):
+    """
+    Intelligently permutates through blocks in blobs.
+    If the same blob shows up in the same place for
+    every blob, the resultant permutations will have
+    this property as well.
+    blobs should be an array containing blobs
+    block_size should be an integer block_size or an
+    array of block sizes.
+    """
+
+    if len(blobs) == 0:
+        return
+
+    if not isinstance(block_size, (int, long)):
+        for size in block_size:
+             for blob in smartPermutateBlobs(blobs, size):
+                 yield blob
+        return
+
+    # First we find the indexes of the chunks that are different
+    different = set()
+    for combo in itertools.combinations(blobs, 2):
+        different |= set(buffertools.blockWiseDiff(block_size, combo[0], combo[1]))
+    
+    # Next we form a set containing the chunks that are different
+    different_chunks = []
+    for blob in blobs:
+        different_chunks.extend([blob[i * block_size:(i + 1) * block_size] for i in different])
+    # Remove duplicates
+    different_chunks = set(different_chunks)
+    
+    # We want to know which chunks are the same, too
+    chunk_len = len(blobs[0]) / block_size
+    same = set(range(0, chunk_len)) - different
+
+    # Now let's mix and match the differnet blocks, for all possible lengths
+    for i in range(1, chunk_len + 1):
+        for mix in itertools.permutations(different_chunks, i):
+            # We add back in the part that stays the same
+            for j in same:
+                mix.insert(j, blobs[0][j * block_size:(j + 1) * block_size])
+            mix = "".join(mix)
+            if mix in blobs:
+                continue
+            yield mix 
Index: /lib/bletchley/buffertools.py
===================================================================
--- /lib/bletchley/buffertools.py	(revision 1)
+++ /lib/bletchley/buffertools.py	(revision 1)
@@ -0,0 +1,119 @@
+'''
+A collection of tools to manipulate buffers of encrypted content
+
+Copyright (C) 2011-2012 Virtual Security Research, LLC
+Author: Timothy D. Morgan, Jason A. Donenfeld
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+import zlib
+
+# Computes the block-wise differences between two strings
+# Blobs must be the same length and their length must be a multiple of block_size
+#
+# Returns a list of block numbers that are different from one another.  
+# Examples:
+# blockWiseDiff(8, '1234567812345678', '12345678XXXXXXXX')
+# => [1]
+#
+# blockWiseDiff(1, '12345', '12345')
+# => []
+#
+
+def blockWiseDiff(block_size, blob1, blob2):
+    if len(blob1) != len(blob2):
+        sys.stderr.write("ERROR: Ciphertexts not the same length.\n")
+        return None
+
+    if (len(blob1) % block_size) != 0:
+        sys.stderr.write("ERROR: Ciphertexts do not have an even multiple of blocks.\n")
+        return None
+
+    blocks1 = [blob1[o:o+block_size] for o in range(0,len(blob1),block_size)]
+    blocks2 = [blob2[o:o+block_size] for o in range(0,len(blob2),block_size)]
+
+    ret_val = []
+    for b in range(0,len(blocks1)):
+        if blocks1[b] != blocks2[b]:
+            ret_val.append(b)
+
+    return ret_val
+
+
+
+def blockWiseColorMap(block_size, blobs):
+    '''
+    Accepts a sequence of blobs and compares all individual blocks 
+    (of block_size) within those blobs.  Returns a dictionary where the
+    keys are blocks from the original blobs whose values were repeated
+    at least once. (Unique blocks in the original blobs will not be
+    represented in the return value.) The values of the returned
+    dictionary are a 32bit integer hash value of the blocks they are
+    associated with.
+    '''
+    block_counts = {}
+    for blob in blobs:
+        for block_off in range(0,len(blob),block_size):
+            block = blob[block_off:block_off+block_size]
+            count = block_counts.get(block, None)
+            if count == 0:
+                block_counts[block] = 1 # more than one exists
+            elif count == None:
+                block_counts[block] = 0  # one exists
+
+    colors = {}
+    for block,count in block_counts.iteritems():
+        if count == 1:
+            # mask needed for portability
+            colors[block] = zlib.crc32(block)%0xFFFFFFFF 
+
+    return colors
+
+
+
+# XORs two buffers (bytes/bytearrays) and returns result
+#
+# If buffers not the same length, returned buffer length 
+# will be that of the shorter buffer.
+#
+def xorBuffers(buff1, buff2):
+    max_len = min(len(buff1), len(buff2))
+
+    ret_val = bytearray(buff1[0:max_len])
+    other = bytearray(buff2[0:max_len])
+    for i in range(0,len(ret_val)):
+        ret_val[i] ^= other[i]
+
+    return ret_val
+
+
+def splitBuffer(buf, block_size):
+	'''
+	Splits a buffer into evenly sized blocks.
+	'''
+	return [buf[i:i + block_size] for i in xrange(0, len(buf), block_size)]
+
+def iterBuffer(buf, block_size):
+	'''
+	Iterates through a buffer in evenly sized blocks.
+	'''
+	return (buf[i:i + block_size] for i in xrange(0, len(buf), block_size))
+
+def pkcs5PadBuffer(buf, block_size):
+	'''
+	Pads the end of a buffer using PKCS#5 padding.
+	'''
+	padding = block_size - (len(buf) % block_size)
+	return buf + (chr(padding) * padding)
Index: /lib/bletchley/chosen.py
===================================================================
--- /lib/bletchley/chosen.py	(revision 1)
+++ /lib/bletchley/chosen.py	(revision 1)
@@ -0,0 +1,75 @@
+'''
+A collection of tools to assist in analyzing encrypted blobs of data 
+through chosen plaintext attacks.
+
+Copyright (C) 2011-2012 Virtual Security Research, LLC
+Author: Timothy D. Morgan
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+import sys
+from buffertools import blockWiseDiff
+
+
+# Chosen plaintext attack on ECB encryption
+#
+# The encryptionOracle should accept a single string argument, 
+# encrypt that argument as part of a larger (unknown) plaintext string,
+# and then return the ciphertext.
+#
+# This function will then return a dictionary with information about the 
+# algorithm and chosen string attributes, including:
+#  block_size - the algorithm's block size
+#  chosen_offset - the chosen string's offset within the plaintext
+#  fragment_length - the length of a chosen from the chosen_offset to the
+#                    end of its current block
+#
+def ECB_FindChosenOffset(encryptionOracle):
+    ret_val = {}
+
+    # Guaranteed to have one block boundary on 128 bit block ciphers
+    chosen_length = 17
+    chosen = 'O'*chosen_length
+    base = encryptionOracle(chosen)
+
+    chosen = 'X' + 'O'*(chosen_length-1)
+    test_result = encryptionOracle(chosen)
+
+    different_blocks = blockWiseDiff(1, base, test_result)
+    block_size = len(different_blocks)
+    # Sanity check
+    different_blocks = blockWiseDiff(block_size, base, test_result)
+    if different_blocks == None:
+        sys.stderr.write("ERROR: Block size test yielded undiff-able ciphertexts.\n")
+        return None
+    if len(different_blocks) > 1:
+        sys.stderr.write("ERROR: Block size test yielded multiple altered blocks (not ECB mode?).\n")
+        return None
+
+    for i in range(2,chosen_length):
+        chosen = 'X'*i + 'O'*(chosen_length-i)
+        test_result = encryptionOracle(chosen)
+        different_blocks = blockWiseDiff(block_size, base, test_result)
+        
+        if different_blocks == None or len(different_blocks) == 0 or len(different_blocks) > 2:
+            sys.stderr.write("ERROR: Offset detection yielded inconsistent block diffs.\n")
+            return None
+        if len(different_blocks) == 2:
+            break
+
+    ret_val['block_size'] = block_size
+    ret_val['fragment_length'] = i-1
+    ret_val['chosen_offset'] = max(different_blocks)*block_size - ret_val['fragment_length']
+
+    return ret_val
Index: /lib/bletchley/known.py
===================================================================
--- /lib/bletchley/known.py	(revision 1)
+++ /lib/bletchley/known.py	(revision 1)
@@ -0,0 +1,33 @@
+'''
+A collection of tools to assist in analyzing encrypted blobs of data 
+through known plaintext attacks.
+
+Copyright (C) 2011-2012 Virtual Security Research, LLC
+Author: Timothy D. Morgan
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+'''
+
+from buffertools import xorBuffers
+
+# Attack OFB mode with static IV
+#
+def OFB_TestKnownPlaintext(plaintext, ciphertext1, ciphertext2):
+    ret_val = []
+
+    p1p2 = xorBuffers(ciphertext1,ciphertext2)
+    for i in range(0,len(p1p2)-len(plaintext)):
+        ret_val.append(xorBuffers(p1p2[i:i+len(plaintext)], plaintext))
+        
+    return ret_val
+
Index: /src/nextrand.c
===================================================================
--- /src/nextrand.c	(revision 1)
+++ /src/nextrand.c	(revision 1)
@@ -0,0 +1,78 @@
+/*
+Simple tool to generate the next [num] subsequent random numbers using Java
+Random.nextInt(), given any two sequential outputs of this method.
+
+Copyright (C) 2012 Virtual Security Research, LLC
+Author: Dan J. Rosenberg
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License, version 3,
+ as published by the Free Software Foundation.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define MULTIPLIER 25214903917L
+#define ADDEND 11L
+#define MASK ((1L << 48) - 1) 
+
+unsigned long long seed;
+
+int nextInt()
+{
+
+	seed = (seed * MULTIPLIER + ADDEND) & MASK;
+	return (int) (seed >> 16);
+
+}
+
+
+int main(int argc, char **argv)
+{
+
+	int i, num;
+	unsigned long long r1, r2;
+
+
+	if (argc != 4) {
+		printf("[-] Usage: %s rand1 rand2 num\n", argv[0]);
+		return 1;
+	}
+
+	r1 = atoi(argv[1]);
+	r2 = atoi(argv[2]);
+	num = atoi(argv[3]);
+
+	for (i = 0; i < 65536; i++) {
+		seed = (r1 << 16) + i;
+		if ((unsigned int)(((seed * MULTIPLIER + ADDEND) & MASK) >> 16) == (unsigned int)r2) {
+			break;
+		}
+		seed = 0;
+	}
+
+	if (!seed) {
+		printf("[-] Seed not found.\n");
+		return 1;
+	}
+
+	/* Uncomment to print the first two values, which were already provided */
+//	printf("%d\n", (int)r1);
+//	printf("%d\n", nextInt());
+
+	for (i = 0; i < num; i++) {
+		printf("%d\n", nextInt());
+	}
+
+	return 0;
+
+}
