rex-core 0.1.34 → 0.1.35
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 +4 -4
- data/lib/rex/core/version.rb +1 -1
- data/lib/rex/io/fiber_scheduler.rb +156 -0
- data/lib/rex/io/relay_manager.rb +113 -0
- data/lib/rex/io/socket_abstraction.rb +17 -93
- metadata +4 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 722c1d73aa2712e77940801d1f02e44565e0234944e7c50d8d82f6db845bce0f
|
|
4
|
+
data.tar.gz: 295ecbe54da2c7b54f6f19b7c8676140fcb2f18dcc660eaedfef7aa3c5633b9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2e5510ad24f1770fb127efd2cee8b0b08653be57eb15b7c44e51ef03a1fc6d23c5636259c632544a1d7ed17fa08dca289ee2dc3161c730803ab852a63ec677f
|
|
7
|
+
data.tar.gz: a5998d868389e099d9108dcdf9a4f86d0e91b6053f2cc3cb2fea75e84e3f5ddfa86136d018f70cd150817e86d12a455f4bce9ddcf0794f1ab512a64c606c4f40
|
data/lib/rex/core/version.rb
CHANGED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
require 'fiber'
|
|
2
|
+
require 'io/nonblock'
|
|
3
|
+
require 'rex/compat'
|
|
4
|
+
|
|
5
|
+
module Rex
|
|
6
|
+
module IO
|
|
7
|
+
|
|
8
|
+
# This fiber scheduler is heavily base on
|
|
9
|
+
# https://blog.monotone.dev/ruby/2020/12/25/ruby-3-fiber.html
|
|
10
|
+
class FiberScheduler
|
|
11
|
+
def initialize
|
|
12
|
+
@readable = {}
|
|
13
|
+
@writable = {}
|
|
14
|
+
@waiting = {}
|
|
15
|
+
@ready = []
|
|
16
|
+
@pending = []
|
|
17
|
+
@blocking = 0
|
|
18
|
+
@urgent = Rex::Compat.pipe
|
|
19
|
+
@mutex = Mutex.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# This allows the fiber to be scheduled in the #run thread from another thread
|
|
23
|
+
def schedule_fiber(&block)
|
|
24
|
+
@mutex.synchronize do
|
|
25
|
+
@pending << block
|
|
26
|
+
end
|
|
27
|
+
# Wake up the scheduler
|
|
28
|
+
@urgent.last.write_nonblock('.') rescue nil
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run
|
|
32
|
+
while @readable.any? or @writable.any? or @waiting.any? or @blocking.positive? or @ready.any? or @pending.any?
|
|
33
|
+
# Start any pending fibers
|
|
34
|
+
pending_blocks = []
|
|
35
|
+
@mutex.synchronize do
|
|
36
|
+
pending_blocks = @pending.dup
|
|
37
|
+
@pending.clear
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
pending_blocks.each do |block|
|
|
41
|
+
fiber = Fiber.new(blocking: false, &block)
|
|
42
|
+
fiber.resume
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
begin
|
|
46
|
+
readable, writable = ::IO.select(@readable.keys + [@urgent.first], @writable.keys, [], 0.1)
|
|
47
|
+
rescue ::IOError
|
|
48
|
+
cleanup_closed_ios
|
|
49
|
+
next
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Drain the urgent pipe
|
|
53
|
+
if readable&.include?(@urgent.first)
|
|
54
|
+
@urgent.first.read_nonblock(1024) rescue nil
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
readable&.each do |io|
|
|
58
|
+
next if io == @urgent.first
|
|
59
|
+
|
|
60
|
+
if fiber = @readable.delete(io)
|
|
61
|
+
fiber.resume
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
writable&.each do |io|
|
|
66
|
+
if fiber = @writable.delete(io)
|
|
67
|
+
fiber.resume
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@waiting.keys.each do |fiber|
|
|
72
|
+
if current_time > @waiting[fiber]
|
|
73
|
+
@waiting.delete(fiber)
|
|
74
|
+
fiber.resume
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
ready, @ready = @ready, []
|
|
79
|
+
ready.each do |fiber|
|
|
80
|
+
fiber.resume
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def io_wait(io, events, timeout)
|
|
86
|
+
unless (events & ::IO::READABLE).zero?
|
|
87
|
+
@readable[io] = Fiber.current
|
|
88
|
+
end
|
|
89
|
+
unless (events & ::IO::WRITABLE).zero?
|
|
90
|
+
@writable[io] = Fiber.current
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Fiber.yield
|
|
94
|
+
events
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def kernel_sleep(duration = nil)
|
|
98
|
+
block(:sleep, duration)
|
|
99
|
+
true
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def block(blocker, timeout = nil)
|
|
103
|
+
if timeout
|
|
104
|
+
@waiting[Fiber.current] = current_time + timeout
|
|
105
|
+
begin
|
|
106
|
+
Fiber.yield
|
|
107
|
+
ensure
|
|
108
|
+
@waiting.delete(Fiber.current)
|
|
109
|
+
end
|
|
110
|
+
else
|
|
111
|
+
@blocking += 1
|
|
112
|
+
begin
|
|
113
|
+
Fiber.yield
|
|
114
|
+
ensure
|
|
115
|
+
@blocking -= 1
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def unblock(blocker, fiber)
|
|
121
|
+
@ready << fiber
|
|
122
|
+
io = @urgent.last
|
|
123
|
+
io.write_nonblock('.')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def close
|
|
127
|
+
run
|
|
128
|
+
@urgent.each(&:close)
|
|
129
|
+
@urgent = nil
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def closed?
|
|
133
|
+
@urgent.nil?
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def fiber(&block)
|
|
137
|
+
fiber = Fiber.new(blocking: false, &block)
|
|
138
|
+
fiber.resume
|
|
139
|
+
fiber
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
private
|
|
143
|
+
|
|
144
|
+
def current_time
|
|
145
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def cleanup_closed_ios
|
|
149
|
+
@readable.delete_if { |io, _| io.closed? rescue true }
|
|
150
|
+
@writable.delete_if { |io, _| io.closed? rescue true }
|
|
151
|
+
@waiting.delete_if { |fiber, _| !fiber || !fiber.alive? rescue true }
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
end
|
|
156
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require 'rex/io/fiber_scheduler'
|
|
2
|
+
|
|
3
|
+
module Rex
|
|
4
|
+
module IO
|
|
5
|
+
|
|
6
|
+
# An IO RelayManager which will read data from a socket and write it to a sink
|
|
7
|
+
# using a background thread.
|
|
8
|
+
class RelayManager
|
|
9
|
+
attr_reader :thread
|
|
10
|
+
|
|
11
|
+
def initialize
|
|
12
|
+
@thread = nil
|
|
13
|
+
@scheduler = FiberScheduler.new
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Add a IO relay to the manager. This will start relaying data from the source
|
|
17
|
+
# socket to the destination sink immediately. An optional "on_exit" callback
|
|
18
|
+
# can be provided which will be called when the socket is closed.
|
|
19
|
+
#
|
|
20
|
+
# @param sock [::Socket] The source socket that data will be read from.
|
|
21
|
+
# @param sink [#write, #call] A data destination where read data will be sent. It is called
|
|
22
|
+
# with one parameter, the data to be transferred. If the object exposes a #write method, it will
|
|
23
|
+
# be called repeatedly all data is processed and the return value will be used to determine how
|
|
24
|
+
# much of the data was written. If the object exposes a #call method, it will be called once and
|
|
25
|
+
# must handle processing all the data it is provided.
|
|
26
|
+
# @param name [String] A human-friendly name for the relay used in debug output.
|
|
27
|
+
# @param on_exit [#call] A callback to be invoked when sink can no longer be read from.
|
|
28
|
+
def add_relay(sock, sink: nil, name: nil, on_exit: nil)
|
|
29
|
+
@scheduler.schedule_fiber do
|
|
30
|
+
relay_fiber(sock, sink, name, on_exit: on_exit)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
start unless running?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Write all data to the specified IO. This is intended to be used in scenarios
|
|
37
|
+
# where partial writes are possible but not desirable.
|
|
38
|
+
#
|
|
39
|
+
# @param io [#write] An object to write the data to. It must return a number indicating
|
|
40
|
+
# how many bytes of the provided data were processed.
|
|
41
|
+
# @param data [String] The data that should be written.
|
|
42
|
+
def self.io_write_all(io, data)
|
|
43
|
+
offset = 0
|
|
44
|
+
while offset < data.bytesize
|
|
45
|
+
written = io.write(data.byteslice(offset..-1))
|
|
46
|
+
offset += written
|
|
47
|
+
end
|
|
48
|
+
data.bytesize
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def running?
|
|
54
|
+
@thread && @thread.alive?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def start
|
|
58
|
+
return false if running?
|
|
59
|
+
|
|
60
|
+
@thread = Thread.new { run }
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def run
|
|
65
|
+
old_scheduler = Fiber.scheduler
|
|
66
|
+
# A fiber scheduler can be set per-thread
|
|
67
|
+
Fiber.set_scheduler(@scheduler)
|
|
68
|
+
|
|
69
|
+
# Run the scheduler (blocks here)
|
|
70
|
+
@scheduler.run
|
|
71
|
+
ensure
|
|
72
|
+
Fiber.set_scheduler(old_scheduler)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def relay_fiber(sock, sink, name, on_exit: nil)
|
|
76
|
+
loop do
|
|
77
|
+
break if sock.closed?
|
|
78
|
+
|
|
79
|
+
buf = sock.readpartial(32_768)
|
|
80
|
+
write_to_sink(sink, buf)
|
|
81
|
+
end
|
|
82
|
+
rescue EOFError
|
|
83
|
+
nil
|
|
84
|
+
rescue => e
|
|
85
|
+
message = "#{self.class.name}#relay_fiber(name: #{name}): #{e.class} #{e}"
|
|
86
|
+
if defined?(elog) # elog is defined by framework, otherwise use stderr
|
|
87
|
+
elog(message, error: e)
|
|
88
|
+
else
|
|
89
|
+
$stderr.puts message
|
|
90
|
+
end
|
|
91
|
+
ensure
|
|
92
|
+
unless sock.closed?
|
|
93
|
+
sock.close rescue nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
on_exit.call if on_exit
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def write_to_sink(sink, data)
|
|
100
|
+
if sink.respond_to?(:write)
|
|
101
|
+
self.class.io_write_all(sink, data)
|
|
102
|
+
|
|
103
|
+
elsif sink.respond_to?(:call)
|
|
104
|
+
sink.call(data)
|
|
105
|
+
|
|
106
|
+
else
|
|
107
|
+
raise ArgumentError, "Unsupported sink type: #{sink.inspect}"
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
end
|
|
113
|
+
end
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
# -*- coding: binary -*-
|
|
2
2
|
|
|
3
3
|
require 'socket'
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
require 'rex/io/relay_manager'
|
|
5
6
|
|
|
6
7
|
module Rex
|
|
7
8
|
module IO
|
|
@@ -12,6 +13,7 @@ module Rex
|
|
|
12
13
|
#
|
|
13
14
|
###
|
|
14
15
|
module SocketAbstraction
|
|
16
|
+
|
|
15
17
|
###
|
|
16
18
|
#
|
|
17
19
|
# Extension information for required Stream interface.
|
|
@@ -54,7 +56,7 @@ module Rex
|
|
|
54
56
|
def cleanup_abstraction
|
|
55
57
|
lsock.close if lsock and !lsock.closed?
|
|
56
58
|
|
|
57
|
-
monitor_thread.join if monitor_thread&.alive?
|
|
59
|
+
monitor_thread.join if monitor_thread&.alive? && monitor_thread&.object_id != Thread.current.object_id
|
|
58
60
|
|
|
59
61
|
rsock.close if rsock and !rsock.closed?
|
|
60
62
|
|
|
@@ -114,105 +116,27 @@ module Rex
|
|
|
114
116
|
#
|
|
115
117
|
attr_reader :rsock
|
|
116
118
|
|
|
117
|
-
|
|
118
|
-
def close
|
|
119
|
-
@close_requested = true
|
|
120
|
-
@monitor_thread.join
|
|
121
|
-
nil
|
|
122
|
-
end
|
|
119
|
+
protected
|
|
123
120
|
|
|
124
|
-
|
|
125
|
-
|
|
121
|
+
def monitor_rsock(name = 'MonitorRemote')
|
|
122
|
+
if respond_to?(:close_write)
|
|
123
|
+
on_exit = method(:close_write)
|
|
124
|
+
else
|
|
125
|
+
on_exit = nil
|
|
126
126
|
end
|
|
127
127
|
|
|
128
|
-
|
|
129
|
-
attr_writer :monitor_thread
|
|
128
|
+
monitor_sock(rsock, sink: self, name: name, on_exit: on_exit)
|
|
130
129
|
end
|
|
131
130
|
|
|
132
|
-
|
|
131
|
+
def monitor_sock(sock, sink:, name:, on_exit: nil)
|
|
132
|
+
@relay_manager ||= Rex::IO::RelayManager.new
|
|
133
|
+
@relay_manager.add_relay(sock, sink: sink, name: name, on_exit: on_exit)
|
|
134
|
+
end
|
|
133
135
|
|
|
134
|
-
def
|
|
135
|
-
|
|
136
|
-
rsock.monitor_thread = self.monitor_thread = Rex::ThreadFactory.spawn(threadname, false) do
|
|
137
|
-
loop do
|
|
138
|
-
closed = rsock.nil? || rsock.close_requested
|
|
139
|
-
|
|
140
|
-
if closed
|
|
141
|
-
wlog('monitor_rsock: the remote socket has been closed, exiting loop')
|
|
142
|
-
break
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
buf = nil
|
|
146
|
-
|
|
147
|
-
begin
|
|
148
|
-
s = Rex::ThreadSafe.select([rsock], nil, nil, 0.2)
|
|
149
|
-
next if s.nil? || s[0].nil?
|
|
150
|
-
rescue Exception => e
|
|
151
|
-
wlog("monitor_rsock: exception during select: #{e.class} #{e}")
|
|
152
|
-
closed = true
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
unless closed
|
|
156
|
-
begin
|
|
157
|
-
buf = rsock.sysread(32_768)
|
|
158
|
-
if buf.nil?
|
|
159
|
-
closed = true
|
|
160
|
-
wlog('monitor_rsock: closed remote socket due to nil read')
|
|
161
|
-
end
|
|
162
|
-
rescue EOFError => e
|
|
163
|
-
closed = true
|
|
164
|
-
dlog('monitor_rsock: EOF in rsock')
|
|
165
|
-
rescue ::Exception => e
|
|
166
|
-
closed = true
|
|
167
|
-
wlog("monitor_rsock: exception during read: #{e.class} #{e}")
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
unless closed
|
|
172
|
-
total_sent = 0
|
|
173
|
-
total_length = buf.length
|
|
174
|
-
while total_sent < total_length
|
|
175
|
-
begin
|
|
176
|
-
data = buf[total_sent, buf.length]
|
|
177
|
-
|
|
178
|
-
# Note that this must be write() NOT syswrite() or put() or anything like it.
|
|
179
|
-
# Using syswrite() breaks SSL streams.
|
|
180
|
-
sent = write(data)
|
|
181
|
-
|
|
182
|
-
# sf: Only remove the data off the queue is write was successful.
|
|
183
|
-
# This way we naturally perform a resend if a failure occurred.
|
|
184
|
-
# Catches an edge case with meterpreter TCP channels where remote send
|
|
185
|
-
# fails gracefully and a resend is required.
|
|
186
|
-
if sent.nil?
|
|
187
|
-
closed = true
|
|
188
|
-
wlog('monitor_rsock: failed writing, socket must be dead')
|
|
189
|
-
break
|
|
190
|
-
elsif sent > 0
|
|
191
|
-
total_sent += sent
|
|
192
|
-
end
|
|
193
|
-
rescue ::IOError, ::EOFError => e
|
|
194
|
-
closed = true
|
|
195
|
-
wlog("monitor_rsock: exception during write: #{e.class} #{e}")
|
|
196
|
-
break
|
|
197
|
-
end
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
next unless closed
|
|
202
|
-
|
|
203
|
-
begin
|
|
204
|
-
close_write if respond_to?('close_write')
|
|
205
|
-
rescue StandardError
|
|
206
|
-
end
|
|
207
|
-
|
|
208
|
-
break
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
rsock.sysclose
|
|
212
|
-
end
|
|
136
|
+
def monitor_thread
|
|
137
|
+
@relay_manager&.thread
|
|
213
138
|
end
|
|
214
139
|
|
|
215
|
-
attr_accessor :monitor_thread
|
|
216
140
|
attr_writer :lsock, :rsock
|
|
217
141
|
end
|
|
218
142
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rex-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.35
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Metasploit Hackers
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-
|
|
11
|
+
date: 2025-12-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -62,6 +62,8 @@ files:
|
|
|
62
62
|
- lib/rex/exceptions.rb
|
|
63
63
|
- lib/rex/file.rb
|
|
64
64
|
- lib/rex/io/datagram_abstraction.rb
|
|
65
|
+
- lib/rex/io/fiber_scheduler.rb
|
|
66
|
+
- lib/rex/io/relay_manager.rb
|
|
65
67
|
- lib/rex/io/ring_buffer.rb
|
|
66
68
|
- lib/rex/io/socket_abstraction.rb
|
|
67
69
|
- lib/rex/io/stream.rb
|