class ZSteg::CLI::Cli
Constants
- DEFAULT_ACTIONS
Public Class Methods
new(argv = ARGV)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 7 def initialize argv = ARGV @argv = argv end
Public Instance Methods
_extract_data(name)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 234 def _extract_data name case name when /scanline/ Checker::ScanlineChecker.check_image @img when /extradata:imagedata/ @img.imagedata[@img.scanlines.map(&:size).inject(&:+)..-1] when /extradata:(\d+)/ # accessing imagedata implicitly unpacks zlib stream # zlib stream may contain extradata @img.imagedata @img.extradata[$1.to_i] when /imagedata/ @img.imagedata else h = decode_param_string name h[:limit] = @options[:limit] if @options[:limit] != Checker::DEFAULT_LIMIT Extractor.new(@img, @options).extract(h) end end
check()
click to toggle source
actions
# File lib/zsteg/cli/cli.rb, line 257 def check Checker.new(@img, @options).check end
decode_param_string(s)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 206 def decode_param_string s h = {} s.split(',').each do |x| case x when 'lsb' h[:bit_order] = :lsb when 'msb' h[:bit_order] = :msb when /^(\d)b$/, /^b(\d+)$/ h[:bits] = parse_bits($1) when /^(\d)bp$/, /^b(\d+)p$/ h[:bits] = parse_bits($1) h[:pixel_align] = true when /\A[rgba]+\Z/ h[:channels] = [x] when /\Axy|yx|yb|by\Z/i h[:order] = x when 'prime' h[:prime] = true when 'zlib' h[:zlib] = true else raise "uknown param #{x.inspect}" end end h end
extract(name)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 261 def extract name data = _extract_data(name) if name['zlib'] if r = Checker::Zlib.check_data(data) data = r.data else raise "cannot decompress with zlib" end end print data end
load_image(fname)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 181 def load_image fname if File.directory?(fname) puts "[?] #{fname} is a directory".yellow else ZPNG::Image.load(fname, :verbose => @options[:verbose]+1) end rescue ZPNG::Exception, Errno::ENOENT puts "[!] #{$!.inspect}".red end
parse_bits(x)
click to toggle source
# File lib/zsteg/cli/cli.rb, line 191 def parse_bits x case x when '1', 1 # catch NOT A BINARY MASK early 1 when /^0x[0-9a-f]+$/i # hex, mask 0x100 + x.to_i(16) when /^(?:0b)?[01]+$/i # binary, mask 0x100 + x.to_i(2) when /^\d+$/ # decimal, number of bits x.to_i else raise "invalid bits value: #{x.inspect}" end end
run()
click to toggle source
# File lib/zsteg/cli/cli.rb, line 11 def run @actions = [] @options = { verbose: 0, limit: Checker::DEFAULT_LIMIT, order: Checker::DEFAULT_ORDER, step: 1, ystep: 1, } optparser = OptionParser.new do |opts| opts.banner = "Usage: zsteg [options] filename.png [param_string]" opts.separator "" opts.on "-a", "--all", "try all known methods" do @options[:prime] = :all @options[:order] = :all @options[:pixel_align] = :all @options[:bits] = (1..8).to_a # specifying --all on command line explicitly enables extra checks @options[:extra_checks] = true end opts.on "-E", "--extract NAME", "extract specified payload, NAME is like '1b,rgb,lsb'" do |x| @options[:verbose] = -2 # silent ZPNG warnings @actions << [:extract, x] end ################################################################################# opts.separator "\nIteration/extraction params:" ################################################################################# opts.on("-o", "--order X", /all|auto|[bxy,]+/i, "pixel iteration order (default: '#{@options[:order]}')", "valid values: ALL,xy,yx,XY,YX,xY,Xy,bY,...", ){ |x| @options[:order] = x.split(',') } opts.on("-c", "--channels X", /[rgba,1-8]+/, "channels (R/G/B/A) or any combination, comma separated", "valid values: r,g,b,a,rg,bgr,rgba,r3g2b3,..." ) do |x| @options[:channels] = x.split(',') # specifying channels on command line disables extra checks @options[:extra_checks] = false end opts.on("-b", "--bits N", "number of bits, single int value or '1,3,5' or range '1-8'", "advanced: specify individual bits like '00001110' or '0x88'" ) do |x| a = [] if x[-1] == 'p' @options[:pixel_align] = true x = x[0..-2] end x = '1-8' if x == 'all' x.split(',').each do |x1| if x1['-'] t = x1.split('-') a << Range.new(parse_bits(t[0]), parse_bits(t[1])).to_a else a << parse_bits(x1) end end @options[:bits] = a.flatten.uniq # specifying bits on command line disables extra checks @options[:extra_checks] = false end opts.on "--lsb", "least significant bit comes first" do @options[:bit_order] = :lsb end opts.on "--msb", "most significant bit comes first" do @options[:bit_order] = :msb end opts.on "-P", "--prime", "analyze/extract only prime bytes/pixels" do @options[:prime] = true # specifying prime on command line disables extra checks @options[:extra_checks] = false end opts.on("--shift N", Integer, "prepend N zero bits"){ |x| @options[:shift] = x } #opts.on("--step N", Integer, "step") { |x| @options[:step] = x } opts.on("--invert", "invert bits (XOR 0xff)") { @options[:invert] = true } opts.on "--pixel-align", "pixel-align hidden data" do @options[:pixel_align] = true end ################################################################################# opts.separator "\nAnalysis params:" ################################################################################# opts.on("-l", "--limit N", Integer, "limit bytes checked, 0 = no limit (default: #{@options[:limit]})"){ |n| @options[:limit] = n } opts.separator "" opts.on "--[no-]file", "use 'file' command to detect data type (default: YES)" do |x| @options[:file] = x end # TODO # opts.on "--[no-]binwalk", "use 'binwalk' command to detect data type (default: NO)" do |x| # @options[:binwalk] = x # end opts.on "--no-strings", "disable ASCII strings finding (default: enabled)" do |x| @options[:strings] = false end opts.on "-s", "--strings X", %w'first all longest none no', "ASCII strings find mode: first, all, longest, none", "(default: first)" do |x| @options[:strings] = x[0] == 'n' ? false : x.downcase.to_sym end opts.on "-n", "--min-str-len X", Integer, "minimum string length (default: #{Checker::DEFAULT_MIN_STR_LEN})" do |x| @options[:min_str_len] = x end opts.separator "" opts.on "-v", "--verbose", "Run verbosely (can be used multiple times)" do |v| @options[:verbose] += 1 end opts.on "-q", "--quiet", "Silent any warnings (can be used multiple times)" do |v| @options[:verbose] -= 1 end opts.on "-C", "--[no-]color", "Force (or disable) color output (default: auto)" do |x| if defined?(Rainbow) && Rainbow.respond_to?(:enabled=) Rainbow.enabled = x else Sickill::Rainbow.enabled = x end end opts.separator "\nPARAMS SHORTCUT\n"+ "\tzsteg fname.png 2b,b,lsb,xy ==> --bits 2 --channel b --lsb --order xy" end if (argv = optparser.parse(@argv)).empty? puts optparser.help return end @actions = DEFAULT_ACTIONS if @actions.empty? argv.each do |arg| if arg[','] && !File.exist?(arg) @options.merge!(decode_param_string(arg)) argv.delete arg end end argv.each_with_index do |fname,idx| if argv.size > 1 && @options[:verbose] >= 0 puts if idx > 0 puts "[.] #{fname}".green end next unless @img=load_image(@fname=fname) @actions.each do |action| if action.is_a?(Array) self.send(*action) if self.respond_to?(action.first) else self.send(action) if self.respond_to?(action) end end end rescue Errno::EPIPE # output interrupt, f.ex. when piping output to a 'head' command # prevents a 'Broken pipe - <STDOUT> (Errno::EPIPE)' message end