itm4n / PrivescCheck

Privilege Escalation Enumeration Script for Windows
BSD 3-Clause "New" or "Revised" License
2.91k stars 422 forks source link

Execution with Metasploit #27

Closed 0xArt3mis closed 2 years ago

0xArt3mis commented 3 years ago

Hi, thanks for your effort in developing this enumeration script. It's a really nice project and it is really useful for engagements to quickly find privesc vulns, however, sadly this is only fully usable on an interactive powershell session on the system. Somehow the script is not fully usable using reflections or using powershell import commands and executing it, or by executing it via Metasploit. The script starts to execute but stops after the first enumeration.

I was trying to execute the script via metasploit and another commercial C2 without success, would it be possible to make the script more compatible for offensive red-team operations where you only have a session via a beacon / metasploit session etc. and importing it e.g. in your own HostingCLR to execute it?

image image

itm4n commented 3 years ago

Hi, and thank you for your feedback. :+1:

What you experienced in not a "compatibility" issue. It's an actual bug in the code of the second check.

A memory access violation randomly occurs during the execution (even if you are in an interactive session). I need to investigate and identify the source of this error. I noticed that this error occurs more often on Windows 7 / Server 2008 R2 machines.

itm4n commented 3 years ago

It took me a couple of hours but I found the issue and fixed it. It should be ok now.

0xArt3mis commented 3 years ago

Hi thanks. :) I just tried but unfortunately it is still not working correctly, same issue still.

image

itm4n commented 3 years ago

Ok, that's weird. :thinking: I'll have to test from a Meterpreter session to see if I can reproduce the issue.

itm4n commented 3 years ago

I did a few tests. It looks like the problem comes from the behavior of MSF/Merterpreter. I can't even get any output from the script because it times out before letting it finish to execute.

You do get the output if you run each check separately because it takes way less time. I haven't found how to increase the default timeout for the command or session. And I don't even know if it's possible actually. I don't use Meterpreter that much myself.

meterpreter

Anyway, if there is a "compatibility issue", it's because of Metasploit, not the script itself.

Conclusion; I'll let this issue open a few more days in case you know how to fix this on the Metasploit/Meterpreter side, and then I'll close it.

At least, this issue forced me to tackle an actual memory management bug... ¯\_(ツ)_/¯

0xArt3mis commented 3 years ago

Hi, how do you execute it reflectively? I just tried it with another C2 but it was not working either. It was also not working with my own HostingCLR implementation. Do you use Cobalt Strike? I can not test if it works with cobalt strike to first import the script and then execute it. will check further. Is there another version available which provides the function Invoke-PrivescCheck directly in the script or can I make an alias for it?

since you first decode all modules at run-time the C2 says the function is not available after I have imported it.

itm4n commented 3 years ago

Hi, how do you execute it reflectively?

Usually, I don't do that, I execute the script with a simple command line.

Do you use Cobalt Strike?

Nope. Rare to see CS being used in Europe because of sales policy / US laws. I think it's the same in Germany, right?!

Is there another version available which provides the function Invoke-PrivescCheck directly in the script or can I make an alias for it?

I don't understand the question.

since you first decode all modules at run-time the C2 says the function is not available after I have imported it.

True, I decode the script at runtime (mainly to work around AMSI) but it shouldn't affect the way the module is imported. If you want, you can just "cat" all the source files (see "src" folder) into one file and then import this resulting script instead. You will have to disable AMSI first though.

itm4n commented 2 years ago

Nothing new here, I'll close the issue. I'll re-open it if need be. For now, I don't see what I can do. ¯\_(ツ)_/¯

0xArt3mis commented 2 years ago

Hi, sorry for the late response. I got back to the issue and I have some updates.

I think it is missing some exception handling for execution in a custom runspace. I tried to execute it with (https://github.com/Cn33liz/p0wnedShell) to be in a custom runspace. The script executes but still some errors occur. Do you know why these errors occur in a custom runspace?

error_privesc

I was able to execute PrivescCheck with a C2 by reflectively loading privesccheck and by providing the -silent and -report flag, also -extended was working. The output in the file looks good so far, it only says that no mandatory Status variable is available... possibly Status needs a proper powershell window which is not available in a custom runspace.

error_privesc02

but if I don't execute it with -silent and -report I get an error and the script stops executing. So I guess some exceptions/erros are thrown to the output which stop the script from executing in a custom runspace.

Greets.

itm4n commented 2 years ago

Hi! Thanks for the update! :)

In the first case, the script executes but nothing works it seems. The problem is that the script heavily relies on the Windows API to work and it is probably the issue here. Knowing which tool you used though, I should be able to reproduce it and potentially figure it out. :thinking:

In the second case, when you specify the -silent option, no output is returned. Instead, I call Write-Progress to at least show the progression of the script. This cmdlet indeed takes a -Status parameter but I thought it wasn't mandatory. Though it should be easy to fix. That's not really a problem.

Based on your tests, it seems that there is a problem when the script outputs the results in a stream. Using the -Silent option allowed you to work around this issue because no output is returned in this case. I can already think of a more suitable workaround. I could add a switch to specify that you want to output all the results in one block as opposed to outputting them for each check. The problem is that I don't really understand what causes this behavior.

0xArt3mis commented 2 years ago

Hi,

I would really appreciate if this issue can be fixed regarding reflective loading, since this would enable your tool to be used red team assessments where no interactive access is available to the host.

I don't actually need the progress with the fancy write-progress, I just need the output. Maybe it's possible to write a line on the output which task is currently performed or something like that, but pipe the whole output to the file or to the screen without streams.

I think if the tool works in p0wnedshell it will also work on all other C2s which use reflective load with custom CLRs.

itm4n commented 2 years ago

I understand your frustration, but that's no reason for urging me to fix this. If only I could reproduce the issues you report.

  1. With Metasploit / Meterpreter
meterpreter > powershell_import /opt/privesc/PrivescCheck/PrivescCheck.ps1
[+] File successfully imported. No result was returned.
meterpreter > powershell_execute ". C:\_WORKSPACE\PrivescCheck\PrivescCheck.ps1;Invoke-PrivescCheck"
[-] Error running command powershell_execute: Rex::TimeoutError Operation timed out.

As previously described, I do get a timeout error, but it seems it's a well-known issue (see https://github.com/rapid7/metasploit-framework/issues/6274) and there is no plan to fix it. You will get this error for any script that takes more than 15s to run.

However, as you can read in this comment, there is a workaround because the script does run in the background. You just don't get the output in your Meterpreter session.

So, if I use the -Report option to write the script output to a file and wait for the script to complete, it works.

meterpreter > powershell_execute "Invoke-PrivescCheck -report c:\_temp\privesc" 
[-] Error running command powershell_execute: Rex::TimeoutError Operation timed out.
meterpreter > download c:\\_temp\\privesc.txt
[*] Downloading: c:\_temp\privesc.txt -> /home/itm4n/privesc.txt
[*] Downloaded 44.56 KiB of 44.56 KiB (100.0%): c:\_temp\privesc.txt -> /home/itm4n/privesc.txt
[*] download   : c:\_temp\privesc.txt -> /home/itm4n/privesc.txt

And I do get the full output of the script from the file.

$ cat privesc.txt                                                  
+------+------------------------------------------------+------+
| TEST | USER > Identity                                | INFO |
+------+------------------------------------------------+------+
| DESC | Get the full name of the current user (domain +       |
|      | username) along with the associated Security          |
|      | Identifier (SID).                                     |
+------+-------------------------------------------------------+
[*] Found 1 result(s).

Name             : DESKTOP-U7FQ7U5\lab-user
SID              : S-1-5-21-2454586350-3165884344-1968964800-1002
IntegrityLevel   : Medium Mandatory Level (S-1-16-8192)
SessionId        : 1
TokenId          : 00000000-09fae49c
AuthenticationId : 00000000-000ff273
OriginId         : 00000000-000003e7
ModifiedId       : 00000000-055ac34a
Source           : User32 (00000000-000ff08c)

[...]

Granted it's not an ideal solution for a red team engagement but since it's a limitation from the Metasploit framework itself, there is nothing more I can do on that front.

  1. With p0wnedShell

I also tried to run the script with p0wnedShell. Again, the script runs smoothly and I don't get any error.

p0wnedShell C:\_WORKSPACE\PrivescCheck> . .\privesccheck.ps1; Invoke-PrivescCheck
+------+------------------------------------------------+------+
| TEST | USER > Identity                                | INFO |
+------+------------------------------------------------+------+
| DESC | Get the full name of the current user (domain +       |
|      | username) along with the associated Security          |
|      | Identifier (SID).                                     |
+------+-------------------------------------------------------+
[*] Found 1 result(s).

Name             : DESKTOP-U7FQ7U5\lab-user
SID              : S-1-5-21-2454586350-3165884344-1968964800-1002
IntegrityLevel   : Medium Mandatory Level (S-1-16-8192)
SessionId        : 1
TokenId          : 00000000-0a63be89
AuthenticationId : 00000000-000ff273
OriginId         : 00000000-000003e7
ModifiedId       : 00000000-055ac34a
Source           : User32  (00000000-000ff08c)

[...]

To be completely honest, the script did throw a "random" exception at some point (because of a non-existing service), which caused p0wnedShell to terminate the execution of the script. However, I'm not able to reliably reproduce this behavior.

Regarding the Write-Progress issue, I added a function that checks whether the script is running within a console window or not. I also added the -Status option that seems to be required in PSv2. This should prevent the errors you encountered from being thrown when running within a custom runtime.

0xArt3mis commented 2 years ago

Hi,

thank you for your effort. I don't want to urge you to fix it, I just wanted to state the importance since your tool is really good already for penetration tests but wanted it to extend for red team assessments. I can use the alternate execution you already provided, so this is totally fine for me.

One last question: Do you actually have plans to integrate exception handling with some try/catch clauses? Wouldn't it solve the problem that the script stops when an exception is raised?

Thanks again and sorry if I misspoke and this was misunderstood.

itm4n commented 2 years ago

This was indeed a misunderstanding, and also some frustration from my part as well because this is a tool I actively maintain as far as possible, and I try to fix as much things as I can based on the feedback I get from people like you. In this case, it is even more frustrating because I cannot reliably reproduce the issue you are reporting.

Anyway, the code definitely needs a full review because my coding style improved over the years and is no longer consistent with the coding style I used when I started. However, there are now thousands of lines of code so, as you can imagine, this will not be an easy task.

So yeah, you are right, error handling needs to be improved, and so does a number of other things. I will see if I can find some code analysis tool to help me in this task. Though, I will probably have to invest some time to manually review every piece of code manually as well.

Thanks again for your feedback.

srborines commented 2 years ago

Hi everyone!

About the execution of a PowerShell command without the 15 seconds limitation I have written something here in this issue, I clone it here just to make it easier:

Some days ago I found myself in the situation that I needed to execute a command through Meterpreter, this command was taking more than 15 seconds so I was receiving the "Timeout error", at this point I had two options:

  • OPTION 1: spawn a PowerShell terminal with the command 'powershell_shell' and interact directly with it [for my case I didn't want to do this].

  • OPTION 2: Extend the global Meterpreter timeout to a greater value [this was the option I selected]

So trying to find the solution to the option 2 I realized that the 15 seconds timeout it's a limitation of the Metasploit client, no the implant. It means that to extend the timeout it's as easy as before start our Metasploit modify the local ruby files in our computer with the next line:

sed -i 's/self.response_timeout/60/' /usr/src/metasploit-framework/lib/rex/post/meterpreter/packet_dispatcher.rb

You can replace '60' with the number of seconds you want to extend the timeout, by default as we know it's 15.

Hope it helps!

itm4n commented 2 years ago

So........... I finally completely solved this issue.

First, thank you @srborines for your message, it pointed me in the right direction. Setting an hardcoded value in the source code is far from ideal though, so I searched the code to see how the value self.response_timeout is set. This led me to the options of the Metasploit client, and most importantly the associated documentation.

From the official documentation at https://www.offensive-security.com/metasploit-unleashed/msfconsole-commands/, you can read the following.

msf6 exploit(multi/handler) > sessions -h
Usage: sessions [options] or sessions [id]

Active session manipulation and interaction.

OPTIONS:
[...]
    -t, --timeout <seconds>              Set a response timeout (default: 15)
[...]

This is exactly what we need, no need to change the value manually in the code, there is an option for that. If we want to set a timeout of 2 minutes for instance, we just have to do this.

msf6 exploit(multi/handler) > sessions -t 120 -i 1
[*] Starting interaction with 1...

meterpreter > powershell_execute "Invoke-PrivescCheck"
[+] Command execution completed:
+------+------------------------------------------------+------+
| TEST | USER > Identity                                | INFO |
+------+------------------------------------------------+------+
| DESC | Get the full name of the current user (domain +       |
|      | username) along with the associated Security          |
|      | Identifier (SID).                                     |
+------+-------------------------------------------------------+
[*] Found 1 result(s).

Name             : DESKTOP-U7FQ7U5\lab-user
SID              : S-1-5-21-2454586350-3165884344-1968964800-1002
IntegrityLevel   : Medium Mandatory Level (S-1-16-8192)
SessionId        : 1
TokenId          : 00000000-010ccf26
AuthenticationId : 00000000-000b5e83
OriginId         : 00000000-000003e7
ModifiedId       : 00000000-000b5f0d
Source           : User32
meterpreter > 

It worked! No timeout error, but only the result of the first check is displayed. This was already reported by @CT-H00K in previous messages but I did not notice this one particular detail at the time.

After taking a closer look at the output, I saw that it was truncated after User32. A part of this value is missing!

And then I remembered that the "token source" is actually returned as a byte array, not a string. The consequence is that this value may contain null bytes. On Windows, in the command prompt, this is not an issue. In Metasploit though, it is a different story, this null byte is interpreted as a string terminator, hence why the rest of the output is not shown.

I made a slight modification in the script to strip this null byte, and now the entire output is displayed in Metasploit. :partying_face: