rex 2.0.5 → 2.0.7

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 (98) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rex/exploitation/egghunter.rb +4 -6
  3. data/lib/rex/exploitation/powershell/psh_methods.rb +9 -0
  4. data/lib/rex/java/serialization.rb +2 -1
  5. data/lib/rex/java/serialization/builder.rb +94 -0
  6. data/lib/rex/java/serialization/model.rb +29 -18
  7. data/lib/rex/java/serialization/model/annotation.rb +2 -2
  8. data/lib/rex/java/serialization/model/field.rb +2 -2
  9. data/lib/rex/java/serialization/model/new_array.rb +8 -3
  10. data/lib/rex/java/serialization/model/new_class_desc.rb +3 -3
  11. data/lib/rex/java/serialization/model/new_enum.rb +4 -4
  12. data/lib/rex/java/serialization/model/new_object.rb +17 -10
  13. data/lib/rex/ole/direntry.rb +1 -1
  14. data/lib/rex/ole/samples/create_ole.rb +0 -0
  15. data/lib/rex/ole/samples/dir.rb +0 -0
  16. data/lib/rex/ole/samples/dump_stream.rb +0 -0
  17. data/lib/rex/ole/samples/ole_info.rb +0 -0
  18. data/lib/rex/parser/foundstone_nokogiri.rb +1 -1
  19. data/lib/rex/parser/fs/ntfs.rb +252 -0
  20. data/lib/rex/parser/openvas_nokogiri.rb +2 -0
  21. data/lib/rex/payloads/win32/kernel.rb +3 -3
  22. data/lib/rex/post/meterpreter/client_core.rb +172 -64
  23. data/lib/rex/post/meterpreter/extensions/priv/priv.rb +3 -2
  24. data/lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb +12 -10
  25. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/api_constants.rb +64 -37
  26. data/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +8 -2
  27. data/lib/rex/post/meterpreter/extensions/stdapi/ui.rb +15 -3
  28. data/lib/rex/post/meterpreter/packet.rb +41 -38
  29. data/lib/rex/post/meterpreter/packet_dispatcher.rb +7 -1
  30. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +17 -4
  31. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb +11 -4
  32. data/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/ui.rb +1 -1
  33. data/lib/rex/proto.rb +2 -0
  34. data/lib/rex/proto/acpp.rb +17 -0
  35. data/lib/rex/proto/acpp/client.rb +29 -0
  36. data/lib/rex/proto/acpp/message.rb +183 -0
  37. data/lib/rex/proto/http/client.rb +1 -2
  38. data/lib/rex/proto/iax2/call.rb +22 -3
  39. data/lib/rex/proto/iax2/client.rb +1 -0
  40. data/lib/rex/proto/kerberos.rb +13 -0
  41. data/lib/rex/proto/kerberos/client.rb +213 -0
  42. data/lib/rex/proto/kerberos/credential_cache.rb +19 -0
  43. data/lib/rex/proto/kerberos/credential_cache/cache.rb +81 -0
  44. data/lib/rex/proto/kerberos/credential_cache/credential.rb +151 -0
  45. data/lib/rex/proto/kerberos/credential_cache/element.rb +49 -0
  46. data/lib/rex/proto/kerberos/credential_cache/key_block.rb +62 -0
  47. data/lib/rex/proto/kerberos/credential_cache/principal.rb +70 -0
  48. data/lib/rex/proto/kerberos/credential_cache/time.rb +69 -0
  49. data/lib/rex/proto/kerberos/crypto.rb +21 -0
  50. data/lib/rex/proto/kerberos/crypto/rc4_hmac.rb +65 -0
  51. data/lib/rex/proto/kerberos/crypto/rsa_md5.rb +15 -0
  52. data/lib/rex/proto/kerberos/model.rb +133 -0
  53. data/lib/rex/proto/kerberos/model/ap_req.rb +98 -0
  54. data/lib/rex/proto/kerberos/model/authenticator.rb +143 -0
  55. data/lib/rex/proto/kerberos/model/authorization_data.rb +85 -0
  56. data/lib/rex/proto/kerberos/model/checksum.rb +59 -0
  57. data/lib/rex/proto/kerberos/model/element.rb +67 -0
  58. data/lib/rex/proto/kerberos/model/enc_kdc_response.rb +215 -0
  59. data/lib/rex/proto/kerberos/model/encrypted_data.rb +171 -0
  60. data/lib/rex/proto/kerberos/model/encryption_key.rb +106 -0
  61. data/lib/rex/proto/kerberos/model/kdc_request.rb +166 -0
  62. data/lib/rex/proto/kerberos/model/kdc_request_body.rb +315 -0
  63. data/lib/rex/proto/kerberos/model/kdc_response.rb +141 -0
  64. data/lib/rex/proto/kerberos/model/krb_error.rb +219 -0
  65. data/lib/rex/proto/kerberos/model/last_request.rb +82 -0
  66. data/lib/rex/proto/kerberos/model/pre_auth_data.rb +104 -0
  67. data/lib/rex/proto/kerberos/model/pre_auth_enc_time_stamp.rb +126 -0
  68. data/lib/rex/proto/kerberos/model/pre_auth_pac_request.rb +81 -0
  69. data/lib/rex/proto/kerberos/model/principal_name.rb +116 -0
  70. data/lib/rex/proto/kerberos/model/ticket.rb +151 -0
  71. data/lib/rex/proto/kerberos/pac.rb +36 -0
  72. data/lib/rex/proto/kerberos/pac/client_info.rb +53 -0
  73. data/lib/rex/proto/kerberos/pac/element.rb +52 -0
  74. data/lib/rex/proto/kerberos/pac/logon_info.rb +566 -0
  75. data/lib/rex/proto/kerberos/pac/priv_svr_checksum.rb +29 -0
  76. data/lib/rex/proto/kerberos/pac/server_checksum.rb +30 -0
  77. data/lib/rex/proto/kerberos/pac/type.rb +121 -0
  78. data/lib/rex/proto/rmi.rb +7 -0
  79. data/lib/rex/proto/rmi/model.rb +31 -0
  80. data/lib/rex/proto/rmi/model/call.rb +60 -0
  81. data/lib/rex/proto/rmi/model/continuation.rb +76 -0
  82. data/lib/rex/proto/rmi/model/dgc_ack.rb +62 -0
  83. data/lib/rex/proto/rmi/model/element.rb +143 -0
  84. data/lib/rex/proto/rmi/model/output_header.rb +86 -0
  85. data/lib/rex/proto/rmi/model/ping.rb +41 -0
  86. data/lib/rex/proto/rmi/model/ping_ack.rb +41 -0
  87. data/lib/rex/proto/rmi/model/protocol_ack.rb +100 -0
  88. data/lib/rex/proto/rmi/model/return_data.rb +60 -0
  89. data/lib/rex/socket.rb +9 -1
  90. data/lib/rex/socket/tcp_server.rb +3 -0
  91. data/lib/rex/ui/text/dispatcher_shell.rb +4 -4
  92. data/lib/rex/ui/text/output/tee.rb +2 -0
  93. data/lib/rex/zip/samples/comment.rb +0 -0
  94. data/lib/rex/zip/samples/mkwar.rb +0 -0
  95. data/lib/rex/zip/samples/mkzip.rb +0 -0
  96. data/lib/rex/zip/samples/recursive.rb +0 -0
  97. data/rex.gemspec +1 -1
  98. metadata +56 -2
@@ -0,0 +1,29 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Kerberos
6
+ module Pac
7
+ # This class provides a representation of a PAC_PRIVSVR_CHECKSUM structure, which contains the
8
+ # checksum using the key of the KDC.
9
+ class PrivSvrChecksum < Element
10
+
11
+ # @!attribute version
12
+ # @return [Fixnum] The checksum type
13
+ attr_accessor :checksum
14
+
15
+ # Encodes the Rex::Proto::Kerberos::Pac::PacPrivSvrChecksum
16
+ #
17
+ # @return [String]
18
+ def encode
19
+ encoded = ''
20
+ encoded << [checksum].pack('V')
21
+ encoded << "\x00" * 16
22
+
23
+ encoded
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Kerberos
6
+ module Pac
7
+ # This class provides a representation of a PAC_SERVER_CHECKSUM structure, which contains the
8
+ # checksum using the key of the server.
9
+ class ServerChecksum < Element
10
+
11
+ # @!attribute version
12
+ # @return [Fixnum] The checksum type
13
+ attr_accessor :checksum
14
+
15
+ # Encodes the Rex::Proto::Kerberos::Pac::ServerChecksum
16
+ #
17
+ # @return [String]
18
+ def encode
19
+ encoded = ''
20
+ encoded << [checksum].pack('V')
21
+ encoded << "\x00" * 16
22
+
23
+ encoded
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,121 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Kerberos
6
+ module Pac
7
+ # This class provides a representation of a PAC_TYPE structure, the topmost structure
8
+ # of the PAC.
9
+ class Type < Element
10
+
11
+ # @!attribute buffers
12
+ # @return [Array<Rex::Proto::Kerberos::Pac::Element>] The array of PAC_INFO_BUFFER structures
13
+ attr_accessor :buffers
14
+ # @!attribute checksum
15
+ # @return [Fixnum] The type of checksum to use when encoding PAC-TYPE
16
+ attr_accessor :checksum
17
+
18
+ # Encodes the Rex::Proto::Kerberos::Pac::Type
19
+ #
20
+ # @return [String]
21
+ def encode
22
+ offset_one = 0
23
+ offset_two = 0
24
+
25
+ draft = ''
26
+ draft << encode_buffers_length
27
+ draft << encode_version
28
+ draft << encode_pac_info_buffers
29
+
30
+ # Encode buffers
31
+ buffers.each do |buffer|
32
+ if buffer.class == ServerChecksum
33
+ offset_one = draft.length + 4
34
+ elsif buffer.class == PrivSvrChecksum
35
+ offset_two = draft.length + 4
36
+ end
37
+
38
+ buffer_encoded = buffer.encode
39
+ draft << buffer_encoded
40
+ draft << "\x00" * ((buffer_encoded.length + 7) / 8 * 8 - buffer_encoded.length)
41
+ end
42
+
43
+ checksum_draft = make_checksum(draft)
44
+ double_checksum = make_checksum(checksum_draft)
45
+
46
+ encoded = ''
47
+ encoded << draft[0..(offset_one - 1)]
48
+ encoded << checksum_draft
49
+ encoded << draft[(offset_one + checksum_draft.length)..(offset_two - 1)]
50
+ encoded << double_checksum
51
+ encoded << draft[(offset_two + double_checksum.length)..(draft.length - 1)]
52
+
53
+ encoded
54
+ end
55
+
56
+ private
57
+
58
+ # Encodes the number of buffers contained in the PAC
59
+ #
60
+ # @return [String]
61
+ def encode_buffers_length
62
+ [buffers.length].pack('V')
63
+ end
64
+
65
+ # Encodes the PAC version
66
+ #
67
+ # @return [String]
68
+ def encode_version
69
+ [VERSION].pack('V')
70
+ end
71
+
72
+ # Encodes the PAC_INFO_BUFFER data
73
+ #
74
+ # @return [String]
75
+ def encode_pac_info_buffers
76
+ offset = 8 + buffers.length * 16
77
+ encoded = ''
78
+ buffers.each do |buffer|
79
+ case buffer
80
+ when ClientInfo
81
+ encoded << [PAC_CLIENT_INFO].pack('V')
82
+ when LogonInfo
83
+ encoded << [PAC_LOGON_INFO].pack('V')
84
+ when PrivSvrChecksum
85
+ encoded << [PAC_PRIVSVR_CHECKSUM].pack('V')
86
+ when ServerChecksum
87
+ encoded << [PAC_SERVER_CHECKSUM].pack('V')
88
+ end
89
+
90
+ buffer_length = buffer.encode.length
91
+
92
+ encoded << [buffer_length].pack('V')
93
+ encoded << [offset].pack('Q<')
94
+
95
+ offset = (offset + buffer_length + 7) / 8 * 8
96
+ end
97
+
98
+ encoded
99
+ end
100
+
101
+ # Calculates the checksum for the PAC data
102
+ #
103
+ # @param data [String] the data to checksum
104
+ # @return [String] the checksum result
105
+ # @raise [NotImplementedError] if checksum schema isn't supported
106
+ def make_checksum(data)
107
+ res = ''
108
+ case checksum
109
+ when RSA_MD5
110
+ res = checksum_rsa_md5(data)
111
+ else
112
+ raise ::NotImplementedError, 'PAC-TYPE checksum not supported'
113
+ end
114
+
115
+ res
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,7 @@
1
+ # -*- coding: binary -*-
2
+
3
+ # JAVA RMI Wire protocol implementation
4
+ # http://docs.oracle.com/javase/7/docs/platform/rmi/spec/rmi-protocol.html
5
+
6
+ require 'rex/proto/rmi/model'
7
+
@@ -0,0 +1,31 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Rmi
6
+ module Model
7
+ SIGNATURE = 'JRMI'
8
+ STREAM_PROTOCOL = 0x4b
9
+ SINGLE_OP_PROTOCOL = 0x4c
10
+ MULTIPLEX_PROTOCOL = 0x4d
11
+ CALL_MESSAGE = 0x50
12
+ PING_MESSAGE = 0x52
13
+ DGC_ACK_MESSAGE = 0x54
14
+ PROTOCOL_ACK = 0x4e
15
+ PROTOCOL_NOT_SUPPORTED = 0x4f
16
+ RETURN_DATA = 0x51
17
+ PING_ACK = 0x53
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'rex/proto/rmi/model/element'
24
+ require 'rex/proto/rmi/model/output_header'
25
+ require 'rex/proto/rmi/model/protocol_ack'
26
+ require 'rex/proto/rmi/model/continuation'
27
+ require 'rex/proto/rmi/model/call'
28
+ require 'rex/proto/rmi/model/return_data'
29
+ require 'rex/proto/rmi/model/dgc_ack'
30
+ require 'rex/proto/rmi/model/ping'
31
+ require 'rex/proto/rmi/model/ping_ack'
@@ -0,0 +1,60 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Rmi
6
+ module Model
7
+ # This class provides a representation of an RMI call message
8
+ class Call < Element
9
+
10
+ # @!attribute message_id
11
+ # @return [Fixnum] the message id
12
+ attr_accessor :message_id
13
+ # @!attribute call_data
14
+ # @return [Rex::Java::Serialization::Model::Stream] the serialized call data
15
+ attr_accessor :call_data
16
+
17
+ private
18
+
19
+ # Reads the message id from the IO
20
+ #
21
+ # @param io [IO] the IO to read from
22
+ # @return [String]
23
+ # @raise [RuntimeError] if fails to decode the message id
24
+ def decode_message_id(io)
25
+ message_id = read_byte(io)
26
+ unless message_id == CALL_MESSAGE
27
+ raise ::RuntimeError, 'Failed to decode Call message id'
28
+ end
29
+
30
+ message_id
31
+ end
32
+
33
+ # Reads and deserializes the call data from the IO
34
+ #
35
+ # @param io [IO] the IO to read from
36
+ # @return [Rex::Java::Serialization::Model::Stream]
37
+ def decode_call_data(io)
38
+ call_data = Rex::Java::Serialization::Model::Stream.decode(io)
39
+
40
+ call_data
41
+ end
42
+
43
+ # Encodes the message_id field
44
+ #
45
+ # @return [String]
46
+ def encode_message_id
47
+ [message_id].pack('C')
48
+ end
49
+
50
+ # Encodes the address field
51
+ #
52
+ # @return [String]
53
+ def encode_call_data
54
+ call_data.encode
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,76 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Rmi
6
+ module Model
7
+ # This class provides a representation of an RMI continuation stream
8
+ class Continuation < Element
9
+
10
+ # @!attribute length
11
+ # @return [Fixnum] the end point address length
12
+ attr_accessor :length
13
+ # @!attribute address
14
+ # @return [String] the end point address
15
+ attr_accessor :address
16
+ # @!attribute port
17
+ # @return [Fixnum] the end point port
18
+ attr_accessor :port
19
+
20
+ private
21
+
22
+ # Reads the end point identifier address length from the IO
23
+ #
24
+ # @param io [IO] the IO to read from
25
+ # @return [Fixnum]
26
+ def decode_length(io)
27
+ length = read_short(io)
28
+
29
+ length
30
+ end
31
+
32
+ # Reads the end point address from the IO
33
+ #
34
+ # @param io [IO] the IO to read from
35
+ # @return [String]
36
+ def decode_address(io)
37
+ version = read_string(io, length)
38
+
39
+ version
40
+ end
41
+
42
+ # Reads the end point port from the IO
43
+ #
44
+ # @param io [IO] the IO to read from
45
+ # @return [Fixnum]
46
+ def decode_port(io)
47
+ port = read_int(io)
48
+
49
+ port
50
+ end
51
+
52
+ # Encodes the length field
53
+ #
54
+ # @return [String]
55
+ def encode_length
56
+ [length].pack('n')
57
+ end
58
+
59
+ # Encodes the address field
60
+ #
61
+ # @return [String]
62
+ def encode_address
63
+ address
64
+ end
65
+
66
+ # Encodes the port field
67
+ #
68
+ # @return [String]
69
+ def encode_port
70
+ [port].pack('N')
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,62 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Rmi
6
+ module Model
7
+ # This class provides a representation of an RMI DbgACK stream. It is an acknowledgement
8
+ # directed to a server's distributed garbage collector that indicates that remote objects
9
+ # in a return value from a server have been received by the client.
10
+ class DgcAck < Element
11
+
12
+ # @!attribute stream_id
13
+ # @return [Fixnum] the input stream id
14
+ attr_accessor :stream_id
15
+ # @!attribute unique_identifier
16
+ # @return [String] the unique identifier
17
+ attr_accessor :unique_identifier
18
+
19
+ private
20
+
21
+ # Reads the stream id from the IO
22
+ #
23
+ # @param io [IO] the IO to read from
24
+ # @return [String]
25
+ # @raise [RuntimeError] if fails to decode stream id
26
+ def decode_stream_id(io)
27
+ stream_id = read_byte(io)
28
+ unless stream_id == DGC_ACK_MESSAGE
29
+ raise ::RuntimeError, 'Failed to decode DgcAck stream id'
30
+ end
31
+
32
+ stream_id
33
+ end
34
+
35
+ # Reads the unique identifier from the IO
36
+ #
37
+ # @param io [IO] the IO to read from
38
+ # @return [String]
39
+ def decode_unique_identifier(io)
40
+ unique_identifier = read_string(io, 14)
41
+
42
+ unique_identifier
43
+ end
44
+
45
+ # Encodes the stream_id field
46
+ #
47
+ # @return [String]
48
+ def encode_stream_id
49
+ [stream_id].pack('C')
50
+ end
51
+
52
+ # Encodes the unique_identifier field
53
+ #
54
+ # @return [String]
55
+ def encode_unique_identifier
56
+ unique_identifier
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,143 @@
1
+ # -*- coding: binary -*-
2
+
3
+ module Rex
4
+ module Proto
5
+ module Rmi
6
+ module Model
7
+ class Element
8
+
9
+ include Rex::Proto::Rmi::Model
10
+
11
+ def self.attr_accessor(*vars)
12
+ @attributes ||= []
13
+ @attributes.concat vars
14
+ super(*vars)
15
+ end
16
+
17
+ # Retrieves the element class fields
18
+ #
19
+ # @return [Array]
20
+ def self.attributes
21
+ @attributes
22
+ end
23
+
24
+ # Creates a Rex::Proto::Rmi::Model::Element with data from the IO.
25
+ #
26
+ # @param io [IO] the IO to read data from
27
+ # @return [Rex::Proto::Rmi::Model::Element]
28
+ def self.decode(io)
29
+ elem = self.new
30
+ elem.decode(io)
31
+
32
+ elem
33
+ end
34
+
35
+ def initialize(options = {})
36
+ self.class.attributes.each do |attr|
37
+ if options.has_key?(attr)
38
+ m = (attr.to_s + '=').to_sym
39
+ self.send(m, options[attr])
40
+ end
41
+ end
42
+ end
43
+
44
+ # Retrieves the element instance fields
45
+ #
46
+ # @return [Array]
47
+ def attributes
48
+ self.class.attributes
49
+ end
50
+
51
+ # Decodes the Rex::Proto::Rmi::Model::Element from the input.
52
+ #
53
+ # @raise [NoMethodError]
54
+ # @return [Rex::Proto::Rmi::Model::Element]
55
+ def decode(io)
56
+ self.class.attributes.each do |attr|
57
+ dec_method = ("decode_#{attr}").to_sym
58
+ decoded = self.send(dec_method, io)
59
+ assign_method = (attr.to_s + '=').to_sym
60
+ self.send(assign_method, decoded)
61
+ end
62
+
63
+ self
64
+ end
65
+
66
+ # Encodes the Rex::Proto::Rmi::Model::Element into an String.
67
+ #
68
+ # @raise [NoMethodError]
69
+ # @return [String]
70
+ def encode
71
+ encoded = ''
72
+ self.class.attributes.each do |attr|
73
+ m = ("encode_#{attr}").to_sym
74
+ encoded << self.send(m) if self.send(attr)
75
+ end
76
+
77
+ encoded
78
+ end
79
+
80
+ private
81
+
82
+ # Reads a byte from an IO
83
+ #
84
+ # @param io [IO] the IO to read from
85
+ # @return [Fixnum]
86
+ # @raise [RuntimeError] if the byte can't be read from io
87
+ def read_byte(io)
88
+ raw = io.read(1)
89
+ raise ::RuntimeError, 'Failed to read byte' unless raw
90
+
91
+ raw.unpack('C')[0]
92
+ end
93
+
94
+ # Reads a two bytes short from an IO
95
+ #
96
+ # @param io [IO] the IO to read from
97
+ # @return [Fixnum]
98
+ # @raise [RuntimeError] if the short can't be read from io
99
+ def read_short(io)
100
+ raw = io.read(2)
101
+
102
+ unless raw && raw.length == 2
103
+ raise ::RuntimeError, 'Failed to read short'
104
+ end
105
+
106
+ raw.unpack('n')[0]
107
+ end
108
+
109
+ # Reads a four bytes int from an IO
110
+ #
111
+ # @param io [IO] the IO to read from
112
+ # @return [Fixnum]
113
+ # @raise [RuntimeError] if the int can't be read from io
114
+ def read_int(io)
115
+ raw = io.read(4)
116
+
117
+ unless raw && raw.length == 4
118
+ raise ::RuntimeError, 'Failed to read short'
119
+ end
120
+
121
+ raw.unpack('N')[0]
122
+ end
123
+
124
+ # Reads an string from an IO
125
+ #
126
+ # @param io [IO] the IO to read from
127
+ # @param length [Fixnum] the string length
128
+ # @return [String]
129
+ # @raise [RuntimeError] if the string can't be read from io
130
+ def read_string(io, length)
131
+ raw = io.read(length)
132
+
133
+ unless raw && raw.length == length
134
+ raise ::RuntimeError, 'Failed to read string'
135
+ end
136
+
137
+ raw
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end