Permalink
Browse files

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

  • Loading branch information...
1 parent 1c03c56 commit 3f46e83210bc50e47b006fc0de9196d77682da4d @struct committed Mar 12, 2011
Showing with 340 additions and 49 deletions.
  1. +22 −2 README.markdown
  2. +1 −1 common/constants.rb
  3. +5 −0 common/helpers.rb
  4. +49 −3 common/parse_config_file.rb
  5. +187 −0 crash.rb
  6. +19 −1 handlers.rb
  7. +57 −42 nerve.rb
View
@@ -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,4 +2,4 @@
LINUX_OS = /linux/i
OSX_OS = /darwin/i
-NERVE_VERSION = 1.7
+NERVE_VERSION = 1.8
View
@@ -0,0 +1,5 @@
+class Nerve
+ ## get breakpoint name for address
+
+ ## get breakpoint address for name
+end
@@ -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
@@ -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
Oops, something went wrong.

0 comments on commit 3f46e83

Please sign in to comment.