librex 0.0.1 → 0.0.3
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.
- data/README +4 -0
- data/lib/rex/exploitation/cmdstager.rb +9 -133
- data/lib/rex/exploitation/cmdstager/base.rb +170 -0
- data/lib/rex/exploitation/cmdstager/debug_asm.rb +142 -0
- data/lib/rex/exploitation/cmdstager/debug_write.rb +136 -0
- data/lib/rex/exploitation/cmdstager/tftp.rb +63 -0
- data/lib/rex/exploitation/cmdstager/vbs.rb +128 -0
- data/lib/rex/io/stream.rb +2 -2
- data/lib/rex/io/stream_server.rb +1 -1
- data/lib/rex/job_container.rb +7 -6
- data/lib/rex/mime/header.rb +12 -10
- data/lib/rex/mime/message.rb +57 -26
- data/lib/rex/ole/directory.rb +5 -4
- data/lib/rex/ole/samples/create_ole.rb +0 -0
- data/lib/rex/ole/samples/dir.rb +0 -0
- data/lib/rex/ole/samples/dump_stream.rb +1 -1
- data/lib/rex/ole/samples/ole_info.rb +0 -0
- data/lib/rex/parser/nexpose_xml.rb +131 -0
- data/lib/rex/parser/nmap_xml.rb +1 -0
- data/lib/rex/peparsey/pe.rb +21 -3
- data/lib/rex/post/meterpreter/client.rb +6 -1
- data/lib/rex/post/meterpreter/client_core.rb +2 -2
- data/lib/rex/post/meterpreter/extensions/priv/priv.rb +19 -18
- data/lib/rex/post/meterpreter/packet.rb +68 -0
- data/lib/rex/post/meterpreter/packet_dispatcher.rb +2 -2
- data/lib/rex/post/meterpreter/packet_response_waiter.rb +5 -5
- data/lib/rex/post/meterpreter/ui/console.rb +2 -0
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +5 -2
- data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +2 -2
- data/lib/rex/proto/dcerpc/client.rb.ut.rb +0 -0
- data/lib/rex/proto/http/client.rb +8 -3
- data/lib/rex/proto/http/packet.rb +11 -1
- data/lib/rex/proto/smb/client.rb +1 -1
- data/lib/rex/proto/smb/utils.rb +72 -24
- data/lib/rex/proto/tftp.rb +3 -0
- data/lib/rex/proto/tftp/constants.rb +37 -0
- data/lib/rex/proto/tftp/server.rb +249 -0
- data/lib/rex/proto/tftp/server.rb.ut.rb +28 -0
- data/lib/rex/script/meterpreter.rb +6 -0
- data/lib/rex/services/local_relay.rb +2 -2
- data/lib/rex/socket/ip.rb +9 -8
- data/lib/rex/socket/range_walker.rb +43 -5
- data/lib/rex/socket/udp.rb +11 -4
- data/lib/rex/text.rb +42 -19
- data/lib/rex/ui/interactive.rb +24 -22
- data/lib/rex/ui/text/irb_shell.rb +4 -2
- data/lib/rex/ui/text/output/file.rb +6 -0
- data/lib/rex/ui/text/shell.rb +14 -18
- data/lib/rex/zip/samples/comment.rb +0 -0
- data/lib/rex/zip/samples/mkwar.rb +0 -0
- data/lib/rex/zip/samples/mkzip.rb +0 -0
- data/lib/rex/zip/samples/recursive.rb +0 -0
- metadata +20 -5
@@ -24,6 +24,17 @@ TLV_META_TYPE_COMPRESSED = (1 << 29)
|
|
24
24
|
TLV_META_TYPE_GROUP = (1 << 30)
|
25
25
|
TLV_META_TYPE_COMPLEX = (1 << 31)
|
26
26
|
|
27
|
+
# Exclude compressed from the mask since other meta types (e.g. RAW) can also
|
28
|
+
# be compressed
|
29
|
+
TLV_META_MASK = (
|
30
|
+
TLV_META_TYPE_STRING |
|
31
|
+
TLV_META_TYPE_UINT |
|
32
|
+
TLV_META_TYPE_RAW |
|
33
|
+
TLV_META_TYPE_BOOL |
|
34
|
+
TLV_META_TYPE_GROUP |
|
35
|
+
TLV_META_TYPE_COMPLEX
|
36
|
+
)
|
37
|
+
|
27
38
|
#
|
28
39
|
# TLV base starting points
|
29
40
|
#
|
@@ -114,6 +125,63 @@ class Tlv
|
|
114
125
|
end
|
115
126
|
end
|
116
127
|
|
128
|
+
def inspect
|
129
|
+
utype = type ^ TLV_META_TYPE_COMPRESSED
|
130
|
+
meta = case (utype & TLV_META_MASK)
|
131
|
+
when TLV_META_TYPE_STRING; "STRING"
|
132
|
+
when TLV_META_TYPE_UINT; "INT"
|
133
|
+
when TLV_META_TYPE_RAW; "RAW"
|
134
|
+
when TLV_META_TYPE_BOOL; "BOOL"
|
135
|
+
when TLV_META_TYPE_GROUP; "GROUP"
|
136
|
+
when TLV_META_TYPE_COMPLEX; "COMPLEX"
|
137
|
+
else; 'unknown-meta-type'
|
138
|
+
end
|
139
|
+
stype = case type
|
140
|
+
when TLV_TYPE_REQUEST_ID; "REQUEST-ID"
|
141
|
+
when TLV_TYPE_METHOD; "METHOD"
|
142
|
+
when TLV_TYPE_RESULT; "RESULT"
|
143
|
+
when TLV_TYPE_EXCEPTION; "EXCEPTION"
|
144
|
+
when TLV_TYPE_STRING; "STRING"
|
145
|
+
when TLV_TYPE_UINT; "UINT"
|
146
|
+
when TLV_TYPE_BOOL; "BOOL"
|
147
|
+
|
148
|
+
when TLV_TYPE_LENGTH; "LENGTH"
|
149
|
+
when TLV_TYPE_DATA; "DATA"
|
150
|
+
when TLV_TYPE_FLAGS; "FLAGS"
|
151
|
+
|
152
|
+
when TLV_TYPE_CHANNEL_ID; "CHANNEL-ID"
|
153
|
+
when TLV_TYPE_CHANNEL_TYPE; "CHANNEL-TYPE"
|
154
|
+
when TLV_TYPE_CHANNEL_DATA; "CHANNEL-DATA"
|
155
|
+
when TLV_TYPE_CHANNEL_DATA_GROUP; "CHANNEL-DATA-GROUP"
|
156
|
+
when TLV_TYPE_CHANNEL_CLASS; "CHANNEL-CLASS"
|
157
|
+
when TLV_TYPE_CHANNEL_PARENTID; "CHANNEL-PARENTID"
|
158
|
+
|
159
|
+
when TLV_TYPE_SEEK_WHENCE; "SEEK-WHENCE"
|
160
|
+
when TLV_TYPE_SEEK_OFFSET; "SEEK-OFFSET"
|
161
|
+
when TLV_TYPE_SEEK_POS; "SEEK-POS"
|
162
|
+
|
163
|
+
when TLV_TYPE_EXCEPTION_CODE; "EXCEPTION-CODE"
|
164
|
+
when TLV_TYPE_EXCEPTION_STRING; "EXCEPTION-STRING"
|
165
|
+
|
166
|
+
when TLV_TYPE_LIBRARY_PATH; "LIBRARY-PATH"
|
167
|
+
when TLV_TYPE_TARGET_PATH; "TARGET-PATH"
|
168
|
+
when TLV_TYPE_MIGRATE_PID; "MIGRATE-PID"
|
169
|
+
when TLV_TYPE_MIGRATE_LEN; "MIGRATE-LEN"
|
170
|
+
when TLV_TYPE_MIGRATE_PAYLOAD; "MIGRATE-PAYLOAD"
|
171
|
+
when TLV_TYPE_MIGRATE_ARCH; "MIGRATE-ARCH"
|
172
|
+
|
173
|
+
# Extension classes don't exist yet, so can't use their constants
|
174
|
+
# here.
|
175
|
+
#when Extensions::Stdapi::TLV_TYPE_IP; 'ip-address'
|
176
|
+
else; "unknown-#{type}"
|
177
|
+
end
|
178
|
+
val = value.inspect
|
179
|
+
if val.length > 50
|
180
|
+
val = val[0,50] + ' ..."'
|
181
|
+
end
|
182
|
+
"#<#{self.class} type=#{stype} meta-type=#{meta} #{self.class.to_s =~ /Packet/ ? "tlvs=#{@tlvs.inspect}" : "value=#{val}"} >"
|
183
|
+
end
|
184
|
+
|
117
185
|
##
|
118
186
|
#
|
119
187
|
# Conditionals
|
@@ -71,7 +71,7 @@ module PacketDispatcher
|
|
71
71
|
response = send_packet_wait_response(packet, t)
|
72
72
|
|
73
73
|
if (response == nil)
|
74
|
-
raise TimeoutError
|
74
|
+
raise TimeoutError.new("Send timed out")
|
75
75
|
elsif (response.result != 0)
|
76
76
|
e = RequestError.new(packet.method, response.result)
|
77
77
|
|
@@ -186,7 +186,7 @@ module PacketDispatcher
|
|
186
186
|
# thread above.
|
187
187
|
while(not @finish)
|
188
188
|
if(@pqueue.empty?)
|
189
|
-
select(nil, nil, nil, 0.10)
|
189
|
+
::IO.select(nil, nil, nil, 0.10)
|
190
190
|
next
|
191
191
|
end
|
192
192
|
|
@@ -59,19 +59,19 @@ class PacketResponseWaiter
|
|
59
59
|
def wait(interval)
|
60
60
|
if( interval and interval == -1 )
|
61
61
|
while(not self.done)
|
62
|
-
select(nil, nil, nil, 0.1)
|
63
|
-
end
|
64
|
-
else
|
62
|
+
::IO.select(nil, nil, nil, 0.1)
|
63
|
+
end
|
64
|
+
else
|
65
65
|
begin
|
66
66
|
Timeout.timeout(interval) {
|
67
67
|
while(not self.done)
|
68
|
-
select(nil, nil, nil, 0.1)
|
68
|
+
::IO.select(nil, nil, nil, 0.1)
|
69
69
|
end
|
70
70
|
}
|
71
71
|
rescue Timeout::Error
|
72
72
|
self.response = nil
|
73
73
|
end
|
74
|
-
end
|
74
|
+
end
|
75
75
|
return self.response
|
76
76
|
end
|
77
77
|
|
@@ -86,8 +86,11 @@ class Console::CommandDispatcher::Stdapi::Fs
|
|
86
86
|
print_line("Usage: cd directory")
|
87
87
|
return true
|
88
88
|
end
|
89
|
-
|
90
|
-
|
89
|
+
if args[0] =~ /\%(\w*)\%/
|
90
|
+
client.fs.dir.chdir(client.fs.file.expand_path(args[0].upcase))
|
91
|
+
else
|
92
|
+
client.fs.dir.chdir(args[0])
|
93
|
+
end
|
91
94
|
|
92
95
|
return true
|
93
96
|
end
|
@@ -460,8 +460,8 @@ class Console::CommandDispatcher::Stdapi::Sys
|
|
460
460
|
|
461
461
|
print_line("Computer: " + info['Computer'])
|
462
462
|
print_line("OS : " + info['OS'])
|
463
|
-
print_line("Arch : " + info['Architecture'])
|
464
|
-
print_line("Language: " + info['System Language'])
|
463
|
+
print_line("Arch : " + info['Architecture']) if info['Architecture']
|
464
|
+
print_line("Language: " + info['System Language']) if info['System Language']
|
465
465
|
|
466
466
|
return true
|
467
467
|
end
|
File without changes
|
@@ -301,15 +301,18 @@ class Client
|
|
301
301
|
end
|
302
302
|
|
303
303
|
#
|
304
|
-
# Transmit
|
304
|
+
# Transmit an HTTP request and receive the response
|
305
|
+
# If persist is set, then the request will attempt
|
306
|
+
# to reuse an existing connection.
|
305
307
|
#
|
306
|
-
def send_recv(req, t = -1)
|
308
|
+
def send_recv(req, t = -1, persist=false)
|
309
|
+
@pipeline = persist
|
307
310
|
send_request(req)
|
308
311
|
read_response(t)
|
309
312
|
end
|
310
313
|
|
311
314
|
#
|
312
|
-
# Send
|
315
|
+
# Send an HTTP request to the server
|
313
316
|
#
|
314
317
|
def send_request(req)
|
315
318
|
connect
|
@@ -362,10 +365,12 @@ class Client
|
|
362
365
|
rblob = rbody.to_s + rbufq.to_s
|
363
366
|
tries = 0
|
364
367
|
begin
|
368
|
+
# XXX This doesn't deal with chunked encoding or "Content-type: text/html; charset=..."
|
365
369
|
while tries < 20 and resp.headers["Content-Type"]== "text/html" and rblob !~ /<\/html>/i
|
366
370
|
buff = conn.get_once(-1, 0.05)
|
367
371
|
break if not buff
|
368
372
|
rblob += buff
|
373
|
+
tries += 1
|
369
374
|
end
|
370
375
|
rescue ::Errno::EPIPE, ::EOFError, ::IOError
|
371
376
|
end
|
@@ -92,6 +92,7 @@ class Packet
|
|
92
92
|
end
|
93
93
|
end
|
94
94
|
rescue
|
95
|
+
# This rescue might be a problem because it will swallow TimeoutError
|
95
96
|
self.error = $!
|
96
97
|
return ParseCode::Error
|
97
98
|
end
|
@@ -331,8 +332,17 @@ protected
|
|
331
332
|
# Remove any leading newlines or spaces
|
332
333
|
self.bufq.lstrip!
|
333
334
|
|
335
|
+
# If we didn't get a newline, then this might not be the full
|
336
|
+
# length, go back and get more.
|
337
|
+
# e.g.
|
338
|
+
# first packet: "200"
|
339
|
+
# second packet: "0\r\n\r\n<html>..."
|
340
|
+
if not bufq.index("\n")
|
341
|
+
return
|
342
|
+
end
|
343
|
+
|
334
344
|
# Extract the actual hexadecimal length value
|
335
|
-
clen = self.bufq.slice!(/^[a-
|
345
|
+
clen = self.bufq.slice!(/^[a-fA-F0-9]+\r?\n/)
|
336
346
|
|
337
347
|
clen.rstrip! if (clen)
|
338
348
|
|
data/lib/rex/proto/smb/client.rb
CHANGED
data/lib/rex/proto/smb/utils.rb
CHANGED
@@ -347,26 +347,74 @@ CONST = Rex::Proto::SMB::Constants
|
|
347
347
|
#
|
348
348
|
# Process Type 3 NTLM Message (in Base64)
|
349
349
|
#
|
350
|
+
# from http://www.innovation.ch/personal/ronald/ntlm.html
|
351
|
+
#
|
352
|
+
# struct {
|
353
|
+
# byte protocol[8]; // 'N', 'T', 'L', 'M', 'S', 'S', 'P', '\0'
|
354
|
+
# byte type; // 0x03
|
355
|
+
# byte zero[3];
|
356
|
+
#
|
357
|
+
# short lm_resp_len; // LanManager response length (always 0x18)
|
358
|
+
# short lm_resp_len; // LanManager response length (always 0x18)
|
359
|
+
# short lm_resp_off; // LanManager response offset
|
360
|
+
# byte zero[2];
|
361
|
+
#
|
362
|
+
# short nt_resp_len; // NT response length (always 0x18)
|
363
|
+
# short nt_resp_len; // NT response length (always 0x18)
|
364
|
+
# short nt_resp_off; // NT response offset
|
365
|
+
# byte zero[2];
|
366
|
+
#
|
367
|
+
# short dom_len; // domain string length
|
368
|
+
# short dom_len; // domain string length
|
369
|
+
# short dom_off; // domain string offset (always 0x40)
|
370
|
+
# byte zero[2];
|
371
|
+
#
|
372
|
+
# short user_len; // username string length
|
373
|
+
# short user_len; // username string length
|
374
|
+
# short user_off; // username string offset
|
375
|
+
# byte zero[2];
|
376
|
+
#
|
377
|
+
# short host_len; // host string length
|
378
|
+
# short host_len; // host string length
|
379
|
+
# short host_off; // host string offset
|
380
|
+
# byte zero[6];
|
381
|
+
#
|
382
|
+
# short msg_len; // message length
|
383
|
+
# byte zero[2];
|
384
|
+
#
|
385
|
+
# short flags; // 0x8201
|
386
|
+
# byte zero[2];
|
387
|
+
#
|
388
|
+
# byte dom[*]; // domain string (unicode UTF-16LE)
|
389
|
+
# byte user[*]; // username string (unicode UTF-16LE)
|
390
|
+
# byte host[*]; // host string (unicode UTF-16LE)
|
391
|
+
# byte lm_resp[*]; // LanManager response
|
392
|
+
# byte nt_resp[*]; // NT response
|
393
|
+
# } type_3_message
|
394
|
+
#
|
350
395
|
def self.process_type3_message(message)
|
351
396
|
decode = Rex::Text.decode_base64(message.strip)
|
352
|
-
type = decode[8]
|
397
|
+
type = decode[8,1].unpack("C").first
|
353
398
|
if (type == 3)
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
399
|
+
lm_len = decode[12,2].unpack("v").first
|
400
|
+
lm_offset = decode[16,2].unpack("v").first
|
401
|
+
lm = decode[lm_offset, lm_len].unpack("H*").first
|
402
|
+
|
403
|
+
nt_len = decode[20,2].unpack("v").first
|
404
|
+
nt_offset = decode[24,2].unpack("v").first
|
405
|
+
nt = decode[nt_offset, nt_len].unpack("H*").first
|
406
|
+
|
407
|
+
dom_len = decode[28,2].unpack("v").first
|
408
|
+
dom_offset = decode[32,2].unpack("v").first
|
409
|
+
domain = decode[dom_offset, dom_len]
|
410
|
+
|
411
|
+
user_len = decode[36,2].unpack("v").first
|
412
|
+
user_offset = decode[40,2].unpack("v").first
|
413
|
+
user = decode[user_offset, user_len]
|
414
|
+
|
415
|
+
host_len = decode[44,2].unpack("v").first
|
416
|
+
host_offset = decode[48,2].unpack("v").first
|
417
|
+
host = decode[host_offset, host_len]
|
370
418
|
|
371
419
|
return domain, user, host, lm, nt
|
372
420
|
else
|
@@ -386,13 +434,13 @@ CONST = Rex::Proto::SMB::Constants
|
|
386
434
|
win_name = Rex::Text.to_unicode(win_name)
|
387
435
|
decode = Rex::Text.decode_base64(message.strip)
|
388
436
|
|
389
|
-
type = decode[8]
|
437
|
+
type = decode[8,1].unpack("C").first
|
390
438
|
|
391
439
|
if (type == 1)
|
392
440
|
# A type 1 message has been received, lets build a type 2 message response
|
393
441
|
|
394
|
-
reqflags = decode[12
|
395
|
-
reqflags =
|
442
|
+
reqflags = decode[12,4]
|
443
|
+
reqflags = reqflags.unpack("V").first
|
396
444
|
|
397
445
|
if (reqflags & CONST::REQUEST_TARGET) == CONST::REQUEST_TARGET
|
398
446
|
|
@@ -470,12 +518,12 @@ CONST = Rex::Proto::SMB::Constants
|
|
470
518
|
def self.downgrade_type_message(message)
|
471
519
|
decode = Rex::Text.decode_base64(message.strip)
|
472
520
|
|
473
|
-
type = decode[8]
|
521
|
+
type = decode[8,1].unpack("C").first
|
474
522
|
|
475
523
|
if (type > 0 and type < 4)
|
476
524
|
reqflags = decode[12..15] if (type == 1 or type == 3)
|
477
525
|
reqflags = decode[20..23] if (type == 2)
|
478
|
-
reqflags =
|
526
|
+
reqflags = reqflags.unpack("V")
|
479
527
|
|
480
528
|
# Remove NEGOTIATE_NTLMV2_KEY and NEGOTIATE_ALWAYS_SIGN, this lowers the negotiation
|
481
529
|
# down to LMv1/NTLMv1.
|
@@ -497,9 +545,9 @@ CONST = Rex::Proto::SMB::Constants
|
|
497
545
|
idx = 0
|
498
546
|
0.upto(3) do |cnt|
|
499
547
|
if (type == 2)
|
500
|
-
decode[23-cnt] =
|
548
|
+
decode[23-cnt] = [flags[idx,1]].pack("C")
|
501
549
|
else
|
502
|
-
decode[15-cnt] =
|
550
|
+
decode[15-cnt] = [flags[idx,1]].pack("C")
|
503
551
|
end
|
504
552
|
idx += 2
|
505
553
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# $Id: constants.rb 9334 2010-05-21 00:15:10Z jduck $
|
2
|
+
require 'rex/proto/tftp'
|
3
|
+
|
4
|
+
module Rex
|
5
|
+
module Proto
|
6
|
+
module TFTP
|
7
|
+
|
8
|
+
OPCODES = %w{ Unknown RRQ WRQ DATA ACK ERROR }
|
9
|
+
OpRead = 1
|
10
|
+
OpWrite = 2
|
11
|
+
OpData = 3
|
12
|
+
OpAck = 4
|
13
|
+
OpError = 5
|
14
|
+
|
15
|
+
|
16
|
+
ERRCODES = [
|
17
|
+
"Undefined",
|
18
|
+
"File not found",
|
19
|
+
"Access violation",
|
20
|
+
"Disk full or allocation exceeded",
|
21
|
+
"Illegal TFTP operation",
|
22
|
+
"Unknown transfer ID",
|
23
|
+
"File already exists",
|
24
|
+
"No such user"
|
25
|
+
]
|
26
|
+
|
27
|
+
ErrFileNotFound = 1
|
28
|
+
ErrAccessViolation = 2
|
29
|
+
ErrDiskFull = 3
|
30
|
+
ErrIllegalOperation = 4
|
31
|
+
ErrUnknownTransferId = 5
|
32
|
+
ErrFileExists = 6
|
33
|
+
ErrNoSuchUser = 7
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,249 @@
|
|
1
|
+
# $Id: server.rb 9375 2010-05-26 22:39:56Z jduck $
|
2
|
+
require 'rex/socket'
|
3
|
+
require 'rex/proto/tftp'
|
4
|
+
|
5
|
+
module Rex
|
6
|
+
module Proto
|
7
|
+
module TFTP
|
8
|
+
|
9
|
+
#
|
10
|
+
# Little util function
|
11
|
+
#
|
12
|
+
def self.get_string(data)
|
13
|
+
ret = data.slice!(0,data.index("\x00"))
|
14
|
+
# Slice off the nul byte.
|
15
|
+
data.slice!(0,1)
|
16
|
+
ret
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
##
|
21
|
+
#
|
22
|
+
# TFTP Server class
|
23
|
+
#
|
24
|
+
##
|
25
|
+
class Server
|
26
|
+
|
27
|
+
def initialize(port = 69, listen_host = '0.0.0.0', context = {})
|
28
|
+
self.listen_host = listen_host
|
29
|
+
self.listen_port = port
|
30
|
+
self.context = context
|
31
|
+
self.sock = nil
|
32
|
+
@shutting_down = false
|
33
|
+
|
34
|
+
self.files = []
|
35
|
+
self.transfers = []
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
#
|
40
|
+
# Start the TFTP server
|
41
|
+
#
|
42
|
+
def start
|
43
|
+
self.sock = Rex::Socket::Udp.create(
|
44
|
+
'LocalHost' => listen_host,
|
45
|
+
'LocalPort' => listen_port,
|
46
|
+
'Context' => context
|
47
|
+
)
|
48
|
+
|
49
|
+
self.thread = Thread.new {
|
50
|
+
monitor_socket
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
#
|
56
|
+
# Stop the TFTP server
|
57
|
+
#
|
58
|
+
def stop
|
59
|
+
@shutting_down = true
|
60
|
+
|
61
|
+
# Wait a maximum of 30 seconds for all transfers to finish.
|
62
|
+
start = Time.now
|
63
|
+
while (self.transfers.length > 0)
|
64
|
+
::IO.select(nil, nil, nil, 0.5)
|
65
|
+
dur = Time.now - start
|
66
|
+
break if (dur > 30)
|
67
|
+
end
|
68
|
+
|
69
|
+
self.files.clear
|
70
|
+
self.thread.kill
|
71
|
+
self.sock.close
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
#
|
76
|
+
# Register a filename and content for a client to request
|
77
|
+
#
|
78
|
+
def register_file(fn, content)
|
79
|
+
self.files << {
|
80
|
+
:name => fn,
|
81
|
+
:data => content
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
#
|
87
|
+
# Send an error packet w/the specified code and string
|
88
|
+
#
|
89
|
+
def send_error(from, num)
|
90
|
+
if (num < 1 or num >= ERRCODES.length)
|
91
|
+
# ignore..
|
92
|
+
return
|
93
|
+
end
|
94
|
+
pkt = [OpError, num].pack('nn')
|
95
|
+
pkt << ERRCODES[num]
|
96
|
+
pkt << "\x00"
|
97
|
+
send_packet(from, pkt)
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
#
|
102
|
+
# Send a single packet to the specified host
|
103
|
+
#
|
104
|
+
def send_packet(from, pkt)
|
105
|
+
self.sock.sendto(pkt, from[0], from[1])
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
#
|
110
|
+
# Find the hash entry for a file that may be offered
|
111
|
+
#
|
112
|
+
def find_file(fname)
|
113
|
+
self.files.each do |f|
|
114
|
+
if (fname == f[:name])
|
115
|
+
return f
|
116
|
+
end
|
117
|
+
end
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
attr_accessor :listen_host, :listen_port, :context
|
123
|
+
attr_accessor :sock, :files, :transfers
|
124
|
+
attr_accessor :thread
|
125
|
+
|
126
|
+
|
127
|
+
protected
|
128
|
+
|
129
|
+
#
|
130
|
+
# See if there is anything to do.. If so, dispatch it.
|
131
|
+
#
|
132
|
+
def monitor_socket
|
133
|
+
while true
|
134
|
+
rds = [@sock]
|
135
|
+
wds = []
|
136
|
+
self.transfers.each do |tr|
|
137
|
+
if (not tr[:last_sent])
|
138
|
+
wds << @sock
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
eds = [@sock]
|
143
|
+
|
144
|
+
r,w,e = ::IO.select(rds,wds,eds,1)
|
145
|
+
|
146
|
+
if (r != nil and r[0] == self.sock)
|
147
|
+
buf,host,port = self.sock.recvfrom(65535)
|
148
|
+
# Lame compatabilitiy :-/
|
149
|
+
from = [host, port]
|
150
|
+
dispatch_request(from, buf)
|
151
|
+
end
|
152
|
+
|
153
|
+
#
|
154
|
+
# Check to see if transfers need maintenance
|
155
|
+
#
|
156
|
+
self.transfers.each do |tr|
|
157
|
+
# Are we awaiting an ack?
|
158
|
+
if (tr[:last_sent])
|
159
|
+
elapsed = Time.now - tr[:last_sent]
|
160
|
+
if (elapsed >= 3)
|
161
|
+
# max retries reached?
|
162
|
+
if (tr[:retries] < 3)
|
163
|
+
#puts "[-] ack timed out, resending block"
|
164
|
+
tr[:last_sent] = nil
|
165
|
+
tr[:retries] += 1
|
166
|
+
else
|
167
|
+
#puts "[-] maximum tries reached, terminating transfer"
|
168
|
+
self.transfers.delete(tr)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
elsif (w != nil and w[0] == self.sock)
|
172
|
+
# No ack waiting, send next block..
|
173
|
+
chunk = tr[:file][:data].slice(tr[:offset], 512)
|
174
|
+
if (chunk and chunk.length >= 0)
|
175
|
+
pkt = [OpData, tr[:block]].pack('nn')
|
176
|
+
pkt << chunk
|
177
|
+
send_packet(tr[:from], pkt)
|
178
|
+
tr[:last_sent] = Time.now
|
179
|
+
else
|
180
|
+
# no more chunks.. transfer is most likely done.
|
181
|
+
self.transfers.delete(tr)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
#
|
190
|
+
# Dispatch a packet that we received
|
191
|
+
#
|
192
|
+
def dispatch_request(from, buf)
|
193
|
+
|
194
|
+
op = buf.unpack('n')[0]
|
195
|
+
buf.slice!(0,2)
|
196
|
+
|
197
|
+
#start = "[*] TFTP - %s:%u - %s" % [from[0], from[1], OPCODES[op]]
|
198
|
+
|
199
|
+
case op
|
200
|
+
when OpRead
|
201
|
+
# Process RRQ packets
|
202
|
+
fn = TFTP::get_string(buf)
|
203
|
+
mode = TFTP::get_string(buf).downcase
|
204
|
+
|
205
|
+
#puts "%s %s %s" % [start, fn, mode]
|
206
|
+
|
207
|
+
if (not @shutting_down) and (file = self.find_file(fn))
|
208
|
+
self.transfers << {
|
209
|
+
:from => from,
|
210
|
+
:file => file,
|
211
|
+
:block => 1,
|
212
|
+
:offset => 0,
|
213
|
+
:last_sent => nil,
|
214
|
+
:retries => 0
|
215
|
+
}
|
216
|
+
else
|
217
|
+
#puts "[-] file not found!"
|
218
|
+
send_error(from, ErrFileNotFound)
|
219
|
+
end
|
220
|
+
|
221
|
+
when OpAck
|
222
|
+
# Process ACK packets
|
223
|
+
block = buf.unpack('n')[0]
|
224
|
+
#puts "%s %d" % [start, block]
|
225
|
+
|
226
|
+
self.transfers.each do |tr|
|
227
|
+
if (from == tr[:from] and block == tr[:block])
|
228
|
+
# acked! send the next block
|
229
|
+
tr[:block] += 1
|
230
|
+
tr[:offset] += 512
|
231
|
+
tr[:last_sent] = nil
|
232
|
+
tr[:retries] = 0
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
else
|
237
|
+
# Other packets are unsupported
|
238
|
+
#puts start
|
239
|
+
send_error(from, ErrAccessViolation)
|
240
|
+
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|