psrp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +3 -0
- data/DemoDLL_RemoteProcess.dll +0 -0
- data/Invoke-ReflectivePEInjection.ps1 +2952 -0
- data/LICENSE +202 -0
- data/README.md +16 -0
- data/lib/psrp.rb +175 -0
- data/lib/response_handler.rb +134 -0
- data/lib/transport.rb +208 -0
- data/lib/version.rb +7 -0
- data/lib/wsmv/command_output_processor.rb +153 -0
- data/lib/wsmv/commands/base.rb +293 -0
- data/lib/wsmv/commands/close_shell.rb +48 -0
- data/lib/wsmv/commands/create_pipeline.rb +64 -0
- data/lib/wsmv/commands/init_runspace_pool.rb +92 -0
- data/lib/wsmv/commands/receive.rb +81 -0
- data/lib/wsmv/commands/send_data.rb +64 -0
- data/lib/wsmv/psrp_message.rb +289 -0
- data/lib/wsmv/templates/create_pipeline.xml.erb +93 -0
- data/lib/wsmv/templates/init_runspacepool.xml.erb +221 -0
- data/lib/wsmv/templates/runspace_availability.xml.erb +5 -0
- data/lib/wsmv/templates/session_capability.xml.erb +7 -0
- data/psrp.gemspec +39 -0
- data/script.ps1 +2945 -0
- data/test_psrp.rb +32 -0
- metadata +181 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require_relative 'base'
|
18
|
+
|
19
|
+
module PSRP
|
20
|
+
module WSMV
|
21
|
+
# WSMV message to close a remote shell
|
22
|
+
class CloseShell < Base
|
23
|
+
def initialize(session_opts, shell_id)
|
24
|
+
@session_opts = session_opts
|
25
|
+
@shell_id = shell_id
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def create_header(header)
|
31
|
+
header << Gyoku.xml(close_header)
|
32
|
+
end
|
33
|
+
|
34
|
+
def create_body(_body)
|
35
|
+
# no body
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def close_header
|
41
|
+
merge_headers(shared_headers(@session_opts),
|
42
|
+
resource_uri_shell(RESOURCE_URI_POWERSHELL),
|
43
|
+
action_delete,
|
44
|
+
selector_shell_id(@shell_id))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Matt Wrock <matt@mattwrock.com>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative 'base'
|
19
|
+
|
20
|
+
module PSRP
|
21
|
+
module WSMV
|
22
|
+
# WSMV message to execute a command via psrp
|
23
|
+
class CreatePipeline < Base
|
24
|
+
attr_accessor :shell_id
|
25
|
+
|
26
|
+
def initialize(session_opts, shell_id, pipeline)
|
27
|
+
@session_opts = session_opts
|
28
|
+
@shell_id = shell_id
|
29
|
+
@pipeline = pipeline
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def create_header(header)
|
35
|
+
header << Gyoku.xml(command_headers)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_body(body)
|
39
|
+
body.tag!("#{NS_WIN_SHELL}:CommandLine") do |cl|
|
40
|
+
cl << Gyoku.xml(command_body)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# MS-PSRP 3.1.5.3.3
|
47
|
+
# MS-PSRP 2.2.2.10
|
48
|
+
def command_body
|
49
|
+
{
|
50
|
+
"#{NS_WIN_SHELL}:Command" => '',
|
51
|
+
"#{NS_WIN_SHELL}:Arguments" => encode_bytes(@pipeline)
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def command_headers
|
57
|
+
merge_headers(shared_headers(@session_opts),
|
58
|
+
resource_uri_shell(RESOURCE_URI_POWERSHELL),
|
59
|
+
action_command,
|
60
|
+
selector_shell_id(shell_id))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Matt Wrock <matt@mattwrock.com>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative 'base'
|
19
|
+
|
20
|
+
module PSRP
|
21
|
+
module WSMV
|
22
|
+
|
23
|
+
|
24
|
+
# WSMV message to create a remote shell
|
25
|
+
class InitRunspacePool < Base
|
26
|
+
attr_accessor :shell_id
|
27
|
+
|
28
|
+
def initialize(session_opts)
|
29
|
+
@session_opts = session_opts
|
30
|
+
@shell_id = SecureRandom.uuid.to_s.upcase
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def create_header(header)
|
36
|
+
header << Gyoku.xml(shell_headers)
|
37
|
+
end
|
38
|
+
|
39
|
+
def create_body(body)
|
40
|
+
body.tag!("#{NS_WIN_SHELL}:Shell") { |s| s << Gyoku.xml(shell_body) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def shell_body
|
46
|
+
body = {
|
47
|
+
"#{NS_WIN_SHELL}:InputStreams" => 'stdin pr',
|
48
|
+
"#{NS_WIN_SHELL}:OutputStreams" => 'stdout'
|
49
|
+
}
|
50
|
+
session_capabilities = PSRP::MessageEncoder.new(shell_id, nil, :SESSION_CAPABILITY)
|
51
|
+
runspace_init = PSRP::MessageEncoder.new(shell_id, nil, :INIT_RUNSPACEPOOL)
|
52
|
+
body['creationXml'] = encode_bytes(session_capabilities.fragments[0] + runspace_init.fragments[0])
|
53
|
+
body[:attributes!] = {
|
54
|
+
'creationXml' => {
|
55
|
+
'xmlns' => 'http://schemas.microsoft.com/powershell'
|
56
|
+
}
|
57
|
+
}
|
58
|
+
body
|
59
|
+
end
|
60
|
+
|
61
|
+
def header_opts
|
62
|
+
{
|
63
|
+
"#{NS_WSMAN_DMTF}:OptionSet" => {
|
64
|
+
"#{NS_WSMAN_DMTF}:Option" => 2.1,
|
65
|
+
:attributes! => {
|
66
|
+
"#{NS_WSMAN_DMTF}:Option" => {
|
67
|
+
'Name' => 'protocolversion',
|
68
|
+
'MustComply' => 'true'
|
69
|
+
}
|
70
|
+
}
|
71
|
+
},
|
72
|
+
:attributes! => {
|
73
|
+
"#{NS_WSMAN_DMTF}:OptionSet" => {
|
74
|
+
'env:mustUnderstand' => 'true'
|
75
|
+
}
|
76
|
+
}
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
def shell_headers
|
81
|
+
merge_headers(shared_headers(@session_opts),
|
82
|
+
resource_uri_shell(RESOURCE_URI_POWERSHELL),
|
83
|
+
header_opts,
|
84
|
+
action_create,
|
85
|
+
selector_shell_id(shell_id))
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
4
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require_relative 'base'
|
19
|
+
|
20
|
+
module PSRP
|
21
|
+
module WSMV
|
22
|
+
# WSMV message to get output from a remote shell
|
23
|
+
class ReceiveOutput < Base
|
24
|
+
def initialize(session_opts, command_out_opts)
|
25
|
+
@session_opts = session_opts
|
26
|
+
@shell_id = command_out_opts[:shell_id]
|
27
|
+
@command_id = command_out_opts[:command_id]
|
28
|
+
@shell_uri = command_out_opts[:shell_uri] || RESOURCE_URI_POWERSHELL
|
29
|
+
@out_streams = command_out_opts[:out_streams] || %w(stdout)
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
def create_header(header)
|
35
|
+
header << Gyoku.xml(output_header)
|
36
|
+
end
|
37
|
+
|
38
|
+
def create_body(body)
|
39
|
+
body.tag!("#{NS_WIN_SHELL}:Receive") { |cl| cl << Gyoku.xml(output_body) }
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def output_header
|
45
|
+
merge_headers(shared_headers(@session_opts),
|
46
|
+
resource_uri_shell(@shell_uri),
|
47
|
+
action_receive,
|
48
|
+
header_opts,
|
49
|
+
selector_shell_id(@shell_id)
|
50
|
+
)
|
51
|
+
end
|
52
|
+
|
53
|
+
def header_opts
|
54
|
+
{
|
55
|
+
"#{NS_WSMAN_DMTF}:OptionSet" => {
|
56
|
+
"#{NS_WSMAN_DMTF}:Option" => 'TRUE', :attributes! => {
|
57
|
+
"#{NS_WSMAN_DMTF}:Option" => {
|
58
|
+
'Name' => 'WSMAN_CMDSHELL_OPTION_KEEPALIVE'
|
59
|
+
}
|
60
|
+
}
|
61
|
+
},
|
62
|
+
"#{NS_WSMAN_DMTF}:OperationTimeout" => 'PT20.000S'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def output_body
|
67
|
+
if @command_id
|
68
|
+
{
|
69
|
+
"#{NS_WIN_SHELL}:DesiredStream" => @out_streams.join(' '), :attributes! => {
|
70
|
+
"#{NS_WIN_SHELL}:DesiredStream" => {
|
71
|
+
'CommandId' => @command_id
|
72
|
+
}
|
73
|
+
}
|
74
|
+
}
|
75
|
+
else
|
76
|
+
{ "#{NS_WIN_SHELL}:DesiredStream" => 'stdout' }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
#
|
3
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
4
|
+
#
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
# you may not use this file except in compliance with the License.
|
7
|
+
# You may obtain a copy of the License at
|
8
|
+
#
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
# See the License for the specific language governing permissions and
|
15
|
+
# limitations under the License.
|
16
|
+
|
17
|
+
require_relative 'base'
|
18
|
+
|
19
|
+
module PSRP
|
20
|
+
module WSMV
|
21
|
+
class SendData < Base
|
22
|
+
|
23
|
+
def initialize(session_opts, shell_id, command_id, pipeline)
|
24
|
+
@session_opts = session_opts
|
25
|
+
@shell_id = shell_id
|
26
|
+
@command_id = command_id
|
27
|
+
@pipeline = pipeline
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def create_header(header)
|
33
|
+
header << Gyoku.xml(command_headers)
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_body(body)
|
37
|
+
body.tag!("#{NS_WIN_SHELL}:Send") do |cl|
|
38
|
+
cl << Gyoku.xml(command_body)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def command_body
|
45
|
+
{
|
46
|
+
"#{NS_WIN_SHELL}:Stream" => encode_bytes(@pipeline),
|
47
|
+
:attributes! => {
|
48
|
+
"#{NS_WIN_SHELL}:Stream" => {
|
49
|
+
'Name': 'stdin',
|
50
|
+
'CommandId': @command_id
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
end
|
55
|
+
|
56
|
+
def command_headers
|
57
|
+
merge_headers(shared_headers(@session_opts),
|
58
|
+
resource_uri_shell(RESOURCE_URI_POWERSHELL),
|
59
|
+
action_send,
|
60
|
+
selector_shell_id(@shell_id))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,289 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
#
|
3
|
+
# Copyright 2015 Matt Wrock <matt@mattwrock.com>
|
4
|
+
# Copyright 2016 Shawn Neal <sneal@sneal.net>
|
5
|
+
# Copyright 2016 Sam Oluwalana <soluwalana@gmail.com>
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
8
|
+
# you may not use this file except in compliance with the License.
|
9
|
+
# You may obtain a copy of the License at
|
10
|
+
#
|
11
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
12
|
+
#
|
13
|
+
# Unless required by applicable law or agreed to in writing, software
|
14
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
15
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
16
|
+
# See the License for the specific language governing permissions and
|
17
|
+
# limitations under the License.
|
18
|
+
|
19
|
+
require 'erubis'
|
20
|
+
|
21
|
+
|
22
|
+
# PowerShell Remoting Protcol module
|
23
|
+
module PSRP
|
24
|
+
|
25
|
+
|
26
|
+
class Message
|
27
|
+
# Length of all the blob header fields:
|
28
|
+
# BOM, pipeline_id, runspace_pool_id, message_type, blob_destination
|
29
|
+
BLOB_HEADER_LEN = 43
|
30
|
+
|
31
|
+
# Maximum allowed length of the blob
|
32
|
+
BLOB_MAX_LEN = 32768 - BLOB_HEADER_LEN
|
33
|
+
|
34
|
+
# All known PSRP message types
|
35
|
+
MESSAGE_TYPES = {
|
36
|
+
SESSION_CAPABILITY: 0x00010002,
|
37
|
+
INIT_RUNSPACEPOOL: 0x00010004,
|
38
|
+
PUBLIC_KEY: 0x00010005,
|
39
|
+
ENCRYPTED_SESSION_KEY: 0x00010006,
|
40
|
+
PUBLIC_KEY_REQUEST: 0x00010007,
|
41
|
+
CONNECT_RUNSPACEPOOL: 0x00010008,
|
42
|
+
RUNSPACEPOOL_INIT_DATA: 0x0002100B,
|
43
|
+
RESET_RUNSPACE_STATE: 0x0002100C,
|
44
|
+
SET_MAX_RUNSPACES: 0x00021002,
|
45
|
+
SET_MIN_RUNSPACES: 0x00021003,
|
46
|
+
RUNSPACE_AVAILABILITY: 0x00021004,
|
47
|
+
RUNSPACEPOOL_STATE: 0x00021005,
|
48
|
+
CREATE_PIPELINE: 0x00021006,
|
49
|
+
GET_AVAILABLE_RUNSPACES: 0x00021007,
|
50
|
+
USER_EVENT: 0x00021008,
|
51
|
+
APPLICATION_PRIVATE_DATA: 0x00021009,
|
52
|
+
GET_COMMAND_METADATA: 0x0002100A,
|
53
|
+
RUNSPACEPOOL_HOST_CALL: 0x00021100,
|
54
|
+
RUNSPACEPOOL_HOST_RESPONSE: 0x00021101,
|
55
|
+
PIPELINE_INPUT:0x00041002,
|
56
|
+
END_OF_PIPELINE_INPUT: 0x00041003,
|
57
|
+
PIPELINE_OUTPUT: 0x00041004,
|
58
|
+
ERROR_RECORD: 0x00041005,
|
59
|
+
PIPELINE_STATE: 0x00041006,
|
60
|
+
DEBUG_RECORD: 0x00041007,
|
61
|
+
VERBOSE_RECORD: 0x00041008,
|
62
|
+
WARNING_RECORD: 0x00041009,
|
63
|
+
PROGRESS_RECORD: 0x00041010,
|
64
|
+
INFORMATION_RECORD: 0x00041011,
|
65
|
+
PIPELINE_HOST_CALL: 0x00041100,
|
66
|
+
PIPELINE_HOST_RESPONSE: 0x00041101
|
67
|
+
}
|
68
|
+
|
69
|
+
|
70
|
+
# Format a UUID into a GUID compatible byte array for Windows
|
71
|
+
#
|
72
|
+
# https://msdn.microsoft.com/en-us/library/windows/desktop/aa373931(v=vs.85).aspx
|
73
|
+
# typedef struct _GUID {
|
74
|
+
# DWORD Data1;
|
75
|
+
# WORD Data2;
|
76
|
+
# WORD Data3;
|
77
|
+
# BYTE Data4[8];
|
78
|
+
# } GUID;
|
79
|
+
#
|
80
|
+
# @param uuid [String] Canonical hex format with hypens.
|
81
|
+
# @return [Array<byte>] UUID in a Windows GUID compatible byte array layout.
|
82
|
+
def uuid_to_windows_guid_bytes(uuid)
|
83
|
+
return [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] unless uuid
|
84
|
+
b = uuid.scan(/[0-9a-fA-F]{2}/).map { |x| x.to_i(16) }
|
85
|
+
b[0..3].reverse + b[4..5].reverse + b[6..7].reverse + b[8..15]
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class MessageDecoder < Message
|
90
|
+
attr :message_id, :fragment_id, :fragment_flags, :blob_length, :destination, :message_type, :client_runspace, :client_pid, :data
|
91
|
+
|
92
|
+
def initialize(raw_text)
|
93
|
+
# Message ID - 8bytes
|
94
|
+
# Fragment ID - 8bytes
|
95
|
+
# reserved - 6bits
|
96
|
+
# end fragment - 1bit
|
97
|
+
# start fragment - 1bit
|
98
|
+
# blob_length - 4bytes
|
99
|
+
# destination - 4bytes
|
100
|
+
# message_type - 4bytes
|
101
|
+
# client runspace GUID - 16bytes
|
102
|
+
# client PID GUID - 16bytes
|
103
|
+
# BOM 3bytes
|
104
|
+
#
|
105
|
+
# Data - blob_length - 43 bytes (40 blob bytes + BOM)
|
106
|
+
unencoded = Base64.decode64(raw_text)
|
107
|
+
fields = unencoded.unpack('Q>2CL>L<2h32h32C3A*')
|
108
|
+
|
109
|
+
@message_id = fields[0]
|
110
|
+
@fragment_id = fields[1]
|
111
|
+
@fragment_flags = fields[2]
|
112
|
+
@blob_length = fields[3]
|
113
|
+
|
114
|
+
if is_start_fragment?
|
115
|
+
@destination = fields[4]
|
116
|
+
@message_type = fields[5]
|
117
|
+
@client_runspace = fields[6]
|
118
|
+
@client_pid = fields[7]
|
119
|
+
@data = fields[11]
|
120
|
+
else
|
121
|
+
fields = unencoded.unpack('Q>2CL>A*')
|
122
|
+
@destination = nil
|
123
|
+
@message_type = nil
|
124
|
+
@client_runspace = nil
|
125
|
+
@client_pid = nil
|
126
|
+
@data = fields[4]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def is_end_fragment?
|
131
|
+
(@fragment_flags & 2) != 0
|
132
|
+
end
|
133
|
+
|
134
|
+
def is_start_fragment?
|
135
|
+
(@fragment_flags & 1) != 0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# PowerShell Remoting Protocol base message.
|
140
|
+
# http://download.microsoft.com/download/9/5/E/95EF66AF-9026-4BB0-A41D-A4F81802D92C/%5BMS-PSRP%5D.pdf
|
141
|
+
class MessageEncoder < Message
|
142
|
+
|
143
|
+
attr :fragments
|
144
|
+
|
145
|
+
TEMPLATES = {
|
146
|
+
SESSION_CAPABILITY: 'session_capability',
|
147
|
+
INIT_RUNSPACEPOOL: 'init_runspacepool',
|
148
|
+
CREATE_PIPELINE: 'create_pipeline',
|
149
|
+
RUNSPACE_AVAILABILITY: 'runspace_availability'
|
150
|
+
|
151
|
+
}
|
152
|
+
|
153
|
+
@@obj_id = 0
|
154
|
+
|
155
|
+
# Creates a new PSRP message instance
|
156
|
+
# @param id [Fixnum] The incrementing fragment id.
|
157
|
+
# @param shell_id [String] The UUID of the remote shell/runspace pool.
|
158
|
+
# @param command_id [String] The UUID to correlate the command/pipeline
|
159
|
+
# response.
|
160
|
+
# @param message_type [Fixnum] The PSRP MessageType. This is most commonly
|
161
|
+
# specified in hex, e.g. 0x00010002.
|
162
|
+
# @param payload [String] The PSRP payload as serialized XML
|
163
|
+
def initialize(shell_id, command_id, message_type, context = nil)
|
164
|
+
fail 'shell_id cannot be nil' if shell_id.nil?
|
165
|
+
@@obj_id += 1
|
166
|
+
@id = @@obj_id
|
167
|
+
@shell_id = shell_id
|
168
|
+
@command_id = command_id
|
169
|
+
@message_type = MESSAGE_TYPES[message_type]
|
170
|
+
@payload = render(TEMPLATES[message_type], context).force_encoding('utf-8').bytes
|
171
|
+
|
172
|
+
num_fragments = 1
|
173
|
+
num_fragments += (@payload.length / BLOB_MAX_LEN)
|
174
|
+
if (@payload.length % BLOB_MAX_LEN) == 0
|
175
|
+
num_fragments -= 1
|
176
|
+
end
|
177
|
+
|
178
|
+
@fragments = []
|
179
|
+
|
180
|
+
for i in 0..(num_fragments - 1)
|
181
|
+
start_pos = (i * BLOB_MAX_LEN)
|
182
|
+
end_pos = ((i + 1) * BLOB_MAX_LEN) - 1
|
183
|
+
|
184
|
+
if num_fragments == 1
|
185
|
+
flags = [3]
|
186
|
+
elsif i == 0
|
187
|
+
flags = [1]
|
188
|
+
elsif i == (num_fragments - 1)
|
189
|
+
flags = [2]
|
190
|
+
else
|
191
|
+
flags = [0]
|
192
|
+
end
|
193
|
+
|
194
|
+
@fragments.push(bytes(@payload[start_pos..end_pos], i, flags))
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
# Renders the specified template with the given context
|
199
|
+
# @param template [String] The base filename of the PSRP message template.
|
200
|
+
# @param context [Hash] Any options required for rendering the template.
|
201
|
+
# @return [String] The rendered XML PSRP message.
|
202
|
+
# @api private
|
203
|
+
def render(template, context = nil)
|
204
|
+
template_path = File.expand_path(
|
205
|
+
"#{File.dirname(__FILE__)}/templates/#{template}.xml.erb")
|
206
|
+
template = File.read(template_path)
|
207
|
+
Erubis::Eruby.new(template).result(context)
|
208
|
+
end
|
209
|
+
|
210
|
+
# Returns the raw PSRP message bytes ready for transfer to Windows inside a
|
211
|
+
# WinRM message.
|
212
|
+
# @return [Array<Byte>] Unencoded raw byte array of the PSRP message.
|
213
|
+
# rubocop:disable Metrics/AbcSize
|
214
|
+
def bytes(blob, frag_id, flags)
|
215
|
+
|
216
|
+
# Packet fragment headers
|
217
|
+
message = message_id
|
218
|
+
message += fragment_id(frag_id)
|
219
|
+
message += flags
|
220
|
+
|
221
|
+
if frag_id == 0
|
222
|
+
# packet fragment header
|
223
|
+
message += blob_length(blob, BLOB_HEADER_LEN)
|
224
|
+
|
225
|
+
# PSRP message header
|
226
|
+
message += blob_destination
|
227
|
+
message += message_type
|
228
|
+
message += runspace_pool_id
|
229
|
+
message += pipeline_id
|
230
|
+
message += byte_order_mark + blob
|
231
|
+
|
232
|
+
else
|
233
|
+
# packet fragment header
|
234
|
+
message += blob_length(blob, 0)
|
235
|
+
# PSRP fragment
|
236
|
+
message += blob
|
237
|
+
end
|
238
|
+
message
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
def message_id
|
244
|
+
int64be(@id)
|
245
|
+
end
|
246
|
+
|
247
|
+
def fragment_id(frag_id)
|
248
|
+
int64be(frag_id)
|
249
|
+
end
|
250
|
+
|
251
|
+
def blob_length(blob, header_len)
|
252
|
+
int16be(blob.length + header_len)
|
253
|
+
end
|
254
|
+
|
255
|
+
def blob_destination
|
256
|
+
[2, 0, 0, 0]
|
257
|
+
end
|
258
|
+
|
259
|
+
def message_type
|
260
|
+
int16le(@message_type)
|
261
|
+
end
|
262
|
+
|
263
|
+
def runspace_pool_id
|
264
|
+
uuid_to_windows_guid_bytes(@shell_id)
|
265
|
+
end
|
266
|
+
|
267
|
+
def pipeline_id
|
268
|
+
uuid_to_windows_guid_bytes(@command_id)
|
269
|
+
end
|
270
|
+
|
271
|
+
def byte_order_mark
|
272
|
+
[239, 187, 191]
|
273
|
+
end
|
274
|
+
|
275
|
+
def int64be(int64)
|
276
|
+
[int64 >> 32, int64 & 0x00000000ffffffff].pack('N2').unpack('C8')
|
277
|
+
end
|
278
|
+
|
279
|
+
def int16be(int16)
|
280
|
+
[int16].pack('N').unpack('C4')
|
281
|
+
end
|
282
|
+
|
283
|
+
def int16le(int16)
|
284
|
+
[int16].pack('N').unpack('C4').reverse
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|