# frozen_string_literal: true # # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-support is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-support 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-support. If not, see . # require 'ronin/support/binary/ctypes/mixin' module Ronin module Support module Binary # # Represents a stack that can have binary data pushed to or popped from # it. # # ## Features # # * Supports configurable endianness and architecture. # * Can dump out a formatting binary string for new a stack. # * Can parse an existing stack dump string. # * Supports negative indexing. # # ## Examples # # Creating a new stack: # # stack = Binary::Stack.new # # => # # stack.push 0x41414141 # # => # # stack.push 0x7fffffffdde0 # # => # # stack.push -1 # # => # # stack.to_s # # => => "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xE0\xDD\xFF\xFF\xFF\x7F\x00\x00AAAA\x00\x00\x00\x00" # # Creating a stack from an existing String: # # stack = Binary::Stack.new("\x41\x00\x00\x00\x00\x00\x00\x00\x42\x00\x00\x00\x00\x00\x00\x00") # stack[0] # # => 65 # stack[8] # # => 66 # # Negative indexing within the stack: # # stack.push(65) # stack.push(66) # stack[-8] # # => 65 # stack[-16] # # => 66 # # **Note:** negative indexes are treated relative to the beginning of the # stack, since stacks grow downward in the address space. # # @api public # # @since 1.0.0 # class Stack include CTypes::Mixin # The "machine word" from the {#type_system}. # # @return [CTypes::UInt32Type, CTypes::UInt64Type] attr_reader :machine_word # The number of machine words on the stack. # # @return [Integer] attr_reader :length # The size of the stack in bytes. # # @return [Integer] attr_reader :size # The underlying buffer for the stack. # # @return [String] attr_reader :string # # Initializes the stack. # # @param [String, nil] string # Optional existing stack contents. # # @param [Hash{Symbol => Object}] kwargs # Additional keyword arguments. # # @option kwargs [:little, :big, :net, nil] :endian # The desired endianness of the values within the stack. # # @option kwargs [: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 # The desired architecture for the values within the stack. # def initialize(string=nil, **kwargs) initialize_type_system(**kwargs) @machine_word = @type_system::MACHINE_WORD if string @string = string @size = @string.bytesize @length = @size / @machine_word.size else @string = String.new @size = 0 @length = 0 end end # # Accesses a machine word at the given index within the stack. # # @param [Integer] index # The byte offset within the stack to read from. # # @return [Integer] # The value at the index within the stack. # # @raise [IndexError] # # @note # negative offsets are treated as relative to the bottom or the # beginning of the stack, since stack grow downward in the address # space. # def [](index) offset = if index < 0 then @size + index else index end if (offset + @machine_word.size) > @size raise(IndexError,"index #{index} is out of bounds: 0...#{@size}") end data = @string[offset,@machine_word.size] return @machine_word.unpack(data) end # # Sets a machine word at the given index within the stack. # # @param [Integer] index # The byte offset within the stack to write to. # # @param [Integer] value # The value to write at the index within the stack. # # @return [Integer] # The written value. # # @raise [IndexError] # # @note # negative offsets are treated as relative to the bottom or the # beginning of the stack, since stacks grow downward in the address # space. # def []=(index,value) offset = if index < 0 then @size + index else index end if (offset + @machine_word.size) > @size raise(IndexError,"index #{index} is out of bounds: 0...#{@size}") end @string[offset,@machine_word.size] = @machine_word.pack(value) return value end # # Pushes a value onto the top of the stack (or to the end of the # underlying buffer). # # @param [Integer] value # The value to push onto the stack. # # @return [self] # def push(value) data = @machine_word.pack(value) @string.insert(0,data) @length += 1 @size += @machine_word.size return self end # # Pops a value off the top of the stack (or to the end of the underlying # buffer). # # @return [Integer] # The value popped from the stack. # def pop data = @string.byteslice(0,@machine_word.size) value = @machine_word.unpack(data) @length -= 1 @size -= @machine_word.size @string = @string.byteslice(@machine_word.size,@size) return value end # # Cnnverts the stack into a String. # # @return [String] # The underlying buffer for the stack. # def to_s @string end alias to_str to_s # # Inspects the stack object. # # @return [String] # def inspect "#<#{self.class}: #{@string.inspect}>" end end end end end