# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# Ronin is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin 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 Ronin. If not, see .
#
require 'ronin/cli/command'
require 'ronin/cli/binary_template'
module Ronin
class CLI
module Commands
#
# Packs values into binary data.
#
# ## Usage
#
# ronin pack [options] TYPE:VALUE [...]
#
# ## Options
#
# -E, --endian little|big|net Sets the endianness
# -A x86|x86_64|ppc|ppc64|mips|mips_le|mips_be|mips64|mips64_le|mips64_be|arm|arm_le|arm_be|arm64|arm64_le|arm64_be,
# --arch Sets the architecture
# -O linux|macos|windows|android|apple_ios|bsd|freebsd|openbsd|netbsd,
# --os Sets the OS
# -x, --hexdump Hexdumps the packed data, instead of writing it out
# --output PATH Optional output file to write to
# -h, --help Print help information
#
# ## Arguments
#
# TYPE:VALUE A value and it's type.
#
# ### Types
#
# Native Little-endian Big-endian Network-endian
# ------ ------------- ---------- --------------
#
# char
# uchar
# byte
# string
# int int_le int_be int_net
# int8
# int16 int16_le int16_be int16_net
# int32 int32_le int32_be int32_net
# int64 int64_le int64_be int64_net
# short short_le short_be short_net
# long long_le long_be long_net
# long_long long_long_le long_long_be long_long_net
# uint uint_le uint_be uint_net
# uint8
# uint1616 uint16_le uint16_be uint16_net
# uint3232 uint32_le uint32_be uint32_net
# uint6464 uint64_le uint64_be uint64_net
# ushort ushort_le ushort_be ushort_net
# ulong ulong_le ulong_be ulong_net
# ulong_long ulong_long_le ulong_long_be ulong_long_net
# float float_le float_be float_net
# float32 float32_le float32_be float32_net
# float64 float64_le float64_be float64_net
# double double_le double_be double_net
# pointer pointer_le pointer_be pointer_net
#
# ## Examples
#
# ronin pack int32:-1 uint32:0x12345678 char:A string:hello
# ronin pack int32[4]:1,2,3,4 string[3]:hello,world
# ronin pack uint32_le:0x12345678
# ronin pack uint32_be:0x12345678
# ronin pack --endian big int:4096 uint:0x12345678
# ronin pack --arch arm_le int:4096 long:0x12345678
# ronin pack --arch x86_64 --os windows uint:0x12345678
#
# @since 2.1.0
#
class Pack < Command
include BinaryTemplate
option :hexdump, short: '-x',
desc: 'Hexdumps the packed data, instead of writing it out' do
require 'hexdump'
end
option :output, value: {
type: String,
usage: 'PATH'
},
desc: 'Optional output file to write to'
argument :element, required: true,
repeats: true,
usage: 'TYPE:VALUE',
desc: "A value and it's C type"
examples [
'int32:-1 uint32:0x12345678 char:A string:hello',
'int32[4]:1,2,3,4 string[3]:hello,world',
'uint32_le:0x12345678',
'uint32_be:0x12345678',
'--endian big int:4096 uint:0x12345678',
'--arch arm_le int:4096 long:0x12345678',
'--arch x86_64 --os windows uint:0x12345678'
]
description "Packs values into binary data"
man_page 'ronin-pack.1'
#
# Runs the `ronin pack` command.
#
# @param [Array] args
# The `TYPE:VALUE` strings to parse and pack.
#
def run(*args)
# perform an initial parsing of the arguments to extract types/values
types, values = parse_types_and_values(args)
# build the template using the parsed `TYPE`s
template = build_template(types)
# parse the values, but using the resolved C types.
values = parse_values(template.types,values)
# finally pack the parsed values using the binary template
data = template.pack(*values)
if options[:hexdump]
data.hexdump
else
write_output(data)
end
end
#
# Prints the help information for the arguments and lists `TYPE`s.
#
def help_arguments
super
puts
puts <] args
# The `TYPE:VALUE` argumens to parse.
#
# @return [(Array, Array)]
# The parsed types and values.
#
def parse_types_and_values(args)
types = []
values = []
args.each do |string|
type, value = string.split(':',2)
types << parse_type(type)
values << value
end
return types, values
end
#
# Performs a second parsing of the values based on their desired
# C types.
#
# @param [Array] types
# The desired types of the values.
#
# @param [Array>] values
# The values to further parse.
#
def parse_values(types,values)
# now parse the values based on their resolved CType types
values.map.with_index do |value,index|
parse_value(types[index],value)
end
end
#
# Parses the value based on it's C type.
#
# @param [Ronin::Support::Binary::CType::Type] ctype
# The C type of the value.
#
# @param [String] string
# The raw unparsed string.
#
# @return [Array, Array, Array, Integer, Float, String]
# The parsed value.
#
def parse_value(ctype,string)
case ctype
when Support::Binary::CTypes::ArrayType,
Support::Binary::CTypes::ArrayObjectType,
Support::Binary::CTypes::UnboundedArrayType
parse_array_value(ctype,string)
when Support::Binary::CTypes::IntType,
Support::Binary::CTypes::UIntType
parse_int(string)
when Support::Binary::CTypes::FloatType
parse_float(string)
when Support::Binary::CTypes::CharType,
Support::Binary::CTypes::StringType
string
else
raise(NotImplementedError,"unable to parse value for CType #{ctype.class}")
end
end
#
# Parses an array.
#
# @param [Ronin::Support::Binary::CTypes::Type] ctype
# The C type that represents the array type.
#
# @param [String] string
# The raw string to parse.
#
# @return [Array, Array, Array]
# The parsed array.
#
def parse_array_value(ctype,string)
# create array of the desired size
array = if ctype.length.finite?
Array.new(ctype.length,ctype.type.uninitialized_value)
else
[]
end
string.split(/(?!\\),/).each_with_index do |element,index|
array[index] = parse_value(ctype.type,element)
end
return array
end
#
# Parses an integer value.
#
# @param [String] string
# The raw string to parse.
#
# @return [Integer]
# The parsed Integer.
#
def parse_int(string)
Integer(string)
rescue ArgumentError
print_error "cannot parse integer: #{string}"
exit(-1)
end
#
# Parses an float value.
#
# @param [String] string
# The raw string to parse.
#
# @return [Float]
# The parsed Float.
#
def parse_float(string)
Float(string)
rescue ArgumentError
print_error "cannot parse float: #{string}"
exit(-1)
end
#
# Writes the packed data to the `--output` file or stdout.
#
# @param [String] data
# The packed data to output.
#
def write_output(data)
if options[:output]
File.binwrite(options[:output],data)
else
stdout.write(data)
end
end
end
end
end
end