1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-10-29 18:07:27 +01:00

move rex bintools into new gem

move all the *scan *parsey code out into
the new rex-bin_tools gem

MS-1691
This commit is contained in:
David Maloney 2016-08-15 14:01:43 -05:00
parent 8c70086170
commit d2a6c2e9ca
No known key found for this signature in database
GPG Key ID: DEDBA9DC3A913DB2
33 changed files with 10 additions and 5219 deletions

View File

@ -32,6 +32,7 @@ PATH
recog
redcarpet
rex-arch
rex-bin_tools
rex-java
rex-ole
rex-powershell
@ -225,6 +226,13 @@ GEM
redcarpet (3.3.4)
rex-arch (0.1.1)
rex-text
rex-bin_tools (0.1.0)
metasm
rex-arch
rex-core
rex-struct2
rex-text
rex-core (0.1.1)
rex-java (0.1.2)
rex-ole (0.1.2)
rex-text

View File

@ -1,104 +0,0 @@
# -*- coding: binary -*-
require 'tempfile'
require 'rex/file'
require 'rex/text'
module Rex
module Assembly
###
#
# This class uses nasm to assemble and disassemble stuff.
#
###
class Nasm
@@nasm_path = 'nasm'
@@ndisasm_path = 'ndisasm'
#
# Ensures that the nasm environment is sane.
#
def self.check
@@nasm_path =
Rex::FileUtils.find_full_path('nasm') ||
Rex::FileUtils.find_full_path('nasm.exe') ||
Rex::FileUtils.find_full_path('nasmw.exe') ||
raise(RuntimeError, "No nasm installation was found.")
@@ndisasm_path =
Rex::FileUtils.find_full_path('ndisasm') ||
Rex::FileUtils.find_full_path('ndisasm.exe') ||
Rex::FileUtils.find_full_path('ndisasmw.exe') ||
raise(RuntimeError, "No ndisasm installation was found.")
end
#
# Assembles the supplied assembly and returns the raw opcodes.
#
def self.assemble(assembly, bits=32)
check
# Open the temporary file
tmp = Tempfile.new('nasmXXXX')
tmp.binmode
tpath = tmp.path
opath = tmp.path + '.out'
# Write the assembly data to a file
tmp.write("BITS #{bits}\n" + assembly)
tmp.flush()
tmp.seek(0)
# Run nasm
if (system(@@nasm_path, '-f', 'bin', '-o', opath, tpath) == false)
raise RuntimeError, "Assembler did not complete successfully: #{$?.exitstatus}"
end
# Read the assembled text
rv = ::IO.read(opath)
# Remove temporary files
File.unlink(opath)
tmp.close(true)
rv
end
#
# Disassembles the supplied raw opcodes
#
def self.disassemble(raw, bits=32)
check
tmp = Tempfile.new('nasmout')
tmp.binmode
tfd = File.open(tmp.path, "wb")
tfd.write(raw)
tfd.flush()
tfd.close
p = ::IO.popen("\"#{@@ndisasm_path}\" -b #{bits} \"#{tmp.path}\"")
o = ''
begin
until p.eof?
o += p.read
end
ensure
p.close
end
tmp.close(true)
o
end
end
end
end

View File

@ -1,385 +0,0 @@
# -*- coding: binary -*-
module Rex
###
#
# This class provides os-specific functionality
#
###
module Compat
STD_INPUT_HANDLE = -10
STD_OUTPUT_HANDLE = -11
STD_ERROR_HANDLE = -12
GENERIC_READ = 0x80000000
GENERIC_WRITE = 0x40000000
GENERIC_EXECUTE = 0x20000000
FILE_SHARE_READ = 0x00000001
FILE_SHARE_WRITE = 0x00000002
OPEN_EXISTING = 0x00000003
ENABLE_LINE_INPUT = 2
ENABLE_ECHO_INPUT = 4
ENABLE_PROCESSED_INPUT = 1
#
# Platform detection
#
@@is_windows = @@is_cygwin = @@is_macosx = @@is_linux = @@is_bsdi = @@is_freebsd = @@is_netbsd = @@is_openbsd = @@is_java = false
@@loaded_win32api = false
@@loaded_tempfile = false
@@loaded_fileutils = false
def self.is_windows
return @@is_windows if @@is_windows
@@is_windows = (RUBY_PLATFORM =~ /mswin(32|64)|mingw(32|64)/) ? true : false
end
def self.is_cygwin
return @@is_cygwin if @@is_cygwin
@@is_cygwin = (RUBY_PLATFORM =~ /cygwin/) ? true : false
end
def self.is_macosx
return @@is_macosx if @@is_macosx
@@is_macosx = (RUBY_PLATFORM =~ /darwin/) ? true : false
end
def self.is_linux
return @@is_linux if @@is_linux
@@is_linux = (RUBY_PLATFORM =~ /linux/) ? true : false
end
def self.is_bsdi
return @@is_bsdi if @@is_bsdi
@@is_bsdi = (RUBY_PLATFORM =~ /bsdi/i) ? true : false
end
def self.is_netbsd
return @@is_netbsd if @@is_netbsd
@@is_netbsd = (RUBY_PLATFORM =~ /netbsd/) ? true : false
end
def self.is_freebsd
return @@is_freebsd if @@is_freebsd
@@is_freebsd = (RUBY_PLATFORM =~ /freebsd/) ? true : false
end
def self.is_openbsd
return @@is_openbsd if @@is_openbsd
@@is_openbsd = (RUBY_PLATFORM =~ /openbsd/) ? true : false
end
def self.is_java
return @@is_java if @@is_java
@@is_java = (RUBY_PLATFORM =~ /java/) ? true : false
end
def self.is_wow64
return false if not is_windows
is64 = false
begin
buff = "\x00" * 4
Win32API.new("kernel32","IsWow64Process",['L','P'],'L').call(-1, buff)
is64 = (buff.unpack("V")[0]) == 1 ? true : false
rescue ::Exception
end
is64
end
def self.cygwin_to_win32(path)
if(path !~ /^\/cygdrive/)
return ::IO.popen("cygpath -w #{path}", "rb").read.strip
end
dir = path.split("/")
dir.shift
dir.shift
dir[0] = dir[0] + ":"
dir.join("\\")
end
def self.open_file(url='')
case RUBY_PLATFORM
when /cygwin/
path = self.cygwin_to_win32(url)
system(["cmd", "cmd"], "/c", "explorer", path)
else
self.open_browser(url)
end
end
def self.open_browser(url='http://google.com/')
case RUBY_PLATFORM
when /cygwin/
if(url[0,1] == "/")
self.open_file(url)
end
return if not @@loaded_win32api
Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", url, nil, nil, 0)
when /mswin32|mingw/
return if not @@loaded_win32api
Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", url, nil, nil, 0)
when /darwin/
system("open #{url}")
else
# Search through the PATH variable (if it exists) and chose a browser
# We are making an assumption about the nature of "PATH" so tread lightly
if defined? ENV['PATH']
# "xdg-open" is more general than "sensible-browser" and can be useful for lots of
# file types -- text files, pcaps, or URLs. It's nearly always
# going to use the application the user is expecting. If we're not
# on something Debian-based, fall back to likely browsers.
['xdg-open', 'sensible-browser', 'firefox', 'firefox-bin', 'opera', 'konqueror', 'chromium-browser'].each do |browser|
ENV['PATH'].split(':').each do |path|
# Does the browser exists?
if File.exist?("#{path}/#{browser}")
system("#{browser} #{url} &")
return
end
end
end
end
end
end
def self.open_webrtc_browser(url='http://google.com/')
case RUBY_PLATFORM
when /mswin2|mingw|cygwin/
paths = [
"Google\\Chrome\\Application\\chrome.exe",
"Mozilla Firefox\\firefox.exe",
"Opera\\launcher.exe"
]
prog_files = ENV['ProgramFiles']
paths = paths.map { |p| "#{prog_files}\\#{p}" }
# Old chrome path
app_data = ENV['APPDATA']
paths << "#{app_data}\\Google\\Chrome\\Application\\chrome.exe"
paths.each do |path|
if File.exist?(path)
args = (path =~ /chrome\.exe/) ? "--allow-file-access-from-files" : ""
system("\"#{path}\" #{args} \"#{url}\"")
return true
end
end
when /darwin/
['Google Chrome.app', 'Firefox.app'].each do |browser|
browser_path = "/Applications/#{browser}"
if File.directory?(browser_path)
args = (browser_path =~ /Chrome/) ? "--args --allow-file-access-from-files" : ""
system("open #{url} -a \"#{browser_path}\" #{args} &")
return true
end
end
else
if defined? ENV['PATH']
['google-chrome', 'chrome', 'chromium', 'firefox' , 'firefox', 'opera'].each do |browser|
ENV['PATH'].split(':').each do |path|
browser_path = "#{path}/#{browser}"
if File.exist?(browser_path)
args = (browser_path =~ /Chrome/) ? "--allow-file-access-from-files" : ""
system("#{browser_path} #{args} #{url} &")
return true
end
end
end
end
end
false
end
def self.open_email(addr)
case RUBY_PLATFORM
when /mswin32|cygwin/
return if not @@loaded_win32api
Win32API.new("shell32.dll", "ShellExecute", ["PPPPPL"], "L").call(nil, "open", "mailto:"+addr, nil, nil, 0)
when /darwin/
system("open mailto:#{addr}")
else
# ?
end
end
def self.play_sound(path)
case RUBY_PLATFORM
when /cygwin/
path = self.cygwin_to_win32(path)
return if not @@loaded_win32api
Win32API.new("winmm.dll", "sndPlaySoundA", ["SI"], "I").call(path, 0x20000)
when /mswin32/
return if not @@loaded_win32api
Win32API.new("winmm.dll", "sndPlaySoundA", ["SI"], "I").call(path, 0x20000)
when /darwin/
system("afplay #{path} >/dev/null 2>&1")
else
system("aplay #{path} >/dev/null 2>&1")
end
end
def self.getenv(var)
if (is_windows and @@loaded_win32api)
f = Win32API.new("kernel32", "GetEnvironmentVariable", ["P", "P", "I"], "I")
buff = "\x00" * 16384
sz = f.call(var, buff, buff.length)
return nil if sz == 0
buff[0,sz]
else
ENV[var]
end
end
def self.setenv(var,val)
if (is_windows and @@loaded_win32api)
f = Win32API.new("kernel32", "SetEnvironmentVariable", ["P", "P"], "I")
f.call(var, val + "\x00")
else
ENV[var]= val
end
end
#
# Obtain the path to our interpreter
#
def self.win32_ruby_path
return nil if ! (is_windows and @@loaded_win32api)
gmh = Win32API.new("kernel32", "GetModuleHandle", ["P"], "L")
gmf = Win32API.new("kernel32", "GetModuleFileName", ["LPL"], "L")
mod = gmh.call(nil)
inf = "\x00" * 1024
gmf.call(mod, inf, 1024)
inf.unpack("Z*")[0]
end
#
# Call WinExec (equiv to system("cmd &"))
#
def self.win32_winexec(cmd)
return nil if ! (is_windows and @@loaded_win32api)
exe = Win32API.new("kernel32", "WinExec", ["PL"], "L")
exe.call(cmd, 0)
end
#
# Verify the Console2 environment
#
def self.win32_console2_verify
return nil if ! (is_windows and @@loaded_win32api)
buf = "\x00" * 512
out = Win32API.new("kernel32", "GetStdHandle", ["L"], "L").call(STD_OUTPUT_HANDLE)
res = Win32API.new("kernel32","GetConsoleTitle", ["PL"], "L").call(buf, buf.length-1) rescue 0
( res > 0 and buf.index("Console2 command").nil? ) ? false : true
end
#
# Expand a 8.3 path to a full path
#
def self.win32_expand_path(path)
return nil if ! (is_windows and @@loaded_win32api)
glp = Win32API.new('kernel32', 'GetLongPathName', 'PPL', 'L')
buf = "\x00" * 260
len = glp.call(path, buf, buf.length)
buf[0, len]
end
#
# Platform independent socket pair
#
def self.pipe
if (! is_windows())
# Standard pipes should be fine
return ::IO.pipe
end
# Create a socket connection for Windows
serv = nil
port = 1024
while (! serv and port < 65535)
begin
serv = TCPServer.new('127.0.0.1', (port += 1))
rescue ::Exception
end
end
pipe1 = TCPSocket.new('127.0.0.1', port)
# Accept the forked child
pipe2 = serv.accept
# Shutdown the server
serv.close
return [pipe1, pipe2]
end
#
# Copy a file to a temporary path
#
def self.temp_copy(path)
raise RuntimeError,"missing Tempfile" if not @@loaded_tempfile
fd = File.open(path, "rb")
tp = Tempfile.new("msftemp")
tp.binmode
tp.write(fd.read(File.size(path)))
tp.close
fd.close
tp
end
#
# Delete an opened temporary file
#
def self.temp_delete(tp)
raise RuntimeError,"missing FileUtils" if not @@loaded_fileutils
begin
FileUtils.rm(tp.path)
rescue
end
end
#
# Initialization
#
if(is_windows or is_cygwin)
begin
require "Win32API"
@@loaded_win32api = true
rescue ::Exception
end
end
begin
require "tempfile"
@@loaded_tempfile = true
rescue ::Exception
end
begin
require "fileutils"
@@loaded_fileutils = true
rescue ::Exception
end
end
end

View File

@ -1,9 +0,0 @@
# -*- coding: binary -*-
module Rex
module ElfParsey
end
end
require 'rex/elfparsey/elf'

View File

@ -1,121 +0,0 @@
# -*- coding: binary -*-
require 'rex/elfparsey/elfbase'
require 'rex/elfparsey/exceptions'
require 'rex/image_source'
module Rex
module ElfParsey
class Elf < ElfBase
attr_accessor :elf_header, :program_header, :base_addr, :isource
def initialize(isource)
offset = 0
base_addr = 0
# ELF Header
elf_header = ElfHeader.new(isource.read(offset, ELF_HEADER_SIZE))
# Data encoding
ei_data = elf_header.e_ident[EI_DATA,1].unpack("C")[0]
e_phoff = elf_header.e_phoff
e_phentsize = elf_header.e_phentsize
e_phnum = elf_header.e_phnum
# Program Header Table
program_header = []
e_phnum.times do |i|
offset = e_phoff + (e_phentsize * i)
program_header << ProgramHeader.new(
isource.read(offset, PROGRAM_HEADER_SIZE), ei_data
)
if program_header[-1].p_type == PT_LOAD && program_header[-1].p_flags & PF_EXEC > 0
base_addr = program_header[-1].p_vaddr
end
end
self.elf_header = elf_header
self.program_header = program_header
self.base_addr = base_addr
self.isource = isource
end
def self.new_from_file(filename, disk_backed = false)
file = ::File.new(filename)
# file.binmode # windows... :\
if disk_backed
return self.new(ImageSource::Disk.new(file))
else
obj = new_from_string(file.read)
file.close
return obj
end
end
def self.new_from_string(data)
return self.new(ImageSource::Memory.new(data))
end
#
# Returns true if this binary is for a 64-bit architecture.
#
def ptr_64?
unless [ ELFCLASS32, ELFCLASS64 ].include?(
elf_header.e_ident[EI_CLASS,1].unpack("C*")[0])
raise ElfHeaderError, 'Invalid class', caller
end
elf_header.e_ident[EI_CLASS,1].unpack("C*")[0] == ELFCLASS64
end
#
# Returns true if this binary is for a 32-bit architecture.
# This check does not take into account 16-bit binaries at the moment.
#
def ptr_32?
ptr_64? == false
end
#
# Converts a virtual address to a string representation based on the
# underlying architecture.
#
def ptr_s(rva)
(ptr_32?) ? ("0x%.8x" % rva) : ("0x%.16x" % rva)
end
def offset_to_rva(offset)
base_addr + offset
end
def rva_to_offset(rva)
rva - base_addr
end
def read(offset, len)
isource.read(offset, len)
end
def read_rva(rva, len)
isource.read(rva_to_offset(rva), len)
end
def index(*args)
isource.index(*args)
end
def close
isource.close
end
end
end
end

View File

@ -1,265 +0,0 @@
# -*- coding: binary -*-
require 'rex/struct2'
module Rex
module ElfParsey
class ElfBase
# ELF Header
ELF_HEADER_SIZE = 52
EI_NIDENT = 16
ELF32_EHDR_LSB = Rex::Struct2::CStructTemplate.new(
[ 'string', 'e_ident', EI_NIDENT, '' ],
[ 'uint16v', 'e_type', 0 ],
[ 'uint16v', 'e_machine', 0 ],
[ 'uint32v', 'e_version', 0 ],
[ 'uint32v', 'e_entry', 0 ],
[ 'uint32v', 'e_phoff', 0 ],
[ 'uint32v', 'e_shoff', 0 ],
[ 'uint32v', 'e_flags', 0 ],
[ 'uint16v', 'e_ehsize', 0 ],
[ 'uint16v', 'e_phentsize', 0 ],
[ 'uint16v', 'e_phnum', 0 ],
[ 'uint16v', 'e_shentsize', 0 ],
[ 'uint16v', 'e_shnum', 0 ],
[ 'uint16v', 'e_shstrndx', 0 ]
)
ELF32_EHDR_MSB = Rex::Struct2::CStructTemplate.new(
[ 'string', 'e_ident', EI_NIDENT, '' ],
[ 'uint16n', 'e_type', 0 ],
[ 'uint16n', 'e_machine', 0 ],
[ 'uint32n', 'e_version', 0 ],
[ 'uint32n', 'e_entry', 0 ],
[ 'uint32n', 'e_phoff', 0 ],
[ 'uint32n', 'e_shoff', 0 ],
[ 'uint32n', 'e_flags', 0 ],
[ 'uint16n', 'e_ehsize', 0 ],
[ 'uint16n', 'e_phentsize', 0 ],
[ 'uint16n', 'e_phnum', 0 ],
[ 'uint16n', 'e_shentsize', 0 ],
[ 'uint16n', 'e_shnum', 0 ],
[ 'uint16n', 'e_shstrndx', 0 ]
)
# e_type This member identifies the object file type
ET_NONE = 0 # No file type
ET_REL = 1 # Relocatable file
ET_EXEC = 2 # Executable file
ET_DYN = 3 # Shared object file
ET_CORE = 4 # Core file
ET_LOPROC = 0xff00 # Processor-specific
ET_HIPROC = 0xffff # Processor-specific
#
# e_machine This member's value specifies the required architecture for an
# individual file.
#
# ET_NONE = 0 # No machine
EM_M32 = 1 # AT&T WE 32100
EM_SPARC = 2 # SPARC
EM_386 = 3 # Intel Architecture
EM_68K = 4 # Motorola 68000
EM_88K = 5 # Motorola 88000
EM_860 = 7 # Intel 80860
EM_MIPS = 8 # MIPS RS3000 Big-Endian
EM_MIPS_RS4_BE = 10 # MIPS RS4000 Big-Endian
# e_version This member identifies the object file version
EV_NONE = 0 # Invalid version
EV_CURRENT = 1 # Current version
# ELF Identification
# e_ident[] Identification indexes
EI_MAG0 = 0 # File identification
EI_MAG1 = 1 # File identification
EI_MAG2 = 2 # File identification
EI_MAG3 = 3 # File identification
EI_CLASS = 4 # File class
EI_DATA = 5 # Data encoding
EI_VERSION = 6 # File version
EI_PAD = 7 # Start of padding bytes
# EI_NIDENT = 16 # Size of e_ident[]
#
# EI_MAG0 to EI_MAG3 A file's first 4 bytes hold a "magic number",
# identifying the file as an ELF object file.
#
ELFMAG0 = 0x7f # e_ident[EI_MAG0]
ELFMAG1 = ?E # e_ident[EI_MAG1]
ELFMAG2 = ?L # e_ident[EI_MAG2]
ELFMAG3 = ?F # e_ident[EI_MAG3]
ELFMAG = ELFMAG0.chr + ELFMAG1.chr + ELFMAG2.chr + ELFMAG3.chr
# EI_CLASS Identifies the file's class, or capacity
ELFCLASSNONE = 0 # Invalid class
ELFCLASS32 = 1 # 32-bit objects
ELFCLASS64 = 2 # 64-bit objects
#
# EI_DATA Specifies the data encoding of the processor-specific data in
# the object file. The following encodings are currently defined.
#
ELFDATANONE = 0 # Invalid data encoding
ELFDATA2LSB = 1 # Least significant byte first
ELFDATA2MSB = 2 # Most significant byte first
class GenericStruct
attr_accessor :struct
def initialize(_struct)
self.struct = _struct
end
# The following methods are just pass-throughs for struct
# Access a value
def v
struct.v
end
# Access a value by array
def [](*args)
struct[*args]
end
# Obtain an array of all fields
def keys
struct.keys
end
def method_missing(meth, *args)
v[meth.to_s] || (raise NoMethodError.new, meth)
end
end
class GenericHeader < GenericStruct
end
class ElfHeader < GenericHeader
def initialize(rawdata)
# Identify the data encoding and parse ELF Header
elf_header = ELF32_EHDR_LSB.make_struct
if !elf_header.from_s(rawdata)
raise ElfHeaderError, "Couldn't parse ELF Header", caller
end
if elf_header.v['e_ident'][EI_DATA,1].unpack('C')[0] == ELFDATA2MSB
elf_header = ELF32_EHDR_MSB.make_struct
if !elf_header.from_s(rawdata)
raise ElfHeaderError, "Couldn't parse ELF Header", caller
end
end
unless [ ELFDATA2LSB, ELFDATA2MSB ].include?(
elf_header.v['e_ident'][EI_DATA,1].unpack('C')[0])
raise ElfHeaderError, "Invalid data encoding", caller
end
# Identify the file as an ELF object file
unless elf_header.v['e_ident'][EI_MAG0, 4] == ELFMAG
raise ElfHeaderError, 'Invalid magic number', caller
end
self.struct = elf_header
end
def e_ident
struct.v['e_ident']
end
end
# Program Header
PROGRAM_HEADER_SIZE = 32
ELF32_PHDR_LSB = Rex::Struct2::CStructTemplate.new(
[ 'uint32v', 'p_type', 0 ],
[ 'uint32v', 'p_offset', 0 ],
[ 'uint32v', 'p_vaddr', 0 ],
[ 'uint32v', 'p_paddr', 0 ],
[ 'uint32v', 'p_filesz', 0 ],
[ 'uint32v', 'p_memsz', 0 ],
[ 'uint32v', 'p_flags', 0 ],
[ 'uint32v', 'p_align', 0 ]
)
ELF32_PHDR_MSB = Rex::Struct2::CStructTemplate.new(
[ 'uint32n', 'p_type', 0 ],
[ 'uint32n', 'p_offset', 0 ],
[ 'uint32n', 'p_vaddr', 0 ],
[ 'uint32n', 'p_paddr', 0 ],
[ 'uint32n', 'p_filesz', 0 ],
[ 'uint32n', 'p_memsz', 0 ],
[ 'uint32n', 'p_flags', 0 ],
[ 'uint32n', 'p_align', 0 ]
)
# p_flags This member tells which permissions should have the segment
# Flags
PF_EXEC = 1
PF_WRITE = 2
PF_READ = 4
#
# p_type This member tells what kind of segment this array element
# describes or how to interpret the array element's information.
#
# Segment Types
PT_NULL = 0
PT_LOAD = 1
PT_DYNAMIC = 2
PT_INTERP = 3
PT_NOTE = 4
PT_SHLIB = 5
PT_PHDR = 6
PT_LOPROC = 0x70000000
PT_HIPROC = 0x7fffffff
class ProgramHeader < GenericHeader
def initialize(rawdata, ei_data)
# Identify the data encoding and parse Program Header
if ei_data == ELFDATA2LSB
program_header = ELF32_PHDR_LSB.make_struct
elsif ei_data == ELFDATA2MSB
program_header = ELF32_PHDR_MSB.make_struct
else
raise ElfHeaderError, "Invalid data encoding", caller
end
if !program_header.from_s(rawdata)
raise ProgramHeaderError, "Couldn't parse Program Header", caller
end
self.struct = program_header
end
end
end
end
end

View File

@ -1,25 +0,0 @@
# -*- coding: binary -*-
module Rex
module ElfParsey
class ElfError < ::RuntimeError
end
class ParseError < ElfError
end
class ElfHeaderError < ParseError
end
class ProgramHeaderError < ParseError
end
class BoundsError < ElfError
end
class ElfParseyError < ElfError
end
end
end

View File

@ -1,10 +0,0 @@
# -*- coding: binary -*-
module Rex
module ElfScan
end
end
require 'rex/elfscan/scanner'
require 'rex/elfscan/search'

View File

@ -1,226 +0,0 @@
# -*- coding: binary -*-
require 'metasm'
module Rex
module ElfScan
module Scanner
class Generic
attr_accessor :elf, :regex
def initialize(elf)
self.elf = elf
end
def config(param)
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
elf.program_header.each do |program_header|
# Scan only loadable segment entries in the program header table
if program_header.p_type == Rex::ElfParsey::ElfBase::PT_LOAD
hits = scan_segment(program_header, param)
hits.each do |hit|
rva = hit[0]
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
$stdout.puts elf.ptr_s(rva) + " " + message
if(param['disasm'])
message.gsub!("; ", "\n")
if message.include?("retn")
message.gsub!("retn", "ret")
end
begin
d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, message).disassemble
rescue Metasm::ParseError
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [message].pack('H*'))
end
addr = 0
while ((di = d2.disassemble_instruction(addr)))
disasm = "0x%08x\t" % (rva + addr)
disasm << di.instruction.to_s
$stdout.puts disasm
addr = di.next_addr
end
end
end
end
end
end
def scan_segment(program_header, param={})
[]
end
end
class JmpRegScanner < Generic
def config(param)
regnums = param['args']
# build a list of the call bytes
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
jmps = _build_byte_list(0xe0, regnums)
pushs1 = _build_byte_list(0x50, regnums)
pushs2 = _build_byte_list(0xf0, regnums)
regexstr = '('
if !calls.empty?
regexstr += "\xff[#{calls}]|"
end
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
self.regex = Regexp.new(regexstr, nil, 'n')
end
# build a list for regex of the possible bytes, based on a base
# byte and a list of register numbers..
def _build_byte_list(base, regnums)
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
end
def _ret_size(offset)
case elf.read(offset, 1)
when "\xc3"
return 1
when "\xc2"
return 3
end
raise "Cannot read at offset: #{offset}"
end
def _parse_ret(data)
if data.length == 1
return "ret"
else
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
end
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while (offset = elf.index(regex, offset)) != nil
rva = elf.offset_to_rva(offset)
message = ''
parse_ret = false
byte1 = elf.read(offset, 1).unpack('C')[0]
if byte1 == 0xff
byte2 = elf.read(offset+1, 1).unpack('C')[0]
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
case byte2 & 0xf8
when 0xd0
message = "call #{regname}"
offset += 2
when 0xe0
message = "jmp #{regname}"
offset += 2
when 0xf0
retsize = _ret_size(offset+2)
message = "push #{regname}; " + _parse_ret(elf.read(offset+2, retsize))
offset += 2 + retsize
else
raise "Unexpected value at #{offset}"
end
else
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
retsize = _ret_size(offset+1)
message = "push #{regname}; " + _parse_ret(elf.read(offset+1, retsize))
offset += 1 + retsize
end
hits << [ rva, message ]
end
return hits
end
end
class PopPopRetScanner < JmpRegScanner
def config(param)
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while offset < program_header.p_offset + program_header.p_filesz &&
(offset = elf.index(regex, offset)) != nil
rva = elf.offset_to_rva(offset)
message = ''
pops = elf.read(offset, 2)
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack('C*')[0] & 0x7)
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack('C*')[0] & 0x7)
message = "pop #{reg1}; pop #{reg2}; "
retsize = _ret_size(offset+2)
message += _parse_ret(elf.read(offset+2, retsize))
offset += 2 + retsize
hits << [ rva, message ]
end
return hits
end
end
class RegexScanner < JmpRegScanner
def config(param)
self.regex = Regexp.new(param['args'], nil, 'n')
end
def scan_segment(program_header, param={})
offset = program_header.p_offset
hits = []
while offset < program_header.p_offset + program_header.p_filesz &&
(offset = elf.index(regex, offset)) != nil
idx = offset
buf = ''
mat = nil
while (! (mat = buf.match(regex)))
buf << elf.read(idx, 1)
idx += 1
end
rva = elf.offset_to_rva(offset)
hits << [ rva, buf.unpack("H*") ]
offset += buf.length
end
return hits
end
end
end
end
end

View File

@ -1,44 +0,0 @@
# -*- coding: binary -*-
module Rex
module ElfScan
module Search
class DumpRVA
attr_accessor :elf
def initialize(elf)
self.elf = elf
end
def config(param)
@address = param['args']
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
# Adjust based on -A and -B flags
pre = param['before'] || 0
suf = param['after'] || 16
@address -= pre
@address = 0 if (@address < 0 || ! @address)
buf = elf.read_rva(@address, suf)
$stdout.puts elf.ptr_s(@address) + " " + buf.unpack("H*")[0]
end
end
class DumpOffset < DumpRVA
def config(param)
begin
@address = elf.offset_to_rva(param['args'])
rescue Rex::ElfParsey::BoundsError
end
end
end
end
end
end

View File

@ -1,160 +0,0 @@
# -*- coding: binary -*-
require 'find'
require 'rex/compat'
require 'tempfile'
module Rex
###
#
# This class provides helper methods for dealing with files that are not
# supplied by the standard ruby API.
#
###
module FileUtils
#
# This method joins the paths together in Unix format.
#
def self.normalize_unix_path(*strs)
new_str = strs * '/'
new_str = new_str.gsub!("//", "/") while new_str.index("//")
new_str
end
#
# This method joins the paths together in Windows format.
# All reserved characters will be filtered out, including:
# " * : < > ? \ / |
#
def self.normalize_win_path(*strs)
# Convert to the same format so the parsing is easier
s = strs * '\\'
# Filter out double slashes
s = s.gsub(/\\\\/, '\\') while s.index('\\\\')
# Keep the trailing slash if exists
trailing_s = ('\\' if s =~ /\\$/) || ''
# Check the items (fie/dir) individually
s = s.split(/\\/)
# Parse the path prefix
prefix = (s[0] || '').gsub(/[\*<>\?\/]/, '')
# Delete the original prefix. We want the new one later.
s.delete_at(0)
# Filter out all the reserved characters
s.map! {|e| e.gsub(/["\*:<>\?\\\/|]/, '') }
# Put the modified prefix back
s.insert(0, prefix)
# And then safely join the items
s *= '\\'
# Add the trailing slash back if exists
s << trailing_s
end
#
# This method cleans the supplied path of directory traversal sequences
# It must accept path/with/..a/folder../starting/or/ending/in/two/dots
# but clean ../something as well as path/with/..\traversal
#
def self.clean_path(old)
path = old
while(path.index(/\/..\/|\/..\\|\\..\\|\\..\/|\A..\\|\A..\//) != nil)
path.gsub!(/\A..\\|\A..\//,'') #eliminate starting ..\ or ../
path.gsub!(/\/..\/|\/..\\/,'/') #clean linux style
path.gsub!(/\\..\\|\\..\//,'\\') #clean windows style
end
path
end
#
# This method searches the PATH environment variable for
# a fully qualified path to the supplied file name.
#
def self.find_full_path(file_name)
# Check for the absolute fast first
if (file_name[0,1] == "/" and ::File.exist?(file_name) and ::File::Stat.new(file_name))
return file_name
end
path = Rex::Compat.getenv('PATH')
if (path)
path.split(::File::PATH_SEPARATOR).each { |base|
begin
# Deal with Windows paths surrounded by quotes. Prevents
# silliness like trying to look for
# '"C:\\framework\\nmap"\\nmap.exe' which will always fail.
base = $1 if base =~ /^"(.*)"$/
path = base + ::File::SEPARATOR + file_name
if (::File::Stat.new(path) and not ::File.directory?(path))
return path
end
rescue
end
}
end
return nil
end
end
class Quickfile < ::Tempfile
def initialize(*args)
super(*args)
self.binmode
ObjectSpace.undefine_finalizer(self)
end
end
module Find
#
# Identical to Find.find from Ruby, but follows symlinks to directories.
# See http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/68671
#
def self.find(*paths)
paths.collect!{|d| d.dup}
while file = paths.shift
catch(:prune) do
yield file.dup.taint
next unless File.exist? file
begin
if File.stat(file).directory? then
d = Dir.open(file)
begin
for f in d
next if f == "." or f == ".."
if File::ALT_SEPARATOR and file =~ /^(?:[\/\\]|[A-Za-z]:[\/\\]?)$/ then
f = file + f
elsif file == "/" then
f = "/" + f
else
f = File.join(file, f)
end
paths.unshift f.untaint
end
ensure
d.close
end
end
rescue Errno::ENOENT, Errno::EACCES
end
end
end
end
def self.prune
throw :prune
end
end
end

View File

@ -1,10 +0,0 @@
# -*- coding: binary -*-
module Rex
module ImageSource
end
end
require 'rex/image_source/disk'
require 'rex/image_source/memory'

View File

@ -1,58 +0,0 @@
# -*- coding: binary -*-
require 'rex/image_source/image_source'
require 'rex/struct2'
module Rex
module ImageSource
class Disk < ImageSource
attr_accessor :file, :file_offset, :size
WINDOW_SIZE = 4096
WINDOW_OVERLAP = 64
def initialize(_file, _offset = 0, _len = nil)
_len = _file.stat.size if !_len
self.file = _file
self.file_offset = _offset
self.size = _len
end
def read(offset, len)
if offset < 0 || offset+len > size
raise RangeError, "Offset #{offset} outside of image source", caller
end
file.seek(file_offset + offset)
file.read(len)
end
def index(search, offset = 0)
# do a sliding window search across the disk
while offset < size
# get a full window size if we can, we
# don't want to read past our boundaries
wsize = size - offset
wsize = WINDOW_SIZE if wsize > WINDOW_SIZE
window = self.read(offset, wsize)
res = window.index(search)
return res + offset if res
offset += WINDOW_SIZE - WINDOW_OVERLAP
end
end
def subsource(offset, len)
self.class.new(file, file_offset+offset, len)
end
def close
file.close
end
end
end
end

View File

@ -1,48 +0,0 @@
# -*- coding: binary -*-
module Rex
module ImageSource
class ImageSource
#
# Um, just some abstract class stuff I guess, this is the interface
# that any image sources should subscribe to...
#
def subsource(offset, len)
raise "do something"
end
def size
raise "do something"
end
def file_offset
raise "do something"
end
def close
raise "do something"
end
def read_asciiz(offset)
# FIXME, make me better
string = ''
loop do
begin
char = read(offset, 1)
rescue RangeError
break
end
break if char.nil? || char == "\x00"
offset += 1
string << char
end
return string
end
end
end
end

View File

@ -1,35 +0,0 @@
# -*- coding: binary -*-
require 'rex/image_source/image_source'
require 'rex/struct2'
module Rex
module ImageSource
class Memory < ImageSource
attr_accessor :rawdata, :size, :file_offset
def initialize(_rawdata, _file_offset = 0)
self.rawdata = _rawdata
self.size = _rawdata.length
self.file_offset = _file_offset
end
def read(offset, len)
rawdata[offset, len]
end
def subsource(offset, len)
self.class.new(rawdata[offset, len], offset + file_offset)
end
def close
end
def index(*args)
rawdata.index(*args)
end
end
end
end

View File

@ -1,9 +0,0 @@
# -*- coding: binary -*-
module Rex
module MachParsey
end
end
require 'rex/machparsey/mach'

View File

@ -1,31 +0,0 @@
# -*- coding: binary -*-
module Rex
module MachParsey
class MachError < ::RuntimeError
end
class MachParseError < MachError
end
class MachHeaderError < MachParseError
end
class ProgramHeaderError < MachParseError
end
class BoundsError < MachError
end
class FatError < ::RuntimeError
end
class FatParseError < FatError
end
class FatHeaderError < FatParseError
end
end
end

View File

@ -1,209 +0,0 @@
# -*- coding: binary -*-
require 'rex/machparsey/machbase'
require 'rex/machparsey/exceptions'
require 'rex/image_source'
module Rex
module MachParsey
class Mach < MachBase
attr_accessor :mach_header, :segments, :isource, :bits, :endian, :arch, :fat_offset
def initialize(isource, offset = 0, fat = false)
_parse_mach_header(isource, offset)
if fat == true
self.fat_offset = offset
else
self.fat_offset = 0
end
self.isource = isource
end
def _parse_mach_header(isource, offset)
self.mach_header = MachHeader.new(isource.read(offset, MACH_HEADER_SIZE_64))
bits = mach_header.bits
endian = mach_header.endian
ncmds = mach_header.ncmds
if bits == BITS_32
offset += MACH_HEADER_SIZE
else
offset += MACH_HEADER_SIZE_64
end
segments = []
ncmds.times do
load_command = LoadCommand.new(isource.read(offset, LOAD_COMMAND_SIZE), endian)
case load_command.cmd
when LC_SEGMENT
segments << Segment.new(isource.read(offset, SEGMENT_COMMAND_SIZE), bits, endian)
when LC_SEGMENT_64
segments << Segment.new(isource.read(offset, SEGMENT_COMMAND_SIZE_64), bits, endian)
end
offset += load_command.cmdsize
end
self.mach_header = mach_header
self.segments = segments
self.isource = isource
self.bits = bits
self.endian = endian
return segments
end
def self.new_from_file(filename, disk_backed = false)
file = ::File.open(filename, "rb")
if disk_backed
return self.new(ImageSource::Disk.new(file))
else
obj = new_from_string(file.read)
file.close
return obj
end
end
def self.new_from_string(data)
return self.new(ImageSource::Memory.new(data))
end
def ptr_64?
mach_header.bits == BITS_64
end
def ptr_32?
ptr_64? == false
end
def ptr_s(vaddr)
(ptr_32?) ? ("0x%.8x" % vaddr) : ("0x%.16x" % vaddr)
end
def read(offset, len)
isource.read(fat_offset + offset, len)
end
def index(*args)
isource.index(*args)
end
def close
isource.close
end
end
class Fat < FatBase
attr_accessor :fat_header, :fat_archs, :machos, :isource
def initialize(isource, offset = 0)
self.fat_archs = []
self.machos = []
self.isource = isource
self.fat_header = FatHeader.new(isource.read(offset, FAT_HEADER_SIZE))
if !self.fat_header
raise FatHeaderError, "Could not parse FAT header"
end
print "Detected " + self.fat_header.nfat_arch.to_s + " archs in binary.\n"
offset += FAT_HEADER_SIZE
self.fat_header.nfat_arch.times do
fat_arch = FatArch.new(isource.read(offset, FAT_ARCH_SIZE), self.fat_header.endian)
self.fat_archs << fat_arch
self.machos << Mach.new(isource, fat_arch.offset, true)
offset += FAT_ARCH_SIZE
end
end
#this is useful for debugging but we don't use it for anything.
def _parse_fat_header(isource, offset)
archs = []
nfat_arch = self.fat_header.nfat_arch
print "Number of archs in binary: " + nfat_arch.to_s + "\n"
nfat_arch.times do
arch = FatArch.new(isource.read(offset, FAT_ARCH_SIZE), self.endian)
case arch.cpu_type
when CPU_TYPE_I386
print "i386\n"
when CPU_TYPE_X86_64
print "x86_64\n"
when CPU_TYPE_ARM
print "Arm\n"
when CPU_TYPE_POWERPC
print "Power PC\n"
when CPU_TYPE_POWERPC64
print "Power PC 64\n"
end
offset += FAT_ARCH_SIZE
end
end
def self.new_from_file(filename, disk_backed = false)
file = ::File.open(filename, "rb")
if disk_backed
return self.new(ImageSource::Disk.new(file))
else
obj = new_from_string(file.read)
file.close
return obj
end
end
def self.new_from_string(data)
return self.new(ImageSource::Memory.new(data))
end
def ptr_64?
mach_header.bits == BITS_64
end
def ptr_32?
ptr_64? == false
end
def ptr_s(vaddr)
(ptr_32?) ? ("0x%.8x" % vaddr) : ("0x%.16x" % vaddr)
end
def read(offset, len)
isource.read(offset, len)
end
def index(*args)
isource.index(*args)
end
def close
isource.close
end
end
end
end

View File

@ -1,408 +0,0 @@
# -*- coding: binary -*-
require 'rex/struct2'
module Rex
module MachParsey
require 'rex/machparsey/exceptions'
require 'rex/struct2'
class GenericStruct
attr_accessor :struct
def initialize(_struct)
self.struct = _struct
end
# Access a value
def v
struct.v
end
# Access a value by array
def [](*args)
struct[*args]
end
# Obtain an array of all fields
def keys
struct.keys
end
def method_missing(meth, *args)
v[meth.to_s] || (raise NoMethodError.new, meth)
end
end
class GenericHeader < GenericStruct
end
BITS_32 = 0
BITS_64 = 1
ENDIAN_LSB = 0
ENDIAN_MSB = 1
class MachBase
MH_MAGIC = 0xfeedface
MH_MAGIC_64 = 0xfeedfacf
MH_CIGAM = 0xcefaedfe
MH_CIGAM_64 = 0xcffaedfe
MACH_HEADER_SIZE = 28
MACH_HEADER_SIZE_64 = 32
MACH_HEADER_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'magic', 0],
['uint32v', 'cputype', 0],
['uint32v', 'cpusubtype',0],
['uint32v', 'filetype', 0],
['uint32v', 'ncmds', 0],
['uint32v', 'sizeofcmds',0],
['uint32v', 'flags', 0]
)
MACH_HEADER_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'magic', 0],
['uint32n', 'cputype', 0],
['uint32n', 'cpusubtype',0],
['uint32n', 'filetype', 0],
['uint32n', 'ncmds', 0],
['uint32n', 'sizeofcmds',0],
['uint32n', 'flags', 0]
)
MACH_HEADER_64_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'magic', 0],
['uint32v', 'cputype', 0],
['uint32v', 'cpusubtype',0],
['uint32v', 'filetype', 0],
['uint32v', 'ncmds', 0],
['uint32v', 'sizeofcmds',0],
['uint32v', 'flags', 0],
['uint32v', 'reserved', 0]
)
MACH_HEADER_64_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'magic', 0],
['uint32n', 'cputype', 0],
['uint32n', 'cpusubtype',0],
['uint32n', 'filetype', 0],
['uint32n', 'ncmds', 0],
['uint32n', 'sizeofcmds',0],
['uint32n', 'flags', 0],
['uint32n', 'reserved', 0]
)
#cpu types for Mach-O binaries
CPU_TYPE_I386 = 0x7
CPU_TYPE_X86_64 = 0x01000007
CPU_TYPE_ARM = 0xC
CPU_TYPE_POWERPC = 0x12
CPU_TYPE_POWERPC64 = 0x01000012
CPU_SUBTYPE_LITTLE_ENDIAN = 0
CPU_SUBTYPE_BIG_ENDIAN = 1
LC_SEGMENT = 0x1 #/* segment of this file to be mapped */
LC_SYMTAB = 0x2 #/* link-edit stab symbol table info */
LC_SYMSEG = 0x3 #/* link-edit gdb symbol table info (obsolete) */
LC_THREAD = 0x4 #/* thread */
LC_UNIXTHREAD = 0x5 #/* unix thread (includes a stack) */
LC_LOADFVMLIB = 0x6 #/* load a specified fixed VM shared library */
LC_IDFVMLIB = 0x7 #/* fixed VM shared library identification */
LC_IDENT = 0x8 #/* object identification info (obsolete) */
LC_FVMFILE = 0x9 #/* fixed VM file inclusion (internal use) */
LC_PREPAGE = 0xa #/* prepage command (internal use) */
LC_DYSYMTAB = 0xb #/* dynamic link-edit symbol table info */
LC_LOAD_DYLIB = 0xc #/* load a dynamicly linked shared library */
LC_ID_DYLIB = 0xd #/* dynamicly linked shared lib identification */
LC_LOAD_DYLINKER = 0xe #/* load a dynamic linker */
LC_ID_DYLINKER = 0xf #/* dynamic linker identification */
LC_PREBOUND_DYLIB = 0x10 #/* modules prebound for a dynamicly */
LC_SEGMENT_64 = 0x19 #/* segment of this file to be mapped */
class MachHeader < GenericHeader
attr_accessor :bits, :endian
def initialize(rawdata)
mach_header = MACH_HEADER_LSB.make_struct
if !mach_header.from_s(rawdata)
raise MachHeaderError, "Could't access Mach-O Magic", caller
end
if mach_header.v['magic'] == MH_MAGIC
endian = ENDIAN_LSB
bits = BITS_32
mach_header = MACH_HEADER_LSB.make_struct
elsif mach_header.v['magic'] == MH_CIGAM
bits = BITS_32
endian = ENDIAN_MSB
mach_header = MACH_HEADER_MSB.make_struct
elsif mach_header.v['magic'] == MH_MAGIC_64
endian = ENDIAN_LSB
bits = BITS_64
mach_header = MACH_HEADER_LSB.make_struct
elsif mach_header.v['magic'] == MH_CIGAM_64
endian = ENDIAN_MSB
bits = BITS_64
mach_header = MACH_HEADER_MSB.make_struct
else
raise MachHeaderError, "Couldn't find Mach Magic", caller
end
if !mach_header.from_s(rawdata)
raise MachHeaderError, "Could't process Mach-O Header", caller
end
self.struct = mach_header
self.endian = endian
self.bits = bits
end
end
LOAD_COMMAND_SIZE = 8
LOAD_COMMAND_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v','cmd',0],
['uint32v','cmdsize',0]
)
LOAD_COMMAND_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n','cmd',0],
['uint32n','cmdsize',0]
)
class LoadCommand < GenericHeader
def initialize(rawdata, endian)
if endian == ENDIAN_MSB
load_command = LOAD_COMMAND_MSB.make_struct
else
load_command = LOAD_COMMAND_LSB.make_struct
end
if !load_command.from_s(rawdata)
raise MachParseError, "Couldn't parse load command"
end
self.struct = load_command
end
end
SEGMENT_COMMAND_SIZE = 56
SEGMENT_COMMAND_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'cmd', 0],
['uint32v', 'cmdsize', 0],
['string', 'segname', 16, ''],
['uint32v', 'vmaddr', 0],
['uint32v', 'vmsize', 0],
['uint32v', 'fileoff', 0],
['uint32v', 'filesize', 0],
['uint32v', 'maxprot', 0],
['uint32v', 'initprot', 0],
['uint32v', 'nsects', 0],
['uint32v', 'flags', 0]
)
SEGMENT_COMMAND_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'cmd', 0],
['uint32n', 'cmdsize', 0],
['string', 'segname', 16, ''],
['uint32n', 'vmaddr', 0],
['uint32n', 'vmsize', 0],
['uint32n', 'fileoff', 0],
['uint32n', 'filesize', 0],
['uint32n', 'maxprot', 0],
['uint32n', 'initprot', 0],
['uint32n', 'nsects', 0],
['uint32n', 'flags', 0]
)
SEGMENT_COMMAND_SIZE_64 = 72
SEGMENT_COMMAND_64_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'cmd', 0],
['uint32v', 'cmdsize', 0],
['string', 'segname', 16, ''],
['uint64v', 'vmaddr', 0],
['uint64v', 'vmsize', 0],
['uint64v', 'fileoff', 0],
['uint64v', 'filesize', 0],
['uint32v', 'maxprot', 0],
['uint32v', 'initprot', 0],
['uint32v', 'nsects', 0],
['uint32v', 'flags', 0]
)
SEGMENT_COMMAND_64_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'cmd', 0],
['uint32n', 'cmdsize', 0],
['string', 'segname', 16, ''],
['uint64n', 'vmaddr', 0],
['uint64n', 'vmsize', 0],
['uint64n', 'fileoff', 0],
['uint64n', 'filesize', 0],
['uint32n', 'maxprot', 0],
['uint32n', 'initprot', 0],
['uint32n', 'nsects', 0],
['uint32n', 'flags', 0]
)
class Segment < GenericHeader
attr_accessor :_bits, :_endian
def initialize(rawdata, bits, endian)
self._bits = bits
if bits == BITS_64
if endian == ENDIAN_MSB
segment_command = SEGMENT_COMMAND_64_MSB.make_struct
else
segment_command = SEGMENT_COMMAND_64_LSB.make_struct
end
else
if endian == ENDIAN_MSB
segment_command = SEGMENT_COMMAND_MSB.make_struct
else
segment_command = SEGMENT_COMMAND_LSB.make_struct
end
end
if !segment_command.from_s(rawdata)
raise MachParseError, "Couldn't parse segment command"
end
self.struct = segment_command
end
def Segname
v['segname']
end
def Vmaddr
v['vmaddr']
end
def Vmsize
v['vmsize']
end
def FileOff
v['fileoff']
end
def FileSize
v['filesize']
end
end
class Thread < GenericHeader
def initialize(rawdata)
end
end
end
FAT_MAGIC = 0xcafebabe
FAT_CIGAM = 0xbebafeca
FAT_HEADER_SIZE = 8
FAT_HEADER_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'magic', 0],
['uint32v', 'nfat_arch',0]
)
FAT_HEADER_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'magic', 0],
['uint32n', 'nfat_arch',0]
)
FAT_ARCH_SIZE = 20
FAT_ARCH_LSB = Rex::Struct2::CStructTemplate.new(
['uint32v', 'cpu_type', 0],
['uint32v', 'cpu_subtype',0],
['uint32v', 'offset', 0],
['uint32v', 'size', 0],
['uint32v', 'align', 0]
)
FAT_ARCH_MSB = Rex::Struct2::CStructTemplate.new(
['uint32n', 'cpu_type', 0],
['uint32n', 'cpu_subtype',0],
['uint32n', 'offset', 0],
['uint32n', 'size', 0],
['uint32n', 'align', 0]
)
class FatBase
class FatHeader < GenericHeader
attr_accessor :nfat_arch, :endian, :exists
def initialize(rawdata)
fat_header = FAT_HEADER_LSB.make_struct
if !fat_header.from_s(rawdata)
#raise something
end
magic = fat_header.v['magic']
if magic == FAT_MAGIC
endian = ENDIAN_LSB
elsif magic == FAT_CIGAM
endian = ENDIAN_MSB
fat_header = FAT_HEADER_MSB.make_struct
if !fat_header.from_s(rawdata)
raise FatHeaderError, "Could not parse FAT header"
end
else
self.exists = 0
return
end
self.nfat_arch = fat_header.v['nfat_arch']
self.struct = fat_header
self.endian = endian
end
end
class FatArch < GenericHeader
attr_accessor :cpu_type, :cpu_subtype, :offset, :size
def initialize(rawdata, endian)
if endian == ENDIAN_LSB
fat_arch = FAT_ARCH_LSB.make_struct
else
fat_arch = FAT_ARCH_MSB.make_struct
end
if !fat_arch.from_s(rawdata)
raise FatHeaderError, "Could not parse arch from FAT header"
end
self.cpu_type = fat_arch.v['cpu_type']
self.cpu_subtype = fat_arch.v['cpu_subtype']
self.offset = fat_arch.v['offset']
self.size = fat_arch.v['size']
self.struct = fat_arch
end
end
class Thread < GenericHeader
def initialize(rawdata)
end
end
end
end
end

View File

@ -1,9 +0,0 @@
# -*- coding: binary -*-
module Rex
module MachScan
end
end
require 'rex/machscan/scanner'

View File

@ -1,217 +0,0 @@
# -*- coding: binary -*-
module Rex
module MachScan
module Scanner
class Generic
attr_accessor :mach, :fat, :regex
def initialize(binary)
if binary.class == Rex::MachParsey::Mach
self.mach = binary
else
self.fat = binary
end
end
def config(param)
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
if !self.mach
for mach in fat.machos
if mach.mach_header.cputype == 0x7 #since we only support intel for the time being its all we process
self.mach = mach
end
end
end
self.mach.segments.each do |segment|
if segment.segname.include? "__TEXT"
scan_segment(segment, param).each do |hit|
vaddr = hit[0]
message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
$stdout.puts self.mach.ptr_s(vaddr - self.mach.fat_offset) + " " + message
end
end
end
end
def scan_segment(segment, param={})
[]
end
end
class JmpRegScanner < Generic
def config(param)
regnums = param['args']
# build a list of the call bytes
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
jmps = _build_byte_list(0xe0, regnums)
pushs1 = _build_byte_list(0x50, regnums)
pushs2 = _build_byte_list(0xf0, regnums)
regexstr = '('
if !calls.empty?
regexstr += "\xff[#{calls}]|"
end
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
self.regex = Regexp.new(regexstr, nil, 'n')
end
# build a list for regex of the possible bytes, based on a base
# byte and a list of register numbers..
def _build_byte_list(base, regnums)
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
end
def _ret_size(offset)
case mach.read(offset, 1)
when "\xc3"
return 1
when "\xc2"
return 3
end
$stderr.puts("Invalid return instruction")
end
def _parse_ret(data)
if data.length == 1
return "ret"
else
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
end
end
def scan_segment(segment, param={})
base_addr = segment.vmaddr
segment_offset = segment.fileoff
offset = segment_offset
hits = []
while (offset = mach.index(regex, offset)) != nil
vaddr = base_addr + (offset - segment_offset)
message = ''
parse_ret = false
byte1 = mach.read(offset, 1).unpack("C*")[0]
if byte1 == 0xff
byte2 = mach.read(offset+1, 1).unpack("C*")[0]
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
case byte2 & 0xf8
when 0xd0
message = "call #{regname}"
offset += 2
when 0xe0
message = "jmp #{regname}"
offset += 2
when 0xf0
retsize = _ret_size(offset+2)
message = "push #{regname}; " + _parse_ret(mach.read(offset+2, retsize))
offset += 2 + retsize
else
raise "Unexpected value at offset: #{offset}"
end
else
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
retsize = _ret_size(offset+1)
message = "push #{regname}; " + _parse_ret(mach.read(offset+1, retsize))
offset += 1 + retsize
end
hits << [ vaddr, message ]
end
return hits
end
end
class PopPopRetScanner < JmpRegScanner
def config(param)
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
end
def scan_segment(segment, param={})
base_addr = segment.vmaddr
segment_offset = segment.fileoff
offset = segment_offset
hits = []
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
vaddr = base_addr + (offset - segment_offset)
message = ''
pops = mach.read(offset, 2)
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack("C*")[0] & 0x7)
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack("C*")[0] & 0x7)
message = "pop #{reg1}; pop #{reg2}; "
retsize = _ret_size(offset+2)
message += _parse_ret(mach.read(offset+2, retsize))
offset += 2 + retsize
hits << [ vaddr, message ]
end
return hits
end
end
class RegexScanner < JmpRegScanner
def config(param)
self.regex = Regexp.new(param['args'], nil, 'n')
end
def scan_segment(segment, param={})
base_addr = segment.vmaddr
segment_offset = segment.fileoff
offset = segment_offset
hits = []
while offset < segment.fileoff + segment.filesize && (offset = mach.index(regex, offset)) != nil
idx = offset
buf = ''
mat = nil
while (! (mat = buf.match(regex)))
buf << mach.read(idx, 1)
idx += 1
end
vaddr = base_addr + (offset - segment_offset)
hits << [ vaddr, buf.unpack("H*") ]
offset += buf.length
end
return hits
end
end
end
end
end

View File

@ -1,10 +0,0 @@
# -*- coding: binary -*-
module Rex
module PeParsey
end
end
require 'rex/peparsey/pe'
require 'rex/peparsey/pe_memdump'

View File

@ -1,30 +0,0 @@
# -*- coding: binary -*-
module Rex
module PeParsey
class PeError < ::RuntimeError
end
class ParseError < PeError
end
class DosHeaderError < ParseError
end
class FileHeaderError < ParseError
end
class OptionalHeaderError < ParseError
end
class BoundsError < PeError
end
class PeParseyError < PeError
end
class SkipError < PeError
end
end end

View File

@ -1,210 +0,0 @@
# -*- coding: binary -*-
require 'rex/image_source'
require 'rex/peparsey/exceptions'
require 'rex/peparsey/pebase'
require 'rex/peparsey/section'
require 'rex/struct2'
module Rex
module PeParsey
class Pe < PeBase
def initialize(isource)
#
# DOS Header
#
# Parse the initial dos header, starting at the file beginning
#
offset = 0
dos_header = self.class._parse_dos_header(isource.read(offset, IMAGE_DOS_HEADER_SIZE))
#
# File Header
#
# If there is going to be a PE, the dos header tells us where to find it
# So now we try to parse the file (pe) header
#
offset += dos_header.e_lfanew
# most likely an invalid e_lfanew...
if offset > isource.size
raise FileHeaderError, "e_lfanew looks invalid", caller
end
file_header = self.class._parse_file_header(isource.read(offset, IMAGE_FILE_HEADER_SIZE))
#
# Optional Header
#
# After the file header, we find the optional header. Right now
# we require a optional header. Despite it's name, all binaries
# that we are interested in should have one. We need this
# header for a lot of stuff, so we die without it...
#
offset += IMAGE_FILE_HEADER_SIZE
optional_header = self.class._parse_optional_header(
isource.read(offset, file_header.SizeOfOptionalHeader)
)
if !optional_header
raise OptionalHeaderError, "No optional header!", caller
end
base = optional_header.ImageBase
#
# Section Headers
#
# After the optional header should be the section headers.
# We know how many there should be from the file header...
#
offset += file_header.SizeOfOptionalHeader
num_sections = file_header.NumberOfSections
section_headers = self.class._parse_section_headers(
isource.read(offset, IMAGE_SIZEOF_SECTION_HEADER * num_sections)
)
#
# End of Headers
#
# After the section headers (which are padded to FileAlignment)
# we should find the section data, described by the section
# headers...
#
# So this is the end of our header data, lets store this
# in an image source for possible access later...
#
offset += IMAGE_SIZEOF_SECTION_HEADER * num_sections
offset = self.class._align_offset(offset, optional_header.FileAlignment)
header_section = Section.new(isource.subsource(0, offset), 0, nil)
#
# Sections
#
# So from here on out should be section data, and then any
# trailing data (like authenticode and stuff I think)
#
sections = [ ]
section_headers.each do |section_header|
rva = section_header.VirtualAddress
size = section_header.SizeOfRawData
file_offset = section_header.PointerToRawData
sections << Section.new(
isource.subsource(file_offset, size),
rva,
section_header
)
end
#
# Save the stuffs!
#
# We have parsed enough to load the file up here, now we just
# save off all of the structures and data... We will
# save our fake header section, the real sections, etc.
#
#
# These should not be accessed directly
#
self._isource = isource
self._dos_header = dos_header
self._file_header = file_header
self._optional_header = optional_header
self._section_headers = section_headers
self.image_base = base
self.sections = sections
self.header_section = header_section
self._config_header = _parse_config_header()
self._tls_header = _parse_tls_header()
# These can be accessed directly
self.hdr = HeaderAccessor.new
self.hdr.dos = self._dos_header
self.hdr.file = self._file_header
self.hdr.opt = self._optional_header
self.hdr.sections = self._section_headers
self.hdr.config = self._config_header
self.hdr.tls = self._tls_header
self.hdr.exceptions = self._exception_header
# We load the exception directory last as it relies on hdr.file to be created above.
self._exception_header = _load_exception_directory()
end
#
# Return everything that's going to be mapped in the process
# and accessable. This should include all of the sections
# and our "fake" section for the header data...
#
def all_sections
[ header_section ] + sections
end
#
# Returns true if this binary is for a 64-bit architecture.
#
def ptr_64?
[
IMAGE_FILE_MACHINE_IA64,
IMAGE_FILE_MACHINE_ALPHA64,
IMAGE_FILE_MACHINE_AMD64
].include?(self._file_header.Machine)
end
#
# Returns true if this binary is for a 32-bit architecture.
# This check does not take into account 16-bit binaries at the moment.
#
def ptr_32?
ptr_64? == false
end
#
# Converts a virtual address to a string representation based on the
# underlying architecture.
#
def ptr_s(va)
(ptr_32?) ? ("0x%.8x" % va) : ("0x%.16x" % va)
end
#
# Converts a file offset into a virtual address
#
def file_offset_to_va(offset)
image_base + file_offset_to_rva(offset)
end
#
# Read raw bytes from the specified offset in the underlying file
#
# NOTE: You should pass raw file offsets into this, not offsets from
# the beginning of the section. If you need to read from within a
# section, add section.file_offset prior to passing the offset in.
#
def read(offset, len)
_isource.read(offset, len)
end
def size
_isource.size
end
def length
_isource.size
end
end end end

View File

@ -1,61 +0,0 @@
# -*- coding: binary -*-
require 'rex/image_source'
require 'rex/peparsey/exceptions'
require 'rex/peparsey/pebase'
require 'rex/peparsey/section'
require 'rex/struct2'
#
# This class is for use with memdump.exe generated dump images. It basically
# just lies, gets the ImageBase from the file name, and generates 1 big
# header_section with all of the data in it...
#
module Rex
module PeParsey
class PeMemDump < Pe
def self.new_from_string(data)
raise NotImplementError
end
def self.new_from_file(filename, disk_backed = false)
if filename[-4, 4] != '.rng'
raise "Not a .rng file: #{filename}"
end
if filename[-9, 9] == "index.rng"
raise SkipError
end
file = File.open(filename, 'rb')
if disk_backed
obj = ImageSource::Disk.new(file)
else
obj = ImageSource::Memory.new(file.read)
obj.close
end
return self.new(obj, filename.gsub(/.*[\/\\]/, '')[0,8].hex)
end
def initialize(isource, base)
self._isource = isource
self.header_section = Section.new(isource, base, nil)
self.sections = [ self.header_section ]
self.image_base = 0
end
def all_sections
self.sections
end
# No 64-bit support
def ptr_64?
false
end
end end end

File diff suppressed because it is too large Load Diff

View File

@ -1,128 +0,0 @@
# -*- coding: binary -*-
require 'rex/peparsey/exceptions'
require 'rex/peparsey/pebase'
require 'rex/struct2'
module Rex
module PeParsey
class Section
attr_accessor :_section_header, :_isource
attr_accessor :base_rva
#
# Initialize a section.
#
# isource - The ImageSource class backing the image
# base_vma - The address of this section base
# section_header - The section header (struct2) although this is not
# required, which is why there is a base_vma. This can be nil.
#
def initialize(isource, base_rva, section_header = nil)
self._isource = isource
self.base_rva = base_rva
self._section_header = section_header
end
def file_offset
_isource.file_offset
end
def size
_isource.size
end
def name
# a section header is not required
return nil if !_section_header
# FIXME make this better...
_section_header.v['Name'].gsub(/\x00+$/n, '')
end
def flags
# a section header is not required
return nil if !_section_header
_section_header.v['Characteristics']
end
def vma
# a section header is not required
return nil if !_section_header
_section_header.v['VirtualAddress']
end
def raw_size
# a section header is not required
return nil if !_section_header
_section_header.v['SizeOfRawData']
end
def _check_offset(offset, len = 1)
if offset < 0 || offset+len > size
raise BoundsError, "Offset #{offset} outside of section", caller
end
end
def read(offset, len)
_check_offset(offset, len)
return _isource.read(offset, len)
end
def read_rva(rva, len)
return read(rva_to_offset(rva), len)
end
def read_asciiz(offset)
_check_offset(offset)
return _isource.read_asciiz(offset)
end
def read_asciiz_rva(rva)
return read_asciiz(rva_to_offset(rva))
end
def index(*args)
_isource.index(*args)
end
def offset_to_rva(offset)
if !contains_offset?(offset)
raise BoundsError, "Offset #{offset} outside of section", caller
end
return offset + base_rva
end
def file_offset_to_rva(foffset)
return offset_to_rva(foffset - file_offset)
end
def rva_to_offset(rva)
offset = rva - base_rva
if !contains_offset?(offset)
raise BoundsError, "RVA #{rva} outside of section", caller
end
return offset
end
def rva_to_file_offset(rva)
return rva_to_offset(rva) + file_offset
end
def contains_offset?(offset)
offset >= 0 && offset < size
end
def contains_file_offset?(foffset)
contains_offset?(foffset - file_offset)
end
def contains_rva?(rva)
contains_offset?(rva - base_rva)
end
end
end end

View File

@ -1,11 +0,0 @@
# -*- coding: binary -*-
module Rex
module PeScan
end
end
require 'rex/pescan/analyze'
require 'rex/pescan/scanner'
require 'rex/pescan/search'

View File

@ -1,366 +0,0 @@
# -*- coding: binary -*-
module Rex
module PeScan
module Analyze
require "rex/text/table"
class Fingerprint
attr_accessor :pe
def initialize(pe)
self.pe = pe
end
def config(param)
@sigs = {}
name = nil
regx = ''
epon = 0
sidx = 0
fd = File.open(param['database'], 'rb')
fd.each_line do |line|
case line
when /^\s*#/
next
when /\[\s*(.*)\s*\]/
if (name)
@sigs[ name ] = [regx, epon]
end
name = $1 + " [#{ sidx+=1 }]"
epon = 0
next
when /signature\s*=\s*(.*)/
pat = $1.strip
regx = ''
pat.split(/\s+/).each do |c|
next if c.length != 2
regx << (c.index('?') ? '.' : "\\x#{c}")
end
when /ep_only\s*=\s*(.*)/
epon = ($1 =~ /^T/i) ? 1 : 0
end
end
if (name and ! @sigs[name])
@sigs[ name ] = [regx, epon]
end
fd.close
end
def scan(param)
config(param)
epa = pe.hdr.opt.AddressOfEntryPoint
buf = pe.read_rva(epa, 256) || ""
@sigs.each_pair do |name, data|
begin
if (buf.match(Regexp.new('^' + data[0], nil, 'n')))
$stdout.puts param['file'] + ": " + name
end
rescue RegexpError
$stderr.puts "Invalid signature: #{name} #{data[0]}"
end
end
end
end
class Information
attr_accessor :pe
def initialize(pe)
self.pe = pe
end
def add_fields(tbl, obj, fields)
fields.each do |name|
begin
tbl << [name, "0x%.8x" % obj.send(name)]
rescue ::NoMethodError => e
$stderr.puts "Invalid field #{name}"
end
end
end
def scan(param)
$stdout.puts "\n\n"
tbl = table("Image Headers", ['Name', 'Value'])
add_fields(tbl, pe.hdr.file, %W{
Characteristics
SizeOfOptionalHeader
PointerToSymbolTable
TimeDateStamp
NumberOfSections
Machine
})
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
tbl = table("Optional Image Headers", ['Name', 'Value'])
add_fields(tbl, pe.hdr.opt, %W{
ImageBase
Magic
MajorLinkerVersion
MinorLinkerVersion
SizeOfCode
SizeOfInitializeData
SizeOfUninitializeData
AddressOfEntryPoint
BaseOfCode
BaseOfData
SectionAlignment
FileAlignment
MajorOperatingSystemVersion
MinorOperatingSystemVersion
MajorImageVersion
MinorImageVersion
MajorSubsystemVersion
MinorSubsystemVersion
Win32VersionValue
SizeOfImage
SizeOfHeaders
CheckSum
Subsystem
DllCharacteristics
SizeOfStackReserve
SizeOfStackCommit
SizeOfHeapReserve
SizeOfHeapCommit
LoaderFlags
NumberOfRvaAndSizes
})
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
# Get DllCharacteristics (in Integer)
dllcharacteristics = pe.hdr.opt.struct[23].value
if (dllcharacteristics > 0)
tbl = table("DllCharacteristics", ['Flag', 'Value'])
# http://msdn.microsoft.com/en-us/library/ms680339(v=vs.85).aspx
traits = {
:ASLR => 'False', #IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE
:Integrity => 'False', #IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY
:NX => 'False', #IMAGE_DLLCHARACTERISTICS_NX_COMPAT
:Isolation => 'False', #IMAGE_DLLCHARACTERISTICS_NO_ISOLATION
:SEH => 'False', #IMAGE_DLLCHARACTERISTICS_NO_SEH
:Bind => 'False', #IMAGE_DLLCHARACTERISTICS_NO_BIND
:WDM => 'False', #IMAGE_DLLCHARACTERISTICS_WDM_DRIVER
:Terminal => 'False' #IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE
}
# Convert integer to an bit array
c_bits = ("%32d" %dllcharacteristics.to_s(2)).split('').map { |e| e.to_i }.reverse
# Check characteristics
traits[:ASLR] = 'True' if c_bits[6] == 1 #0x0040
traits[:Integrity] = 'True' if c_bits[7] == 1 #0x0080
traits[:NX] = 'True' if c_bits[8] == 1 #0x0100
traits[:Isolation] = 'True' if c_bits[9] == 1 #0x0200
traits[:SEH] = 'True' if c_bits[10] == 1 #0x0400
traits[:Bind] = 'True' if c_bits[11] == 1 #0x0800
traits[:WDM] = 'True' if c_bits[13] == 1 #2000
traits[:Terminal] = 'True' if c_bits[15] == 1 #0x8000
# Putting results to table
traits.each do |trait_name, trait_value|
tbl << [trait_name, trait_value]
end
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
if (pe.exports)
tbl = table("Exported Functions", ['Ordinal', 'Name', 'Address'])
pe.exports.entries.each do |ent|
tbl << [ent.ordinal, ent.name, "0x%.8x" % pe.rva_to_vma(ent.rva)]
end
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
# Rex::PeParsey::Pe doesn't seem to give us any offset information for each function,
# which makes it difficult to calculate the actual addresses for them. So instead we
# are using Metasm::COFF::ImportDirectory to do this task. The ability to see
# addresses is mainly for ROP.
if (pe.imports)
tbl = table("Imported Functions", ['Library', 'Address', 'Ordinal', 'Name'])
exefmt = Metasm::AutoExe.orshellcode{ Metasm.const_get('x86_64').new }
exe = exefmt.decode_file(pe._isource.file.path)
ibase = pe.image_base
exe_imports = exe.imports
exe_imports.each do |lib|
lib_name = lib.libname
ini_offset = lib.iat_p
func_table = lib.imports
offset = 0
func_table.each do |func|
func_addr = "0x%08x" %(ibase + ini_offset + offset)
tbl << [lib_name, func_addr, func.hint, func.name]
offset += 4
end
end
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
if(pe.config)
tbl = table("Configuration Header", ['Name', 'Value'])
add_fields(tbl, pe.config, %W{
Size
TimeDateStamp
MajorVersion
MinorVersion
GlobalFlagsClear
GlobalFlagsSet
CriticalSectionDefaultTimeout
DeCommitFreeBlockThreshold
DeCommitTotalFreeThreshold
LockPrefixTable
MaximumAllocationSize
VirtualMemoryThreshold
ProcessAffinityMask
ProcessHeapFlags
CSDVersion
Reserved1
EditList
SecurityCookie
SEHandlerTable
SEHandlerCount
})
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
if(pe.resources)
tbl = table("Resources", ['ID', 'Language', 'Code Page', 'Size', 'Name'])
pe.resources.keys.sort.each do |rkey|
res = pe.resources[rkey]
tbl << [rkey, res.lang, res.code, res.size, res.file]
end
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
tbl = table("Section Header", ["Name", "VirtualAddress", "SizeOfRawData", "Characteristics"])
pe.sections.each do |sec|
tbl << [ sec.name, *[sec.vma, sec.raw_size, sec.flags].map{|x| "0x%.8x" % x} ]
end
$stdout.puts tbl.to_s
$stdout.puts "\n\n"
end
def table(name, cols)
Rex::Text::Table.new(
'Header' => name,
'Columns' => cols
)
end
end
class Ripper
require "fileutils"
attr_accessor :pe
def initialize(pe)
self.pe = pe
end
def scan(param)
dest = param['dir']
if (param['file'])
dest = File.join(dest, File.basename(param['file']))
end
::FileUtils.mkdir_p(dest)
pe.resources.keys.sort.each do |rkey|
res = pe.resources[rkey]
path = File.join(dest, rkey.split('/')[1] + '_' + res.file)
fd = File.new(path, 'wb')
fd.write(res.data)
fd.close
end
end
end
class ContextMapDumper
attr_accessor :pe
def initialize(pe)
self.pe = pe
end
def scan(param)
dest = param['dir']
path = ''
::FileUtils.mkdir_p(dest)
if(not (param['dir'] and param['file']))
$stderr.puts "No directory or file specified"
return
end
if (param['file'])
path = File.join(dest, File.basename(param['file']) + ".map")
end
fd = File.new(path, "wb")
pe.all_sections.each do |section|
# Skip over known bad sections
next if section.name == ".data"
next if section.name == ".reloc"
offset = 0
while offset < section.size
byte = section.read(offset, 1)[0]
if byte != 0
chunkbase = pe.rva_to_vma(section.base_rva) + offset
data = ''
while byte != 0
data << byte
offset += 1
byte = 0
byte = section.read(offset, 1)[0] if offset < section.size
end
buff = nil
buff = [ 0x01, chunkbase, data.length, data].pack("CNNA*") if data.length > 0
fd.write(buff) if buff
end
offset += 1
end
end
fd.close
end
end
# EOC
end
end
end

View File

@ -1,230 +0,0 @@
# -*- coding: binary -*-
require 'metasm'
module Rex
module PeScan
module Scanner
class Generic
attr_accessor :pe, :regex
def initialize(pe)
self.pe = pe
end
def config(param)
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
pe.all_sections.each do |section|
hits = scan_section(section, param)
hits.each do |hit|
vma = pe.rva_to_vma(hit[0])
next if (param['filteraddr'] and [vma].pack("V").reverse !~ /#{param['filteraddr']}/)
msg = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1]
$stdout.puts pe.ptr_s(vma) + " " + msg
if(param['disasm'])
#puts [msg].pack('H*').inspect
insns = []
msg.gsub!("; ", "\n")
if msg.include?("retn")
msg.gsub!("retn", "ret")
end
#puts msg
begin
d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, msg).disassemble
rescue Metasm::ParseError
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [msg].pack('H*'))
end
addr = 0
while ((di = d2.disassemble_instruction(addr)))
insns << di.instruction
disasm = "0x%08x\t" % (vma + addr)
disasm << di.instruction.to_s
$stdout.puts disasm
addr = di.next_addr
end
# ::Rex::Assembly::Nasm.disassemble([msg].pack("H*")).split("\n").each do |line|
# $stdout.puts "\tnasm: #{line.strip}"
#end
end
end
end
end
def scan_section(section, param={})
[]
end
end
class JmpRegScanner < Generic
def config(param)
regnums = param['args']
# build a list of the call bytes
calls = _build_byte_list(0xd0, regnums - [4]) # note call esp's don't work..
jmps = _build_byte_list(0xe0, regnums)
pushs1 = _build_byte_list(0x50, regnums)
pushs2 = _build_byte_list(0xf0, regnums)
regexstr = '('
if !calls.empty?
regexstr += "\xff[#{calls}]|"
end
regexstr += "\xff[#{jmps}]|([#{pushs1}]|\xff[#{pushs2}])(\xc3|\xc2..))"
self.regex = Regexp.new(regexstr, nil, 'n')
end
# build a list for regex of the possible bytes, based on a base
# byte and a list of register numbers..
def _build_byte_list(base, regnums)
regnums.collect { |regnum| Regexp.escape((base | regnum).chr) }.join('')
end
def _ret_size(section, index)
d = section.read(index, 1)
case d
when "\xc3"
return 1
when "\xc2"
return 3
end
raise RuntimeError, "invalid return opcode"
end
def _parse_ret(data)
if data.length == 1
return "ret"
else
return "retn 0x%04x" % data[1, 2].unpack('v')[0]
end
end
def scan_section(section, param={})
index = 0
hits = [ ]
while (index = section.index(regex, index)) != nil
rva = section.offset_to_rva(index)
message = ''
parse_ret = false
byte1 = section.read(index, 1).unpack("C*")[0]
if byte1 == 0xff
byte2 = section.read(index+1, 1).unpack("C*")[0]
regname = Rex::Arch::X86.reg_name32(byte2 & 0x7)
case byte2 & 0xf8
when 0xd0
message = "call #{regname}"
index += 2
when 0xe0
message = "jmp #{regname}"
index += 2
when 0xf0
retsize = _ret_size(section, index+2)
message = "push #{regname}; " + _parse_ret(section.read(index+2, retsize))
index += 2 + retsize
else
raise "wtf"
end
else
regname = Rex::Arch::X86.reg_name32(byte1 & 0x7)
retsize = _ret_size(section, index+1)
message = "push #{regname}; " + _parse_ret(section.read(index+1, retsize))
index += 1 + retsize
end
hits << [ rva, message ]
end
return hits
end
end
class PopPopRetScanner < JmpRegScanner
def config(param)
pops = _build_byte_list(0x58, (0 .. 7).to_a - [4]) # we don't want pop esp's...
self.regex = Regexp.new("[#{pops}][#{pops}](\xc3|\xc2..)", nil, 'n')
end
def scan_section(section, param={})
index = 0
hits = [ ]
while index < section.size && (index = section.index(regex, index)) != nil
rva = section.offset_to_rva(index)
message = ''
pops = section.read(index, 2)
reg1 = Rex::Arch::X86.reg_name32(pops[0,1].unpack("C*")[0] & 0x7)
reg2 = Rex::Arch::X86.reg_name32(pops[1,1].unpack("C*")[0] & 0x7)
message = "pop #{reg1}; pop #{reg2}; "
retsize = _ret_size(section, index+2)
message += _parse_ret(section.read(index+2, retsize))
index += 2 + retsize
hits << [ rva, message ]
end
return hits
end
end
class RegexScanner < Generic
def config(param)
self.regex = Regexp.new(param['args'], nil, 'n')
end
def scan_section(section, param={})
index = 0
hits = [ ]
while index < section.size && (index = section.index(regex, index)) != nil
idx = index
buf = ''
mat = nil
while (! (mat = buf.match(regex)))
buf << section.read(idx, 1)
idx += 1
end
rva = section.offset_to_rva(index)
hits << [ rva, buf.unpack("H*") ]
index += buf.length
end
return hits
end
end
end
end
end

View File

@ -1,68 +0,0 @@
# -*- coding: binary -*-
module Rex
module PeScan
module Search
require "rex/assembly/nasm"
class DumpRVA
attr_accessor :pe
def initialize(pe)
self.pe = pe
end
def config(param)
@address = pe.vma_to_rva(param['args'])
end
def scan(param)
config(param)
$stdout.puts "[#{param['file']}]"
# Adjust based on -A and -B flags
pre = param['before'] || 0
suf = param['after'] || 16
@address -= pre
@address = 0 if (@address < 0 || ! @address)
begin
buf = pe.read_rva(@address, suf)
rescue ::Rex::PeParsey::PeParseyError
return
end
$stdout.puts pe.ptr_s(pe.rva_to_vma(@address)) + " " + buf.unpack("H*")[0]
if(param['disasm'])
insns = []
buf.gsub!("; ", "\n")
if buf.include?("retn")
buf.gsub!("retn", "ret")
end
d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, buf)
addr = 0
while ((di = d2.disassemble_instruction(addr)))
insns << di.instruction
disasm = "0x%08x\t" % (pe.rva_to_vma(@address) + addr)
disasm << di.instruction.to_s
$stdout.puts disasm
addr = di.next_addr
end
end
end
end
class DumpOffset < DumpRVA
def config(param)
begin
@address = pe.file_offset_to_rva(param['args'])
rescue Rex::PeParsey::BoundsError
end
end
end
end
end
end

View File

@ -132,6 +132,8 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency 'rex-arch'
# Library for working with OLE.
spec.add_runtime_dependency 'rex-ole'
# Library for parsing and manipulating executable binaries
spec.add_runtime_dependency 'rex-bin_tools'
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
# NoMethodError undefined method `dlopen' for Fiddle:Module

View File

@ -1,60 +0,0 @@
require 'rex/file'
RSpec.describe Rex::FileUtils do
context "Class methods" do
context ".normalize_win_path" do
it "should convert an absolute path as an array into Windows format" do
expect(described_class.normalize_win_path('C:\\', 'hello', 'world')).to eq("C:\\hello\\world")
end
it "should convert an absolute path as a string into Windows format" do
expect(described_class.normalize_win_path('C:\\hello\\world')).to eq("C:\\hello\\world")
end
it "should convert a relative path" do
expect(described_class.normalize_win_path('/', 'test', 'me')).to eq("\\test\\me")
expect(described_class.normalize_win_path('\\temp')).to eq("\\temp")
expect(described_class.normalize_win_path('temp')).to eq("temp")
end
it "should keep the trailing slash if exists" do
expect(described_class.normalize_win_path('/', 'test', 'me\\')).to eq("\\test\\me\\")
expect(described_class.normalize_win_path('\\temp\\')).to eq("\\temp\\")
end
it "should convert a path without reserved characters" do
expect(described_class.normalize_win_path('C:\\', 'Windows:')).to eq("C:\\Windows")
expect(described_class.normalize_win_path('C:\\Windows???\\test')).to eq("C:\\Windows\\test")
end
it "should convert a path without double slashes" do
expect(described_class.normalize_win_path('C:\\\\\\', 'Windows')).to eq("C:\\Windows")
expect(described_class.normalize_win_path('C:\\\\\\Hello World\\\\whatever.txt')).to eq("C:\\Hello World\\whatever.txt")
expect(described_class.normalize_win_path('C:\\\\')).to eq("C:\\")
expect(described_class.normalize_win_path('\\test\\\\test\\\\')).to eq("\\test\\test\\")
end
end
context ".normalize_unix_path" do
it "should convert an absolute path as an array into Unix format" do
expect(described_class.normalize_unix_path('/etc', '/passwd')).to eq("/etc/passwd")
end
it "should convert an absolute path as a string into Unix format" do
expect(described_class.normalize_unix_path('/etc/passwd')).to eq('/etc/passwd')
end
it "should still give me a trailing slash if I have it" do
expect(described_class.normalize_unix_path('/etc/folder/')).to eq("/etc/folder/")
end
it "should convert a path without double slashes" do
expect(described_class.normalize_unix_path('//etc////passwd')).to eq("/etc/passwd")
expect(described_class.normalize_unix_path('/etc////', 'passwd')).to eq('/etc/passwd')
end
end
end
end