rapid7/metasploit-framework

View on GitHub
lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb

Summary

Maintainability
D
1 day
Test Coverage
# -*- coding: binary -*-
require 'rex/post/meterpreter/extensions/stdapi/railgun/library_helper'

module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Railgun

#
# Utility methods and constants for dealing with most types of variables.
#
class  Util

  # Bring in some useful string manipulation utility functions
  include LibraryHelper

  # Data type size info: http://msdn.microsoft.com/en-us/library/s3f49ktz(v=vs.80).aspx
  PRIMITIVE_TYPE_SIZES = {
    :int => 4,
    :__int8 => 1,
    :__int16 => 2,
    :__int32 => 4,
    :__int64 => 8,
    :bool => 1,
    :char => 1,
    :short => 2,
    :long => 4,
    :long_long => 8,
    :float => 4,
    :double => 8,
    :long_double => 8,
    :wchar_t => 2,
  }

  #
  # Maps a data type to its corresponding primitive or special type
  # +:pointer+.  Note, primitive types are mapped to themselves.
  #
  # typedef info: http://msdn.microsoft.com/en-us/library/aa383751(v=vs.85).aspx
  TYPE_DEFINITIONS = {
    ##
    # Primitives
    ##
    :int => :int,
    :__int8 => :__int8,
    :__int16 => :__int16,
    :__int32 => :__int32,
    :__int64 => :__int64,
    :bool => :bool,
    :char => :char,
    :short => :short,
    :long => :long,
    :long_long => :long_long,
    :float => :float,
    :double => :double,
    :long_double => :long_double,
    :wchar_t => :wchar_t,
    ##
    # Non-pointers
    ##
    #typedef WORD ATOM;
    :ATOM => :short,
    #typedef int BOOL;
    :BOOL => :int,
    #typedef BYTE BOOLEAN;
    :BOOLEAN => :char,
    #typedef unsigned char BYTE;
    :BYTE => :char,
    #typedef char CHAR;
    :CHAR => :char,
    #typedef DWORD COLORREF;
    :COLORREF => :long,
    #typedef unsigned long DWORD;
    :DWORD => :long,
    #typedef unsigned int DWORD32;
    :DWORD32 => :int,
    #typedef unsigned __int64 DWORD64;
    :DWORD64 => :__int64,
    #typedef float FLOAT;
    :FLOAT => :float,
    #typedef int HFILE;
    :HFILE => :int,
    #typedef LONG HRESULT;
    :HRESULT => :long,
    #typedef int INT;
    :INT => :int,
    #typedef signed int INT32;
    :INT32 => :int,
    #typedef signed __int64 INT64;
    :INT64 => :__int64,
    #typedef WORD LANGID;
    :LANGID => :short,
    #typedef DWORD LCID;
    :LCID => :long,
    #typedef DWORD LCTYPE;
    :LCTYPE => :long,
    #typedef DWORD LGRPID;
    :LGRPID => :long,
    #typedef long LONG;
    :LONG => :long,
    #typedef signed int LONG32;
    :LONG32 => :int,
    #typedef __int64 LONG64;
    :LONG64 => :__int64,
    #typedef PDWORD PLCID;
    :PLCID => :pointer,
    #typedef LPVOID SC_LOCK;
    :SC_LOCK => :pointer,
    #typedef short SHORT;
    :SHORT => :short,
    #typedef unsigned char UCHAR;
    :UCHAR => :char,
    #typedef unsigned int UINT;
    :UINT => :int,
    #typedef unsigned int UINT32;
    :UINT32 => :int,
    #typedef unsigned long ULONG;
    :ULONG => :long,
    #typedef unsigned int ULONG32;
    :ULONG32 => :int,
    #typedef unsigned __int64 ULONG64;
    :ULONG64 => :__int64,
    #typedef unsigned short USHORT;
    :USHORT => :short,
    #typedef wchar_t WCHAR;
    :WCHAR => :wchar_t,
    #typedef unsigned short WORD;
    :WORD => :short,
    ##
    # Pointers declared with *
    ##
    #typedef DWORD* LPCOLORREF;
    :LPCOLORREF => :pointer,
    #typedef void* LPCVOID;
    :LPCVOID => :pointer,
    #typedef WCHAR* LPCWSTR;
    :LPCWSTR => :pointer,
    #typedef DWORD* LPDWORD;
    :LPDWORD => :pointer,
    #typedef HANDLE* LPHANDLE;
    :LPHANDLE => :pointer,
    #typedef int* LPINT;
    :LPINT => :pointer,
    #typedef long* LPLONG;
    :LPLONG => :pointer,
    #typedef CHAR* LPSTR;
    :LPSTR => :pointer,
    #typedef void* LPVOID;
    :LPVOID => :pointer,
    #typedef WORD* LPWORD;
    :LPWORD => :pointer,
    #typedef WCHAR* LPWSTR;
    :LPWSTR => :pointer,
    #typedef BOOL* PBOOL;
    :PBOOL => :pointer,
    #typedef BOOLEAN* PBOOLEAN;
    :PBOOLEAN => :pointer,
    #typedef BYTE* PBYTE;
    :PBYTE => :pointer,
    #typedef CHAR* PCHAR;
    :PCHAR => :pointer,
    #typedef CHAR* PCSTR;
    :PCSTR => :pointer,
    #typedef WCHAR* PCWSTR;
    :PCWSTR => :pointer,
    #typedef DWORD* PDWORD;
    :PDWORD => :pointer,
    #typedef DWORDLONG* PDWORDLONG;
    :PDWORDLONG => :pointer,
    #typedef DWORD_PTR* PDWORD_PTR;
    :PDWORD_PTR => :pointer,
    #typedef DWORD32* PDWORD32;
    :PDWORD32 => :pointer,
    #typedef DWORD64* PDWORD64;
    :PDWORD64 => :pointer,
    #typedef FLOAT* PFLOAT;
    :PFLOAT => :pointer,
    #typedef HANDLE* PHANDLE;
    :PHANDLE => :pointer,
    #typedef HKEY* PHKEY;
    :PHKEY => :pointer,
    #typedef int* PINT;
    :PINT => :pointer,
    #typedef INT_PTR* PINT_PTR;
    :PINT_PTR => :pointer,
    #typedef INT32* PINT32;
    :PINT32 => :pointer,
    #typedef INT64* PINT64;
    :PINT64 => :pointer,
    #typedef LONG* PLONG;
    :PLONG => :pointer,
    #typedef LONGLONG* PLONGLONG;
    :PLONGLONG => :pointer,
    #typedef LONG_PTR* PLONG_PTR;
    :PLONG_PTR => :pointer,
    #typedef LONG32* PLONG32;
    :PLONG32 => :pointer,
    #typedef LONG64* PLONG64;
    :PLONG64 => :pointer,
    #typedef SHORT* PSHORT;
    :PSHORT => :pointer,
    #typedef SIZE_T* PSIZE_T;
    :PSIZE_T => :pointer,
    #typedef SSIZE_T* PSSIZE_T;
    :PSSIZE_T => :pointer,
    #typedef CHAR* PSTR;
    :PSTR => :pointer,
    #typedef TBYTE* PTBYTE;
    :PTBYTE => :pointer,
    #typedef TCHAR* PTCHAR;
    :PTCHAR => :pointer,
    #typedef UCHAR* PUCHAR;
    :PUCHAR => :pointer,
    #typedef UINT* PUINT;
    :PUINT => :pointer,
    #typedef UINT_PTR* PUINT_PTR;
    :PUINT_PTR => :pointer,
    #typedef UINT32* PUINT32;
    :PUINT32 => :pointer,
    #typedef UINT64* PUINT64;
    :PUINT64 => :pointer,
    #typedef ULONG* PULONG;
    :PULONG => :pointer,
    #typedef ULONGLONG* PULONGLONG;
    :PULONGLONG => :pointer,
    #typedef ULONG_PTR* PULONG_PTR;
    :PULONG_PTR => :pointer,
    #typedef ULONG32* PULONG32;
    :PULONG32 => :pointer,
    #typedef ULONG64* PULONG64;
    :PULONG64 => :pointer,
    #typedef USHORT* PUSHORT;
    :PUSHORT => :pointer,
    #typedef void* PVOID;
    :PVOID => :pointer,
    #typedef WCHAR* PWCHAR;
    :PWCHAR => :pointer,
    #typedef WORD* PWORD;
    :PWORD => :pointer,
    #typedef WCHAR* PWSTR;
    :PWSTR => :pointer,
    #typedef HANDLE HACCEL;
    :HACCEL => :pointer,
    ##
    #  Handles
    ##
    #typedef PVOID HANDLE;
    :HANDLE => :pointer,
    #typedef HANDLE HBITMAP;
    :HBITMAP => :pointer,
    #typedef HANDLE HBRUSH;
    :HBRUSH => :pointer,
    #typedef HANDLE HCOLORSPACE;
    :HCOLORSPACE => :pointer,
    #typedef HANDLE HCONV;
    :HCONV => :pointer,
    #typedef HANDLE HCONVLIST;
    :HCONVLIST => :pointer,
    #typedef HANDLE HDC;
    :HDC => :pointer,
    #typedef HANDLE HDDEDATA;
    :HDDEDATA => :pointer,
    #typedef HANDLE HDESK;
    :HDESK => :pointer,
    #typedef HANDLE HDROP;
    :HDROP => :pointer,
    #typedef HANDLE HDWP;
    :HDWP => :pointer,
    #typedef HANDLE HENHMETAFILE;
    :HENHMETAFILE => :pointer,
    #typedef HANDLE HFONT;
    :HFONT => :pointer,
    #typedef HANDLE HGDIOBJ;
    :HGDIOBJ => :pointer,
    #typedef HANDLE HGLOBAL;
    :HGLOBAL => :pointer,
    #typedef HANDLE HHOOK;
    :HHOOK => :pointer,
    #typedef HANDLE HICON;
    :HICON => :pointer,
    #typedef HANDLE HINSTANCE;
    :HINSTANCE => :pointer,
    #typedef HANDLE HKEY;
    :HKEY => :pointer,
    #typedef HANDLE HKL;
    :HKL => :pointer,
    #typedef HANDLE HLOCAL;
    :HLOCAL => :pointer,
    #typedef HANDLE HMENU;
    :HMENU => :pointer,
    #typedef HANDLE HMETAFILE;
    :HMETAFILE => :pointer,
    #typedef HANDLE HPALETTE;
    :HPALETTE => :pointer,
    #typedef HANDLE HPEN;
    :HPEN => :pointer,
    #typedef HANDLE HRGN;
    :HRGN => :pointer,
    #typedef HANDLE HRSRC;
    :HRSRC => :pointer,
    #typedef HANDLE HSZ;
    :HSZ => :pointer,
    #typedef HANDLE WINSTA;
    :WINSTA => :pointer,
    #typedef HANDLE HWND;
    :HWND => :pointer,
    #typedef HANDLE SC_HANDLE;
    :SC_HANDLE => :pointer,
    #typedef HANDLE SERVICE_STATUS_HANDLE;
    :SERVICE_STATUS_HANDLE => :pointer,
  }

  # param 'railgun' is a Railgun instance.
  # param 'arch' is the client.arch
  def initialize(railgun, arch)
    @railgun = railgun
    @is_64bit = arch == ARCH_X64
    @process_heap = nil
  end

  #
  # Given a packed pointer, unpacks it according to architecture
  #
  def unpack_pointer(packed_pointer)
    if is_64bit
      # Assume little endian
      packed_pointer.unpack('Q<')[0]
    else
      packed_pointer.unpack('V')[0]
    end
  end

  #
  # Returns true if +pointer+ will be considered a 'null' pointer.
  #
  # If +pointer+ is nil or 0, returns true
  # If +pointer+ is a String, if 0 after unpacking, returns true
  # false otherwise
  #
  # See #unpack_pointer
  #
  def is_null_pointer(pointer)
    if pointer.kind_of? String
      pointer = unpack_pointer(pointer)
    end

    return pointer.nil? || pointer == 0
  end

  def alloc_and_write_data(data)
    return nil if data.nil? || process_heap.nil?

    result = railgun.kernel32.HeapAlloc(process_heap, railgun.const('HEAP_ZERO'), data.length)
    return nil if result['return'].nil?

    addr = result['return']
    return nil unless railgun.memwrite(addr, data, data.length)

    addr
  end

  def free_data(*ptrs)
    return false if ptrs.empty?
    return false if process_heap.nil?

    results = railgun.multi(
      ptrs.map { |ptr| ['kernel32', 'HeapFree', [process_heap, 0, ptr.to_i]] }
    )
    results.map { |res| res['return'] }.all?
  end

  #
  # Reads null-terminated unicode strings from memory.
  #
  # Given a pointer to a null terminated array of WCHARs, return a ruby
  # String. If +pointer+ is NULL (see #is_null_pointer) returns an empty
  # string.
  #
  def read_wstring(pointer, length = nil)
    # Return an empty string for null pointers
    if is_null_pointer(pointer)
      return ''
    end

    # If length not provided, use lstrlenW
    if length.nil?
      length = railgun.kernel32.lstrlenW(pointer)['return']
    end

    # Retrieve the array of characters
    chars = read_array(:WCHAR, length, pointer)

    # Concatenate the characters and convert to a ruby string
    str = uniz_to_str(chars.join(''))

    return str
  end

  #
  # Write Unicode strings to memory.
  #
  # Given a string, returns a pointer to a null terminated WCHARs array.
  #
  def alloc_and_write_wstring(value)
    return nil if value.nil?

    alloc_and_write_data(str_to_uni_z(value))
  end

  alias free_wstring free_data

  #
  # Write ASCII strings to memory.
  #
  # Given a  string, returns a pointer to a null terminated CHARs array.
  # InitializeStr(&Str,"string");
  #
  def alloc_and_write_string(value)
    return nil if value.nil?

    alloc_and_write_data(str_to_ascii_z(value))
  end

  alias free_string free_data

  #
  # Reads null-terminated ASCII strings from memory.
  #
  # Given a pointer to a null terminated array of CHARs, return a ruby
  # String. If +pointer+ is NULL (see #is_null_pointer) returns an empty
  # string.
  #
  def read_string(pointer, length=nil)
    if is_null_pointer(pointer)
      return ''
    end

    unless length
      length = railgun.kernel32.lstrlenA(pointer)['return']
    end

    chars = read_array(:CHAR, length, pointer)
    return chars.join('')
  end

  #
  # Read a given number of bytes from memory or from a provided buffer.
  #
  # If +buffer+ is not provided, read +size+ bytes from the client's memory.
  # If +buffer+ is provided, reads +size+ characters from the index of +address+.
  #
  def memread(address, size, buffer = nil)
    if buffer.nil?
      return railgun.memread(address, size)
    else
      return buffer[address .. (address + size - 1)]
    end
  end

  #
  # Read and unpack a pointer from the given buffer at a given offset
  #
  def read_pointer(buffer, offset = 0)
    unpack_pointer(buffer[offset, (offset + pointer_size)])
  end

  #
  # Reads data structures and several windows data types
  #
  def read_data(type, position, buffer = nil)
    if buffer.nil?
      buffer = memread(position, sizeof_type(type))
      position = 0
    end

    # If we're asked to read a data structure, deligate to read_struct
    if is_struct_type?(type)
      return read_struct(type, buffer, position)
    end

    # If the type is an array with a given size...
    #    BYTE[3] for example or BYTE[ENCRYPTED_PWLEN] or even PDWORD[23]
    if is_array_type?(type)
      # Separate the element type from the size of the array
      element_type, length = split_array_type(type)

      # Have read_array take care of the rest
      return read_array(element_type, length, position, buffer)
    end

    size = sizeof_type(type)
    raw  = memread(position, size, buffer)

    # read/unpack data for the types we have hard-coded support for
    case type
    when :LPWSTR
      # null-terminated string of 16-bit Unicode characters
      return read_wstring(read_pointer(raw))
    when :DWORD
      # Both on x86 and x64, DWORD is 32 bits
      return raw.unpack('V').first
    when :BOOL
      return raw.unpack('V').first == 1
    when :LONG
      return raw.unpack('V').first
    end

    #If nothing worked thus far, return it raw
    return raw
  end

  #
  # Read +length+ number of instances of +type+ from +bufptr+ .
  #
  # +bufptr+ is an index in +buffer+ or, if +buffer+ is nil, a memory address
  #
  def read_array(type, length, bufptr, buffer = nil)
    if length <= 0
      return []
    end

    size = sizeof_type(type)
    # Grab the bytes that the array consists of
    buffer = memread(bufptr, size * length, buffer)

    offset = 0

    1.upto(length).map do |n|
      data = read_data(type, offset, buffer)

      offset = offset + size

      data
    end
  end

  #
  # Construct the data structure described in +definition+ from +buffer+
  # starting from the index +offset+
  #
  def read_struct(definition, buffer, offset = 0)
    data = {}

    offsets = struct_offsets(definition, offset)

    definition.each do |mapping|
      key, data_type = mapping

      data[key] = read_data(data_type, offsets.shift, buffer)
    end

    data
  end


  # Returns true if the data type is a pointer, false otherwise
  def is_pointer_type?(type)
    return TYPE_DEFINITIONS[type] == :pointer
  end

  # Returns whether the given type represents an array of another type
  # For example BYTE[3], BYTE[ENCRYPTED_PWLEN], or even PDWORD[23]
  def is_array_type?(type)
    return type =~ /^\w+\[\w+\]$/ ? true : false
  end

  # Returns true if the type passed describes a data structure, false otherwise
  def is_struct_type?(type)
    return type.kind_of? Array
  end


  # Returns the pointer size for this architecture
  def pointer_size
    is_64bit ? 8 : 4
  end

  # Return the size, in bytes, of the given type
  def sizeof_type(type)
    if is_pointer_type?(type)
      return pointer_size
    end

    if type.kind_of? String
      if is_array_type?(type)
        element_type, length = split_array_type(type)
        return length * sizeof_type(element_type)
      else
        return sizeof_type(type.to_sym)
      end
    end

    if is_struct_type?(type)
      return sizeof_struct(type)
    end

    if TYPE_DEFINITIONS.has_key?(type)
      primitive = TYPE_DEFINITIONS[type]

      if primitive == :pointer
        return pointer_size
      end

      if PRIMITIVE_TYPE_SIZES.has_key?(primitive)
        return PRIMITIVE_TYPE_SIZES[primitive]
      else
        raise "Type #{type} was mapped to non-existent primitive #{primitive}"
      end
    end

    raise "Unable to determine size for type #{type}."
  end

  #
  # Calculates the size of +struct+ after alignment.
  #
  def sizeof_struct(struct)
    offsets = struct_offsets(struct, 0)
    last_data_size = sizeof_type(struct.last[1])
    size_no_padding = offsets.last + last_data_size

    return size_no_padding + calc_padding(size_no_padding)
  end

  #
  # Given a description of a data structure, returns an Array containing
  # the offset from the beginning for each subsequent element, taking into
  # consideration alignment and padding.
  #
  def struct_offsets(definition, offset)
    padding = 0
    offsets = []
    definition.each do |mapping|
      key, data_type = mapping
      if sizeof_type(data_type) > padding
        offset = offset + padding
      end

      offsets.push(offset)

      offset = offset + sizeof_type(data_type)
      padding = calc_padding(offset)
    end

    offsets
  end

  # http://en.wikipedia.org/wiki/Data_structure_alignment
  def required_alignment
    is_64bit ? 8 : 4
  end

  #
  # Number of bytes that needed to be added to be aligned.
  #
  def calc_padding(offset)
    align = required_alignment

    # If offset is not aligned...
    if (offset % align) != 0
      # Calculate padding needed to be aligned
      align - (offset & (align - 1))
    else
      0
    end
  end

  #
  # Given an explicit array definition (e.g. BYTE[23]) return size (e.g. 23) and
  # and +type+ (e.g. BYTE). If a constant is given, attempt to resolve it
  # that constant.
  #
  def split_array_type(type)
    if type =~ /^(\w+)\[(\w+)\]$/
      element_type = $1
      length = $2
      unless length =~ /^\d+$/
        length = railgun.const(length)
      end

      return element_type.to_sym, length.to_i
    else
      raise "Can not split non-array type #{type}"
    end
  end

  #
  # Evaluates a bit field, returning a hash representing the meaning and
  # state of each bit.
  #
  # Parameters:
  #   +value+:: a bit field represented by a Integer
  #   +mappings+:: { 'WINAPI_CONSTANT_NAME' => :descriptive_symbol, ... }
  #
  # Returns:
  #   { :descriptive_symbol => true/false, ... }
  #
  def judge_bit_field(value, mappings)
    flags = {}

    mappings.each do |constant_name, key|
      flags[key] = (value & railgun.const(constant_name)) != 0
    end

    flags
  end

  protected

  attr_accessor :railgun, :is_64bit

  private

  def process_heap
    return @process_heap unless @process_heap.nil?

    handle = railgun.kernel32.GetProcessHeap()['return']
    return nil if handle == 0
    @process_heap = handle
  end
end # Util
end # Railgun
end # Stdapi
end # Extensions
end # Meterpreter
end # Post
end # Rex