-
Notifications
You must be signed in to change notification settings - Fork 7
/
fuzz.rb
executable file
·220 lines (172 loc) · 5.29 KB
/
fuzz.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#!/usr/bin/env ruby
require './rand.rb'
require './trace.rb'
require './eqfuncs.rb'
require 'metasm'
include Metasm
class MallocFail < RuntimeError
end
class String
alias :len :length
end
class Bignum
def hex; self.to_s(16) end
end
class Fixnum
def hex; self.to_s(16) end
end
class FuzzMem < ApiHook
attr_reader :bps, :dasm
def initialize(file,inp,bps=nil,dasm=nil)
$stderr.puts "tracing...\n\n"
@dasm = dasm
@dasm ||= AutoExe.decode_file(file).disassemble
@bps = bps
@bps ||= Trace.new(eqFuncsList,file,inp,@dasm).owned
raise "Sorry you haven't control any function arguments, cant fuzz..." if @bps.empty?
@rnd = FuzzLib.new
@dbg = OS.current.create_debugger(file + " " + inp)
@dbg.callback_exception = lambda {|s| rep_vuln s}
@fuzz = {}
@file = File.open(File.expand_path(file + '.' + @dbg.pid.to_s + '.fuzz'),"wb")
@ret_reg = case @dbg.cpu.shortname
when 'x64' then :rax
when 'ia32' then :eax
else raise 'unsupported arch'
end
@base_reg = case @dbg.cpu.shortname
when 'x64' then :rbp
when 'ia32' then :ebp
else raise 'unsupported arch'
end
@saved_ctx = {}
$stderr.puts "\n\n#{file}@#{@dbg.pid}: fuzzing ...\n"
setup = @bps.map { |f| {:address => f[0], :condition => false}}
super(@dbg,setup,lambda {|a| hook(a)},nil)
### jizaz ruby api suck so hard...
begin
File.unlink(@file.path) if @file.size == 0
rescue
File.unlink(@file.path) if File.size(@file.path) == 0
end
@file.close
print "\n"
end
def rep_vuln(status)
$stderr.puts "\n#### SIG#{status[:signal]} ####\n"
case status[:signal]
when "SEGV", "ABRT", "FPE", "ILL"
then
@file.puts @dbg.di_at @dbg.pc
@file.puts "\nPid #{status[:status].pid} got SIG#{status[:signal]}! ;]\n"
@fuzz.each { |fbeg,store|
name = (@dasm.label_alias[fbeg] || [fbeg]).first
@file.puts "Fuzzed #{name}@#{fbeg.inspect} with\n"
store[:fuzz].each { |n,a|
@file.puts "\targ_#{n}:\t#{a.inspect}\n"
}
}
stacktrace
@file.puts @dbg.ctx.do_getregs
@file.flush
end
end
def stacktrace
@file.puts "\nBacktrace: "
@dbg.stacktrace.reverse { |a,n| @file.puts "#{n}@#{a.hex}" }
end
def hook(args)
fbeg = @dasm.find_function_start(@dbg.pc) #.instruction.args)
@fuzz[fbeg] ||= {}
@fuzz[fbeg][:alloc] ||= []
@fuzz[fbeg][:fuzz] ||= []
if not @fuzz[fbeg][:alloc].empty?
@fuzz[fbeg][:alloc].each { |i,str|
ret = @dbg.func_retval
$stderr.puts "malloc ret: #{ret.hex}"
@dbg[ret,str.len] = str
@dbg.ctx.do_setregs @saved_ctx[fbeg]
@saved_ctx.delete fbeg
patch_arg(i-1,ret)
@fuzz[fbeg][:fuzz] << [i,str]
}
else
(@bps[fbeg] || []).each do |i|
str = @rnd.rndFunc[].to_s
@saved_ctx[fbeg] = @dbg.ctx.do_getregs
alloc(str)
@fuzz[fbeg][:alloc] << [i,str]
end
end
end
### treat @str@ as local variable to function
def alloc(str)
# func = @dasm.function_at(@dbg.pc)
# return nil if func.nil? or func.return_address.nil?
# leave = false
# def is_end?(di)
# (leave = true; di.opcode.name =~ /^leave$/) or
# (di.opcode.name =~ /^pop$/ and di.instruction.args[0].symbolic == :ebp and
# @dbg.di_at(di.address+1).opcode.name =~ /^ret$/)
# end
# def is_mov_ebp_esp?
# di = @dbg.di_at @dbg.pc
# fst,snd = @dbg.di_at(@dbg.pc).instruction.args.map { |a| a.symbolic}
# di.opcode.name =~ /^mov$/ and fst == @base_reg and snd == @dbg.register_sp
# end
# def is_stack_align?
# di = @dbg.di_at @dbg.pc
# reg = @dbg.di_at(@dbg.pc).instruction.args.first.symbolic
# di.opcode.name =~ /^sub$/ and reg == @dbg.register_sp
# end
str << "\x00" if str[-1] != "\x00"
malloc(str)
end
def find_libc
mc = @dbg.modules.find { |m| m.path =~ /^\/lib.*\/libc/}
return [mc.addr,mc.path]
end
### find malloc adres based od libc offset
def find_malloc
base,path = find_libc
bin = AutoExe.decode_file(path)
off = bin.label_addr('malloc')
return (off > base ? off : base + off)
end
### deref malloc address from .got
def deref_malloc
bin = AutoExe.decode_file("/proc/#{@dbg.pid}/exe")
if got = bin.relocations.find {|r| r.symbol.name == /^malloc$/ }
base,path = find_libc
a = got.offset
return ( a > base ? a : @dbg.memory_read_int(a) )
end
return nil
end
def push_arg(arg,n=0)
case @dbg.cpu.shortname
when 'ia32' then @dbg.sp -= 4 ; @dbg[@dbg.sp,4] = [arg].pack('V')
when 'x64' then @dbg.func_arg_set(n,arg)
else raise 'unsupported arch'
end
end
## only x86-linux at this point
def malloc(str)
malloc_addr = find_malloc
$stderr.puts "[*] hooking call to malloc@#{malloc_addr.hex} ret: #{@dbg.pc.hex}"
@dbg.sp -= @dbg.cpu.size / 8
push_arg(str.len)
@dbg.func_retaddr_set(@dbg.pc)
@dbg.pc = malloc_addr
end
end
if __FILE__ == $0
file = ARGV.shift || './a.out'
inp = ARGV.shift || 'AAAA'
bps = dasm = nil
while true
x = FuzzMem.new(file,inp,bps,dasm)
bps ||= x.bps
dasm ||= x.dasm
end
end