Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PSRP Response Objects can span over multiple WSMV messages #284

Open
obeleh opened this issue Mar 22, 2018 · 0 comments
Open

PSRP Response Objects can span over multiple WSMV messages #284

obeleh opened this issue Mar 22, 2018 · 0 comments

Comments

@obeleh
Copy link

obeleh commented Mar 22, 2018

Hi

A while ago I started porting this project to python asyncio. The result can be found here I've found a bug in my port that is also present in this project.

In this part of the code you can see that a new Defragmenter is constructed for every WSMV message link to file:

def read_message(wsmv_message, wait_for_done_state = false)
  messages = []
  defragmenter = MessageDefragmenter.new
  read_response(wsmv_message, wait_for_done_state) do |stream|
    message = defragmenter.defragment(stream[:text])
    next unless message
    if block_given?
      yield message
    else
      messages.push(message)
    end
  end
  messages unless block_given?
end

The reason that this project probably hasn't caught the issue is because PSRP messages usually contain a start and an end fragment when retrieving lists of Powershell objects. In my project I convert my results into json and then only because I request a large chunk of data the fragments span over multiple WSMV messages. Usually you get lots of Objects with a few Fragments. However if you convert to json you can produce one Object which spans multiple fragments.

Let me make it more concrete. This is my code that does part of the defragmenting:

@classmethod
def streams_to_messages(cls, streams):
    stream_messages = {}
    cur_message_bytes = None
    print('Stream')
    for stream_type, fragment in cls.streams_to_fragments(streams):
        print(f'Fragment O:{fragment.object_id} F:{fragment.fragment_id} S:{bool(fragment.start_fragment)}: E:{bool(fragment.end_fragment)}')
        if stream_type not in stream_messages:
            stream_messages[stream_type] = []

        if fragment.start_fragment:
            cur_message_bytes = fragment.blob
        else:
            cur_message_bytes += fragment.blob

        if fragment.end_fragment:
            message = cls.message_from(cur_message_bytes)
            decoded = PsOutputDecoder.decode(message)
            stream_messages[stream_type].append(decoded)

As you can see I log the object_id, fragment_id and whether or not it's an end and/or start fragment.

For example if you start the following request:
Get-WmiObject Win32_Product every WSMV message contains a start and end fragment.

WSMV message
Stream
Fragment O:1 F:0 S:True: E:True
Fragment O:2 F:0 S:True: E:True
WSMV message
Stream
Fragment O:3 F:0 S:True: E:True
WSMV message
Stream
Fragment O:4 F:0 S:True: E:True
Fragment O:5 F:0 S:True: E:True
Fragment O:6 F:0 S:True: E:True
Fragment O:7 F:0 S:True: E:True
Fragment O:8 F:0 S:True: E:True
-- part left out for readability
Fragment O:1243 F:0 S:True: E:True
Fragment O:1244 F:0 S:True: E:True

But if you do this: Get-WmiObject Win32_Product | ConvertTo-Json -Compress you get one big result object containing multiple fragments:

WSMV message
Stream
Fragment O:1 F:0 S:True: E:True
Fragment O:2 F:0 S:True: E:True
WSMV message
Stream
Fragment O:3 F:0 S:True: E:True
WSMV message
Stream
Fragment O:4 F:0 S:True: E:False
Fragment O:4 F:1 S:False: E:False
Fragment O:4 F:2 S:False: E:False
Fragment O:4 F:3 S:False: E:False
WSMV message
Stream
Fragment O:4 F:4 S:False: E:False
-- at this point aiowinrm crashes because it initialises the bytes buffer when it sees a start fragment

Because this library also initialises a defragmenter for every WSMV message it essentially misses output data:

def defragment(base64_bytes)
  fragment = fragment_from(Base64.decode64(base64_bytes))

  @messages[fragment.object_id] ||= []
  @messages[fragment.object_id].push fragment

  if fragment.end_fragment
    blob = []
    @messages.delete(fragment.object_id).each { |frag| blob += frag.blob }
    return message_from(blob.pack('C*'))
  end
end

As you can see the fragment data is only decoded at the end. But if the end is not within the current WSMV message it's never decoded

@obeleh obeleh changed the title PSRP Fragments can span over WSMV messages PSRP Response Objects can span over multiple WSMV messages Mar 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant