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

execute_dotnet_assembly fix parameters managing #14304

Merged

Conversation

b4rtik
Copy link
Contributor

@b4rtik b4rtik commented Oct 23, 2020

This PR fix a problem in execute_dotnet_assembly while running assemblies with Main signature (string[] args) and no passed parameters

This module must handle the following main signature cases:

Main(string[] args) (with and without parameters passed)
Main()

The current implementation does not correctly handle the case where the signature is

Main(string[] args)

and no parameters are passed.

Verification

To verify the correct functioning of the fix it is necessary to build the following assembly

TestParameters.exe : source

TestNoParameters.exe : source

Main(string[] args) with no parameter

  • Start msfconsole
  • use post/windows/manage/execute_dotnet_assembly
  • set DOTNET_EXE /home/msfusr/builds/TestParameters.exe
  • set SESSION 1
  • run
  • Verify The module terminates successfully and the output is something like the following
msf5 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against DESKTOP-RNTSAG
[*] Launching notepad.exe to host CLR...
[+] Process 12164 launched.
[*] Reflectively injecting the Host DLL into 12164..
[*] Injecting Host into 12164...
[*] Host injected. Copy assembly into 12164...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : C:\WINDOWS\system32\notepad.exe
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf5 post(windows/manage/execute_dotnet_assembly) >

Main(string[] args) with parameters

  • Start msfconsole
  • use post/windows/manage/execute_dotnet_assembly
  • set DOTNET_EXE /home/msfusr/builds/TestParameters.exe
  • set ARGUMENTS pippo
  • set SESSION 1
  • run
  • Verify The module terminates successfully and the output is something like the following
msf5 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against DESKTOP-RA33NLQ
[*] Launching notepad.exe to host CLR...
[+] Process 3556 launched.
[*] Reflectively injecting the Host DLL into 3556..
[*] Injecting Host into 3556...
[*] Host injected. Copy assembly into 3556...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : pippo
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf5 post(windows/manage/execute_dotnet_assembly) >

Main()

  • Start msfconsole
  • use post/windows/manage/execute_dotnet_assembly
  • set DOTNET_EXE /home/msfusr/builds/TestNoParameters.exe
  • unset ARGUMENTS
  • set SESSION 1
  • run
  • Verify The module terminates successfully and the output is something like the following
msf5 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against DESKTOP-RA33NLQ
[*] Launching notepad.exe to host CLR...
[+] Process 11584 launched.
[*] Reflectively injecting the Host DLL into 11584..
[*] Injecting Host into 11584...
[*] Host injected. Copy assembly into 11584...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Execution without parameters
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf5 post(windows/manage/execute_dotnet_assembly) >

Fix a problem running assemblies with Main signature (string[] args) and no passed parameters
@smcintyre-r7
Copy link
Contributor

Code changes look good, so I'll move on to testing this. Thanks for catching and fixing this issue.

@smcintyre-r7
Copy link
Contributor

Quick example showing reproducing the original issue:

msf6 post(windows/manage/execute_dotnet_assembly) > show options 

Module options (post/windows/manage/execute_dotnet_assembly):

   Name            Current Setting                               Required  Description
   ----            ---------------                               --------  -----------
   AMSIBYPASS      true                                          yes       Enable Amsi bypass
   ARGUMENTS                                                     no        Command line arguments
   DOTNET_EXE      /home/smcintyre/MsfPr14304TestParameters.exe  yes       Assembly file name
   ETWBYPASS       true                                          yes       Enable Etw bypass
   PID             0                                             no        Pid  to inject
   PPID            0                                             no        Process Identifier for PPID spoofing when creating a new process. (0 = no PPID spoofing)
   PROCESS         notepad.exe                                   no        Process to spawn
   SESSION         -1                                            yes       The session to run this module on.
   USETHREADTOKEN  true                                          no        Spawn process with thread impersonation
   WAIT            10                                            no        Time in seconds to wait

msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 6976 launched.
[*] Reflectively injecting the Host DLL into 6976..
[*] Injecting Host into 6976...
[*] Host injected. Copy assembly into 6976...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Failed pMethodInfo->Invoke_3  w/hr 0x8002000e
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > set ARGUMENTS x
ARGUMENTS => x
msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 9464 launched.
[*] Reflectively injecting the Host DLL into 9464..
[*] Injecting Host into 9464...
[*] Host injected. Copy assembly into 9464...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : x
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) >

Copy link
Contributor

@smcintyre-r7 smcintyre-r7 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright so I've had a chance to review this more thoroughly and I'm not sure this is the best way to address the issue.

First, while these changes do fix both cases for the .NET binary that takes parameters, now the binary that does not seems to be broken which is a regression. If the binary used a signature that expected arguments, I think it's reasonable that it would fail to run when no parameters are passed.

Next, I'm not a fan of these special argssize numbers for a few reasons. First it's a bit difficult to read and not particularly expressive. Looks like 1 is no arguments, 2 is arguments which may or may not be present? I see that the size includes the NULL terminator for the parameters which is fine, but using 2 as a special value is less than ideal because it's the same when it's double null terminated and when a single character is the argument. It's also difficult from a user perspective to know when datastore['ARGUMENTS'] will be nil and when it will be "" which are handled differently using this logic. I suspect this has something to do with the regression I mentioned.

What I would propose instead is restoring how datastore['ARGUMENTS'] is processed and specify the signature in a separate OptEnum. Something like:
OptEnum.new(['Signature', [true, 'The function signature', 'Automatic', ['Automatic', 'Main()', 'Main(string[])'])

You'd then pass the signature type as another value you like you do argssize and assembly_size. You could easily use 1 for Main() and 2 for Main(string[]). These values should be defined as constants or an enum of some kind. On the Metasploit side you'd do something to the effect of:

params = ""
case datastore['Signature']
when 'Automatic'
  signature = datastore['Arguments'].blank? ? 1 : 2
when 'Main()'
  signature = 1
when 'Main(string[])'
  signature = 2
  params << datastore['ARGUMENTS']
end
params << "\x00"
argssize = params.length

This would retain the original behavior of assuming no parameters when no arguments are specified but also allow the user to explicitly define it.

Also like in HostingCLR.cpp lines 276 and 278, there's an off by one error. Since raw_args_length includes the NULL terminator, it does not need to be increased by one. You can drop the +1 on each of those two lines.

I know that's alot, so let me konw if you have any questions.

@b4rtik
Copy link
Contributor Author

b4rtik commented Oct 30, 2020

Your proposal seemed interesting to me. I had some doubts about the usability, but I implemented and tested it a bit. I found the behavior much more consistent. In addition, having an explicit option for the sign with the options command the user can realize the configuration error, in the previous version it was much less understandable.

For the signature parameter I preferred to use a single byte instead of an integer.

@smcintyre-r7
Copy link
Contributor

I pushed up some changes in 0ccb50a.

  • Recompiled the executable, which we have to do for external contributors
  • Adjusted how the arguments are packed, and switched to using standard booleans (non-zero values are true vs 1/2)
  • Used a SIGNATURES constant to improve clarity
  • Fixed appending the datastore arguments even when the signature was automatically selected
  • Added .gitignore files to avoid adding unnecessary build files

Then with that in place, I tested the parameter and no parameter bins with their correct signatures and the automatic detection. In all cases, they appear to be working as intended. Once the tests pass, I'll go ahead and land this. Thanks!

Testing Output
msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 5644 launched.
[*] Reflectively injecting the Host DLL into 5644..
[*] Injecting Host into 5644...
[*] Host injected. Copy assembly into 5644...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Execution without parameters
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > show options 

Module options (post/windows/manage/execute_dotnet_assembly):

   Name            Current Setting                                 Required  Description
   ----            ---------------                                 --------  -----------
   AMSIBYPASS      true                                            yes       Enable Amsi bypass
   ARGUMENTS                                                       no        Command line arguments
   DOTNET_EXE      /home/smcintyre/MsfPr14304TestNoParameters.exe  yes       Assembly file name
   ETWBYPASS       true                                            yes       Enable Etw bypass
   PID             0                                               no        Pid  to inject
   PPID            0                                               no        Process Identifier for PPID spoofing when creating a new process. (0 = no PPID spoofing)
   PROCESS         notepad.exe                                     no        Process to spawn
   SESSION         -1                                              yes       The session to run this module on.
   Signature       Main()                                          yes       The Main function signature (Accepted: Automatic, Main(), Main(string[]))
   USETHREADTOKEN  true                                            no        Spawn process with thread impersonation
   WAIT            10                                              no        Time in seconds to wait

msf6 post(windows/manage/execute_dotnet_assembly) > set Signature Automatic
Signature => Automatic
msf6 post(windows/manage/execute_dotnet_assembly) > run
msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 12028 launched.
[*] Reflectively injecting the Host DLL into 12028..
[*] Injecting Host into 12028...
[*] Host injected. Copy assembly into 12028...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Execution without parameters
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > set DOTNET_EXE /home/smcintyre/MsfPr14304TestParameters.exe
DOTNET_EXE => /home/smcintyre/MsfPr14304TestParameters.exe
msf6 post(windows/manage/execute_dotnet_assembly) > show options 

Module options (post/windows/manage/execute_dotnet_assembly):

   Name            Current Setting                               Required  Description
   ----            ---------------                               --------  -----------
   AMSIBYPASS      true                                          yes       Enable Amsi bypass
   ARGUMENTS                                                     no        Command line arguments
   DOTNET_EXE      /home/smcintyre/MsfPr14304TestParameters.exe  yes       Assembly file name
   ETWBYPASS       true                                          yes       Enable Etw bypass
   PID             0                                             no        Pid  to inject
   PPID            0                                             no        Process Identifier for PPID spoofing when creating a new process. (0 = no PPID spoofing)
   PROCESS         notepad.exe                                   no        Process to spawn
   SESSION         -1                                            yes       The session to run this module on.
   Signature       Automatic                                     yes       The Main function signature (Accepted: Automatic, Main(), Main(string[]))
   USETHREADTOKEN  true                                          no        Spawn process with thread impersonation
   WAIT            10                                            no        Time in seconds to wait

msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 12268 launched.
[*] Reflectively injecting the Host DLL into 12268..
[*] Injecting Host into 12268...
[*] Host injected. Copy assembly into 12268...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Failed pMethodInfo->Invoke_3  w/hr 0x8002000e
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > set ARGUMENTS test
ARGUMENTS => test
msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 2584 launched.
[*] Reflectively injecting the Host DLL into 2584..
[*] Injecting Host into 2584...
[*] Host injected. Copy assembly into 2584...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : test
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > show options 

Module options (post/windows/manage/execute_dotnet_assembly):

   Name            Current Setting                               Required  Description
   ----            ---------------                               --------  -----------
   AMSIBYPASS      true                                          yes       Enable Amsi bypass
   ARGUMENTS       test                                          no        Command line arguments
   DOTNET_EXE      /home/smcintyre/MsfPr14304TestParameters.exe  yes       Assembly file name
   ETWBYPASS       true                                          yes       Enable Etw bypass
   PID             0                                             no        Pid  to inject
   PPID            0                                             no        Process Identifier for PPID spoofing when creating a new process. (0 = no PPID spoofing)
   PROCESS         notepad.exe                                   no        Process to spawn
   SESSION         -1                                            yes       The session to run this module on.
   Signature       Automatic                                     yes       The Main function signature (Accepted: Automatic, Main(), Main(string[]))
   USETHREADTOKEN  true                                          no        Spawn process with thread impersonation
   WAIT            10                                            no        Time in seconds to wait

msf6 post(windows/manage/execute_dotnet_assembly) > set SIGNATURE Main(string[]) 
SIGNATURE => Main(string[])
msf6 post(windows/manage/execute_dotnet_assembly) > run
msf6 post(windows/manage/execute_dotnet_assembly) > run

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 7124 launched.
[*] Reflectively injecting the Host DLL into 7124..
[*] Injecting Host into 7124...
[*] Host injected. Copy assembly into 7124...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : test
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) > rerun
[*] Reloading module...

[*] Running module against WIN10DEV
[*] Launching notepad.exe to host CLR...
[+] Process 5804 launched.
[*] Reflectively injecting the Host DLL into 5804..
[*] Injecting Host into 5804...
[*] Host injected. Copy assembly into 5804...
[*] Assembly copied.
[*] Executing...
[*] Start reading output
[+] Args length : 1
[+] Arg : test
[+] Running etw bypass
[+] Running Amsi bypass
[+] Succeeded
[*] End output.
[+] Execution finished.
[*] Post module execution completed
msf6 post(windows/manage/execute_dotnet_assembly) >

@jmartin-tech
Copy link
Contributor

@msjenkins-r7 test this please.

@smcintyre-r7 smcintyre-r7 merged commit 76ab0ee into rapid7:master Nov 10, 2020
@smcintyre-r7
Copy link
Contributor

smcintyre-r7 commented Nov 10, 2020

Release Notes

Updated the post/windows/manage/execute_dotnet_assembly module to properly handle different signatures for the entry point of the code it is injecting.

@pbarry-r7 pbarry-r7 added the rn-fix release notes fix label Dec 9, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug module rn-fix release notes fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants