Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

lots of fixes, bump to 1.8, add crash.rb

  • Loading branch information...
commit 3f46e83210bc50e47b006fc0de9196d77682da4d 1 parent 1c03c56
@struct authored
View
24 README.markdown
@@ -51,13 +51,13 @@
Nerve is a simple tool, but we plan to grow it with optional add ons:
- - A waiting mode that runs and polls for new processes matching a target process description
- Lots of helper scripts for breakpoints such as heap inspection, in memory fuzzing, SSL reads etc...
- Helper methods and better named instance variables for making breakpoint scripts easier to write
- Better output such as graphviz, statistics, function arguments etc...
- An HTML5 canvas output mode
- A basic RubyWX GUI
- Redis database support
+ - Cleaner 1.8 and 1.9 support for launching processes
- Nerve is also helping us find the areas of Ragweed that need the most improvement
## Requirements
@@ -98,7 +98,7 @@
## Configuration File Example
Keywords in configuration files:
- (order does not matter)
+ (the order does not matter but each line represents a unique breakpoint)
bp - An address (or a symbolic name for Win32) where the debugger should set a breakpoint
name - A name describing the breakpoint, typically a symbol or function name
@@ -120,6 +120,25 @@
OS X Configuration Example:
bp=0x12345678, name=function_name, bpc=6
+## Process Launching Configurations
+
+ You can instruct Nerve to launch a target process with arguments and environment
+ variables of your choosing. Nerve takes the -x flag along with a filename containing
+ your configuration. Be aware that Nerve currently uses system() to launch the process
+ which means stdout will be written to by the new process. Supporting Process.spawn()
+ is easy but its also not Ruby 1.9 compatible. This is on my list to rework!
+
+ Process launching configuration keywords
+
+ target - The location of the application you want to run
+ args - A string of arguments to pass to the application
+ env - A string of environment variables for the application
+
+ target: /usr/bin/gcalctool
+ args: -s 1+1
+ env: BLAH=test
+ env: MYLIBPATH=/usr/lib
+
## Breakpoint Scripts
Nerve supports breakpoint scripts that run when a breakpoint you have specified is executed. These
@@ -171,6 +190,7 @@
on_invalid_disposition
on_invalid_handle
on_load_dll
+ on_iot_trap
on_output_debug_string
on_priv_instruction
on_rip
View
2  common/constants.rb
@@ -2,4 +2,4 @@
LINUX_OS = /linux/i
OSX_OS = /darwin/i
-NERVE_VERSION = 1.7
+NERVE_VERSION = 1.8
View
5 common/helpers.rb
@@ -0,0 +1,5 @@
+class Nerve
+ ## get breakpoint name for address
+
+ ## get breakpoint address for name
+end
View
52 common/parse_config_file.rb
@@ -1,22 +1,68 @@
class Nerve
- ## This could use some work
+ def parse_exec_proc(file)
+
+ return if file.nil?
+
+ fd = File.open(file)
+ proc_control = %w[ target args env ]
+
+ lines = fd.readlines
+ lines.map { |x| x.chomp }
+
+ exec_proc.args = Array.new
+ exec_proc.env = Hash.new
+
+ lines.each do |tl|
+ if tl[0].chr == ';' or tl.nil? then next end
+
+ k,v,l = tl.split(':')
+
+ if k.match(/target/)
+ ## Dirty little hack if a : is used
+ ## in the target path (C:\Windows...)
+ if !l.nil?
+ v = "#{v}:#{l}"
+ end
+ v.gsub!(/[\n]+/, "")
+ v.gsub!(/[\s]+/, "")
+ exec_proc.target = v
+ end
+
+ if k.match(/args/)
+ v.gsub!(/[\n]+/, "")
+ exec_proc.args = v
+ end
+
+ if k.match(/env/)
+ v.gsub!(/[\n]+/, "")
+ k,v = v.split(/=/)
+ k.gsub!(/[\s]+/, "")
+ exec_proc.env.store(k,v)
+ end
+ end
+ end
+
def parse_config_file(file)
+
+ return if file.nil?
+
fd = File.open(file)
## All the handlers a user can script
+ ## There is no specific order to this
hdlrs = %w[ on_access_violation on_alignment on_attach on_bounds on_breakpoint on_continue
on_create_process on_create_thread on_detach on_divide_by_zero on_exit on_exit_process
on_exit_thread on_fork_child on_illegalinst on_int_overflow on_invalid_disposition
on_invalid_handle on_load_dll on_output_debug_string on_priv_instruction on_rip on_segv
on_signal on_sigstop on_sigchild on_sigterm on_sigtrap on_single_step on_stack_overflow
- on_stop on_unload_dll ]
+ on_stop on_unload_dll on_iot_trap on_guard_page ]
lines = fd.readlines
lines.map { |x| x.chomp }
lines.each do |tl|
- if tl.match(';') or tl.nil? then next end
+ if tl[0].chr == ';' or tl.nil? then next end
hdlrs.each do |l|
if tl.match(/#{l}/)
View
187 crash.rb
@@ -0,0 +1,187 @@
+## Crash is a partial MSEC (!exploitable) WinDbg extension
+## ported to use the ragweed debugging library. It has been
+## test with the Nerve debugger,
+##
+## Usage:
+##
+## Catch a bad debug event like segfault or illegal instruction
+## then pass your ragweed instance to this class:
+##
+## Crash.new(@ragweed).exploitable?
+##
+## Thats it! The class will use your ragweed instance to
+## determine the state of the process. This is done examining
+## the last signal or debug event the process received and
+## the register states.
+
+## THIS CODE IS EXPERIMENTAL AND UNFINISHED :)
+
+require 'rubygems'
+require 'ragweed'
+
+class Crash
+ EXPLOITABLE = 1
+ POSSIBLY_EXPLOITABLE = 2
+ NOT_EXPLOITABLE = 3
+ UNKNOWN = 4
+
+ attr_accessor :state, :status, :ragweed
+
+ def initialize(rw)
+ @ragweed = rw
+ status = UNKNOWN
+
+ case
+ when RUBY_PLATFORM =~ WINDOWS_OS
+ crash_win32
+ when RUBY_PLATFORM =~ LINUX_OS
+ crash_linux
+ when RUBY_PLATFORM =~ OSX_OS
+ crash_osx
+ end
+ end
+
+ ## Crash.exploitable?
+ ## Who needs !exploitable when you've got exploitable?
+ def exploitable?
+ return true if status == EXPLOITABLE or status == POSSIBLY_EXPLOITABLE
+ return false
+ end
+
+ def crash_win32
+ event = @ragweed.event
+ context = @ragweed.context(event)
+
+ ## !! unused !!
+ @state = OpenStruct.new
+ @state.crash_address
+ @state.raw_instruction
+ @state.stack_trace
+
+ status = reg_check(context.eip)
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ILLEGAL_INSTRUCTION
+ puts "Illegal instruction indicates attacker controlled code flow - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::PRIV_INSTRUCTION
+ puts "Privileged instruction indicates attacker controlled code flow - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ON_GUARD_PAGE
+ puts "Guard page violation - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::BUFFER_OVERRUN
+ puts "/GS stack cookie has been corrupted - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::HEAP_CORRUPTION
+ puts "Heap corruption has been detected - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_DEP and
+ event.exception_address > 0x1000
+ puts "DEP Access Violation not near NULL - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_DEP and
+ event.exception_address < 0x1000
+ puts "DEP Access Violation near NULL - NOT EXPLOITABLE"
+ status = NOT_EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_WRITE and
+ event.exception_address > 0x1000
+ puts "Write Access Violation not near NULL - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_WRITE and
+ event.exception_address < 0x1000
+ puts "Write Access Violation near NULL - NOT EXPLOITABLE"
+ status = NOT_EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_READ and
+ event.exception_address > 0x1000
+ puts "Read Access Violation not near NULL - EXPLOITABLE"
+ status = EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and
+ event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_READ and
+ event.exception_address < 0x1000
+ puts "Read Access Violation near NULL - NOT EXPLOITABLE"
+ status = NOT_EXPLOITABLE
+ end
+
+ if event.exception_code == Ragweed::Wrap32::ExceptionCodes::DIVIDE_BY_ZERO
+ puts "Divide by zero - NOT EXPLOITABLE"
+ status = NOT_EXPLOITABLE
+ end
+ end
+
+ def crash_linux
+ r = @ragweed.get_registers
+ status = reg_check(r.eip)
+ status = reg_check(r.ebp)
+
+ case ragweed.signal
+ when Ragweed::Wraptux::Signal::SIGILL
+ puts "Illegal instruction indicates attacker controlled code flow - EXPLOITABLE"
+ status = EXPLOITABLE
+ when Ragweed::Wraptux::Signal::SIGIOT
+ puts "IOT Trap may indicate an exploitable crash (stack cookie?) - POSSIBLY EXPLOITABLE"
+ status = POSSIBLY_EXPLOITABLE
+ when Ragweed::Wraptux::Signal::SIGSEGV
+ puts "A segmentation fault may be exploitable, needs further analysis - POSSIBLY EXPLOITABLE"
+ status = POSSIBLY_EXPLOITABLE
+ end
+ end
+
+ def crash_osx
+ ## TODO
+ end
+
+ def get_stack_trace
+ ## Not implemented yet
+ end
+
+ ## Really only works in Linux right now
+ def reg_check(reg)
+ ## TODO: Uhh these are not yet implemented in ragweed
+ ## Win32 - TEB Parsing
+ stack_range = @ragweed.get_stack_range
+ heap_range = @ragweed.get_heap_range
+
+ case reg
+ when stack_range.first..stack_range.last
+ puts "Executing instructions from the stack - EXPLOITABLE"
+ return EXPLOITABLE
+
+ when 0x41414141
+ puts "Register is controllable AAAA... - EXPLOITABLE"
+ return EXPLOITABLE
+
+ when heap_range.first..heap_range.last
+ puts "Executing instructions from the heap - EXPLOITABLE"
+ return EXPLOITABLE
+
+ when 0x0..0x1000
+ puts "NULL Pointer dereference - NOT EXPLOITABLE (unless you control the offset from NULL)"
+ return NOT_EXPLOITABLE
+ end
+ end
+end
View
20 handlers.rb
@@ -4,6 +4,7 @@
## is also implemented by Ragweed.
require 'common/constants'
+#require 'crash'
case
when RUBY_PLATFORM =~ WINDOWS_OS
@@ -49,11 +50,17 @@ def dump_stats(ev=nil)
end
def on_access_violation(ev)
+ #puts "Exploitable? #{Crash.new(self).exploitable?}"
exec_eh_script("on_access_violation", ev)
dump_stats(ev)
log.str "Access violation!"
end
+ def on_attach
+ exec_eh_script("on_attach")
+ super
+ end
+
def on_exit_process(ev)
exec_eh_script("on_exit_process", ev)
dump_stats(ev)
@@ -133,10 +140,12 @@ def on_priv_instruction(ev)
end
def on_heap_corruption(ev)
+ #puts "Exploitable? #{Crash.new(self).exploitable?}"
exec_eh_script("on_heap_corruption", ev)
end
def on_buffer_overrun(ev)
+ #puts "Exploitable? #{Crash.new(self).exploitable?}"
exec_eh_script("on_buffer_overrun", ev)
end
@@ -207,8 +216,9 @@ def on_segv
log.str "Segmentation Fault!"
exec_eh_script("on_segv")
## This need to be implemented in debuggerosx
- ##self.print_registers
+ self.print_registers
dump_stats
+ #Crash.new(self).exploitable?
exit
end
@@ -229,6 +239,14 @@ def on_illegal_instruction
dump_stats
end
+ def on_iot_trap
+ log.str "IOT Trap!"
+ exec_eh_script("on_iot_trap")
+ dump_stats
+ self.print_registers
+ #Crash.new(self).exploitable?
+ end
+
def on_attach
exec_eh_script("on_attach")
super
View
99 nerve.rb
@@ -1,8 +1,5 @@
#!/usr/bin/env ruby
-## Nerve is a scriptable debugger built on Ragweed
-## Please refer to the README file for more information
-
$: << File.dirname('..')
require 'rubygems'
@@ -17,36 +14,43 @@
require 'common/helpers'
class Nerve
- attr_accessor :opts, :ragweed, :pid, :threads, :breakpoints, :so, :log, :event_handlers
+ attr_accessor :opts, :ragweed, :pid, :threads, :breakpoints, :so, :log, :event_handlers, :exec_proc
def initialize(opts)
@opts = opts
@pid = opts[:pid]
@breakpoints = Array.new
+ @exec_proc = OpenStruct.new
@event_handlers = Hash.new
@threads = Array.new
@out = opts[:out]
@log = NerveLog.new(@out)
+ parse_exec_proc(opts[:ep_file]) if !opts[:ep_file].nil?
+
+ launch_process if !exec_proc.target.nil?
+
case
when RUBY_PLATFORM =~ WINDOWS_OS
parse_config_file(opts[:bp_file])
- if @pid.kind_of?(String) and @pid.to_i == 0
- @ragweed = NerveWin32.find_by_regex(/#{@pid}/)
+ if pid.kind_of?(String) and pid.to_i == 0
+ while @ragweed.nil?
+ @ragweed = NerveWin32.find_by_regex(/#{pid}/)
+ end
else
- @ragweed = NerveWin32.new(@pid.to_i)
+ @ragweed = NerveWin32.new(pid.to_i)
end
if @ragweed.nil?
- puts "Failed to find process: #{@pid}"
+ puts "Failed to find process: #{pid}"
exit
end
@ragweed.log_init(log)
- ## FIX: debugger32 threads returns an OStruct
+ ## TODO: debugger32 threads returns an OStruct
## and pid is not always a Numeric value
@threads = @ragweed.process.threads(true)
@@ -57,18 +61,17 @@ def initialize(opts)
end
when RUBY_PLATFORM =~ LINUX_OS
-
- if @pid.kind_of?(String) and @pid.to_i == 0
- @pid = NerveLinux.find_by_regex(/#{@pid}/).to_i
+ if pid.kind_of?(String) and pid.to_i == 0
+ @pid = NerveLinux.find_by_regex(/#{pid}/).to_i
else
- @pid = @pid.to_i
+ @pid = pid.to_i
end
- @so = NerveLinux.shared_libraries(@pid)
+ @so = NerveLinux.shared_libraries(pid)
parse_config_file(opts[:bp_file])
- @threads = NerveLinux.threads(@pid)
+ @threads = NerveLinux.threads(pid)
self.which_threads
lo = {}
@@ -77,10 +80,10 @@ def initialize(opts)
lo[:fork] = true
end
- @ragweed = NerveLinux.new(@pid, lo)
+ @ragweed = NerveLinux.new(pid, lo)
if @ragweed.nil?
- puts "Failed to find process: #{@pid}"
+ puts "Failed to find process: #{pid}"
exit
end
@@ -90,16 +93,16 @@ def initialize(opts)
parse_config_file(opts[:bp_file])
- if @pid.kind_of?(String) and @pid.to_i.nil?
- @pid = NerveOSX.find_by_regex(/#{@pid}/)
+ if pid.kind_of?(String) and pid.to_i.nil?
+ @pid = NerveOSX.find_by_regex(/#{pid}/)
else
- @pid = @pid.to_i
+ @pid = pid.to_i
end
- @ragweed = NerveOSX.new(@pid)
+ @ragweed = NerveOSX.new(pid)
if @ragweed.nil?
- puts "Failed to find process: #{@pid}"
+ puts "Failed to find process: #{pid}"
exit
end
@@ -139,9 +142,7 @@ def initialize(opts)
exit
end
- catch(:throw) do
- @ragweed.loop
- end
+ catch(:throw) { @ragweed.loop }
## This is commented out because the stats should
## have been dumped already if we reached this
@@ -163,33 +164,25 @@ def set_breakpoints
when RUBY_PLATFORM =~ WINDOWS_OS
if opts[:hook] == true
@ragweed.hook(bp.addr, bp.nargs) do |evt, ctx, dir, args|
+ eval(bp.code) if !bp.code.nil?
- if !bp.code.nil?
- eval(bp.code)
- end
-
- if dir.to_s =~ /enter/
- bp.hits += 1
- end
+ bp.hits += 1 if dir.to_s =~ /enter/
check_bp_max(bp, ctx)
end
else
@ragweed.breakpoint_set(bp.addr) do |evt, ctx|
- if !bp.code.nil?
- eval(bp.code)
- end
+ eval(bp.code) if !bp.code.nil?
bp.hits += 1
+
check_bp_max(bp, ctx)
end
end
when RUBY_PLATFORM =~ LINUX_OS, RUBY_PLATFORM =~ OSX_OS
@ragweed.breakpoint_set(bp.addr.to_i(16), bp.name, (bpl = proc do
- if !bp.code.nil?
- eval(bp.code)
- end
+ eval(bp.code) if !bp.code.nil?
bp.hits += 1
@@ -209,10 +202,28 @@ def check_bp_max(bp, ctx)
bp.flag = false
end
end
+
+ def launch_process
+ if RUBY_PLATFORM =~ WINDOWS_OS
+ exec_proc.env.each_pair { |k,v| ENV[k] = v }
+ proc_info = Ragweed::Wrap32::ProcessInfo.new
+ startup_info = Ragweed::Wrap32::StartupInfo.new
+ target = FFI::MemoryPointer.from_string("#{exec_proc.target} #{exec_proc.args}")
+ ## TODO: Port -f option to CreateProcess. We can pass along DEBUG_PROCESS
+ r = Ragweed::Wrap32::Win::CreateProcessA(nil, target, nil, nil, false, 0x0, nil, nil, startup_info, proc_info)
+ @pid = proc_info[:pid] if r != 0
+ else
+ @pid = fork do
+ exec_proc.env.each_pair { |k,v| ENV[k] = v }
+ exec("#{exec_proc.target} #{exec_proc.args}")
+ end
+ end
+ end
end
NERVE_OPTS = {
- :pid => 0,
+ :pid => nil,
+ :pe_file => nil,
:bp_file => nil,
:out => STDOUT,
:hook => false,
@@ -220,12 +231,16 @@ def check_bp_max(bp, ctx)
}
opts = OptionParser.new do |opts|
- opts.banner = "\nNerve #{NERVE_VERSION} | Chris Rohlf 2009/2010\n\n"
+ opts.banner = "\nNerve #{NERVE_VERSION} | Chris Rohlf 2009-2011\n\n"
opts.on("-p", "--pid PID/Name", "Attach to this pid OR process name (ex: -p 12345 | -p gcalctool | -p notepad.exe)") do |o|
NERVE_OPTS[:pid] = o
end
+ opts.on("-x", "--exec_proc FILE", "Launch a process according to the configuration found in this file") do |o|
+ NERVE_OPTS[:ep_file] = o
+ end
+
opts.on("-b", "--config_file FILE", "Read all breakpoints and handler event configurations from this file") do |o|
NERVE_OPTS[:bp_file] = o
end
@@ -252,9 +267,9 @@ def check_bp_max(bp, ctx)
opts.parse!(ARGV) rescue (STDERR.puts $!; exit 1)
-if NERVE_OPTS[:pid] == nil || NERVE_OPTS[:bp_file] == nil
+if NERVE_OPTS[:pid] == nil
puts opts.banner
- exit
+# exit
end
## Never is still under heavy development
Please sign in to comment.
Something went wrong with that request. Please try again.