CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/rex/post/meterpreter/extensions/stdapi/railgun/library.rb
Views: 1904
1
# -*- coding: binary -*-
2
# Copyright (c) 2010, [email protected]
3
# All rights reserved.
4
#
5
# Redistribution and use in source and binary forms, with or without
6
# modification, are permitted provided that the following conditions are met:
7
# * Redistributions of source code must retain the above copyright
8
# notice, this list of conditions and the following disclaimer.
9
# * Redistributions in binary form must reproduce the above copyright
10
# notice, this list of conditions and the following disclaimer in the
11
# documentation and/or other materials provided with the distribution.
12
# * The names of the author may not be used to endorse or promote products
13
# derived from this software without specific prior written permission.
14
#
15
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
16
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18
# DISCLAIMED. IN NO EVENT SHALL [email protected] BE LIABLE FOR ANY
19
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
24
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
26
require 'rex/post/meterpreter/extensions/stdapi/railgun/library_function'
27
require 'rex/post/meterpreter/extensions/stdapi/railgun/library_helper'
28
require 'rex/post/meterpreter/extensions/stdapi/railgun/buffer_item'
29
require 'rex/post/meterpreter/extensions/stdapi/railgun/tlv'
30
require 'rex/post/meterpreter/packet'
31
32
module Rex
33
module Post
34
module Meterpreter
35
module Extensions
36
module Stdapi
37
module Railgun
38
39
#
40
# Represents a library, e.g. kernel32.dll
41
#
42
class Library
43
44
include LibraryHelper
45
46
@@datatype_map = {
47
'HANDLE' => 'LPVOID',
48
# really should be PVOID* but LPVOID is handled specially with the 'L' prefix to *not* treat it as a pointer, and
49
# for railgun's purposes LPVOID == ULONG_PTR
50
'PHANDLE' => 'PULONG_PTR',
51
'SIZE_T' => 'ULONG_PTR',
52
'PSIZE_T' => 'PULONG_PTR',
53
'PLPVOID' => 'PULONG_PTR',
54
'ULONG' => 'DWORD',
55
'PULONG' => 'PDWORD',
56
'NTSTATUS' => 'DWORD'
57
}.freeze
58
59
attr_accessor :functions
60
attr_reader :library_path
61
62
def initialize(library_path, consts_mgr)
63
@library_path = library_path
64
65
# needed by LibraryHelper
66
@consts_mgr = consts_mgr
67
68
self.functions = {}
69
end
70
71
def known_function_names
72
return functions.keys
73
end
74
75
def get_function(name)
76
return functions[name]
77
end
78
79
#
80
# Perform a function call in this library on the remote system.
81
#
82
# Returns a Hash containing the return value, the result of GetLastError(),
83
# and any +inout+ parameters.
84
#
85
# Raises an exception if +function+ is not a known function in this library,
86
# i.e., it hasn't been defined in a Def.
87
#
88
def call_function(function, args, client)
89
unless function.instance_of? LibraryFunction
90
func_name = function.to_s
91
92
unless known_function_names.include? func_name
93
raise "Library-function #{func_name} not found. Known functions: #{PP.pp(known_function_names, '')}"
94
end
95
96
function = get_function(func_name)
97
end
98
99
return process_function_call(function, args, client)
100
end
101
102
#
103
# Define a function for this library.
104
#
105
# Every function argument is described by a tuple (type,name,direction)
106
#
107
# Example:
108
# add_function("MessageBoxW", # name
109
# "DWORD", # return value
110
# [ # params
111
# ["DWORD","hWnd","in"],
112
# ["PWCHAR","lpText","in"],
113
# ["PWCHAR","lpCaption","in"],
114
# ["DWORD","uType","in"],
115
# ])
116
#
117
# Use +remote_name+ when the actual library name is different from the
118
# ruby variable. You might need to do this for example when the actual
119
# func name is myFunc@4 or when you want to create an alternative version
120
# of an existing function.
121
#
122
# When the new function is called it will return a list containing the
123
# return value and all inout params. See #call_function.
124
#
125
def add_function(name, return_type, params, remote_name=nil, calling_conv='stdcall')
126
return_type = reduce_type(return_type)
127
params = reduce_parameter_types(params)
128
if remote_name == nil
129
remote_name = name
130
end
131
@functions[name] = LibraryFunction.new(return_type, params, remote_name, calling_conv)
132
end
133
134
def build_packet_and_layouts(packet, function, args, arch)
135
case arch
136
when ARCH_X64
137
native = 'Q<'
138
when ARCH_X86
139
native = 'V'
140
else
141
raise NotImplementedError, 'Unsupported architecture (must be ARCH_X86 or ARCH_X64)'
142
end
143
144
# We transmit the immediate stack and three heap-buffers:
145
# in, inout and out. The reason behind the separation is bandwidth.
146
# We don't want to transmit uninitialized data in or no-longer-needed data out.
147
148
# out-only-buffers that are ONLY transmitted on the way BACK
149
out_only_layout = {} # paramName => BufferItem
150
out_only_size_bytes = 0
151
#puts " assembling out-only buffer"
152
function.params.each_with_index do |param_desc, param_idx|
153
#puts " processing #{param_desc[1]}"
154
155
# Special case:
156
# The user can choose to supply a Null pointer instead of a buffer
157
# in this case we don't need space in any heap buffer
158
if param_desc[0][0,1] == 'P' # type is a pointer (except LPVOID where the L negates this)
159
if args[param_idx] == nil
160
next
161
end
162
end
163
164
# we care only about out-only buffers
165
if param_desc[2] == 'out'
166
if !args[param_idx].kind_of? Integer
167
raise "error in param #{param_desc[1]}: Out-only buffers must be described by a number indicating their size in bytes"
168
end
169
buffer_size = args[param_idx]
170
if param_desc[0] == 'PULONG_PTR'
171
# bump up the size for an x64 pointer
172
if arch == ARCH_X64 && buffer_size == 4
173
buffer_size = args[param_idx] = 8
174
end
175
176
if arch == ARCH_X64
177
if buffer_size != 8
178
raise "Please pass 8 for 'out' PULONG_PTR, since they require a buffer of size 8"
179
end
180
elsif arch == ARCH_X86
181
if buffer_size != 4
182
raise "Please pass 4 for 'out' PULONG_PTR, since they require a buffer of size 4"
183
end
184
end
185
end
186
187
out_only_layout[param_desc[1]] = BufferItem.new(param_idx, out_only_size_bytes, buffer_size, param_desc[0])
188
out_only_size_bytes += buffer_size
189
end
190
end
191
192
in_only_layout, in_only_buffer = assemble_buffer('in', function, args, arch)
193
inout_layout, inout_buffer = assemble_buffer('inout', function, args, arch)
194
195
# now we build the stack
196
# every stack dword will be described by two dwords:
197
# first dword describes second dword:
198
# 0 - literal,
199
# 1 = relative to in-only buffer
200
# 2 = relative to out-only buffer
201
# 3 = relative to inout buffer
202
203
# (literal numbers and pointers to buffers we have created)
204
literal_pairs_blob = ""
205
#puts " assembling literal stack"
206
function.params.each_with_index do |param_desc, param_idx|
207
#puts " processing (#{param_desc[0]}, #{param_desc[1]}, #{param_desc[2]})"
208
buffer = nil
209
# is it a pointer to a buffer on our stack
210
if ['PULONG_PTR', 'PDWORD', 'PWCHAR', 'PCHAR', 'PBLOB'].include?(param_desc[0])
211
if ['PWCHAR', 'PCHAR', 'PBLOB'].include?(param_desc[0]) && param_desc[2] == 'in' && args[param_idx].is_a?(Integer)
212
# allow PWCHAR, PCHAR and PBLOB to also be passed as a pointer instead of a buffer
213
buffer = [0].pack(native)
214
num = param_to_number(args[param_idx])
215
buffer += [num].pack(native)
216
elsif args[param_idx] == nil # null pointer?
217
buffer = [0].pack(native) # type: LPVOID (so the library does not rebase it)
218
buffer += [0].pack(native) # value: 0
219
elsif param_desc[2] == 'in'
220
buffer = [1].pack(native)
221
buffer += [in_only_layout[param_desc[1]].addr].pack(native)
222
elsif param_desc[2] == 'out'
223
buffer = [2].pack(native)
224
buffer += [out_only_layout[param_desc[1]].addr].pack(native)
225
elsif param_desc[2] == 'inout'
226
buffer = [3].pack(native)
227
buffer += [inout_layout[param_desc[1]].addr].pack(native)
228
else
229
raise 'unexpected direction'
230
end
231
else
232
#puts " not a pointer"
233
# it's not a pointer (LPVOID is a pointer but is not backed by railgun memory, ala PBLOB)
234
buffer = [0].pack(native)
235
case param_desc[0]
236
when 'LPVOID', 'ULONG_PTR'
237
num = param_to_number(args[param_idx])
238
buffer += [num].pack(native)
239
when 'DWORD'
240
num = param_to_number(args[param_idx])
241
buffer += [num & 0xffffffff].pack(native)
242
when 'WORD'
243
num = param_to_number(args[param_idx])
244
buffer += [num & 0xffff].pack(native)
245
when 'BYTE'
246
num = param_to_number(args[param_idx])
247
buffer += [num & 0xff].pack(native)
248
when 'BOOL'
249
case args[param_idx]
250
when true
251
buffer += [1].pack(native)
252
when false
253
buffer += [0].pack(native)
254
else
255
raise "param #{param_desc[1]}: true or false expected"
256
end
257
else
258
raise "unexpected type for param #{param_desc[1]}"
259
end
260
end
261
262
#puts " adding pair to blob"
263
literal_pairs_blob += buffer
264
#puts " buffer size %X" % buffer.length
265
#puts " blob size so far: %X" % literal_pairs_blob.length
266
end
267
268
layouts = {in: in_only_layout, inout: inout_layout, out: out_only_layout}
269
270
packet.add_tlv(TLV_TYPE_RAILGUN_SIZE_OUT, out_only_size_bytes)
271
packet.add_tlv(TLV_TYPE_RAILGUN_STACKBLOB, literal_pairs_blob)
272
packet.add_tlv(TLV_TYPE_RAILGUN_BUFFERBLOB_IN, in_only_buffer)
273
packet.add_tlv(TLV_TYPE_RAILGUN_BUFFERBLOB_INOUT, inout_buffer)
274
275
packet.add_tlv(TLV_TYPE_RAILGUN_LIBNAME, @library_path)
276
packet.add_tlv(TLV_TYPE_RAILGUN_FUNCNAME, function.remote_name)
277
packet.add_tlv(TLV_TYPE_RAILGUN_CALLCONV, function.calling_conv)
278
[packet, layouts]
279
end
280
281
def build_response(packet, function, layouts, client)
282
case client.native_arch
283
when ARCH_X64
284
native = 'Q<'
285
when ARCH_X86
286
native = 'V'
287
else
288
raise NotImplementedError, 'Unsupported architecture (must be ARCH_X86 or ARCH_X64)'
289
end
290
291
rec_inout_buffers = packet.get_tlv_value(TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_INOUT)
292
rec_out_only_buffers = packet.get_tlv_value(TLV_TYPE_RAILGUN_BACK_BUFFERBLOB_OUT)
293
rec_return_value = packet.get_tlv_value(TLV_TYPE_RAILGUN_BACK_RET)
294
rec_last_error = packet.get_tlv_value(TLV_TYPE_RAILGUN_BACK_ERR)
295
rec_err_msg = packet.get_tlv_value(TLV_TYPE_RAILGUN_BACK_MSG)
296
297
# Error messages come back with trailing CRLF, so strip it out if we do get a message.
298
rec_err_msg.strip! unless rec_err_msg.nil?
299
300
# the hash the function returns
301
return_hash = {
302
'GetLastError' => rec_last_error,
303
'ErrorMessage' => rec_err_msg
304
}
305
306
# process return value
307
case function.return_type
308
when 'LPVOID', 'ULONG_PTR'
309
if client.native_arch == ARCH_X64
310
return_hash['return'] = rec_return_value
311
else
312
return_hash['return'] = rec_return_value & 0xffffffff
313
end
314
when 'DWORD'
315
return_hash['return'] = rec_return_value & 0xffffffff
316
when 'WORD'
317
return_hash['return'] = rec_return_value & 0xffff
318
when 'BYTE'
319
return_hash['return'] = rec_return_value & 0xff
320
when 'BOOL'
321
return_hash['return'] = (rec_return_value != 0)
322
when 'VOID'
323
return_hash['return'] = nil
324
when 'PCHAR'
325
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.read_string(rec_return_value)
326
return_hash['&return'] = rec_return_value
327
when 'PWCHAR'
328
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.read_wstring(rec_return_value)
329
return_hash['&return'] = rec_return_value
330
when 'PULONG_PTR'
331
if client.native_arch == ARCH_X64
332
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.memread(rec_return_value, 8)&.unpack1('Q<')
333
return_hash['&return'] = rec_return_value
334
else
335
return_hash['return'] = rec_return_value == 0 ? nil : client.railgun.util.memread(rec_return_value, 4)&.unpack1('V')
336
return_hash['&return'] = rec_return_value
337
end
338
else
339
raise "unexpected return type: #{function.return_type}"
340
end
341
342
# process out-only buffers
343
layouts[:out].each_pair do |param_name, buffer_item|
344
buffer = rec_out_only_buffers[buffer_item.addr, buffer_item.length_in_bytes]
345
case buffer_item.datatype
346
when 'PULONG_PTR'
347
return_hash[param_name] = buffer.unpack(native).first
348
when 'PDWORD'
349
return_hash[param_name] = buffer.unpack('V').first
350
when 'PCHAR'
351
return_hash[param_name] = asciiz_to_str(buffer)
352
when 'PWCHAR'
353
return_hash[param_name] = uniz_to_str(buffer)
354
when 'PBLOB'
355
return_hash[param_name] = buffer
356
else
357
raise "unexpected type in out-only buffer of #{param_name}: #{buffer_item.datatype}"
358
end
359
end
360
361
# process in-out buffers
362
layouts[:inout].each_pair do |param_name, buffer_item|
363
buffer = rec_inout_buffers[buffer_item.addr, buffer_item.length_in_bytes]
364
case buffer_item.datatype
365
when 'PULONG_PTR'
366
return_hash[param_name] = buffer.unpack(native).first
367
when 'PDWORD'
368
return_hash[param_name] = buffer.unpack('V').first
369
when 'PCHAR'
370
return_hash[param_name] = asciiz_to_str(buffer)
371
when 'PWCHAR'
372
return_hash[param_name] = uniz_to_str(buffer)
373
when 'PBLOB'
374
return_hash[param_name] = buffer
375
else
376
raise "unexpected type in in-out-buffer of #{param_name}: #{buffer_item.datatype}"
377
end
378
end
379
380
return_hash
381
end
382
383
private
384
385
def process_function_call(function, args, client)
386
raise "#{function.params.length} arguments expected. #{args.length} arguments provided." unless args.length == function.params.length
387
388
request, layouts = build_packet_and_layouts(
389
Packet.create_request(COMMAND_ID_STDAPI_RAILGUN_API),
390
function,
391
args,
392
client.native_arch
393
)
394
395
response = client.send_request(request)
396
397
build_response(response, function, layouts, client)
398
end
399
400
# perform type conversions as necessary to reduce the datatypes to their primitives
401
def reduce_parameter_types(params)
402
params.each_with_index do |param, idx|
403
type, name, direction = param
404
params[idx] = [reduce_type(type), name, direction]
405
end
406
407
params
408
end
409
410
def reduce_type(datatype)
411
while @@datatype_map.key?(datatype)
412
datatype = @@datatype_map[datatype]
413
end
414
415
datatype
416
end
417
end
418
419
end; end; end; end; end; end;
420
421