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,1426 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboRex
4
+ module MSRPC
5
+ module RPCFinder
6
+
7
+ # This module provides a set of helper functions
8
+ # that backtracking RPC runtime routines parameters to determine information
9
+ # such as address of security callback function and rpc flags.
10
+ module StaticRPCBacktracer
11
+ include TurboRex::Utils::DisassemblerHelper
12
+
13
+ # RpcServerRegisterIf2/RpcServerRegisterIf3/RpcServerRegisterIfEx
14
+ def bt_security_callback(dasm, trace_flags=false, uuid=nil)
15
+ res = []
16
+
17
+ case dasm.cpu.size
18
+ when 64
19
+ expr_if_handle = 'rcx'
20
+ expr_sec_cb = '[rsp+30h]'
21
+ expr_flag = 'r9' if trace_flags
22
+ mk_struct_proc = Proc.new {
23
+ TurboRex::MSRPC::RPCBase::RPC_SERVER_INTERFACE64.make(pack: 8, align: true)
24
+ }
25
+ when 32
26
+ expr_if_handle = '[esp]'
27
+ expr_sec_cb = '[esp+14h]'
28
+ expr_flag = '[esp+Ch]' if trace_flags
29
+ mk_struct_proc = Proc.new {
30
+ TurboRex::MSRPC::RPCBase::RPC_SERVER_INTERFACE32.make(pack: 4, align: true)
31
+ }
32
+ end
33
+
34
+ fn = ['RpcServerRegisterIf2', 'RpcServerRegisterIf3', 'RpcServerRegisterIfEx']
35
+ fn.each do |f|
36
+ callers = dasm.call_sites(Metasm::Expression[f])
37
+ callers.each do |addr|
38
+ server_if = mk_struct_proc.call
39
+ if_handle = backtrace(addr, dasm, expr_if_handle).first.first
40
+
41
+ next unless raw = dasm.read_raw_data(if_handle, server_if.slength)
42
+ server_if.from_s(raw)
43
+ interface_id = TurboRex::MSRPC::Utils.raw_to_guid_str(server_if['interfaceId'].to_s)
44
+
45
+ found, _ = backtrace(addr, dasm, expr_sec_cb)
46
+ found_flags, _ = backtrace(addr, dasm, expr_flag) if trace_flags
47
+ if dasm.get_section_at(found.first)
48
+ r = {interface_id: interface_id, callback: found.first}
49
+ r[:flags] = found_flags.first.to_i if trace_flags
50
+ res << r
51
+ end
52
+ end
53
+ end
54
+
55
+ res.uniq!
56
+
57
+ case uuid
58
+ when String
59
+ return res.select {|r| r[:interface_id] == uuid}
60
+ when Array
61
+ return res.select { |r| uuid.include?(r[:interface_id])}
62
+ else
63
+ return res
64
+ end
65
+ end
66
+ end
67
+
68
+ class Collection
69
+ attr_reader :server_interfaces
70
+ attr_reader :client_interfaces
71
+
72
+ def initialize
73
+ @server_interfaces = []
74
+ @client_interfaces = []
75
+ end
76
+
77
+ def push_server(i)
78
+ @server_interfaces << i
79
+ end
80
+
81
+ def push_client(i)
82
+ @client_interfaces << i
83
+ end
84
+
85
+ def draw_xrefs
86
+ @server_interfaces.each do |si|
87
+ ci = find_client_by_server(si)
88
+ next if ci.empty?
89
+
90
+ si.xrefs_to << ci
91
+ ci.xrefs_from << si
92
+
93
+ si.uniq!
94
+ ci.uniq!
95
+ end
96
+
97
+ true
98
+ end
99
+
100
+ def find_server_by_client(client)
101
+ @server_interfaces.select { |i| i.interface_id == client.interface_id }
102
+ end
103
+
104
+ def find_client_by_server(server)
105
+ @client_interfaces.select { |i| i.interface_id == server.interface_id }
106
+ end
107
+
108
+ def find_by_interface_id(id, filter = nil)
109
+ case filter
110
+ when nil
111
+ @server_interfaces.select { |i| i.interface_id == id } + \
112
+ @client_interfaces.select { |i| i.interface_id == id }
113
+ when :server
114
+ @server_interfaces.select { |i| i.interface_id == id }
115
+ when :client
116
+ @client_interfaces.select { |i| i.interface_id == id }
117
+ end
118
+ end
119
+
120
+ def find_by_routine(routine)
121
+ @server_interfaces.select { |i| i.routines.include?(routine) } + \
122
+ @client_interfaces.select { |i| i.routines.include?(routine) }
123
+ end
124
+
125
+ def find_by_midl_switches(*switches)
126
+ @server_interfaces.select { |i| i.midl_switches.has_one_of_switches?(switches) } + \
127
+ @client_interfaces.select { |i| i.midl_switches.has_one_of_switches?(switches) }
128
+ end
129
+ end
130
+
131
+ class ImageFinder
132
+ include TurboRex::Utils::DisassemblerHelper
133
+
134
+ class AutoFindConf
135
+ def initialize
136
+ @options = {
137
+ include_client: true,
138
+ find_client_routines: false
139
+ }
140
+ end
141
+
142
+ def build
143
+ @options
144
+ end
145
+
146
+ def exclude_client
147
+ @options[:include_client] = false
148
+ end
149
+
150
+ def only_client
151
+ @options[:only_client] = true
152
+ end
153
+
154
+ def find_client_routines
155
+ @options[:find_client_routines] = true if @options[:include_client]
156
+ end
157
+
158
+ alias only_server exclude_client
159
+ end
160
+
161
+ include Rex::PeParsey
162
+ include TurboRex::MSRPC::RPCBase
163
+ include TurboRex::PEFile
164
+ include TurboRex::PEFile::Scanner
165
+ include TurboRex::MSRPC::RPCFinder::StaticRPCBacktracer
166
+
167
+ class InterfaceModel
168
+ attr_accessor :dasm
169
+ attr_reader :struct
170
+ attr_accessor :protocol
171
+ attr_reader :interface_id
172
+ attr_reader :uuid
173
+ attr_reader :interface_ver
174
+ attr_reader :midl_switches
175
+ attr_reader :finder
176
+
177
+ attr_accessor :decompiler
178
+ attr_accessor :routines
179
+ attr_accessor :xrefs_to
180
+ attr_accessor :xrefs_from
181
+ attr_accessor :endpoints
182
+
183
+ attr_accessor :pproc_fs
184
+ attr_accessor :offset_table
185
+ attr_accessor :ptype_fs
186
+
187
+
188
+ def initialize(struct, finder, dasm = nil)
189
+ @struct = struct # RPC_SERVER_INTERFACE_Klass
190
+ @protocol = get_protocol
191
+ @interface_id = @uuid = TurboRex::MSRPC::Utils.raw_to_guid_str(struct.InterfaceId_Value)
192
+ @interface_ver = get_interface_ver
193
+ @transfer_syntax = TurboRex::MSRPC::Utils.raw_to_guid_str(struct.TransferSyntax_Value)
194
+ @transfer_syntax_ver = get_trans_syntax_ver
195
+ @midl_switches = RPCBase::MIDL_SWITCHES.new
196
+ @dasm = dasm || finder.dasm
197
+ @endpoints = []
198
+ @finder = finder
199
+ @routines = []
200
+ @xrefs_to = []
201
+ @xrefs_from = []
202
+ end
203
+
204
+ def server?
205
+ !@struct.dispatch_table_nullptr?
206
+ end
207
+
208
+ def client?
209
+ !server?
210
+ end
211
+
212
+ def set_fs
213
+ return false if client?
214
+ analysis_midl_switches
215
+
216
+ pe = finder.pe
217
+ @poffset_table = pe.vma_to_file_offset(self.InterpreterInfo.FormatStringOffset_Value)
218
+ @pproc_fs = pe.vma_to_file_offset(self.InterpreterInfo.ProcFormatString_Value)
219
+
220
+ if @midl_switches.has_switch?('Oi')
221
+ @ptype_fs = pe.vma_to_file_offset(self.InterpreterInfo.pStubDesc.pFormatTypes_Value)
222
+ mode = :Oi
223
+ elsif @midl_switches.has_switch?('all') && @midl_switches.arch_64?
224
+ return false # TODO: Implement
225
+ elsif @midl_switches.has_switch?('Oicf')
226
+ @ptype_fs = pe.vma_to_file_offset(self.InterpreterInfo.pStubDesc.pFormatTypes_Value)
227
+ end
228
+
229
+ true
230
+ end
231
+
232
+ def decompile
233
+ return false unless set_fs
234
+
235
+ @finder.ndr_decompiler.decompile(self)
236
+ end
237
+
238
+ def func_in_server_routines?(addr)
239
+ func_in_server_routines(addr).empty? ? false : true
240
+ end
241
+
242
+ def func_in_server_routines(addr)
243
+ return false unless server?
244
+
245
+ @finder.disassemble_executable_sections
246
+ res = []
247
+
248
+ routines = @routines
249
+ routines.each do |r|
250
+ res << r if @finder.has_path?(dasm, r.addr, addr)
251
+ end
252
+
253
+ res
254
+ end
255
+
256
+ def analysis_midl_switches
257
+ # Don't analyze the client interfaces. It is performed when parsing the client dispatch functions.
258
+ if server?
259
+ dispatch_funcs = @struct.DispatchTable.DispatchTable
260
+
261
+ # TODO: Refine the parse methods with format string
262
+ # Oi/Oicf/ndr64
263
+ unless dispatch_funcs.empty?
264
+ begin
265
+ @dasm.disassemble dispatch_funcs[0]
266
+ rescue StandardError
267
+ return false
268
+ end
269
+
270
+ label = @dasm.get_label_at dispatch_funcs[0]
271
+ case label
272
+ when /NdrServerCall2/i
273
+ @midl_switches << %w[Oif Oicf]
274
+ when /NdrServerCall/i
275
+ @midl_switches << %w[Oi Oic]
276
+ when /NdrServerCallNdr64/i
277
+ if @struct.ndr64?
278
+ @midl_switches << 'ndr64'
279
+ @midl_switches << %w[win64 amd64 ia64]
280
+ end
281
+ end
282
+ end
283
+
284
+ if (@struct.Flags & 0x6000000) == 0x6000000 # test /Oicf /protocol all /win64(amd64/ia64)
285
+ interpreter_info = @struct.InterpreterInfo
286
+ unless interpreter_info == 0
287
+ if interpreter_info.nCount >= 2 && interpreter_info.ProcString != 0
288
+ begin
289
+ interpreter_info.pSyntaxInfo.each do |syntax_info|
290
+ disp_funcs = syntax_info.DispatchTable.DispatchFunctions
291
+ @dasm.disassemble disp_funcs.first
292
+ label = @dasm.get_label_at disp_funcs.first
293
+
294
+ next unless label =~ /NdrServerCallAll/
295
+
296
+ @midl_switches << 'all'
297
+ @midl_switches << %w[win64 amd64 ia64]
298
+ @protocol = :all
299
+ end
300
+ rescue StandardError
301
+ # TODO: exception handle
302
+ end
303
+ end
304
+ end
305
+ end
306
+
307
+ # Os is contradictory with '/protocol ndr64/all'
308
+ unless @midl_switches.has_one_of_switches?(%w[Oi Oic Oif Oicf ndr64 all])
309
+ if @struct.interpreter_info_nullptr? && @struct.Flags == 0
310
+ @midl_switches << 'Os'
311
+ end
312
+ end
313
+ end
314
+ end
315
+
316
+ def method_missing(m, *args)
317
+ if m.to_s.start_with?('raw_')
318
+ camelcase_name = camelcase(m.to_s.sub('raw_', ''))
319
+ if @struct.respond_to? camelcase_name
320
+ @struct.public_send camelcase_name, *args
321
+ end
322
+ elsif @struct.respond_to? m
323
+ @struct.public_send m, *args
324
+ else
325
+ begin
326
+ @struct.public_send m, *args
327
+ rescue StandardError
328
+ super(m, *args)
329
+ end
330
+ end
331
+ end
332
+
333
+ private
334
+
335
+ def get_protocol
336
+ @protocol = if @struct.ndr64?
337
+ :ndr64
338
+ elsif @struct.dce?
339
+ :dce
340
+ else
341
+ :unknown
342
+ end
343
+ end
344
+
345
+ def get_interface_ver
346
+ major = @struct.InterfaceId.SyntaxVersion.MajorVersion
347
+ minor = @struct.InterfaceId.SyntaxVersion.MinorVersion
348
+ get_syntax_version(major, minor)
349
+ end
350
+
351
+ def get_trans_syntax_ver
352
+ major = @struct.TransferSyntax.SyntaxVersion.MajorVersion
353
+ minor = @struct.TransferSyntax.SyntaxVersion.MinorVersion
354
+ get_syntax_version(major, minor)
355
+ end
356
+
357
+ def get_syntax_version(_major, _minor)
358
+ major = _major.to_s.unpack('S')[0]
359
+ minor = _minor.to_s.unpack('S')[0]
360
+ "#{major}.#{minor}"
361
+ end
362
+
363
+ def camelcase(str)
364
+ str.split('_').collect(&:capitalize).join
365
+ end
366
+ end
367
+
368
+ attr_reader :pe
369
+ attr_reader :dasm
370
+ attr_reader :ndr_decompiler
371
+ attr_reader :server_interfaces
372
+ attr_reader :midl_server_infos
373
+ attr_reader :midl_syntax_infos
374
+ attr_reader :midl_stubless_proxy_infos
375
+ attr_reader :dispatch_funcs
376
+ attr_reader :server_routines
377
+
378
+ attr_reader :client_interfaces
379
+ attr_reader :client_routines
380
+
381
+ def initialize(pe, _opts = {})
382
+ open_file(pe)
383
+
384
+ @server_interfaces = []
385
+ @midl_server_infos = []
386
+ @midl_stub_descs = []
387
+ @midl_syntax_infos = []
388
+ @midl_stubless_proxy_infos = []
389
+ @server_routines = []
390
+ @dispatch_funcs = []
391
+ @client_interfaces = []
392
+ @dasm = new_dasm
393
+ @collection_proxy = _opts[:collection_proxy]
394
+
395
+ arch = @pe.ptr_32? ? 'x86' : 'x64'
396
+ @ndr_decompiler = TurboRex::MSRPC::Decompiler.new(arch: arch)
397
+ end
398
+
399
+ def self.glob_all(root)
400
+ Dir.glob(root + '/**/*')
401
+ end
402
+
403
+ def self.glob(path, suffixes = nil)
404
+ pattern = []
405
+ suffixes&.each { |suffix| pattern << File.join(path, '*') + suffix }
406
+
407
+ if block_given?
408
+ Dir.glob(pattern) do |filename|
409
+ yield(filename)
410
+ end
411
+ else
412
+ Dir.glob(pattern)
413
+ end
414
+ end
415
+
416
+ def open_file(filename)
417
+ begin
418
+ @pe = TurboRex::PEFile::PE.new_from_file(filename)
419
+ @pe.image_path = Pathname.new(filename)
420
+ rescue FileHeaderError
421
+ return false
422
+ end
423
+
424
+ pe
425
+ end
426
+
427
+ def close
428
+ unless @pe.nil?
429
+ @pe.close
430
+ @pe = nil
431
+ end
432
+
433
+ true
434
+ end
435
+
436
+ def auto_find(&block)
437
+ default = TurboRex::MSRPC::RPCFinder::ImageFinder::AutoFindConf.new
438
+ config = if block_given?
439
+ Docile.dsl_eval(default, &block).build
440
+ else
441
+ default.build
442
+ end
443
+
444
+ internal_auto_find(config)
445
+ end
446
+
447
+ def make_rpc_server_interface(pe)
448
+ if pe.ptr_32?
449
+ TurboRex::MSRPC::RPCBase::RPC_SERVER_INTERFACE32.make(pack: 4, align: true)
450
+ else
451
+ TurboRex::MSRPC::RPCBase::RPC_SERVER_INTERFACE64.make(pack: 8, align: true)
452
+ end
453
+ end
454
+
455
+ def make_midl_server_info(pe)
456
+ if pe.ptr_32?
457
+ TurboRex::MSRPC::RPCBase::MIDL_SERVER_INFO32.make
458
+ else
459
+ TurboRex::MSRPC::RPCBase::MIDL_SERVER_INFO64.make
460
+ end
461
+ end
462
+
463
+ def make_midl_stubless_proxy_info(pe)
464
+ if pe.ptr_32?
465
+ TurboRex::MSRPC::RPCBase::MIDL_STUBLESS_PROXY_INFO32.make(pack: 4, align: true)
466
+ else
467
+ TurboRex::MSRPC::RPCBase::MIDL_STUBLESS_PROXY_INFO64.make(pack: 8, align: true)
468
+ end
469
+ end
470
+
471
+ def make_midl_syntax_info(pe)
472
+ if pe.ptr_32?
473
+ TurboRex::MSRPC::RPCBase::MIDL_SYNTAX_INFO32.make(pack: 4, align: true)
474
+ else
475
+ TurboRex::MSRPC::RPCBase::MIDL_SYNTAX_INFO64.make(pack: 8, align: true)
476
+ end
477
+ end
478
+
479
+ def make_rpc_dispatch_table_t(pe)
480
+ if pe.ptr_32?
481
+ TurboRex::MSRPC::RPCBase::RPC_DISPATCH_TABLE_T.make(pack: 4, align: true)
482
+ else
483
+ TurboRex::MSRPC::RPCBase::RPC_DISPATCH_TABLE_T64.make(pack: 8, align: true)
484
+ end
485
+ end
486
+
487
+ def make_rpc_protseq_endpoint(pe)
488
+ if pe.ptr_32?
489
+ TurboRex::MSRPC::RPCBase::RPC_PROTSEQ_ENDPOINT32.make(pack: 4, align: true)
490
+ else
491
+ TurboRex::MSRPC::RPCBase::RPC_PROTSEQ_ENDPOINT64.make(pack: 8, align: true)
492
+ end
493
+ end
494
+
495
+ def make_midl_stub_desc(pe)
496
+ if pe.ptr_32?
497
+ TurboRex::MSRPC::RPCBase::MIDL_STUB_DESC.make(pack: 4, align: true)
498
+ else
499
+ TurboRex::MSRPC::RPCBase::MIDL_STUB_DESC64.make(pack: 8, align: true)
500
+ end
501
+ end
502
+
503
+ def get_midl_server_info(rpc_server_if)
504
+ reconstruct_midl_server_info(@pe, rpc_server_if)
505
+ end
506
+
507
+ def get_midl_stub_desc(midl_server_info)
508
+ reconstruct_midl_stub_desc(@pe, midl_server_info)
509
+ end
510
+
511
+ def get_stubless_pinfo_from_client_if(rpc_client_if)
512
+ reconstruct_stubless_pinfo(@pe, rpc_client_if)
513
+ end
514
+
515
+ def get_midl_syntax_info(midl_server_info)
516
+ reconstruct_midl_syntax_info(@pe, midl_server_info)
517
+ end
518
+
519
+ def get_dispatch_table(rpc_server_if)
520
+ reconstruct_disptbl_for_server_interface(@pe, rpc_server_if)
521
+ end
522
+
523
+ def get_offset_table(rpc_server_if)
524
+ reconstruct_offset_table(@pe, rpc_server_if)
525
+ end
526
+
527
+ def get_offset_table2(disptbl, midl_server_info)
528
+ reconstruct_offset_table2(@pe, disptbl, midl_server_info)
529
+ end
530
+
531
+ def get_endpoint_info(rpc_server_if)
532
+ reconstruct_endpoint_info(@pe, rpc_server_if)
533
+ end
534
+
535
+ def get_disp_functions(rpc_dispatch_table)
536
+ reconstruct_disp_functions(@pe, rpc_dispatch_table)
537
+ end
538
+
539
+ def get_rpc_server_routines(midl_server_info, count)
540
+ reconstruct_disptbl_for_midl_server_info(@pe, midl_server_info, count)
541
+ end
542
+
543
+ def get_routines_from_server_interface(rpc_server_interface)
544
+ if has_interpreter_info?(rpc_server_interface)
545
+ disptbl = get_dispatch_table(rpc_server_interface)
546
+ midl_server_info = get_midl_server_info(rpc_server_interface)
547
+ count = disptbl['dispatchTableCount'].value
548
+
549
+ get_rpc_server_routines(midl_server_info, count) if count > 0
550
+ end
551
+ end
552
+
553
+ def find_rpc_server_interface(opts = {})
554
+ rpc_server_interface = make_rpc_server_interface(@pe)
555
+ regexp = Regexp.new [rpc_server_interface.slength].pack('V')
556
+ res = []
557
+ opts[:only_data_section] ||= true
558
+
559
+ if opts[:only_data_section]
560
+ @pe.data_sections.each do |s|
561
+ TurboRex::PEFile::Scanner.scan_section(s, regexp).each do |r|
562
+ rpc_server_interface = make_rpc_server_interface(@pe)
563
+ next unless reconstruct_struct_from_pe(@pe, r[0], rpc_server_interface) > 0
564
+
565
+ if validate_rpc_server_interface(@pe, rpc_server_interface)
566
+ yield(rpc_server_interface, r[0]) if block_given?
567
+ res << rpc_server_interface
568
+ end
569
+ end
570
+ end
571
+ else
572
+ addr_info = TurboRex::PEFile::Scanner.scan_all_sections(@pe, regexp)
573
+ unless addr_info.empty?
574
+ addr_info.each do |addr|
575
+ rpc_server_interface = make_rpc_server_interface(@pe)
576
+ if reconstruct_struct_from_pe(@pe, addr[0], rpc_server_interface) > 0
577
+ yield(rpc_server_interface, addr[0]) if block_given?
578
+ res << rpc_server_interface
579
+ end
580
+ end
581
+ end
582
+ end
583
+
584
+ res
585
+ end
586
+
587
+ def find_client_disp_functions(va, dasm, expr, *funcs)
588
+ disassemble_executable_sections(dasm)
589
+ # addrtolabel(dasm)
590
+ dispatch_funcs = []
591
+
592
+ if expr.nil?
593
+ case dasm.cpu.size
594
+ when 64
595
+ expr = 'rcx'
596
+ when 32
597
+ expr = '[esp]'
598
+ end
599
+ end
600
+
601
+ funcs.each do |func|
602
+ callers = dasm.call_sites(Metasm::Expression[func])
603
+ callers.each do |addr|
604
+ found, log = backtrace(addr, dasm, expr)
605
+ next unless found.include?(va)
606
+
607
+ # Finding proc number
608
+ proc_num = nil
609
+ case func
610
+ when 'NdrClientCall' # Oi, conflict with 64-bit platform
611
+ expr_procnum = '[esp+4]'
612
+ switch = :Oi
613
+ when 'NdrClientCall2' # Oicf
614
+ expr_procnum = dasm.cpu.size == 64 ? 'rdx' : '[esp+4]'
615
+ switch = :Oicf
616
+ when 'NdrClientCall3'
617
+ expr_procnum = 'rdx'
618
+ when 'NdrClientCall4' # ?
619
+ expr_procnum = dasm.cpu.size == 64 ? 'rdx' : '[esp+4]'
620
+ end
621
+
622
+ _found, _log = backtrace(addr, dasm, expr_procnum)
623
+
624
+ unless _found.empty?
625
+ if func == 'NdrClientCall3'
626
+ proc_num = _found.first
627
+ else
628
+ _dasm = dasm.dup
629
+ _dasm.c_parser = @ndr_decompiler.parser
630
+ fs_header, header_len = @ndr_decompiler.parse_proc_fs_header_dasm(_dasm, _found[0])
631
+ proc_num = fs_header.oi_header.common.ProcNum
632
+ end
633
+ end
634
+
635
+ yield(addr, dasm) if block_given?
636
+ func_start = dasm.find_function_start(addr)
637
+ dispatch_funcs << {
638
+ dispatch_func: func_start,
639
+ backtrace: [func, va, log],
640
+ proc_num: proc_num
641
+ }
642
+ end
643
+ end
644
+
645
+ dispatch_funcs
646
+ end
647
+
648
+ def find_client_routines(client_if, client_if_addr, dasm = nil)
649
+ dasm = @dasm || (@dasm = new_dasm)
650
+ disp_funcs = []
651
+
652
+ if has_proxy_info?(client_if) # stubless proxy, no dispatch table and thunk table
653
+ if proxy_info = get_stubless_pinfo_from_client_if(client_if)
654
+ pi_obj = TurboRex::MSRPC::RPCBase::MIDL_STUBLESS_PROXY_INFO_Klass.new(proxy_info)
655
+ pinterpreter_info = client_if.InterpreterInfo_Value
656
+ @midl_stubless_proxy_infos << pi_obj
657
+ client_if.midl_switches << %w[all win64 amd64 ia64]
658
+ client_if.link_to pi_obj
659
+ disp_funcs = find_client_disp_functions(pinterpreter_info, dasm, nil, 'NdrClientCall3')
660
+ end
661
+ else
662
+ xrefs = scan_xrefs_immediate(client_if_addr, dasm)
663
+ xrefs.each do |xref|
664
+ midl_stub_desc = make_midl_stub_desc(@pe)
665
+ reconstruct_struct_from_pe(@pe, @pe.vma_to_rva(xref), midl_stub_desc)
666
+ next unless validate_midl_stub_desc(@pe, midl_stub_desc)
667
+
668
+ stub_desc_obj = MIDL_STUB_DESC_Klass.new(midl_stub_desc)
669
+ stub_desc_obj.link_to client_if
670
+ @midl_stub_descs << stub_desc_obj
671
+
672
+ disp_funcs = find_client_disp_functions(xref, dasm, nil, 'NdrClientCall', 'NdrClientCall2', 'NdrClientCall4')
673
+
674
+ # TODO: detect switches when using NdrClientCall4
675
+ next unless b = disp_funcs[0]&.fetch(:backtrace) { }
676
+
677
+ client_if.midl_switches << %w[Oi Oic] if b[0] == 'NdrClientCall'
678
+ if b[0] == 'NdrClientCall2'
679
+ client_if.midl_switches << %w[Oif Oicf]
680
+ end
681
+ end
682
+ end
683
+
684
+ disp_funcs.map do |m|
685
+ r = TurboRex::MSRPC::RPCBase::CLIENT_ROUTINE_Klass.new(m[:dispatch_func])
686
+ r.proc_num = m[:proc_num]
687
+ r
688
+ end.uniq(&:addr)
689
+ end
690
+
691
+ def validate_transfer_syntax(transfer_syntax)
692
+ transfer_syntax.to_s == DCE_TransferSyntax.to_s || transfer_syntax.to_s == NDR64_TransferSyntax.to_s
693
+ end
694
+
695
+ def validate_rpc_server_interface(pe, rpc_server_interface)
696
+ length = rpc_server_interface.slength
697
+ return false unless rpc_server_interface['length'].value == length
698
+ unless validate_transfer_syntax(rpc_server_interface['transferSyntax'])
699
+ return false
700
+ end
701
+
702
+ itpInfo = rpc_server_interface['interpreterInfo'].value # vma
703
+ if itpInfo == 0
704
+ # TODO: Inline stub check
705
+ else
706
+ section = pe._find_section_by_rva(pe.vma_to_rva(itpInfo))
707
+ return false if section.nil?
708
+ return false unless TurboRex::PEFile::Scanner.data_section?(section)
709
+ end
710
+
711
+ dispTable = rpc_server_interface['dispatchTable'].value
712
+ unless dispTable == 0
713
+ section = pe._find_section_by_rva(pe.vma_to_rva(dispTable))
714
+ return false if section.nil?
715
+ return false unless TurboRex::PEFile::Scanner.data_section?(section)
716
+ end
717
+
718
+ true
719
+ end
720
+
721
+ def validate_server_interface_from_pe(pe, address)
722
+ make_rpc_server_interface(pe)
723
+ reconstruct_struct_from_pe(pe, address, rpc_server_interface)
724
+ validate_rpc_server_interface(pe, rpc_server_interface)
725
+ end
726
+
727
+ def validate_midl_stub_desc(pe, struct)
728
+ pfnAllocate = struct['pfnAllocate'].value
729
+ pfnFree = struct['pfnFree'].value
730
+ phandle = struct['implicit_handle_info'].value
731
+ bounds_flag = struct['fCheckBounds'].value
732
+
733
+ # TODO: more check(version)
734
+ pointer_check = pe.valid_vma?(pfnAllocate) && pe.valid_vma?(pfnFree)
735
+ # && pe.valid_vma?(phandle)
736
+ # Rex library valid_vma? method make a mistake here.
737
+ bounds_flag_check = (bounds_flag == 1 || bounds_flag == 0)
738
+
739
+ pointer_check && bounds_flag
740
+ end
741
+
742
+ # The "strict_check" option will use the algorithm of NdrClientCall3
743
+ def validate_stubless_proxy_info(pe, stubless_proxy_info, strict_check = true)
744
+ pTransferSyntax = stubless_proxy_info['pTransferSyntax'].value
745
+ pSyntaxInfo = stubless_proxy_info['pSyntaxInfo'].value
746
+ nCount = stubless_proxy_info['nCount'].value
747
+ dce_transfer = DCE_TransferSyntax.to_s
748
+ ndr64_transfer = NDR64_TransferSyntax.to_s
749
+ len = make_midl_syntax_info(pe).slength
750
+
751
+ return false if pTransferSyntax == 0
752
+
753
+ transfer_syntax = pe._isource.read(pe.vma_to_file_offset(pTransferSyntax), DCE_TransferSyntax.slength)
754
+ unless transfer_syntax == dce_transfer || transfer_syntax == ndr64_transfer
755
+ return false
756
+ end
757
+
758
+ if strict_check
759
+ nCount.times do |i|
760
+ syntaxinfo_trans = pe._isource.read(pe.vma_to_file_offset(pSyntaxInfo + i * len), DCE_TransferSyntax.slength)
761
+ break if syntaxinfo_trans == transfer_syntax
762
+ return false if i + 1 == nCount
763
+ end
764
+ end
765
+
766
+ true
767
+ end
768
+
769
+ def reconstruct_struct_from_pe(pe, rva, cstruct)
770
+ length = cstruct.slength
771
+ data = pe._isource.read(pe.rva_to_file_offset(rva), length)
772
+ cstruct.from_s data
773
+ end
774
+
775
+ def reconstruct_offset_table(pe, server_if)
776
+ if server_if['interpreterInfo'].value != 0
777
+ server_info = reconstruct_midl_server_info(pe, server_if)
778
+
779
+ unless server_info.nil?
780
+ pdisptbl = pe.vma_to_rva(server_if['dispatchTable'].value)
781
+ disptbl = reconstruct_disptbl_from_addr(pe, pdisptbl)
782
+
783
+ reconstruct_offset_table2(pe, disptbl, server_info)
784
+ end
785
+ end
786
+ end
787
+
788
+ def reconstruct_offset_table2(pe, disptbl, midl_server_info)
789
+ poffset_table = pe.vma_to_file_offset(midl_server_info['fmtStringOffset'].value)
790
+ count = disptbl['dispatchTableCount'].value
791
+ pe._isource.read(poffset_table, count).unpack('C'*count)
792
+ end
793
+
794
+ def reconstruct_endpoint_info(pe, server_if)
795
+ endpoints = []
796
+
797
+ if (count = server_if['rpcProtseqEndpointCount'].value) > 0
798
+ rva = pe.vma_to_rva(server_if['rpcProtseqEndpoint'].value)
799
+
800
+ count.times do |i|
801
+ ep = make_rpc_protseq_endpoint(pe)
802
+ reconstruct_struct_from_pe(pe, rva+i*ep.slength, ep)
803
+ pprotseq = pe.vma_to_file_offset(ep['rpcProtocolSequence'].value)
804
+ pendpoint = pe.vma_to_file_offset(ep['endpoint'].value)
805
+
806
+ protseq = TurboRex::MSRPC::Utils.read_cstring(pe._isource, pprotseq)[0]
807
+ ep_name = TurboRex::MSRPC::Utils.read_cstring(pe._isource, pendpoint)[0]
808
+ endpoints << {protseq: protseq, ep_name: ep_name}
809
+ i+=ep.slength
810
+ end
811
+
812
+ end
813
+
814
+ endpoints
815
+ end
816
+
817
+ def reconstruct_stubless_pinfo(pe, client_if)
818
+ proxy_info = make_midl_stubless_proxy_info(pe)
819
+ pinterpreter_info = client_if.InterpreterInfo_Value
820
+ rva = pe.vma_to_rva(pinterpreter_info)
821
+ reconstruct_struct_from_pe(pe, rva, proxy_info)
822
+ proxy_info if validate_stubless_proxy_info(pe, proxy_info)
823
+ end
824
+
825
+ def reconstruct_midl_syntax_info(pe, midl_server_info)
826
+ pSyntaxInfo = midl_server_info['pSyntaxInfo'].value
827
+ count = midl_server_info['nCount'].value
828
+ return nil if count < 0
829
+
830
+ syntax_infos = []
831
+
832
+ if pe.ptr_32?
833
+ len = TurboRex::MSRPC::RPCBase::MIDL_SYNTAX_INFO32.make(pack: 4, align: true).slength
834
+ else
835
+ len = TurboRex::MSRPC::RPCBase::MIDL_SYNTAX_INFO64.make(pack: 8, align: true).slength
836
+ end
837
+
838
+ unless pSyntaxInfo == 0
839
+ count.times do |i|
840
+ rva = pe.vma_to_rva(pSyntaxInfo + i * len)
841
+ midl_syntax_info = make_midl_syntax_info(pe)
842
+ reconstruct_struct_from_pe(pe, rva, midl_syntax_info)
843
+
844
+ syntax_infos << midl_syntax_info
845
+ end
846
+ end
847
+
848
+ syntax_infos
849
+ end
850
+
851
+ def reconstruct_midl_server_info(pe, rpc_server_interface)
852
+ if validate_rpc_server_interface(pe, rpc_server_interface) && has_interpreter_info?(rpc_server_interface)
853
+ rva = pe.vma_to_rva(rpc_server_interface['interpreterInfo'].value)
854
+ midl_server_info = make_midl_server_info(pe)
855
+ reconstruct_struct_from_pe(pe, rva, midl_server_info)
856
+
857
+ midl_server_info
858
+ end
859
+ end
860
+
861
+ def reconstruct_midl_stub_desc(pe, midl_server_info)
862
+ unless midl_server_info['pStubDesc'].value == 0
863
+ rva = pe.vma_to_rva(midl_server_info['pStubDesc'].value)
864
+ midl_stub_desc = make_midl_stub_desc(@pe)
865
+ reconstruct_struct_from_pe(pe, rva, midl_stub_desc)
866
+ midl_stub_desc if validate_midl_stub_desc(pe, midl_stub_desc)
867
+ end
868
+ end
869
+
870
+ def reconstruct_disptbl_for_server_interface(pe, rpc_server_interface)
871
+ rva = pe.vma_to_rva(rpc_server_interface['dispatchTable'].value)
872
+ reconstruct_disptbl_from_addr(pe, rva)
873
+ end
874
+
875
+ def reconstruct_disptbl_from_addr(pe, addr)
876
+ rpc_dispatch_table = make_rpc_dispatch_table_t(pe)
877
+ reconstruct_struct_from_pe(pe, addr, rpc_dispatch_table)
878
+
879
+ rpc_dispatch_table
880
+ end
881
+
882
+ def reconstruct_disp_functions(pe, rpc_dispatch_table)
883
+ count = rpc_dispatch_table['dispatchTableCount'].value
884
+ pdispatch_table = pe.vma_to_rva(rpc_dispatch_table['dispatchTable'].value)
885
+ dispatch_funcs = []
886
+
887
+ if pe.ptr_32?
888
+ ptr_len = 4
889
+ format = 'V'
890
+ func_name = 'read_dword'
891
+ else
892
+ ptr_len = 8
893
+ format = 'Q<'
894
+ func_name = 'read_qword'
895
+ end
896
+
897
+ unless pdispatch_table == 0
898
+ count.times do |i|
899
+ code = "#{func_name}(pe._isource, pe.rva_to_file_offset(pdispatch_table + #{i * ptr_len})).unpack('#{format}')[0]"
900
+ begin
901
+ dispatch_funcs << eval(code)
902
+ rescue StandardError
903
+ next
904
+ end
905
+ end
906
+ end
907
+
908
+ dispatch_funcs
909
+ end
910
+
911
+ def reconstruct_disptbl_for_midl_server_info(pe, midl_server_info, count)
912
+ rva = pe.vma_to_rva(midl_server_info['dispatchTable'].value)
913
+ server_routines = []
914
+
915
+ if pe.ptr_32?
916
+ count.times do
917
+ server_routines << read_dword(pe._isource, pe.rva_to_file_offset(rva)).unpack('V')[0]
918
+ rva += 4
919
+ end
920
+ else
921
+ count.times do
922
+ server_routines << read_qword(pe._isource, pe.rva_to_file_offset(rva)).unpack('Q<')[0]
923
+ rva += 8
924
+ end
925
+ end
926
+
927
+ server_routines
928
+ end
929
+
930
+ def new_dasm
931
+ exe = Metasm::PE.decode_file @pe.image_path.to_s
932
+
933
+ exe.disassembler
934
+ end
935
+
936
+ def scan_xrefs_immediate(addr, dasm = nil)
937
+ dasm ||= (@dasm || new_dasm)
938
+ cpu_size = dasm.cpu.size
939
+ mask = (1 << cpu_size) - 1
940
+ format = (cpu_size == 64 ? 'q' : 'V')
941
+ res = []
942
+
943
+ dasm.sections.sort.each do |start_addr, encoded_data|
944
+ raw = encoded_data.data.to_str
945
+ (0..raw.length - cpu_size / 8).each do |offset|
946
+ data = raw[offset, cpu_size / 8].unpack(format).first
947
+ res << (start_addr + offset) if data == addr
948
+ end
949
+ end
950
+
951
+ res
952
+ end
953
+
954
+
955
+ # Xrefs in the same binary file
956
+ def draw_ifs_xrefs
957
+ @server_interfaces.each do |si|
958
+ @client_interfaces.each do |ci|
959
+ calls = []
960
+ ci.routines.each do |cr|
961
+ unless (res = si.func_in_server_routines(cr.addr)).empty?
962
+ calls << { caller: res, called: cr }
963
+ end
964
+ end
965
+
966
+ next if calls.empty?
967
+
968
+ si.xrefs_from << [ci, calls]
969
+ ci.xrefs_to << [si, calls]
970
+
971
+ si.xrefs_from.uniq!
972
+ si.xrefs_to.uniq!
973
+ end
974
+ end
975
+ end
976
+
977
+
978
+
979
+ def disassemble(addr, dasm = nil)
980
+ dasm ||= (@dasm || new_dasm)
981
+ res = dasm.disassemble addr
982
+ [dasm, res]
983
+ end
984
+
985
+ def disassemble_fast_deep(addr, dasm = nil)
986
+ dasm ||= (@dasm || new_dasm)
987
+ res = dasm.disassemble_fast_deep(addr)
988
+ [dasm, res]
989
+ end
990
+
991
+ def disassemble_fast(addr, dasm = nil)
992
+ dasm ||= (@dasm || new_dasm)
993
+ res = dasm.disassemble_fast(addr)
994
+ [dasm, res]
995
+ end
996
+
997
+ def decompile_func(addr)
998
+ dasm = disassemble_fast(addr)[0]
999
+ dasm.decompiler.decompile(addr) # Metasm::C::Parser
1000
+ end
1001
+
1002
+ def disassemble_executable_sections(dasm = nil)
1003
+ exe_sections = @pe.executable_sections
1004
+ unless exe_sections.empty?
1005
+ dasm ||= (@dasm || new_dasm)
1006
+ add_dasm_all_method(dasm)
1007
+
1008
+ exe_sections.each do |s|
1009
+ dasm.dasm_all(@pe.rva_to_vma(s.base_rva), s.raw_size)
1010
+ end
1011
+
1012
+ addrtolabel(dasm)
1013
+ dasm
1014
+ end
1015
+ end
1016
+
1017
+ private
1018
+
1019
+ def read_dword(isource, address)
1020
+ isource.read(address, 4)
1021
+ end
1022
+
1023
+ def read_qword(isource, address)
1024
+ isource.read(address, 8)
1025
+ end
1026
+
1027
+ def has_interpreter_info?(rpc_server_interface)
1028
+ # Don't assume when flag is set. Must check if InterpreterInfo is a null pointer.
1029
+ (rpc_server_interface['flags'].value & 0x4000000) == 0x4000000 && \
1030
+ rpc_server_interface['interpreterInfo'].value != 0
1031
+ end
1032
+
1033
+ def has_proxy_info?(rpc_server_interface)
1034
+ flags = (begin
1035
+ rpc_server_interface['flags'].value
1036
+ rescue StandardError
1037
+ rpc_server_interface.Flags
1038
+ end)
1039
+ interpreter_info = (begin
1040
+ rpc_server_interface['interpreterInfo'].value
1041
+ rescue StandardError
1042
+ rpc_server_interface.InterpreterInfo
1043
+ end)
1044
+ (flags & 0x2000000) == 0x2000000 && \
1045
+ interpreter_info != 0
1046
+ end
1047
+
1048
+ def internal_auto_find(config = {})
1049
+ @server_interfaces = []
1050
+ @dispatch_funcs = []
1051
+ @server_routines = []
1052
+ @client_routines = []
1053
+ @client_interfaces = []
1054
+ @midl_stub_descs = []
1055
+
1056
+ find_rpc_server_interface do |r, s_addr|
1057
+ next if r.nil?
1058
+ si_obj = TurboRex::MSRPC::RPCBase::RPC_SERVER_INTERFACE_Klass.new(r)
1059
+ interface = InterfaceModel.new si_obj, self
1060
+ interface.endpoints = get_endpoint_info(r)
1061
+
1062
+ if interface.dispatch_table_nullptr? # maybe client
1063
+ next unless config[:include_client]
1064
+
1065
+ @client_interfaces << interface
1066
+ @collection_proxy&.push_client(interface)
1067
+
1068
+ if config[:find_client_routines]
1069
+ cr = find_client_routines(interface, @pe.rva_to_vma(s_addr))
1070
+ @client_routines |= cr
1071
+ interface.routines = cr
1072
+ end
1073
+
1074
+ else # server
1075
+ next if config[:only_client]
1076
+
1077
+ @server_interfaces << interface
1078
+ @collection_proxy&.push_server(interface)
1079
+
1080
+ disp_table = get_dispatch_table(r)
1081
+ disp_func = get_disp_functions(disp_table)
1082
+ @dispatch_funcs |= disp_func
1083
+
1084
+ disp_table_obj = TurboRex::MSRPC::RPCBase::RPC_DISPATCH_TABLE_Klass.new(disp_table)
1085
+ disp_table_obj.link_to disp_func
1086
+ interface.link_to disp_table_obj
1087
+
1088
+ msi = get_midl_server_info(r)
1089
+ if msi.nil? # Inline stub(-Os mode)
1090
+ @dispatch_funcs |= disp_func
1091
+ else
1092
+ msi_obj = TurboRex::MSRPC::RPCBase::MIDL_SERVER_INFO_Klass.new(msi)
1093
+ @midl_server_infos << msi_obj
1094
+ interface.link_to msi_obj
1095
+
1096
+ # find all server routines
1097
+ routines = get_routines_from_server_interface(r)
1098
+ unless routines.nil?
1099
+ r_objs = routines.map { |r| TurboRex::MSRPC::RPCBase::SERVER_ROUTINE_Klass.new(r) }
1100
+ @server_routines |= r_objs
1101
+ interface.routines = r_objs
1102
+ msi_obj.link_to r_objs
1103
+ end
1104
+
1105
+ # reconstruct MIDL_SYNTAX_INFO
1106
+ midl_syntax_info = get_midl_syntax_info(msi)
1107
+ unless midl_syntax_info.nil? || midl_syntax_info.empty?
1108
+ objs = []
1109
+ midl_syntax_info.each do |m|
1110
+ syntax_info_obj = TurboRex::MSRPC::RPCBase::MIDL_SYNTAX_INFO_Klass.new(m)
1111
+ pdisp_tbl = syntax_info_obj.DispatchTable
1112
+ unless pdisp_tbl == 0
1113
+ disptbl = reconstruct_disptbl_from_addr(@pe, @pe.vma_to_rva(pdisp_tbl))
1114
+ disp_funcs = reconstruct_disp_functions(@pe, disptbl)
1115
+ disptbl_obj = RPC_DISPATCH_TABLE_Klass.new(disptbl)
1116
+ disptbl_obj.link_to disp_funcs
1117
+ syntax_info_obj.link_to disptbl_obj
1118
+ end
1119
+
1120
+ @midl_syntax_infos << syntax_info_obj
1121
+ objs << syntax_info_obj
1122
+ end
1123
+
1124
+ msi_obj.link_to objs
1125
+ end
1126
+
1127
+ # reconstruct MIDL_STUB_DESC
1128
+ midl_stub_desc = get_midl_stub_desc(msi)
1129
+ unless midl_stub_desc.nil?
1130
+ stub_desc_obj = TurboRex::MSRPC::RPCBase::MIDL_STUB_DESC_Klass.new(midl_stub_desc)
1131
+ msi_obj.link_to stub_desc_obj
1132
+ end
1133
+
1134
+ # reconstruct offset_table
1135
+ interface.offset_table = get_offset_table2(disp_table, msi)
1136
+ end
1137
+ end
1138
+
1139
+ interface.analysis_midl_switches if config[:analysis_switches]
1140
+ end
1141
+ end
1142
+ end
1143
+
1144
+ class MemoryFinder
1145
+ attr_reader :process
1146
+ attr_reader :header
1147
+
1148
+ include TurboRex::Windows::Utils
1149
+ include TurboRex::MSRPC::Utils
1150
+
1151
+ using ::RefineAllocCStruct
1152
+
1153
+ ## Abstract data model for some key rpc structures
1154
+ class RPC_Interface
1155
+ attr_accessor :flags
1156
+ attr_accessor :interface_type
1157
+ attr_accessor :interface_id
1158
+ attr_accessor :name
1159
+ attr_accessor :syntax
1160
+ end
1161
+
1162
+ class RPC_Endpoint
1163
+ end
1164
+
1165
+ class RPC_AuthInfo
1166
+ end
1167
+
1168
+ def initialize(pid, opts = {})
1169
+ raise 'Not work on non-Windows os.' unless ::OS.windows?
1170
+
1171
+ if opts[:debug_priv]
1172
+ unless Metasm::WinOS.get_debug_privilege
1173
+ raise 'Unable to get SeDebugPrivilege.'
1174
+ end
1175
+ end
1176
+
1177
+ @process = open_process(pid)
1178
+ @mem = @process.memory
1179
+ opts[:force_load] ||= {}
1180
+
1181
+ unless load_headers(opts[:force_load])
1182
+ raise 'Unable to load RPC structure definitions.'
1183
+ end
1184
+
1185
+ @header.prepare_visualstudio
1186
+
1187
+ @gRpcServer = nil
1188
+ @rpc_interfaces = []
1189
+ @server_interfaces = []
1190
+ @endpoints = []
1191
+ end
1192
+
1193
+ def self.list_process_pid
1194
+ TurboRex::Windows.list_all_process_pid
1195
+ end
1196
+
1197
+ def close
1198
+ @process.close
1199
+ end
1200
+
1201
+ def process_handle
1202
+ @process.handle
1203
+ end
1204
+
1205
+ def find_rpc_server
1206
+ @gRpcServer = find_global_rpc_server
1207
+ end
1208
+
1209
+ def enum_rpc_interfaces(rpc_server_t)
1210
+ num_entries = rpc_server_t.InterfaceDict.NumberOfEntries
1211
+ dictsize = num_entries * ptr_len
1212
+
1213
+ rpc_interface_t = @header['RPC_INTERFACE_T'] # read data as RPC_INTERFACE_T
1214
+ begin
1215
+ data = @mem.get_page(rpc_server_t.InterfaceDict.pArray, dictsize)
1216
+ return false if data.nil?
1217
+
1218
+ (0..dictsize).step(ptr_len) do |p|
1219
+ prpc_interface_t = if pe.ptr_32?
1220
+ data[p, ptr_len].unpack('V')[0]
1221
+ else
1222
+ data[p, ptr_len].unpack('Q<')[0]
1223
+ end
1224
+
1225
+ interface_t = rpc_interface_t.from_str(@mem.get_page(prpc_interface_t, rpc_interface_t.size))
1226
+ get_rpc_interfaces_info(interface_t)
1227
+ end
1228
+ rescue StandardError
1229
+ false
1230
+ end
1231
+ end
1232
+
1233
+ def get_rpc_interfaces_info(rpc_interface)
1234
+ info = TurboRex::MSRPC::RPCFinder::MemoryFinder::RPC_Interface.new
1235
+ info.flags = rpc_interface.Flags
1236
+ info.interface_type = get_interface_type(rpc_interface)
1237
+ info.interface_id = TurboRex::MSRPC::Utils.raw_to_guid_str(rpc_interface.RpcServerInterface.InterfaceId.to_string)
1238
+ if info.interface_type == TurboRex::MSRPC::RPCBase::InterfaceType::DCOM
1239
+ info.name = get_com_interface_name(info.interface_id)
1240
+ end
1241
+ info.syntax = TurboRex::MSRPC::Utils.raw_to_guid_str(rpc_interface.RpcServerInterface.TransferSyntax.to_string)
1242
+
1243
+ case info.interface_type
1244
+ when TurboRex::MSRPC::RPCBase::InterfaceType::RPC
1245
+ get_location(rpc_interface.RpcServerInterface.DispatchTable)
1246
+ end
1247
+ end
1248
+
1249
+ def get_interface_type(rpc_interface)
1250
+ if rpc_interface.Flags == @header.numeric_constants.assoc('RPC_IF_OLE')[1]
1251
+ return TurboRex::MSRPC::RPCBase::InterfaceType::OLE
1252
+ end
1253
+
1254
+ uuid = TurboRex::MSRPC::Utils.raw_to_guid_str(rpc_interface.RpcServerInterface.InterfaceId.to_string)
1255
+ if get_com_interface_name(uuid)
1256
+ return TurboRex::MSRPC::RPCBase::InterfaceType::DCOM
1257
+ end
1258
+
1259
+ TurboRex::MSRPC::RPCBase::InterfaceType::RPC
1260
+ end
1261
+
1262
+ def get_com_interface_name(interface_id)
1263
+ require 'win32/registry'
1264
+ case @arch
1265
+ when 'x86'
1266
+ prefix = ''
1267
+ when 'x64'
1268
+ prefix = 'Wow6432Node\\'
1269
+ end
1270
+ begin
1271
+ Win32::Registry::HKEY_CLASSES_ROOT.open(prefix + "Interface\\{#{interface_id}}") do |reg|
1272
+ return reg.read('')[1] # default value
1273
+ end
1274
+ rescue StandardError
1275
+ false
1276
+ end
1277
+ end
1278
+
1279
+ def get_location(_addr)
1280
+ raise NotImplementedError
1281
+ end
1282
+
1283
+ def scan_marker(marker, range, size = marker.size, step = 1)
1284
+ mem = @mem
1285
+ res = []
1286
+
1287
+ range.step(step) do |va|
1288
+ data = mem.get_page(va, size)
1289
+ yield(data, va) if block_given?
1290
+
1291
+ unless data.nil?
1292
+ res << va if data == marker
1293
+ end
1294
+ end
1295
+
1296
+ res
1297
+ end
1298
+
1299
+ def find_global_rpc_server
1300
+ rpcrt4 = @process.modules.select { |m| m.path =~ /rpcrt4.dll/i }[0]
1301
+
1302
+ pe = TurboRex::PEFile::PE.new_from_file(rpcrt4.path)
1303
+ data_section = pe.sections.select { |s| s.name == '.data' }[0]
1304
+ startaddr = rpcrt4.addr + data_section.vma
1305
+ endaddr = startaddr + data_section._section_header.v['Misc']
1306
+ ptr_len = pe.ptr_32? ? 4 : 8 && @header.llp64
1307
+ max_entries = @header.numeric_constants.assoc('MAX_SIMPLE_DICT_ENTRIES')[1]
1308
+ pe.close
1309
+
1310
+ scan_marker(nil, startaddr..endaddr, ptr_len) do |data|
1311
+ pointer = if pe.ptr_32?
1312
+ data.unpack('V')[0]
1313
+ else
1314
+ data.unpack('Q<')[0]
1315
+ end
1316
+
1317
+ rpc_server_t = @header['RPC_SERVER_T'] # read data as RPC_SERVER_T
1318
+ begin
1319
+ data = @mem.get_page(pointer, rpc_server_t.size)
1320
+ rescue StandardError
1321
+ next
1322
+ end
1323
+ rpc_server_t.from_str data
1324
+
1325
+ num_entries = rpc_server_t.InterfaceDict.NumberOfEntries
1326
+ dictsize = num_entries * ptr_len
1327
+ next if num_entries > max_entries || num_entries <= 0
1328
+
1329
+ rpc_interface_t = @header['RPC_INTERFACE_T'] # read data as RPC_INTERFACE_T
1330
+ begin
1331
+ data = @mem.get_page(rpc_server_t.InterfaceDict.pArray, dictsize)
1332
+ next if data.nil?
1333
+
1334
+ (0..dictsize).step(ptr_len) do |p|
1335
+ prpc_interface_t = if pe.ptr_32?
1336
+ data[p, ptr_len].unpack('V')[0]
1337
+ else
1338
+ data[p, ptr_len].unpack('Q<')[0]
1339
+ end
1340
+
1341
+ interface_t = rpc_interface_t.from_str(@mem.get_page(prpc_interface_t, rpc_interface_t.size))
1342
+ if interface_t.pRpcServer == pointer
1343
+ if interface_t.RpcServerInterface.TransferSyntax.to_string == TurboRex::MSRPC::RPCBase::DCE_TransferSyntax.to_s
1344
+ return rpc_server_t
1345
+ end
1346
+ end
1347
+ end
1348
+ rescue StandardError
1349
+ next
1350
+ end
1351
+ end
1352
+ end
1353
+
1354
+ private
1355
+
1356
+ def open_process(pid)
1357
+ p = TurboRex::Windows.open_process(pid, Metasm::WinAPI::PROCESS_VM_READ)
1358
+ raise "Unable to open process #{pid}" if p.nil?
1359
+
1360
+ case p.addrsz
1361
+ when 32
1362
+ @arch = 'x86'
1363
+ when 64
1364
+ @arch = 'x64'
1365
+ end
1366
+
1367
+ p
1368
+ end
1369
+
1370
+ def load_headers(force_load = {})
1371
+ headers_path = TurboRex.root + '/resources/headers/rpc'
1372
+ include_path = TurboRex::Utils.get_all_subdir(headers_path)
1373
+ version_hl = get_version('rpcrt4.dll')
1374
+ version = ((version_hl[0] << 32) + version_hl[1])
1375
+ opts = {}
1376
+ distance = 0
1377
+ approximation = nil
1378
+
1379
+ opts[:include_path] = include_path
1380
+ if force_load[:file] && force_load[:cpu]
1381
+ return force_load_file(force_load)
1382
+ end
1383
+
1384
+ if @process.addrsz == 32
1385
+ opts[:cpu] = Metasm::Ia32
1386
+ pattern = '/v*_x86/rpcinternals.h'
1387
+ elsif @process.addrsz == 64
1388
+ opts[:cpu] = Metasm::X86_64
1389
+ pattern = '/v*_x64/rpcinternals.h'
1390
+ end
1391
+
1392
+ Dir.glob(headers_path + pattern).each do |f|
1393
+ opts[:file] = f
1394
+ native_parser = TurboRex::CStruct::NativeParser.new(nil, opts)
1395
+ initializer = native_parser.parser.toplevel.symbol['RPC_CORE_RUNTIME_VERSION'].initializer
1396
+ initializer.each do |i|
1397
+ if i.rexpr == version
1398
+ @header = native_parser
1399
+ return true
1400
+ else
1401
+ d = (version - i.rexpr).abs
1402
+ distance = d if distance == 0
1403
+
1404
+ if d < distance
1405
+ approximation = [i.rexpr, native_parser]
1406
+ distance = d
1407
+ end
1408
+ end
1409
+ end
1410
+ end
1411
+
1412
+ if force_load[:approximation]
1413
+ @header = approximation[1]
1414
+ @approximation = approximation
1415
+ end
1416
+
1417
+ true
1418
+ end
1419
+
1420
+ def force_load_file(opts)
1421
+ @header = TurboRex::CStruct::NativeParser.new(nil, opts)
1422
+ end
1423
+ end
1424
+ end
1425
+ end
1426
+ end