psrp 0.0.1
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 +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
|
+
|