Skip to content

Instantly share code, notes, and snippets.

@lillesvin lillesvin/bytescramble.rb
Last active Sep 20, 2015

Embed
What would you like to do?
Data bending is fun!
#!/usr/bin/env ruby
##
# ByteScramble
#
# Copyright 2015 Anders K. Madsen <lillesvin@gmail.com>
# Licence: MIT <https://opensource.org/licenses/MIT>
#
# Description:
# ------------
# Takes input from the specified input file or from stdin,
# fucks shit up and outputs to stdout.
# Works well on JPEGs and is pretty damn awful with PNGs.
#
# Examples:
# ---------
# Mess with input.jpg and save to output.jpg:
# bytescramble input.jpg > output.jpg
# or:
# bytescramble < input.jpg > output.jpg
# or:
# cat input.jpg | bytescramble > output.jpg
#
# Use a seed, so the same image gets destroyed in the same way every time:
# bytescramble -e 1234567890987654321 input.jpg > output.jpg
#
# Take a screenshot and glitch it up:
# import -window root jpeg:- | bytescramble > screenshot.jpg
#
# Take screenshot, scramble it and set it as wallpaper:
# import -window root jpeg:- | bytescramble | feh --bg-fill -
#
# Take screenshot, fuck it up and lock the screen using the gliched image as
# background:
# { import -window root jpeg:- | bytescramble | convert - /tmp/lock.png; } && i3lock -i /tmp/lock.png
#
# For all available options see:
# bytescramble --help
#
# There's technically no reason it shouldn't be able to fuck up non-image data,
# I just haven't tried it yet...
#
require 'optparse'
require 'base64'
class ByteScramble
VERSION = "0.5"
attr_reader :file, :bytes, :header, :body, :seed
attr_accessor :options, :rng
def initialize(options)
@options = options
if @options[:seed].nil?
@rng = Random.new
@options[:seed] = @rng.seed
else
@rng = Random.new(@options[:seed])
end
@options[:modifier] = @rng.rand(1..127) unless @options[:modifier]
vprint get_options
end
def fuckup(file)
split_input(file)
fuckshitup
output
end
def split_input(file)
@quads = Base64.encode64(file).scan(/.{1,4}/)
@header = @quads[0...headersize(@options[:headersize])]
@body = @quads[headersize(@options[:headersize])..-1]
end
def fuckshitup
1.upto(@options[:iterations]) do
@body = @body.byte_op(random_range(@body.length), @options[:operator], @options[:modifier], @rng)
end
end
def output
out = Base64.decode64([@header + @body].join)
if @options[:output]
IO.write(@options[:output], out)
else
print out
end
end
def headersize(size)
(size.to_f / 3.to_f).ceil
end
def get_chunksize
@rng.rand(@options[:minchunk]..@options[:maxchunk])
end
def random_range(max)
chunk = get_chunksize
max_offset = max - chunk
offset = @rng.rand(0..max_offset)
offset..(offset + chunk)
end
def get_options
back = ["bytescramble v%s options:" % ByteScramble::VERSION]
@options.each do |k,v|
back << " - %-12s: %s" % [k, v]
end
back.join("\n") + "\n"
end
def vprint(str)
if @options[:verbose]
$stderr.print str
end
end
end
# Some defaults:
options = {
:iterations => 10,
:maxchunk => 32,
:minchunk => 1,
:headersize => 128,
:operator => :rand
}
# Parse command line options
OptionParser.new do |opts|
opts.banner = "Usage: bytescramble [options] [input file]"
opts.separator ""
opts.separator "Reads the provided file or stdin and fucks up them bytes, y'all!"
opts.separator ""
opts.separator "Up-fucking options:"
opts.on("-e", "--seed INT", Integer, "Seed the PRNG with INT") do |e|
options[:seed] = e
end
opts.on("-p", "--operator TYPE", [:xor, :or, :and, :sub, :mult, :div, :rand],
"Operator to use: xor, or, and,",
"sub, mult, div, rand (default: %s)" % options[:operator]) do |p|
options[:operator] = p
end
opts.on("-m", "--modifier INT", Integer, "Modifier for operator (default: ",
"rand(1..127)). Ignored if -p is 'rand'") do |m|
options[:modifier] = m
end
opts.on("-i", "--iterations INT", Integer, "Scramble INT times (default: %s)" % options[:iterations]) do |i|
options[:iterations] = i
end
opts.on("-C", "--max-chunk INT", Integer, "Max chunk size in bytes (default: %s)" % options[:maxchunk]) do |m|
options[:maxchunk] = m
end
opts.on("-c", "--min-chunk INT", Integer, "Min chink size in bytes (default: %s)" % options[:minchunk]) do |n|
options[:minchunk] = n
end
opts.on("-s", "--header-size INT", Integer, "Do not scramble the first INT bytes",
"of the input (default: %s)" % options[:headersize]) do |s|
options[:headersize] = s
end
opts.separator ""
opts.separator "Common options:"
opts.on_tail("-v", "--verbose", "Print some stuff to stderr") do |v|
options[:verbose] = v
end
opts.on_tail("-V", "--version", "Show version info and exit") do
puts "bytescramble v%s" % ByteScramble::VERSION
exit
end
opts.on_tail("-h", "--help", "Show help and exit") do
puts opts
exit
end
end.parse!
# Extend Array a little
class Array
def replace(start, array)
if (start + array.length) > self.length
array = array[0...(self.length - start)]
end
if start > 0
self[0...start] + array + self[(start + array.length)..-1]
else
array + self[(start + array.length)..-1]
end
end
def byte_op(range, op, mod, rng)
new_range = self[range].map do |x|
back = []
Base64.decode64(x).bytes do |b|
case op
when :xor
byte = b ^ mod
when :or
byte = b | mod
when :and
byte = b + mod
when :add
byte = b + mod
when :sub
byte = b - mod
when :mult
byte = b * mod
when :div
byte = b / mod
when :rand
byte = rng.rand(1..128)
end
byte = byte % 128
back << byte.chr
end
Base64.encode64(back.join)
end
self.replace(range.first, new_range)
end
end
# Run
begin
bs = ByteScramble.new(options)
if ARGV.empty?
# Read from stdin
ARGF.binmode
bs.fuckup(ARGF.read)
else
bs.fuckup(IO.read(ARGV[0], {:mode => 'rb'}))
end
rescue Exception => e
$stderr.puts "bytescramble: Exception: %s" % e.to_s
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.