├── scripts ├── on_attach.rb ├── on_load_dll.rb ├── Win32_SSL_read.rb ├── RtlAllocateHeap.rb ├── Win32_Recv_Fuzz.rb └── glibc_malloc.rb ├── common ├── helpers.rb ├── constants.rb ├── common.rb ├── output.rb ├── extends.rb └── parse_config_file.rb ├── example_configuration_files ├── Win32_SSL_read.txt ├── Win32_Recv.txt ├── win32_launch_notepad.txt ├── launch_process.txt ├── Win32_notepad.txt └── generic_ubuntu_11-04_libc_trace.txt ├── utils ├── README ├── linux-symbol.rb └── elf-blocks.rb ├── crash.rb ├── nerve.rb ├── handlers.rb └── README.markdown /scripts/on_attach.rb: -------------------------------------------------------------------------------- 1 | puts "Attached to process #{@pid}" 2 | -------------------------------------------------------------------------------- /common/helpers.rb: -------------------------------------------------------------------------------- 1 | class Nerve 2 | ## get breakpoint name for address 3 | 4 | ## get breakpoint address for name 5 | end 6 | -------------------------------------------------------------------------------- /common/constants.rb: -------------------------------------------------------------------------------- 1 | WINDOWS_OS = /win(dows|32)|i386-mingw32/i 2 | LINUX_OS = /linux/i 3 | OSX_OS = /darwin/i 4 | 5 | NERVE_VERSION = 1.9 6 | -------------------------------------------------------------------------------- /example_configuration_files/Win32_SSL_read.txt: -------------------------------------------------------------------------------- 1 | ; Please see scripts/Win32_SSL_read.rb 2 | 3 | bp=ssleay32!SSL_read, name=SSL_read, code=scripts/Win32_SSL_read.rb, hook=true 4 | -------------------------------------------------------------------------------- /example_configuration_files/Win32_Recv.txt: -------------------------------------------------------------------------------- 1 | ; Please see scripts/Win32_Recv_Fuzz.rb 2 | 3 | bp=ws2_32!recv, name=recv, code=scripts/Win32_Recv_Fuzz.rb, hook=true 4 | ;bp=wsock32!recv, name=recv, code=scripts/Win32_Recv_Fuzz.rb, hook=true 5 | -------------------------------------------------------------------------------- /utils/README: -------------------------------------------------------------------------------- 1 | These optional utilities you can use to generate Nerve configuration files and 2 | generally make your life easier. But beware they have dependencies such as: 3 | 4 | http://github.com/struct/relf 5 | http://github.com/struct/rupe 6 | -------------------------------------------------------------------------------- /scripts/on_load_dll.rb: -------------------------------------------------------------------------------- 1 | ## This script has access to 'ev' the debug event 2 | ## The get_dll_name is a method ragweed exposes 3 | ## These scripts run in the context of the ragweed 4 | ## object so you can use self.{ragweed_methods} here 5 | puts "Loaded DLL: #{self.get_dll_name(ev)} @ #{ev.base_of_dll.to_s(16)}" 6 | -------------------------------------------------------------------------------- /common/common.rb: -------------------------------------------------------------------------------- 1 | class Nerve 2 | def which_threads 3 | if threads.size == 1 4 | pid = threads[0].to_i 5 | return 6 | end 7 | 8 | threads.each { |h| puts h } 9 | puts "Which thread ID do you want to trace?" 10 | pid = STDIN.gets.chomp.to_i 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /example_configuration_files/win32_launch_notepad.txt: -------------------------------------------------------------------------------- 1 | ; This is how you use Nerve to launch a process for Nerve to debug 2 | ; 'target' keyword is used to specify the debugee 3 | ; 'args' keyword is a string of arguments 4 | ; 'env' keyword is key/value pair of environment vars 5 | 6 | target: C:\\Windows\\System32\\notepad.exe 7 | env: BLAH=test 8 | env: MYLIBPATH=/usr/lib 9 | -------------------------------------------------------------------------------- /example_configuration_files/launch_process.txt: -------------------------------------------------------------------------------- 1 | ; This is how you use Nerve to launch a process for Nerve to debug 2 | ; 'target' keyword is used to specify the debugee 3 | ; 'args' keyword is a string of arguments 4 | ; 'env' keyword is key/value pair of environment vars 5 | 6 | target: /tmp/crashy 7 | args: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA 8 | env: BLAH=test 9 | env: MYLIBPATH=/usr/lib 10 | -------------------------------------------------------------------------------- /common/output.rb: -------------------------------------------------------------------------------- 1 | ## Wrapper methods for output 2 | ## Nothing here yet! 3 | 4 | class NerveLog 5 | def initialize(out) 6 | @out = out 7 | end 8 | 9 | def str(s) 10 | @out.puts s 11 | end 12 | 13 | def hit(addr, function_name) 14 | @out.puts "[ #{addr} #{function_name} ]" 15 | end 16 | 17 | def finalize 18 | @out.puts "...Nerve is done!" 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /scripts/Win32_SSL_read.rb: -------------------------------------------------------------------------------- 1 | ## Please see example_configuration_files/Win32_SSL_read.txt 2 | 3 | if dir.to_s =~ /leave/ 4 | addr = @ragweed.process.read32(ctx.esp+4) 5 | len = ctx.eax 6 | 7 | @log.str "Read #{len.to_s(16)} from #{addr.to_s(16)}; #{@ragweed.process.read32(ctx.esp).to_s(16)}" 8 | 9 | if len != -1 10 | buf = @ragweed.process.read(addr, len) 11 | @log.str "Read #{len} from #{addr} got:" 12 | @log.str buf 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /scripts/RtlAllocateHeap.rb: -------------------------------------------------------------------------------- 1 | ## This script is for tracing calls to RtlAllocateHeap 2 | 3 | begin 4 | if dir.to_s =~ /enter/ 5 | @log.str "RtlAllocateHeap -> Size requested #{@ragweed.process.read32(ctx.esp+12)}" 6 | @log.str "RtlAllocateHeap -> Heap handle is @ #{@ragweed.process.read32(ctx.esp+4).to_s(16)}" 7 | else 8 | @log.str "RtlAllocateHeap <- Heap chunk returned @ #{ctx.eax.to_s(16)}" 9 | end 10 | rescue => 11 | puts "Does your configuration use hook=true?" 12 | #puts e.inspect 13 | #puts e.backtrace 14 | #exit 15 | end 16 | -------------------------------------------------------------------------------- /scripts/Win32_Recv_Fuzz.rb: -------------------------------------------------------------------------------- 1 | ## In memory recv() fuzzing 2 | 3 | def mutate(data, len) 4 | s = rand(len-1) 5 | data[s] = rand(255).to_s.pack('i') 6 | return data 7 | end 8 | 9 | if dir.to_s =~ /enter/ 10 | @recv_buf = @ragweed.process.read32(ctx.esp+8) 11 | @maxlen = @ragweed.process.read32(ctx.esp+12) 12 | else 13 | len = ctx.eax 14 | 15 | if len != 0xffffffff 16 | 17 | log_str "--> Read #{len.to_s(16)} to #{@recv_buf.to_s(16)}" 18 | 19 | data = @ragweed.process.read(@recv_buf, len) 20 | @ragweed.process.write(@recv_buf, mutate(data, len)) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /example_configuration_files/Win32_notepad.txt: -------------------------------------------------------------------------------- 1 | ; Example notepad.exe breakpoint file 2 | ; Tested on Win7 & WinXP SP3 3 | 4 | bp=ntdll!RtlAllocateHeap, name=RtlAllocateHeap, bpc=10, code=scripts/RtlAllocateHeap.rb, hook=true 5 | bp=kernel32!CreateFileA, name=CreateFileA, bpc=1 6 | bp=kernel32!DeviceIoControl, name=DeviceIOControl, bpc=3 7 | bp=kernel32!ReadFile, name=ReadFile 8 | bp=kernel32!WriteFile, name=WriteFile 9 | 10 | ; Load a deferred breakpoint 11 | ; Use the fonts menu to trigger 12 | ; Tested on Win7 only! 13 | ;bp=fms.dll!0x2328, name=FMS 14 | 15 | ; Load a deferred breakpoint 16 | ; Use the Print menu to trigger 17 | ; Tested on WinXP SP3 only! 18 | ;bp=mscms!0x2a10, name=MSCMS 19 | 20 | ; This script will load whenever a new DLL/module is loaded 21 | on_load_dll=scripts/on_load_dll.rb 22 | -------------------------------------------------------------------------------- /example_configuration_files/generic_ubuntu_11-04_libc_trace.txt: -------------------------------------------------------------------------------- 1 | ; This is how you set breakpoints within shared objects 2 | ; You can use 'readelf -s' to get the proper address for 3 | ; a function inside of your shared object. 4 | ; The order of the parameters does not matter 5 | ; This example will only work with 32-bit Ubuntu 11.04 6 | 7 | bp=0x0006fef0 name=malloc, lib=/lib/i386-linux-gnu/libc-2.13.so, bpc=5, code=scripts/glibc_malloc.rb 8 | bp=0x00075520, name=memcpy, lib=/lib/i386-linux-gnu/libc-2.13.so 9 | bp=0x000703b0, name=free, lib=/lib/i386-linux-gnu/libc-2.13.so 10 | bp=0x000bfe50, name=read, lib=/lib/i386-linux-gnu/libc-2.13.so 11 | bp=0x000bfed0, name=write, lib=/lib/i386-linux-gnu/libc-2.13.so 12 | on_attach=scripts/on_attach.rb 13 | 14 | ; gcalctool example 15 | ;bp=0x08063e10, name=mp_add 16 | ;on_breakpoint=scripts/on_breakpoint.rb 17 | -------------------------------------------------------------------------------- /utils/linux-symbol.rb: -------------------------------------------------------------------------------- 1 | ## Simple script that uses relf http://github.com/struct/relf 2 | ## to generate a list of breakpoints 3 | ## that might match a function name 4 | ## 5 | ## ruby linux-symbol.rb /usr/local/my_binary authenticate 6 | 7 | require 'relf' 8 | 9 | if !ARGV[0] or !ARGV[1] 10 | puts "I need a file and a symbol!" 11 | exit 12 | end 13 | 14 | d = RELF.new(ARGV[0]) 15 | m = ARGV[1] 16 | 17 | d.parse_dynsym do |sym| 18 | n = d.get_dyn_symbol_name(sym) 19 | if n.match(/#{m}/i) and d.get_symbol_type(sym).match(/FUNC/i) and sym.st_value.to_i != 0 20 | puts sprintf("bp=0x%08x, name=%s\n", sym.st_value.to_i, n) 21 | end 22 | end 23 | 24 | d.parse_symtab do |sym| 25 | n = d.get_sym_symbol_name(sym) 26 | if n.match(/#{m}/i) and d.get_symbol_type(sym).match(/FUNC/i) and sym.st_value.to_i != 0 27 | puts sprintf("bp=0x%08x, name=%s\n", sym.st_value.to_i, n) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /scripts/glibc_malloc.rb: -------------------------------------------------------------------------------- 1 | ## Get and print the register states 2 | ## Read the size argument to malloc 3 | ## Search the heap buffer for a DWORD 4 | 5 | puts "-----------------" 6 | @ragweed.print_registers 7 | regs = @ragweed.get_registers 8 | 9 | size = Ragweed::Wraptux::ptrace(Ragweed::Wraptux::Ptrace::PEEK_TEXT, @pid, regs.esp+4, 0) 10 | @log.str "malloc(#{size})" 11 | 12 | #locs = @ragweed.search_process(0x41414141) 13 | locs = @ragweed.search_heap(0x41414141) 14 | 15 | if !locs.empty? 16 | puts "0x41414141 found at:" 17 | locs.map do |l| 18 | l.map do |i| 19 | puts " -> #{i.to_s(16)} #{@ragweed.get_mapping_name(i)}" 20 | end 21 | end 22 | end 23 | 24 | stack = @ragweed.get_stack_range 25 | heap = @ragweed.get_heap_range 26 | puts "Stack => 0x#{stack.first.first.to_s(16)} ... 0x#{stack.first.last.to_s(16)}" if !stack.empty? 27 | puts "Heap => 0x#{heap.first.first.to_s(16)} ... 0x#{heap.first.last.to_s(16)}" if !heap.empty? 28 | -------------------------------------------------------------------------------- /common/extends.rb: -------------------------------------------------------------------------------- 1 | ## Borrowed from Eric Monti's Ruby Blackbag 2 | require 'stringio' 3 | 4 | class String 5 | def hexdump(opt={}) 6 | s=self 7 | out = opt[:out] || StringIO.new 8 | len = (opt[:len] and opt[:len] > 0)? opt[:len] + (opt[:len] % 2) : 16 9 | 10 | off = opt[:start_addr] || 0 11 | offlen = opt[:start_len] || 8 12 | 13 | hlen=len/2 14 | 15 | s.scan(/(?:.|\n){1,#{len}}/) do |m| 16 | out.write(off.to_s(16).rjust(offlen, "0") + ' ') 17 | 18 | i=0 19 | m.each_byte do |c| 20 | out.write c.to_s(16).rjust(2,"0") + " " 21 | out.write(' ') if (i+=1) == hlen 22 | end 23 | 24 | out.write(" " * (len-i) ) # pad 25 | out.write(" ") if i < hlen 26 | 27 | out.write(" |#{m.tr("\0-\37\177-\377", '.')}|\n") 28 | off += m.length 29 | end 30 | 31 | out.write(off.to_s(16).rjust(offlen,'0') + "\n") 32 | 33 | if out.class == StringIO 34 | out.string 35 | end 36 | end 37 | end 38 | 39 | -------------------------------------------------------------------------------- /utils/elf-blocks.rb: -------------------------------------------------------------------------------- 1 | # Requires: rbdasm from libdasm (Ruby 1.8.7) and http://github.com/struct/relf 2 | # Output: A Nerve configuration file for all branch/entry points 3 | 4 | require 'dasm' 5 | require 'relf' 6 | require 'rubygems' 7 | require 'set' 8 | 9 | if !ARGV[1] 10 | puts "ruby elf-blocks.rb " 11 | exit 12 | end 13 | 14 | output = ARGV[1] 15 | 16 | text = String.new 17 | d = RELF.new(ARGV[0]) 18 | d.parse_dynsym 19 | d.parse_symtab 20 | d.parse_reloc 21 | 22 | d.shdr.each do |s| 23 | if d.get_shdr_name(s) =~ /.text/ 24 | file = File.open(ARGV[0]).read 25 | text = file[s.sh_offset.to_i, s.sh_size.to_i] 26 | @shdr = s 27 | end 28 | end 29 | 30 | dasm = Dasm.new 31 | branches = [Dasm::Instruction::Type::JMP, 32 | Dasm::Instruction::Type::JMPC, 33 | Dasm::Instruction::Type::CALL] 34 | op_imm = Dasm::Operand::Type::Immediate 35 | 36 | bps = Set.new 37 | 38 | start = false 39 | dasm.disassemble(text) do |instruction, offset| 40 | if branches.include? instruction.type 41 | puts "Branch: #{instruction.to_s}" 42 | pc = offset+@shdr.sh_addr 43 | 44 | if instruction.op1 and instruction.op1.type == op_imm 45 | branchdest = (instruction.op1.immediate + instruction.length + pc).to_i 46 | branchdest2 = (instruction.length + pc).to_i 47 | 48 | bps.add( [branchdest, branchdest-@shdr.sh_addr] ) 49 | bps.add( [branchdest2, branchdest2-@shdr.sh_addr] ) 50 | end 51 | end 52 | end 53 | 54 | x = Hash.new 55 | 56 | for sym in d.dynsym_symbols 57 | x[sym.st_value.to_i] = d.get_dyn_symbol_name(sym) 58 | end 59 | 60 | puts "Writing output file to #{output}" 61 | 62 | file = File.open(output, "w+") 63 | 64 | bps.each do |b| 65 | out = "bp=#{b[0].to_hex}, name=block_" 66 | if x.include? b[0] 67 | out += "#{x[b[0]]}" 68 | else 69 | out += "#{b[1]}" 70 | end 71 | file.puts out 72 | end 73 | -------------------------------------------------------------------------------- /common/parse_config_file.rb: -------------------------------------------------------------------------------- 1 | class Nerve 2 | def parse_exec_proc(file) 3 | 4 | return if file.nil? 5 | 6 | fd = File.open(file) 7 | proc_control = %w[ target args env ] 8 | 9 | lines = fd.readlines 10 | lines.map { |x| x.chomp } 11 | 12 | exec_proc.args = Array.new 13 | exec_proc.env = Hash.new 14 | 15 | lines.each do |tl| 16 | if tl[0].chr == ';' or tl.nil? then next end 17 | 18 | k,v,l = tl.split(':') 19 | 20 | if k.match(/target/) 21 | ## Dirty little hack if a : is used 22 | ## in the target path (C:\Windows...) 23 | if !l.nil? 24 | v = "#{v}:#{l}" 25 | end 26 | v.gsub!(/[\n]+/, "") 27 | v.gsub!(/[\s]+/, "") 28 | exec_proc.target = v 29 | end 30 | 31 | if k.match(/args/) 32 | v.gsub!(/[\n]+/, "") 33 | exec_proc.args = v 34 | end 35 | 36 | if k.match(/env/) 37 | v.gsub!(/[\n]+/, "") 38 | k,v = v.split(/=/) 39 | k.gsub!(/[\s]+/, "") 40 | exec_proc.env.store(k,v) 41 | end 42 | end 43 | end 44 | 45 | def parse_config_file(file) 46 | 47 | return if file.nil? 48 | 49 | fd = File.open(file) 50 | 51 | ## All the handlers a user can script 52 | ## There is no specific order to this 53 | hdlrs = %w[ on_access_violation on_alignment on_attach on_bounds on_breakpoint on_continue 54 | on_create_process on_create_thread on_detach on_divide_by_zero on_exit on_exit_process 55 | on_exit_thread on_fork_child on_illegalinst on_int_overflow on_invalid_disposition 56 | on_invalid_handle on_load_dll on_output_debug_string on_priv_instruction on_rip on_segv 57 | on_signal on_sigstop on_sigchild on_sigterm on_sigtrap on_single_step on_stack_overflow 58 | on_stop on_unload_dll on_iot_trap on_guard_page ] 59 | 60 | lines = fd.readlines 61 | lines.map { |x| x.chomp } 62 | 63 | lines.each do |tl| 64 | 65 | if tl[0].chr == ';' or tl.nil? then next end 66 | 67 | hdlrs.each do |l| 68 | if tl.match(/#{l}/) 69 | i,p = tl.split("=") 70 | i.gsub!(/[\s\n]+/, "") 71 | p.gsub!(/[\s\n]+/, "") 72 | p = File.read(p) 73 | event_handlers.store(i,p) 74 | next 75 | end 76 | end 77 | 78 | bp = OpenStruct.new 79 | bp.base = 0 80 | bp.flag = true 81 | bp.hits = 0 82 | bp.hook = false 83 | bp.bpc = nil 84 | bp.nargs = 0 85 | 86 | r = tl.split(",") 87 | 88 | if r.size < 2 then next end 89 | 90 | r.each do |e| 91 | if e.match(/bp=/) 92 | addr = e.split("bp=").last 93 | bp.addr = addr.gsub(/[\s\n]+/, "") 94 | end 95 | 96 | if e.match(/name=/) 97 | name = e.split("name=").last 98 | bp.name = name.gsub(/[\s\n]+/, "") 99 | end 100 | 101 | ## Win32 only until ragweed supports it 102 | if e.match(/hook=/) 103 | hook = e.split("hook=").last 104 | bp.hook = true if hook.gsub(/[\s\n]+/, "") =~ /true/ 105 | end 106 | 107 | if e.match(/bpc=/) 108 | bpc = e.split("bpc=").last 109 | bp.bpc = bpc.to_i 110 | end 111 | 112 | if e.match(/nargs=/) 113 | nargs = e.split("nargs=").last 114 | bp.nargs = nargs.to_i 115 | end 116 | 117 | if e.match(/code=/) 118 | code = e.split("code=").last 119 | c = code.gsub(/[\s\n]+/, "") 120 | r = File.read(c) 121 | bp.code = r 122 | end 123 | 124 | if e.match(/lib=/) 125 | lib = e.split("lib=").last 126 | bp.lib = lib.gsub(/[\s\n]+/, "") 127 | 128 | ## TODO - addr must already be parsed 129 | ## for this to work correctly 130 | if RUBY_PLATFORM =~ LINUX_OS 131 | so.each_pair do |k,v| 132 | if v =~ /#{bp.lib}/ 133 | bp.base = k 134 | end 135 | end 136 | end 137 | end 138 | end 139 | 140 | if bp.base != 0 141 | bp.addr = bp.base.to_i(16)+bp.addr.to_i(16) 142 | bp.addr = sprintf("0x0%x", bp.addr) 143 | end 144 | 145 | bp.hits = 0 146 | breakpoints.push(bp) 147 | end 148 | end 149 | end 150 | -------------------------------------------------------------------------------- /crash.rb: -------------------------------------------------------------------------------- 1 | ## Crash is a partial MSEC (!exploitable) WinDbg extension 2 | ## ported to use the ragweed debugging library. It has been 3 | ## test with the Nerve debugger, 4 | ## 5 | ## Usage: 6 | ## 7 | ## Catch a bad debug event like segfault or illegal instruction 8 | ## then pass your ragweed instance to this class: 9 | ## 10 | ## Crash.new(@ragweed).exploitable? 11 | ## 12 | ## Thats it! The class will use your ragweed instance to 13 | ## determine the state of the process. This is done examining 14 | ## the last signal or debug event the process received and 15 | ## the register states. 16 | 17 | ## THIS CODE IS EXPERIMENTAL AND UNFINISHED :) 18 | 19 | require 'rubygems' 20 | require 'ragweed' 21 | 22 | class Crash 23 | EXPLOITABLE = 1 24 | POSSIBLY_EXPLOITABLE = 2 25 | NOT_EXPLOITABLE = 3 26 | UNKNOWN = 4 27 | 28 | attr_accessor :state, :status, :ragweed 29 | 30 | def initialize(rw) 31 | @ragweed = rw 32 | status = UNKNOWN 33 | 34 | case 35 | when RUBY_PLATFORM =~ WINDOWS_OS 36 | crash_win32 37 | when RUBY_PLATFORM =~ LINUX_OS 38 | crash_linux 39 | when RUBY_PLATFORM =~ OSX_OS 40 | crash_osx 41 | end 42 | end 43 | 44 | ## Crash.exploitable? 45 | ## Who needs !exploitable when you've got exploitable? 46 | def exploitable? 47 | return true if status == EXPLOITABLE or status == POSSIBLY_EXPLOITABLE 48 | return false 49 | end 50 | 51 | def crash_win32 52 | event = @ragweed.event 53 | context = @ragweed.context(event) 54 | 55 | ## !! unused !! 56 | @state = OpenStruct.new 57 | @state.crash_address 58 | @state.raw_instruction 59 | @state.stack_trace 60 | 61 | status = reg_check(context.eip) 62 | 63 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ILLEGAL_INSTRUCTION 64 | puts "Illegal instruction indicates attacker controlled code flow - EXPLOITABLE" 65 | status = EXPLOITABLE 66 | end 67 | 68 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::PRIV_INSTRUCTION 69 | puts "Privileged instruction indicates attacker controlled code flow - EXPLOITABLE" 70 | status = EXPLOITABLE 71 | end 72 | 73 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ON_GUARD_PAGE 74 | puts "Guard page violation - EXPLOITABLE" 75 | status = EXPLOITABLE 76 | end 77 | 78 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::BUFFER_OVERRUN 79 | puts "/GS stack cookie has been corrupted - EXPLOITABLE" 80 | status = EXPLOITABLE 81 | end 82 | 83 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::HEAP_CORRUPTION 84 | puts "Heap corruption has been detected - EXPLOITABLE" 85 | status = EXPLOITABLE 86 | end 87 | 88 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 89 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_DEP and 90 | event.exception_address > 0x1000 91 | puts "DEP Access Violation not near NULL - EXPLOITABLE" 92 | status = EXPLOITABLE 93 | end 94 | 95 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 96 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_DEP and 97 | event.exception_address < 0x1000 98 | puts "DEP Access Violation near NULL - NOT EXPLOITABLE" 99 | status = NOT_EXPLOITABLE 100 | end 101 | 102 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 103 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_WRITE and 104 | event.exception_address > 0x1000 105 | puts "Write Access Violation not near NULL - EXPLOITABLE" 106 | status = EXPLOITABLE 107 | end 108 | 109 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 110 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_WRITE and 111 | event.exception_address < 0x1000 112 | puts "Write Access Violation near NULL - NOT EXPLOITABLE" 113 | status = NOT_EXPLOITABLE 114 | end 115 | 116 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 117 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_READ and 118 | event.exception_address > 0x1000 119 | puts "Read Access Violation not near NULL - EXPLOITABLE" 120 | status = EXPLOITABLE 121 | end 122 | 123 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::ACCESS_VIOLATION and 124 | event.exception_information[0] == Ragweed::Wrap32::ExceptionSubTypes::ACCESS_VIOLATION_TYPE_READ and 125 | event.exception_address < 0x1000 126 | puts "Read Access Violation near NULL - NOT EXPLOITABLE" 127 | status = NOT_EXPLOITABLE 128 | end 129 | 130 | if event.exception_code == Ragweed::Wrap32::ExceptionCodes::DIVIDE_BY_ZERO 131 | puts "Divide by zero - NOT EXPLOITABLE" 132 | status = NOT_EXPLOITABLE 133 | end 134 | end 135 | 136 | def crash_linux 137 | r = @ragweed.get_registers 138 | status = reg_check(r.eip) 139 | status = reg_check(r.ebp) 140 | 141 | case ragweed.signal 142 | when Ragweed::Wraptux::Signal::SIGILL 143 | puts "Illegal instruction indicates attacker controlled code flow - EXPLOITABLE" 144 | status = EXPLOITABLE 145 | when Ragweed::Wraptux::Signal::SIGIOT 146 | puts "IOT Trap may indicate an exploitable crash (stack cookie?) - POSSIBLY EXPLOITABLE" 147 | status = POSSIBLY_EXPLOITABLE 148 | when Ragweed::Wraptux::Signal::SIGSEGV 149 | puts "A segmentation fault may be exploitable, needs further analysis - POSSIBLY EXPLOITABLE" 150 | status = POSSIBLY_EXPLOITABLE 151 | end 152 | end 153 | 154 | def crash_osx 155 | ## TODO 156 | end 157 | 158 | def get_stack_trace 159 | ## Not implemented yet 160 | end 161 | 162 | ## Really only works in Linux right now 163 | def reg_check(reg) 164 | ## TODO: Uhh these are not yet implemented in ragweed 165 | ## Win32 - TEB Parsing 166 | stack_range = @ragweed.get_stack_range 167 | heap_range = @ragweed.get_heap_range 168 | 169 | stack_range.each do |s| 170 | if reg == s.first..s.last 171 | puts "Executing instructions from the stack - EXPLOITABLE" 172 | return EXPLOITABLE 173 | end 174 | end 175 | 176 | heap_range.each do |h| 177 | if reg == h.first..h.last 178 | puts "Executing instructions from the heap - EXPLOITABLE" 179 | return EXPLOITABLE 180 | end 181 | end 182 | 183 | case reg 184 | when 0x41414141 185 | puts "Register is controllable AAAA... - EXPLOITABLE" 186 | return EXPLOITABLE 187 | when 0x0..0x1000 188 | puts "NULL Pointer dereference - NOT EXPLOITABLE (unless you control the offset from NULL)" 189 | return NOT_EXPLOITABLE 190 | end 191 | end 192 | end 193 | -------------------------------------------------------------------------------- /nerve.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | $: << File.dirname(__FILE__) 3 | 4 | require 'rubygems' 5 | require 'ragweed' 6 | require 'optparse' 7 | require 'handlers' 8 | require 'ostruct' 9 | require 'common/parse_config_file' 10 | require 'common/output' 11 | require 'common/common' 12 | require 'common/constants' 13 | require 'common/helpers' 14 | require 'common/extends' 15 | 16 | class Nerve 17 | attr_accessor :opts, :ragweed, :pid, :threads, :breakpoints, :so, :log, :event_handlers, :exec_proc 18 | 19 | def initialize(opts) 20 | @opts = opts 21 | @pid = opts[:pid] 22 | @breakpoints = Array.new 23 | @exec_proc = OpenStruct.new 24 | @event_handlers = Hash.new 25 | @threads = Array.new 26 | @out = opts[:out] 27 | @log = NerveLog.new(@out) 28 | 29 | parse_exec_proc(opts[:ep_file]) if !opts[:ep_file].nil? 30 | 31 | launch_process if !exec_proc.target.nil? 32 | 33 | case 34 | when RUBY_PLATFORM =~ WINDOWS_OS 35 | 36 | parse_config_file(opts[:bp_file]) 37 | 38 | if pid.kind_of?(String) and pid.to_i == 0 39 | while @ragweed.nil? 40 | @ragweed = NerveWin32.find_by_regex(/#{pid}/) 41 | end 42 | else 43 | @ragweed = NerveWin32.new(pid.to_i) 44 | end 45 | 46 | if @ragweed.nil? 47 | puts "Failed to find process: #{pid}" 48 | exit 49 | end 50 | 51 | @ragweed.log_init(log) 52 | 53 | ## TODO: debugger32 threads returns an OStruct 54 | ## and pid is not always a Numeric value 55 | @threads = @ragweed.process.threads(true) 56 | 57 | if !@threads.nil? 58 | @threads.each do |x| 59 | #log.str "#{x.th32OwnerProcessID} => #{x.th32ThreadID})" 60 | end 61 | end 62 | 63 | when RUBY_PLATFORM =~ LINUX_OS 64 | if pid.kind_of?(String) and pid.to_i == 0 65 | @pid = NerveLinux.find_by_regex(/#{pid}/).to_i 66 | else 67 | @pid = pid.to_i 68 | end 69 | 70 | @so = NerveLinux.shared_libraries(pid) 71 | 72 | parse_config_file(opts[:bp_file]) 73 | 74 | @threads = NerveLinux.threads(pid) 75 | self.which_threads 76 | 77 | lo = {} 78 | 79 | if opts[:fork] == true 80 | lo[:fork] = true 81 | end 82 | 83 | @ragweed = NerveLinux.new(pid, lo) 84 | 85 | if @ragweed.nil? 86 | puts "Failed to find process: #{pid}" 87 | exit 88 | end 89 | 90 | @ragweed.log_init(log) 91 | 92 | when RUBY_PLATFORM =~ OSX_OS 93 | 94 | parse_config_file(opts[:bp_file]) 95 | 96 | if pid.kind_of?(String) and pid.to_i.nil? 97 | @pid = NerveOSX.find_by_regex(/#{pid}/) 98 | else 99 | @pid = pid.to_i 100 | end 101 | 102 | @ragweed = NerveOSX.new(pid) 103 | 104 | if @ragweed.nil? 105 | puts "Failed to find process: #{pid}" 106 | exit 107 | end 108 | 109 | @ragweed.log_init(log) 110 | @threads = @ragweed.threads 111 | self.which_threads 112 | end 113 | 114 | @ragweed.save_threads(@threads) 115 | @ragweed.save_handlers(@event_handlers) 116 | 117 | @ragweed.attach if RUBY_PLATFORM !~ WINDOWS_OS 118 | 119 | self.set_breakpoints 120 | 121 | bp_count = 0 122 | @breakpoints.each {|b| bp_count+=1 if b.flag == true } 123 | 124 | log.str "#{bp_count} Breakpoint(s) set ..." 125 | 126 | @ragweed.save_breakpoints(@breakpoints) 127 | 128 | if RUBY_PLATFORM !~ WINDOWS_OS 129 | @ragweed.install_bps 130 | 131 | if opts[:fork] == true and RUBY_PLATFORM =~ LINUX_OS 132 | @ragweed.set_options(Ragweed::Wraptux::Ptrace::SetOptions::TRACEFORK) 133 | end 134 | 135 | @ragweed.continue 136 | end 137 | 138 | trap("INT") do 139 | @ragweed.uninstall_bps if RUBY_PLATFORM !~ WINDOWS_OS 140 | @ragweed.dump_stats 141 | log.finalize 142 | exit 143 | end 144 | 145 | catch(:throw) { @ragweed.loop } 146 | 147 | ## This is commented out because the stats should 148 | ## have been dumped already if we reached this 149 | ## point through some debugger event 150 | #@ragweed.dump_stats 151 | end 152 | 153 | def set_breakpoints 154 | @breakpoints.each do |bp| 155 | 156 | if bp.addr.nil? 157 | bp.flag = false 158 | next 159 | end 160 | 161 | log.str "Setting breakpoint: #{bp.addr}, #{bp.name} #{bp.lib}" 162 | 163 | case 164 | when RUBY_PLATFORM =~ WINDOWS_OS 165 | if bp.hook == true 166 | @ragweed.hook(bp.addr, bp.nargs) do |evt, ctx, dir, args| 167 | eval(bp.code) if !bp.code.nil? 168 | 169 | bp.hits += 1 if dir.to_s =~ /enter/ 170 | 171 | check_bp_max(bp, ctx) 172 | end 173 | else 174 | @ragweed.breakpoint_set(bp.addr) do |evt, ctx| 175 | eval(bp.code) if !bp.code.nil? 176 | 177 | bp.hits += 1 178 | 179 | check_bp_max(bp, ctx) 180 | end 181 | end 182 | 183 | when RUBY_PLATFORM =~ LINUX_OS, RUBY_PLATFORM =~ OSX_OS 184 | @ragweed.breakpoint_set(bp.addr.to_i(16), bp.name, (bpl = proc do 185 | eval(bp.code) if !bp.code.nil? 186 | 187 | bp.hits += 1 188 | 189 | if !bp.bpc.nil? and bp.hits.to_i >= bp.bpc.to_i 190 | bp.flag = false 191 | regs = @ragweed.get_registers 192 | @ragweed.breakpoint_clear(regs.eip-1) 193 | end 194 | end )) 195 | end 196 | end 197 | end 198 | 199 | def check_bp_max(bp, ctx) 200 | if !bp.bpc.nil? and bp.hits.to_i >= bp.bpc.to_i 201 | r = @ragweed.breakpoint_clear(ctx.eip-1) 202 | bp.flag = false 203 | end 204 | end 205 | 206 | def launch_process 207 | if RUBY_PLATFORM =~ WINDOWS_OS 208 | exec_proc.env.each_pair { |k,v| ENV[k] = v } 209 | proc_info = Ragweed::Wrap32::ProcessInfo.new 210 | startup_info = Ragweed::Wrap32::StartupInfo.new 211 | target = FFI::MemoryPointer.from_string("#{exec_proc.target} #{exec_proc.args}") 212 | ## TODO: Port -f option to CreateProcess. We can pass along DEBUG_PROCESS 213 | ## TODO: Environment variables (less important in win32) 214 | r = Ragweed::Wrap32::Win::CreateProcessA(nil, target, nil, nil, false, 0x0, nil, nil, startup_info, proc_info) 215 | @pid = proc_info[:pid] if r != 0 216 | else 217 | @pid = fork do 218 | exec_proc.env.each_pair { |k,v| ENV[k] = v } 219 | exec("#{exec_proc.target} #{exec_proc.args}") 220 | end 221 | end 222 | end 223 | end 224 | 225 | NERVE_OPTS = { 226 | :pid => nil, 227 | :pe_file => nil, 228 | :bp_file => nil, 229 | :out => STDOUT, 230 | :fork => false 231 | } 232 | 233 | opts = OptionParser.new do |opts| 234 | opts.banner = "\nNerve #{NERVE_VERSION} | Chris Rohlf 2009-2011\n\n" 235 | 236 | opts.on("-p", "--pid PID/Name", "Attach to this pid OR process name (ex: -p 12345 | -p gcalctool | -p notepad.exe)") do |o| 237 | NERVE_OPTS[:pid] = o 238 | end 239 | 240 | opts.on("-x", "--exec_proc FILE", "Launch a process according to the configuration found in this file") do |o| 241 | NERVE_OPTS[:ep_file] = o 242 | end 243 | 244 | opts.on("-b", "--config_file FILE", "Read all breakpoints and handler event configurations from this file") do |o| 245 | NERVE_OPTS[:bp_file] = o 246 | end 247 | 248 | opts.on("-o", "--output FILE", "Dump all output to a file (default is STDOUT)") do |o| 249 | NERVE_OPTS[:out] = File.open(o, "w") rescue (bail $!) 250 | end 251 | 252 | ## FIX: Port this feature when Ragweed is ready 253 | if RUBY_PLATFORM =~ LINUX_OS 254 | opts.on("-f", "Optional flag indicates whether or not to trace forked child processes (Linux only)") do |o| 255 | NERVE_OPTS[:fork] = true 256 | end 257 | end 258 | end 259 | 260 | opts.parse!(ARGV) rescue (STDERR.puts $!; exit 1) 261 | 262 | if NERVE_OPTS[:pid] == nil 263 | puts opts.banner 264 | # exit 265 | end 266 | 267 | ## Never is still under heavy development 268 | ## we want to see gross errors for now 269 | #begin 270 | Nerve.new(NERVE_OPTS) 271 | #rescue 272 | #end 273 | -------------------------------------------------------------------------------- /handlers.rb: -------------------------------------------------------------------------------- 1 | ## You can use the methods within these classes to 2 | ## extend the basic Ragweed signal/event handlers. 3 | ## Sometimes you may need to call super if the handler 4 | ## is also implemented by Ragweed. 5 | 6 | require 'common/constants' 7 | #require 'crash' 8 | 9 | case 10 | when RUBY_PLATFORM =~ WINDOWS_OS 11 | class NerveWin32 < Ragweed::Debugger32 12 | 13 | attr_accessor :log, :pid, :nerve_breakpoints, :threads, :event_handlers 14 | 15 | def initialize(pid) 16 | @pid = pid 17 | super 18 | end 19 | 20 | def log_init(l) 21 | @log = l 22 | end 23 | 24 | def save_opts(opts) @opts = opts; end 25 | def save_breakpoints(b) @nerve_breakpoints = b; end 26 | def save_threads(t) @threads = t; end 27 | def save_handlers(h) @event_handlers = h end 28 | 29 | def exec_eh_script(name, ev=nil) 30 | begin 31 | if !@event_handlers[name].nil? 32 | eval(@event_handlers[name]) 33 | end 34 | rescue 35 | end 36 | end 37 | 38 | def dump_stats(ev=nil) 39 | if !ev.nil? 40 | a = self.context(ev) 41 | log.str a.dump 42 | log.str "Pid is #{ev.pid}" 43 | log.str "Tid is #{ev.tid}" 44 | end 45 | nerve_breakpoints.each do |bp| 46 | if bp.hits > 0 47 | log.str "#{bp.addr} - #{bp.name} | #{bp.hits}" 48 | end 49 | end 50 | end 51 | 52 | def on_access_violation(ev) 53 | #puts "Exploitable? #{Crash.new(self).exploitable?}" 54 | exec_eh_script("on_access_violation", ev) 55 | dump_stats(ev) 56 | log.str "Access violation!" 57 | end 58 | 59 | def on_attach 60 | exec_eh_script("on_attach") 61 | super 62 | end 63 | 64 | def on_exit_process(ev) 65 | exec_eh_script("on_exit_process", ev) 66 | dump_stats(ev) 67 | log.str "Process exited!" 68 | super 69 | end 70 | 71 | def on_load_dll(ev) 72 | exec_eh_script("on_load_dll", ev) 73 | super 74 | end 75 | 76 | def on_breakpoint(ev) 77 | exec_eh_script("on_breakpoint", ev) 78 | super 79 | end 80 | 81 | def on_single_step(ev) 82 | exec_eh_script("on_single_step", ev) 83 | super 84 | end 85 | 86 | def on_create_process(ev) 87 | exec_eh_script("on_create_process", ev) 88 | end 89 | 90 | def on_create_thread(ev) 91 | exec_eh_script("on_create_thread", ev) 92 | end 93 | 94 | def on_exit_thread(ev) 95 | exec_eh_script("on_exit_thread", ev) 96 | end 97 | 98 | def on_output_debug_string(ev) 99 | exec_eh_script("on_output_debug_string", ev) 100 | end 101 | 102 | def on_rip(ev) 103 | exec_eh_script("on_rip", ev) 104 | end 105 | 106 | def on_unload_dll(ev) 107 | exec_eh_script("on_unload_dll", ev) 108 | end 109 | 110 | def on_guard_page(ev) 111 | exec_eh_script("on_guard_page", ev) 112 | end 113 | 114 | def on_alignment(ev) 115 | exec_eh_script("on_alignment", ev) 116 | end 117 | 118 | def on_bounds(ev) 119 | exec_eh_script("on_bounds", ev) 120 | end 121 | 122 | def on_divide_by_zero(ev) 123 | exec_eh_script("on_divide_by_zero", ev) 124 | end 125 | 126 | def on_int_overflow(ev) 127 | exec_eh_script("on_int_oveflow", ev) 128 | end 129 | 130 | def on_invalid_handle(ev) 131 | exec_eh_script("on_invalid_handle", ev) 132 | end 133 | 134 | def on_illegal_instruction(ev) 135 | exec_eh_script("on_illegal_instruction", ev) 136 | end 137 | 138 | def on_priv_instruction(ev) 139 | exec_eh_script("on_priv_instruction", ev) 140 | end 141 | 142 | def on_heap_corruption(ev) 143 | #puts "Exploitable? #{Crash.new(self).exploitable?}" 144 | exec_eh_script("on_heap_corruption", ev) 145 | end 146 | 147 | def on_buffer_overrun(ev) 148 | #puts "Exploitable? #{Crash.new(self).exploitable?}" 149 | exec_eh_script("on_buffer_overrun", ev) 150 | end 151 | 152 | def on_stack_overflow(ev) 153 | exec_eh_script("on_stack_overflow", ev) 154 | end 155 | 156 | def on_invalid_disposition(ev) 157 | exec_eh_script("on_invalid_disposition", ev) 158 | end 159 | end 160 | 161 | when RUBY_PLATFORM =~ LINUX_OS 162 | class NerveLinux < Ragweed::Debuggertux 163 | 164 | attr_accessor :log, :pid, :nerve_breakpoints, :threads 165 | 166 | def initialize(pid, opts) 167 | @pid = pid 168 | super 169 | end 170 | 171 | def log_init(l) 172 | @log = l 173 | end 174 | 175 | def save_opts(opts) @opts = opts; end 176 | def save_breakpoints(b) @nerve_breakpoints = b; end 177 | def save_threads(t) @threads = t; end 178 | def save_handlers(h) @event_handlers = h end 179 | 180 | def exec_eh_script(name) 181 | begin 182 | eval(@event_handlers[name]) 183 | rescue 184 | end 185 | end 186 | 187 | def dump_stats 188 | nerve_breakpoints.each do |bp| 189 | if bp.hits > 0 190 | log.str "#{bp.addr} - #{bp.name} | #{bp.hits}" 191 | end 192 | end 193 | end 194 | 195 | def on_fork_child(pid) 196 | @pid = pid 197 | exec_eh_script("on_fork_child") 198 | log.str "Parent process forked a child with pid #{pid}" 199 | end 200 | 201 | def on_sigchild 202 | exec_eh_script("on_sigchild") 203 | log.str "Forked a child process" 204 | end 205 | 206 | def on_sigterm 207 | log.str "Process Terminated!" 208 | exec_eh_script("on_sigterm") 209 | ## This need to be implemented in debuggerosx 210 | #self.print_registers 211 | dump_stats 212 | exit 213 | end 214 | 215 | def on_segv 216 | log.str "Segmentation Fault!" 217 | exec_eh_script("on_segv") 218 | ## This need to be implemented in debuggerosx 219 | self.print_registers 220 | dump_stats 221 | #Crash.new(self).exploitable? 222 | exit 223 | end 224 | 225 | def on_breakpoint 226 | exec_eh_script("on_breakpoint") 227 | super 228 | end 229 | 230 | def on_exit 231 | log.str "Process Exited!" 232 | exec_eh_script("on_exit") 233 | dump_stats 234 | end 235 | 236 | def on_illegal_instruction 237 | log.str "Illegal Instruction!" 238 | exec_eh_script("on_illegal_instruction") 239 | dump_stats 240 | end 241 | 242 | def on_iot_trap 243 | log.str "IOT Trap!" 244 | exec_eh_script("on_iot_trap") 245 | dump_stats 246 | self.print_registers 247 | #Crash.new(self).exploitable? 248 | end 249 | 250 | def on_attach 251 | exec_eh_script("on_attach") 252 | super 253 | end 254 | 255 | def on_detach 256 | exec_eh_script("on_detach") 257 | super 258 | end 259 | 260 | def on_sigtrap 261 | exec_eh_script("on_sigtrap") 262 | super 263 | end 264 | 265 | def on_continue 266 | exec_eh_script("on_continue") 267 | super 268 | end 269 | 270 | def on_sigstop 271 | exec_eh_script("on_sigstop") 272 | super 273 | end 274 | 275 | def on_signal 276 | exec_eh_script("on_signal") 277 | super 278 | end 279 | 280 | def on_single_step 281 | exec_eh_script("on_singlestep") 282 | super 283 | end 284 | end 285 | 286 | when RUBY_PLATFORM =~ OSX_OS 287 | class NerveOSX < Ragweed::Debuggerosx 288 | 289 | attr_accessor :log, :pid, :nerve_breakpoints, :threads 290 | 291 | def initialize(pid) 292 | @pid = pid 293 | super 294 | end 295 | 296 | def log_init(l) 297 | @log = l 298 | end 299 | 300 | def save_opts(opts) @opts = opts; end 301 | def save_breakpoints(b) @nerve_breakpoints = b; end 302 | def save_threads(t) @threads = t; end 303 | def save_handlers(h) @event_handlers = h end 304 | 305 | def exec_eh_script(name,param=nil) 306 | begin 307 | eval(@event_handlers[name]) 308 | rescue 309 | end 310 | end 311 | 312 | def dump_stats 313 | nerve_breakpoints.each do |bp| 314 | if bp.hits > 0 315 | log.str "#{bp.addr} - #{bp.name} | #{bp.hits}" 316 | end 317 | end 318 | end 319 | 320 | def on_sigsegv 321 | log.str "Segmentation Fault!" 322 | exec_eh_script("on_sigsegv") 323 | self.print_regs 324 | dump_stats 325 | exit 326 | end 327 | 328 | def on_sigterm 329 | log.str "Process Terminated!" 330 | exec_eh_script("on_sigterm") 331 | self.print_regs 332 | dump_stats 333 | exit 334 | end 335 | 336 | def on_breakpoint(thread) 337 | exec_eh_script("on_breakpoint", thread) 338 | super 339 | end 340 | 341 | def on_exit 342 | log.str "Process Exited!" 343 | exec_eh_script("on_exit") 344 | dump_stats 345 | super 346 | end 347 | 348 | def on_single_step 349 | exec_eh_script("on_single_step") 350 | super 351 | end 352 | 353 | def on_signal(signal) 354 | exec_eh_script("on_signal", signal) 355 | super 356 | end 357 | 358 | def on_stop(signal) 359 | exec_eh_script("on_stop", signal) 360 | super 361 | end 362 | 363 | def on_continue 364 | exec_eh_script("on_continue") 365 | super 366 | end 367 | 368 | def on_attach 369 | exec_eh_script("on_attach") 370 | super 371 | end 372 | 373 | def on_detach 374 | exec_eh_script("on_detach") 375 | super 376 | end 377 | end 378 | end 379 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Nerve is a simple cross platform (Win32, Linux, OSX) x86 scriptable debugger 2 | 3 | ## What is it? 4 | 5 | Nerve is based on, and requires, Ragweed http://github.com/tduehr/ragweed 6 | Ragweed is a cross platform x86 debugging library written in Ruby. 7 | 8 | To learn more about Ragweed, read this: 9 | http://chargen.matasano.com/chargen/2009/8/27/ruby-for-pentesters-the-dark-side-i-ragweed.html 10 | 11 | Nerve can be a dynamic hit tracer, an in memory fuzzer or a simple scriptable debugger. 12 | All you need to do is give it a configuration file telling it what breakpoints to 13 | set, events to hook and what ruby scripts to execute when it happens. 14 | 15 | Nerve showcases the best part about Ragweed: cross platform debugging. I originally 16 | wrote Nerve as a small Ragweed script that kept stats on the functions my fuzzers 17 | were triggering in a target process. This told me what code paths my fuzzer was 18 | reaching and which ones it wasn't. It only took a few hours to make it work on all Ragweed 19 | supported platforms, and since then it has grown into a much more capable tool. It now 20 | supports configuration files for breakpoints, event handler scripts and more. 21 | 22 | We have included several working examples with Nerve so that you aren't lost the first 23 | time you try it. If you develop some useful scripts with it let us know and we can make 24 | them part of the default package. 25 | 26 | ## Supported Platforms 27 | 28 | Nerve is supported and has been tested on the following platforms: 29 | 30 | Windows XP, 7 31 | Ubuntu Linux 9.10 -> 11.04 32 | Mac OS X 10.5, 10.6 33 | 34 | Ruby 1.8.7 35 | Ruby 1.9.x 36 | 37 | ## Features 38 | 39 | - Cross platform 40 | - Easy configuration files you can write by hand or generate using our tools 41 | - Run Ruby scripts with full access to the debugger when breakpoints are hit 42 | - Run Ruby scripts when specific debugging events occur 43 | - Extend Nerve through handlers.rb or output.rb with minimal code changes 44 | - Nerve comes with a few example breakpoint scripts such as hooking RtlAllocateHeap/malloc 45 | 46 | ## Todo / Ideas 47 | 48 | Nerve is a simple tool, but the plan is to grow it with optional add ons and more... 49 | 50 | - Lots of helper scripts for breakpoints such as heap inspection, in memory fuzzing, SSL reads etc... 51 | - Helper methods and better named instance variables for making breakpoint scripts easier to write 52 | - Better output such as graphviz, statistics, function arguments etc... 53 | - Redis database support for offline analysis of output 54 | - Cleaner 1.8 and 1.9 support for launching processes 55 | - Continous re-attach to any targets that match the process name 56 | - Change the configuration to Ruby code (the text file will be an optional fallback) 57 | - Nerve is also helping us find the areas of Ragweed that need the most improvement 58 | 59 | ## Requirements 60 | 61 | Nerve has one small dependency. But don't worry, theres no need to install an SQL server 62 | or compile any code! The dependency, Ragweed, can be installed via Ruby gems on any platform. 63 | 64 | Ragweed (a cross platform x86 debugger library) 65 | 66 | git clone http://github.com/tduehr/ragweed.git (the preferred method) 67 | 68 | ... OR ... 69 | 70 | gem install -r ragweed (you might get an older version!) 71 | 72 | Ragweed requires FFI which you can install with rubygems: 73 | 74 | gem install -r ffi 75 | 76 | YES, thats it! 77 | 78 | If you want to run the bleeding edge stuff we commit to github everyday then I suggest 79 | checking out the github repositories of both Nerve and Ragweed and executing a 'git pull' 80 | before using the tool. But we can't promise it will work perfectly. 81 | 82 | ## Usage 83 | 84 | $ ruby nerve.rb --help 85 | 86 | Nerve 1.9 | Chris Rohlf 2009-2011 87 | 88 | -p, --pid PID/Name Attach to this pid OR process name (ex: -p 12345 | -p gcalctool | -p notepad.exe) 89 | -x, --exec_proc FILE Launch a process according to the configuration found in this file 90 | -b, --config_file FILE Read all breakpoints and handler event configurations from this file 91 | -o, --output FILE Dump all output to a file (default is STDOUT) 92 | -f Optional flag indicates whether or not to trace forked child processes (Linux only) 93 | 94 | ## Configuration File Example 95 | 96 | Keywords in configuration files: 97 | (the order does not matter but each line represents a unique breakpoint) 98 | 99 | bp - An address (or a symbolic name for Win32) where the debugger should set a breakpoint 100 | name - A name describing the breakpoint, typically a symbol or function name 101 | lib - An optional library name indicating where the symbol can be found, only useful with Linux/OSX 102 | bpc - Number of times to let this breakpoint hit before uninstalling it 103 | code - Location of a script that holds ruby code to be executed when this breakpoint hits 104 | nargs - The number of arguments the function takes (only used with Win32) 105 | hook - A true or false configuration to hook both a function entry and exit (Win32 only) 106 | 107 | -- 108 | 109 | Win32 Configuration Example: 110 | bp=0x12345678, name=SomeFunction, bpc=2, code=scripts/SomeFunctionAnalysis.rb, hook=true 111 | bp=kernel32!CreateFileW, name=CreateFileW, code=scripts/CreateFileW_Analysis.rb 112 | 113 | Linux Configuration Example: 114 | bp=0x12345678, name=function_name, lib=ncurses.so.5.1, bpc=1, code=scripts/ncurses_trace.rb 115 | name=malloc, lib=/lib/tls/i686/cmov/libc-2.11.1.so, bpc=10, bp=0x006ff40, code=scripts/malloc_linux.rb 116 | 117 | OS X Configuration Example: 118 | bp=0x12345678, name=function_name, bpc=6 119 | 120 | ## Process Launching Configurations 121 | 122 | You can instruct Nerve to launch a target process with arguments and environment 123 | variables of your choosing. Nerve takes the -x flag along with a filename containing 124 | your configuration. Be aware that Nerve currently uses exec() to launch processes on 125 | nix. This means stdout will be written to by the new process. Supporting Process.spawn() 126 | is easy but its also not Ruby 1.9 compatible. This is on my list to rework! 127 | 128 | Process launching configuration keywords 129 | 130 | target - The location of the application you want to run 131 | args - A string of arguments to pass to the application 132 | env - A string of environment variables for the application 133 | 134 | target: /usr/bin/gcalctool 135 | args: -s 1+1 136 | env: BLAH=test 137 | env: MYLIBPATH=/usr/lib 138 | 139 | ## Breakpoint Scripts 140 | 141 | Nerve supports breakpoint scripts that run when a breakpoint you have specified is executed. These 142 | can be specified using the 'code=' keyword in your Nerve configuration file (see above). 143 | These scripts run within the scope of Nerve and the Ragweed breakpoint. This means your scripts 144 | have access to all the helper methods and instance variables Ragweed makes available. Documenting 145 | each of these is going to take a bit of time but heres some stuff you can start with. 146 | 147 | Helper Methods: 148 | 149 | (please refer to Ragweed sources for now http://github.com/tduehr/ragweed) 150 | 151 | Instance Variables: 152 | 153 | @ragweed - The Ragweed instance, use this to call all Ragweed methods 154 | 155 | Win32 Specific: 156 | evt - A debugger event 157 | ctx - A context structure holding registers 158 | dir - a string indicating function 'enter' or 'leave' 159 | 160 | ## Event Handlers Configuration Example 161 | 162 | Event handler scripts work just like breakpoint file scripts. They have full access to the debugger 163 | but are triggered when specific debug events occur such as 'on_load_dll'. See handlers.rb for how 164 | they are implemented. Some are OS specific and just wont trigger if you are on a different platform. 165 | 166 | Keywords for configuration files: 167 | 168 | on_access_violation 169 | on_alignment 170 | on_attach 171 | on_bounds 172 | on_buffer_overrun 173 | on_breakpoint 174 | on_continue 175 | on_create_process 176 | on_create_thread 177 | on_detach 178 | on_divide_by_zero 179 | on_exit 180 | on_exit_process 181 | on_exit_thread 182 | on_fork_child 183 | on_guard_page 184 | on_heap_corruption 185 | on_illegal_instruction 186 | on_int_overflow 187 | on_invalid_disposition 188 | on_invalid_handle 189 | on_load_dll 190 | on_iot_trap 191 | on_output_debug_string 192 | on_priv_instruction 193 | on_rip 194 | on_segv 195 | on_sigchild 196 | on_signal 197 | on_sigstop 198 | on_sigterm 199 | on_sigtrap 200 | on_single_step 201 | on_stack_overflow 202 | on_stop 203 | on_unload_dll 204 | 205 | This example will run the My_OnLoad_DLL.rb script whenever the LOAD_DLL debug event occurs: 206 | 207 | on_load_dll=scripts/My_OnLoad_DLL.rb 208 | 209 | ## Examples 210 | 211 | Heres some example output from Nerve running on Ubuntu Linux: 212 | 213 | chris@ubuntu:/# ruby nerve.rb -b example_configuration_files/generic_ubuntu_910_libc_trace.txt -p test 214 | Nerve ... 215 | Setting breakpoint: [ 0x0964f40, malloc /lib/tls/i686/cmov/libc-2.11.1.so ] 216 | Setting breakpoint: [ 0x08055590, mp_add ] 217 | Setting breakpoint: [ 0x0971830, wmemcpy /lib/tls/i686/cmov/libc-2.11.1.so ] 218 | Setting breakpoint: [ 0x0969f20, memcpy /lib/tls/i686/cmov/libc-2.11.1.so ] 219 | Setting breakpoint: [ 0x0964e60, free /lib/tls/i686/cmov/libc-2.11.1.so ] 220 | Setting breakpoint: [ 0x09b2de0, read /lib/tls/i686/cmov/libc-2.11.1.so ] 221 | Setting breakpoint: [ 0x09b2e60, write /lib/tls/i686/cmov/libc-2.11.1.so ] 222 | ^CDumping stats 223 | 0x0a3cf40 - malloc | 5279 224 | 0x08055590 - mp_add | 0 225 | 0x0a49830 - wmemcpy | 0 226 | 0x0a41f20 - memcpy | 0 227 | 0x0a3ce60 - free | 8385 228 | 0x0a8ade0 - read | 0 229 | 0x0a8ae60 - write | 0 230 | ... Done! 231 | 232 | Here is Nerve running on Windows 7 and debugging an example program that calls HeapAlloc. For 233 | this test program we want to run a simple ruby script each time we enter and leave RtlAllocateHeap. 234 | This script should extract the arguments to the function upon entry and the return values on exit. 235 | 236 | ... 237 | #include 238 | #include 239 | 240 | int main(int argc, char *argv[]) 241 | { 242 | void *a; 243 | HANDLE h1 = HeapCreate(0, 1024, 1024); 244 | int i = atol(argv[1]); 245 | 246 | while(1) 247 | { 248 | a = HeapAlloc(h1, HEAP_ZERO_MEMORY, i); 249 | HeapFree(h1, 0, a); 250 | } 251 | 252 | return 0; 253 | } 254 | ... 255 | 256 | Here is the configuration file: 257 | 258 | ... 259 | bp=ntdll!RtlAllocateHeap, name=RtlAllocateHeap, code=scripts/RtlAllocateHeap.rb, hook=true 260 | ... 261 | 262 | And here is the scripts/RtlAllocateHeap.rb referenced in the configuration file: 263 | 264 | ... 265 | ## This script is for Win32 RtlAllocateHeap 266 | 267 | begin 268 | if dir.to_s =~ /enter/ 269 | @log.str "RtlAllocateHeap -> Size requested #{@ragweed.process.read32(ctx.esp+12)}" 270 | @log.str "RtlAllocateHeap -> Heap handle is @ #{@ragweed.process.read32(ctx.esp+4).to_s(16)}" 271 | else 272 | @log.str "RtlAllocateHeap <- Heap chunk returned @ #{ctx.eax.to_s(16)}" 273 | end 274 | rescue => 275 | puts "Does your configuration use hook=true?" 276 | end 277 | ... 278 | 279 | Below is the output of hooking the malloc.exe program: 280 | 281 | PS C:\Nerve> ruby .\nerve.rb -p test.exe -b .\example_configuration_files\Win32_notepad.txt 282 | Nerve ... 283 | Setting breakpoint: [ ntdll!RtlAllocateHeap, RtlAllocateHeap ] 284 | RtlAllocateHeap -> Size requested 1024 285 | RtlAllocateHeap -> Heap handle is @ 750000 286 | RtlAllocateHeap -> Heap chunk returned @ 750590 287 | RtlAllocateHeap -> Size requested 1024 288 | RtlAllocateHeap -> Heap handle is @ 750000 289 | RtlAllocateHeap -> Heap chunk returned @ 750590 290 | RtlAllocateHeap -> Size requested 1024 291 | RtlAllocateHeap -> Heap handle is @ 750000 292 | RtlAllocateHeap -> Heap chunk returned @ 750590 <- ( This is where I CTRL+C the test program ) 293 | RtlAllocateHeap -> Size requested 24 294 | RtlAllocateHeap -> Heap handle is @ 470000 295 | RtlAllocateHeap -> Heap chunk returned @ 47f640 296 | 297 | CONTEXT: 298 | EIP: 77b564f4 299 | 300 | EAX: 000000c0 301 | EBX: 7ffd3000 302 | ECX: 77b6350f 303 | EDX: 00000000 304 | EDI: 00000000 305 | ESI: 002af704 306 | EBP: 002af728 307 | ESP: 002af6c0 308 | EFL: 00000000000000000000001000000010 cvvavrxniiodItszxaxpXc 309 | Dumping stats 310 | Pid is 3224 311 | Tid is 4048 312 | ntdll!RtlAllocateHeap - RtlAllocateHeap | 4 313 | 314 | ## Useful Tips 315 | 316 | - If you need to declare some global variables you should do it in an on_attach 317 | script. This code will only run once when the debugger attaches to the target. 318 | 319 | - On windows you can launch Nerve before launching your process. This avoids the 320 | the need for a process launching script. This is a side effect of how Ragweed 321 | is designed. This should be the default for all platforms in the future. 322 | 323 | ## Disassembly 324 | 325 | Ragweed and Nerve do not ship with a disassembly library. We feel that sort of lies outside of the 326 | scope of a core debugger library. We do however recommend the following Ruby disassembly libraries: 327 | 328 | https://github.com/sophsec/ffi-udis86 - FFI UDis86 library 329 | http://github.com/struct/frasm - A Ruby C extension for distorm64 330 | 331 | ## Who 332 | 333 | Nerve was written by Chris Rohlf with contributions from AlexRad and Timur Duehr 334 | 335 | Ragweed was written by Thomas Ptacek, ported to OSX by Timur Duehr and ported to Linux by Chris Rohlf 336 | 337 | Thanks to the www.Matasano.com team and a few other individuals for providing feedback and ideas 338 | --------------------------------------------------------------------------------