mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-08-28 23:26:18 +02: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:
parent
8c70086170
commit
d2a6c2e9ca
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -1,9 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module ElfParsey
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/elfparsey/elf'
|
@ -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
|
@ -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
|
@ -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
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module ElfScan
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/elfscan/scanner'
|
||||
require 'rex/elfscan/search'
|
@ -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
|
@ -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
|
160
lib/rex/file.rb
160
lib/rex/file.rb
@ -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
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module ImageSource
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/image_source/disk'
|
||||
require 'rex/image_source/memory'
|
@ -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
|
@ -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
|
@ -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
|
@ -1,9 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module MachParsey
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/machparsey/mach'
|
@ -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
|
@ -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
|
@ -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
|
@ -1,9 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module MachScan
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/machscan/scanner'
|
@ -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
|
||||
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module PeParsey
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/peparsey/pe'
|
||||
require 'rex/peparsey/pe_memdump'
|
@ -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
|
@ -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
|
@ -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
@ -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
|
@ -1,11 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
|
||||
module Rex
|
||||
module PeScan
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
require 'rex/pescan/analyze'
|
||||
require 'rex/pescan/scanner'
|
||||
require 'rex/pescan/search'
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user