turborex 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +674 -0
  3. data/README.md +38 -0
  4. data/README.rdoc +19 -0
  5. data/examples/alpc_client.rb +15 -0
  6. data/examples/alpc_server.rb +14 -0
  7. data/examples/com_client.rb +19 -0
  8. data/examples/com_finder.rb +39 -0
  9. data/examples/create_instance.rb +15 -0
  10. data/examples/cstruct.rb +19 -0
  11. data/examples/find_com_client_calls.rb +16 -0
  12. data/examples/find_rpc_security_callback.rb +12 -0
  13. data/examples/rpc_finder.rb +117 -0
  14. data/examples/scan_exports.rb +5 -0
  15. data/examples/scan_imports.rb +5 -0
  16. data/examples/tinysdk.rb +17 -0
  17. data/lib/turborex.rb +21 -0
  18. data/lib/turborex/cstruct.rb +565 -0
  19. data/lib/turborex/cstruct/struct_helper.rb +7 -0
  20. data/lib/turborex/exception.rb +65 -0
  21. data/lib/turborex/fuzzer.rb +204 -0
  22. data/lib/turborex/fuzzer/containers.rb +115 -0
  23. data/lib/turborex/fuzzer/coverage.rb +67 -0
  24. data/lib/turborex/fuzzer/mutators.rb +25 -0
  25. data/lib/turborex/fuzzer/seed.rb +30 -0
  26. data/lib/turborex/monkey.rb +11 -0
  27. data/lib/turborex/msrpc.rb +14 -0
  28. data/lib/turborex/msrpc/decompiler.rb +244 -0
  29. data/lib/turborex/msrpc/midl.rb +747 -0
  30. data/lib/turborex/msrpc/ndrtype.rb +167 -0
  31. data/lib/turborex/msrpc/rpcbase.rb +777 -0
  32. data/lib/turborex/msrpc/rpcfinder.rb +1426 -0
  33. data/lib/turborex/msrpc/utils.rb +70 -0
  34. data/lib/turborex/pefile.rb +8 -0
  35. data/lib/turborex/pefile/pe.rb +61 -0
  36. data/lib/turborex/pefile/scanner.rb +82 -0
  37. data/lib/turborex/utils.rb +321 -0
  38. data/lib/turborex/windows.rb +402 -0
  39. data/lib/turborex/windows/alpc.rb +844 -0
  40. data/lib/turborex/windows/com.rb +266 -0
  41. data/lib/turborex/windows/com/client.rb +84 -0
  42. data/lib/turborex/windows/com/com_finder.rb +330 -0
  43. data/lib/turborex/windows/com/com_registry.rb +100 -0
  44. data/lib/turborex/windows/com/interface.rb +522 -0
  45. data/lib/turborex/windows/com/utils.rb +210 -0
  46. data/lib/turborex/windows/constants.rb +82 -0
  47. data/lib/turborex/windows/process.rb +56 -0
  48. data/lib/turborex/windows/security.rb +12 -0
  49. data/lib/turborex/windows/security/ace.rb +76 -0
  50. data/lib/turborex/windows/security/acl.rb +25 -0
  51. data/lib/turborex/windows/security/security_descriptor.rb +118 -0
  52. data/lib/turborex/windows/tinysdk.rb +89 -0
  53. data/lib/turborex/windows/utils.rb +138 -0
  54. data/resources/headers/alpc/ntdef.h +72 -0
  55. data/resources/headers/alpc/ntlpcapi.h +1014 -0
  56. data/resources/headers/rpc/common.h +162 -0
  57. data/resources/headers/rpc/guiddef.h +191 -0
  58. data/resources/headers/rpc/internal_ndrtypes.h +262 -0
  59. data/resources/headers/rpc/rpc.h +10 -0
  60. data/resources/headers/rpc/rpcdce.h +266 -0
  61. data/resources/headers/rpc/rpcdcep.h +187 -0
  62. data/resources/headers/rpc/rpcndr.h +39 -0
  63. data/resources/headers/rpc/v4_x64/rpcinternals.h +154 -0
  64. data/resources/headers/rpc/wintype.h +517 -0
  65. data/resources/headers/tinysdk/tinysdk.h +5 -0
  66. data/resources/headers/tinysdk/tinysdk/comdef.h +645 -0
  67. data/resources/headers/tinysdk/tinysdk/dbghelp.h +118 -0
  68. data/resources/headers/tinysdk/tinysdk/guiddef.h +194 -0
  69. data/resources/headers/tinysdk/tinysdk/memoryapi.h +12 -0
  70. data/resources/headers/tinysdk/tinysdk/poppack.h +12 -0
  71. data/resources/headers/tinysdk/tinysdk/pshpack4.h +13 -0
  72. data/resources/headers/tinysdk/tinysdk/winnt.h +1059 -0
  73. data/resources/headers/tinysdk/tinysdk/wintype.h +326 -0
  74. metadata +290 -0
@@ -0,0 +1,70 @@
1
+ module TurboRex
2
+ module MSRPC
3
+ module Utils
4
+ extend TurboRex::MSRPC::RPCBase
5
+
6
+ def get_interface_type
7
+ raise NotImplementedError
8
+ end
9
+
10
+ def gen_script_rpc_client_np(opts = {})
11
+ uuid = opts[:uuid]
12
+ version = opts[:version] || '1.0'
13
+ function = opts[:function]
14
+ data = opts[:data]
15
+ pipe = opts[:pipe]
16
+ output = opts[:output] || 'my_rpc_client.rb'
17
+
18
+ template = <<-EOS
19
+ #usage: ruby your_script.rb RHOST USERNAME PASSWORD
20
+ require 'rex'
21
+ require 'rex/encoder/ndr'
22
+
23
+ Rex::Proto::SMB::SimpleClient.class_eval do
24
+ attr_accessor :read_timeout
25
+ end
26
+
27
+ uuid = #{uuid}
28
+ version = #{version}
29
+ protocol = 'ncacn_np'
30
+ rhost = ARGV[0]
31
+ opts = ['#{pipe}']
32
+ handle = Rex::Proto::DCERPC::Handle.new([uuid, version], protocol, rhost, opts)
33
+ function = #{function}
34
+ data = #{data}
35
+
36
+ sock = Rex::Socket::Tcp.create('PeerHost' => rhost, 'PeerPort' => 445)
37
+ dcerpc = Rex::Proto::DCERPC::Client.new(handle, sock, {'smb_user' => ARGV[1], 'smb_pass' => ARGV[2]})
38
+ res = dcerpc.call(function, data, true)
39
+
40
+ puts res
41
+ EOS
42
+
43
+ file = File.new(output, 'rw')
44
+ file.puts template
45
+ file.close
46
+
47
+ true
48
+ end
49
+
50
+ def self.raw_to_guid_str(raw, upcase = true)
51
+ unpacked = raw.unpack("VvvCCa6")
52
+ mac = unpacked[5].unpack("C*")
53
+ unpacked[-1] = '%02x%02x%02x%02x%02x%02x' % mac
54
+ formatted = ("%08x-%04x-%04x-%02x%02x-%s" % unpacked)
55
+ upcase ? formatted.upcase : formatted
56
+ end
57
+
58
+ def self.read_cstring(isource, base=0)
59
+ len=0
60
+ cstr = ""
61
+ until (data=isource.read(base+len, 1)) == "\x00"
62
+ cstr << data
63
+ len+=1
64
+ end
65
+
66
+ return cstr, len
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,8 @@
1
+ require 'turborex/pefile/scanner'
2
+ require 'turborex/pefile/pe'
3
+
4
+ module TurboRex
5
+ module PEFile
6
+
7
+ end
8
+ end
@@ -0,0 +1,61 @@
1
+ require 'rex/peparsey'
2
+ require 'pathname'
3
+ require 'rgl/adjacency'
4
+
5
+ module TurboRex
6
+ module PEFile
7
+ class PE < Rex::PeParsey::Pe
8
+ attr_accessor :image_path
9
+ attr_reader :data_sections
10
+ attr_reader :executable_sections
11
+
12
+ def initialize(isource)
13
+ super(isource)
14
+
15
+ get_data_sections
16
+ get_executable_sections
17
+ end
18
+
19
+ def data_section_names
20
+ unless @data_sections.empty?
21
+ names = []
22
+ @data_sections.each do |section|
23
+ names << section.name
24
+ end
25
+
26
+ return names
27
+ end
28
+
29
+ nil
30
+ end
31
+
32
+ private
33
+
34
+ def get_data_sections
35
+ @data_sections = []
36
+ self.all_sections.each do |section|
37
+ next if section.flags.nil?
38
+ if section.flags & 0x20000000 != 0 #IMAGE_SCN_MEM_EXECUTE
39
+ next
40
+ end
41
+
42
+ unless section.flags & 0x40000000 != 0 #IMAGE_SCN_MEM_READ
43
+ next
44
+ end
45
+
46
+ @data_sections << section
47
+ end
48
+ end
49
+
50
+ def get_executable_sections
51
+ @executable_sections = []
52
+ self.all_sections.each do |section|
53
+ next if section.flags.nil?
54
+ if section.flags & 0x20000000 != 0 #IMAGE_SCN_MEM_EXECUTE
55
+ @executable_sections << section
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,82 @@
1
+ module TurboRex
2
+ module PEFile
3
+ module Scanner
4
+ require 'rgl/path'
5
+
6
+ def self.scan_section(section, regex)
7
+ index = 0
8
+
9
+ hits = []
10
+
11
+ while index < section.size && (index = section.index(regex, index)) != nil
12
+
13
+ idx = index
14
+ buf = ''
15
+ mat = nil
16
+
17
+ while (!(mat = buf.match(regex)))
18
+ buf << section.read(idx, 1)
19
+ idx += 1
20
+ end
21
+
22
+ rva = section.offset_to_rva(index)
23
+
24
+ hits << [rva, buf.unpack("H*")]
25
+ index += buf.length
26
+ end
27
+
28
+ return hits
29
+ end
30
+
31
+ def self.scan_all_sections(pe, regex)
32
+ result = []
33
+
34
+ pe.all_sections.each do |section|
35
+ Scanner.scan_section(section, regex).each do |r|
36
+ result << r
37
+ end
38
+ end
39
+ end
40
+
41
+ def self.data_section?(section)
42
+ if section.flags & 0x20000000 != 0 #IMAGE_SCN_MEM_EXECUTE
43
+ return false
44
+ end
45
+
46
+ unless section.flags & 0x40000000 != 0 #IMAGE_SCN_MEM_READ
47
+ return false
48
+ end
49
+
50
+ return true
51
+ end
52
+
53
+ def has_path?(dasm, addr1, addr2, dg=nil)
54
+ dg = draw_xrefs_dg(dasm, addr1) unless dg
55
+
56
+ v1 = dasm.get_label_at(addr1) || addr1.to_s
57
+ v2 = dasm.get_label_at(addr2) || addr2.to_s
58
+ dg.path?(v1, v2)
59
+ end
60
+
61
+ def draw_xrefs_dg(dasm, addr1)
62
+ g = dasm.function_graph_from(addr1)
63
+ dg = RGL::DirectedAdjacencyGraph.new
64
+
65
+ (g.keys + g.values).flatten.uniq.each do |e|
66
+ label = dasm.get_label_at(e) || e.to_s
67
+ dg.add_vertex label
68
+ end
69
+
70
+ g.each do |k, v|
71
+ kl = dasm.get_label_at(k) || k.to_s
72
+ v.each do |e|
73
+ el = dasm.get_label_at(e) || e.to_s
74
+ dg.add_edge(kl, el)
75
+ end
76
+ end
77
+
78
+ dg
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRex
4
+ module Utils
5
+ def self.get_all_subdir(root_path)
6
+ require 'find'
7
+ paths = []
8
+ Find.find(root_path) do |path|
9
+ if FileTest.directory?(path)
10
+ if File.basename(path)[0] == '.'
11
+ Find.prune
12
+ else
13
+ paths << path
14
+ end
15
+ end
16
+ end
17
+
18
+ paths
19
+ end
20
+
21
+ module DisassemblerHelper
22
+ # https://github.com/jjyg/metasm/blob/master/samples/dasm-plugins/imm2off.rb
23
+ def addrtolabel(dasm)
24
+ bp = dasm.prog_binding.invert
25
+ dasm.decoded.each_value do |di|
26
+ next unless di.is_a?(Metasm::DecodedInstruction)
27
+
28
+ di.each_expr do |e|
29
+ next unless e.is_a?(Metasm::Expression)
30
+
31
+ if l = bp[e.lexpr]
32
+ dasm.add_xref(e.lexpr, Metasm::Xref.new(:addr, di.address))
33
+ e.lexpr = Metasm::Expression[l]
34
+ end
35
+ if l = bp[e.rexpr]
36
+ dasm.add_xref(e.rexpr, Metasm::Xref.new(:addr, di.address))
37
+ e.rexpr = (e.lexpr ? Metasm::Expression[l] : l)
38
+ end
39
+ end
40
+ end
41
+ nil
42
+ end
43
+
44
+ def add_dasm_all_method(dasm)
45
+ dasm.instance_eval %(
46
+ def dasm_all(addrstart, length, method=:disassemble_fast_deep)
47
+ s = get_section_at(addrstart)
48
+ return if not s
49
+ s = s[0]
50
+ boff = s.ptr
51
+ off = 0
52
+ while off < length
53
+ if di = di_at(addrstart + off)
54
+ off += di.bin_length
55
+ elsif @decoded[addrstart+off]
56
+ off += 1
57
+ else
58
+ s.ptr = boff+off
59
+ maydi = cpu.decode_instruction(s, 0)
60
+ if not maydi
61
+ off += 1
62
+ elsif maydi.instruction.to_s =~ /nop|lea (.*), \[\1(?:\+0)?\]|mov (.*), \2|int 3/
63
+ off += maydi.bin_length
64
+ else
65
+ send(method, addrstart+off)
66
+ end
67
+ end
68
+ end
69
+
70
+ count = 0
71
+ off = 0
72
+ while off < length
73
+ addr = addrstart+off
74
+ if di = di_at(addr)
75
+ if di.block_head?
76
+ b = di.block
77
+ if not @function[addr] and b.from_subfuncret.to_a.empty? and b.from_normal.to_a.empty?
78
+ l = auto_label_at(addr, 'sub_orph')
79
+ @function[addrstart+off] = Metasm::DecodedFunction.new
80
+ @function[addrstart+off].finalized = true
81
+ detect_function_thunk(addr)
82
+ count += 1
83
+ end
84
+ end
85
+ off += di.bin_length
86
+ else
87
+ off += 1
88
+ end
89
+ end
90
+
91
+ end
92
+ )
93
+
94
+ dasm
95
+ end
96
+
97
+ def _disassemble_executable_sections(pe, method=:disassemble_fast_deep)
98
+ dasm = pe.disassembler
99
+ executable_sections = pe.section_info.select {|s| s.last.to_s.split(',').include?('MEM_EXECUTE')}
100
+ unless executable_sections.empty?
101
+ add_dasm_all_method(dasm)
102
+ executable_sections.each do |name, address, len|
103
+ dasm.dasm_all(address, len, method)
104
+ end
105
+
106
+ addrtolabel(dasm)
107
+ dasm
108
+ end
109
+ end
110
+
111
+ # https://github.com/jjyg/metasm/blob/2a088ff85e5b873570bc284a97ddd9f8b3b0a03a/metasm/gui/dasm_main.rb#L413
112
+ def backtrace(addr, dasm, e, narg={})
113
+ bd = {}
114
+ expr = Metasm::IndExpression.parse_string(e)
115
+ registers = (begin
116
+ dasm.cpu.dbg_register_list.map(&:to_s)
117
+ rescue StandardError
118
+ []
119
+ end)
120
+ expr.externals.grep(String).each do |w|
121
+ bd[w] = w.downcase.to_sym if registers.include? w.downcase
122
+ end
123
+
124
+ expr = expr.bind(bd).reduce do |e_|
125
+ e_.len ||= dasm.cpu.size / 8 if e_.is_a? Metasm::Indirection; nil
126
+ end
127
+
128
+ log = []
129
+ found = []
130
+ bt_opts = { log: log }
131
+ bt_opts.merge!(narg)
132
+ dasm.backtrace(expr, addr, bt_opts)
133
+ if found_log = log.assoc(:found)
134
+ found_log[1].each do |expr|
135
+ found << dasm.resolve(expr)
136
+ end
137
+ end
138
+
139
+ [found, log]
140
+ end
141
+
142
+ def solve_cppobj_call(dasm, di)
143
+ return unless di.opcode.props[:saveip]
144
+ fptr = dasm.get_xrefs_x(di)
145
+ return if fptr.to_a.length != 1
146
+ fptr = ::Metasm::Expression[fptr.first].reduce_rec
147
+ return unless fptr.kind_of? ::Metasm::Indirection
148
+ return unless fptr.pointer.lexpr.kind_of? Symbol
149
+ return unless fptr.pointer.rexpr.kind_of? Integer
150
+ log = []
151
+ vtbl = dasm.backtrace(fptr.pointer.lexpr, di.address, log: log)
152
+ vtbl.delete ::Metasm::Expression::Unknown
153
+
154
+ if vtbl.empty?
155
+ r = log.reverse_each.detect {|l| l[0] != :found && l[2] != ::Metasm::Expression[:unknown]}
156
+ vtbl = r[2].reduce_rec
157
+ else
158
+ vtbl = vtbl.first
159
+ end
160
+
161
+ vtbl = ::Metasm::Expression[vtbl].reduce_rec
162
+ return unless vtbl.kind_of? ::Metasm::Indirection
163
+ obj = vtbl.pointer
164
+ ptr_size = @dasm.program.cpu.size / 8
165
+ [obj, vtbl, fptr.pointer.rexpr / ptr_size]
166
+ end
167
+
168
+ def solve_guard_icall(dasm, di)
169
+ return unless di.opcode.props[:saveip]
170
+ fptr = dasm.get_xrefs_x(di)
171
+ return if fptr.to_a.length != 1
172
+ fptr = ::Metasm::Expression[fptr.first].reduce_rec
173
+ return unless fptr.kind_of? ::Metasm::Indirection
174
+
175
+ v = case dasm.cpu.size
176
+ when 32
177
+ dasm.program.decode_loadconfig.guard_check_icall
178
+ when 64
179
+ dasm.program.decode_loadconfig.guard_dispatch_icall
180
+ end
181
+
182
+ #f = dasm.decode_dword(dasm.normalize(dasm.prog_binding[v.to_s])).to_s
183
+ if v == fptr.pointer
184
+ case dasm.cpu.size
185
+ when 32
186
+ expr = :ecx
187
+ when 64
188
+ expr = :rax
189
+ end
190
+
191
+ log = []
192
+ res = dasm.backtrace(expr, di.address, log: log)
193
+ res.delete ::Metasm::Expression::Unknown
194
+ if res.empty?
195
+ r = log.reverse_each.detect {|l| l[0] != :found && l[2] != ::Metasm::Expression[:unknown]}
196
+ return r[2].reduce_rec
197
+ end
198
+
199
+ return res.first
200
+ end
201
+ end
202
+ end
203
+
204
+ module COMApiBacktraceHelper
205
+ include DisassemblerHelper
206
+
207
+ def bt_cocreateinstance(dasm, addr, filter={})
208
+ case dasm.cpu.size
209
+ when 32
210
+ expr_rclsid = '[esp]'
211
+ expr_context = '[esp+8]'
212
+ expr_riid = '[esp+12]'
213
+ expr_pv = '[[esp+16]]'
214
+ when 64
215
+ expr_rclsid = 'rcx'
216
+ expr_context = 'r8'
217
+ expr_riid = 'r9'
218
+ expr_pv = '[[rsp+32]]'
219
+ end
220
+
221
+ rclsid, context, riid, pv = [:unknown]*4
222
+ # rclsid
223
+ found, _ = backtrace(addr, dasm, expr_rclsid)
224
+ unless found.empty?
225
+ raw_rclsid = dasm.read_raw_data(found.first, 16)
226
+ rclsid = TurboRex::MSRPC::Utils.raw_to_guid_str(raw_rclsid)
227
+ if filter[:rclsid]
228
+ return unless rclsid == filter[:rclsid]
229
+ end
230
+ end
231
+
232
+ #context
233
+ found, _ = backtrace(addr, dasm, expr_context)
234
+ unless found.empty?
235
+ context = found.first
236
+ if filter[:context]
237
+ return unless context == filter[:context]
238
+ end
239
+ end
240
+
241
+ # riid
242
+ found, _ = backtrace(addr, dasm, expr_riid)
243
+ unless found.empty?
244
+ raw_riid = dasm.read_raw_data(found.first, 16)
245
+ riid = TurboRex::MSRPC::Utils.raw_to_guid_str(raw_riid)
246
+ if filter[:riid]
247
+ return unless riid == filter[:riid]
248
+ end
249
+ end
250
+
251
+ # pv
252
+ log = []
253
+ found, _ = backtrace(addr, dasm, expr_pv, log: log)
254
+ found.delete ::Metasm::Expression::Unknown
255
+ if found.empty?
256
+ r = log.reverse_each.detect {|l| l[0] != :found && l[2] != ::Metasm::Expression[:unknown]}
257
+ pv = r[2].reduce_rec
258
+ else
259
+ pv = found.first
260
+ end
261
+
262
+
263
+ {rclsid: rclsid, context: context, riid: riid, pv: pv}
264
+ end
265
+
266
+ # TODO: Backtrace ServerInfo
267
+ def bt_cocreateinstanceex(dasm, addr, filter={})
268
+ case dasm.cpu.size
269
+ when 32
270
+ expr_rclsid = '[esp]'
271
+ expr_context = '[esp+8]'
272
+ expr_count = '[esp+16]'
273
+ expr_results = '[esp+20]'
274
+ when 64
275
+ expr_rclsid = 'rcx'
276
+ expr_context = 'r8'
277
+ expr_count = 'dword ptr [rsp+32]'
278
+ expr_results = '[rsp+40]'
279
+ end
280
+
281
+ rclsid, context, iids = [:unknown]*3
282
+
283
+ # rclsid
284
+ found, _ = backtrace(addr, dasm, expr_rclsid)
285
+ unless found.empty?
286
+ raw_rclsid = dasm.read_raw_data(found.first, 16)
287
+ rclsid = TurboRex::MSRPC::Utils.raw_to_guid_str(raw_rclsid)
288
+ if filter[:rclsid]
289
+ return unless rclsid == filter[:rclsid]
290
+ end
291
+ end
292
+
293
+ #context
294
+ found, _ = backtrace(addr, dasm, expr_context)
295
+ unless found.empty?
296
+ context = found.first
297
+ if filter[:context]
298
+ return unless context == filter[:context]
299
+ end
300
+ end
301
+
302
+ # results and count
303
+ found, _ = backtrace(addr, dasm, expr_count)
304
+ unless found.empty?
305
+ count = found.first
306
+ iids = []
307
+ size = dasm.alloc_c_struct('MULTI_QI').sizeof
308
+ count.times do |i|
309
+ expr_iid = "[#{expr_results}+#{i*size}]"
310
+ found, _ = backtrace(addr, dasm, expr_iid)
311
+ unless found.empty?
312
+ iids << found.first
313
+ end
314
+ end
315
+ end
316
+
317
+ {rclsid: rclsid, context: context, iids: iids}
318
+ end
319
+ end
320
+ end
321
+ end