Search This Blog

Friday, 1 December 2017

PowerShell Golden Egg - Delete files/folders on Reboot the Microsoft Way (MoveFileEx)

Yeah, just do it on reboot...

So simple, eh? Can't delete the script while it's running. but once the script reboots the PC, there's just that to delete and you're done. So delete it on the reboot yeah? If only it was that simple...

Run\RunOnce

So Microsoft have these "run and runonce" reg keys, one set for each user, one set for the machine itself. But as people have discovered, the time at which these keys execute is between OS starting to load and when the user logon is initiated, but this is before the desktop has loaded, which can complicate things and this has been depreciated in Windows 10 in favour of Asynchronous commands. Also, through my own experiences, I can attest to the complications of having to set a run\runonce command in script which requires some interesting syntax skill and it's worth nothing that the current user run\runonce keys run under the credentials of THAT user. So if they aren't an administrator, the command will be run in a standard user context with the restrictions that apply. All in all, I found this approach buggy, prone to many unusual errors dependent on OS or machine and insufferably complex for something as simple as deleting on reboot.

How do Microsoft do it?

The PendingFileRenameOperations key is a red herring.

Installers, updates and all kinds of packages delete files on reboot and they aren't leveraging keys like this, so what's the correct approach? Well, according to Microsoft documentation, the information for these file changes on reboot is kept in the  'HKLM\SYSTEM\CurrentControlSet\Control\Session Manager' key under the value 'PendingFileRenameOperations' which is a 'REG_MULTI_SZ' or MultiString in PowerShell. Now this seems like a good idea, and in a basic format of source and destination, but you soon find that the strings in the multistring array are null terminated, meaning, they need to have this at end of the string for the values to be read correctly and missing these or messing them up can corrupt the key, causing the computer to constantly think there's pending changes. So trying to keep the null values intact while parsing the key through a PowerShell script seems daunting. Instead...

MoveFileEx!!!


Microsoft have their own Windows API function (or method, my jargon is flexible) for handling this key in the correct manner, so if we declare a new type with the call to that definition, we can use the function to have Windows delete the file on reboot correctly. This poor guy on reddit was so close and I give him many thanks for basically handing me most of the code but the thread is locked and I can't seem to contact the guy. Still, for anyone reading this...

THE ANSWER!!!

Null... ish... (That reddit guy's answer)

PowerShell has a few ideas of what 'null' actually is. Is it an object with a type, but no value; an object without type; the absence of an object? Well, the one that answered the question for me was '[Management.Automation.Language.NullString]::Value()'. This is important because the destination of the MoveFileEx function has to be inputted as the correct type of null for the command to succeed which is what that guy needed.

Add-Type but no Remove-Type

It's also worth noting that I made some use of try catch to check for type loading. This is because the type is loaded in the AppDomain context and can't be unloaded unless a new session is established. The default namespace for these autogenerated types is 'Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes'.

The Code

function Move-OnReboot([parameter(Mandatory=$True)]$Path, $Destination){
Begin{
try{
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]|Out-Null
}catch{
$memberDefinition = @’
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
‘@
Add-Type -Name MoveFileUtils -MemberDefinition $memberDefinition
}
}
Process{
$Path="$((Resolve-Path $Path).Path)"
if ($Destination){
$Destination = $executionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Destination)
}else{$Destination = [Management.Automation.Language.NullString]::Value}
$MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]::MoveFileEx($Path, $Destination, $MOVEFILE_DELAY_UNTIL_REBOOT)
}
End{}
}


Stick this in a script, module, whatever.
Pass it a path.
Pass it a destination to move to another path or omit to delete the original on reboot.
Get a boolean success or fail on return.

Files First - Bonus Material

One joyful quirk of the MoveFileEx operation is that although you can set a folder to delete on reboot, it will not actually perform the command if the folder contains files. This means you must set the files within the folder to delete before you set the folder to delete.

Bonus, bonus...

My script is contained in one folder when deployed, so I made a simple compression of the function code specific for deleting the script folder and containing files only, no sub folders. Placed in a script, will schedule the parent folder and all files for deletion. Put the header at the top of your script and the footer just before the last reboot call.

# Header
$scriptPath=$MyInvocation.MyCommand.Definition
$scriptFolder=(Split-Path -Parent $scriptPath)
function D-OR([parameter(Mandatory=$True)]$Path){
$Path="$((Resolve-Path $Path).Path)"
try{
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]|Out-Null
}catch{
$memberDefinition = @’
[DllImport("kernel32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern bool MoveFileEx(string lpExistingFileName, string lpNewFileName, int dwFlags);
‘@
Add-Type -Name MoveFileUtils -MemberDefinition $memberDefinition
}
"Attempting to delete $Path on Reboot"
[Microsoft.PowerShell.Commands.AddType.AutoGeneratedTypes.MoveFileUtils]::MoveFileEx($Path,[Management.Automation.Language.NullString]::Value,4)
}
# Footer
if(Test-Path $scriptFolder){
 Get-ChildItem $scriptFolder|Select-Object -ExpandProperty FullName|ForEach-Object{D-OR $_}
 D-OR $scriptFolder
}

Sunday, 26 November 2017

PowerShell Tip - Compare Secure Strings

No frills...

So the PC is freshly imaged, but needs connecting to the domain using a script. No available way to authenticate the password before making the method call and fat fingers can screw up the join. For a little extra assurance, enter password twice and compare, but SecureStrings are not comparable, so:

Function Password-DoubleCheck{
  do{
    $pass1=Read-Host -Prompt 'Enter User Password' -AsSecureString
    $pass2=Read-Host -Prompt 'Verfy User Password' -AsSecureString
    $chk1 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass1))
    $chk2 = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($pass2))
    if($chk1-ceq$chk2){$validPass=$True}else{$validPass=$False;Write-Host 'Passwords do not match...'}
  }
  until($validPass)
  $pass1
}

Use like:
$password=(Password-DoubleCheck)

Derived from: http://techibee.com/powershell/compare-secure-strings-entered-through-powershell/422

Wednesday, 22 November 2017

PowerShell Tip - Determine if the OS 64 bit in a consistent way

No frills...

Just wanted a consistent way of determining if the operating system was 32 or 64 bit regardless of PowerShell version or OS.

Function OSIs-64Bit{if([IntPtr]::Size-eq 4){$false}else{$true}}

Paste atop a script and use in an if statement like:
if(OSIs-64Bit){'Do some wicked stuff'}

Tuesday, 13 June 2017

Minecraft Launcher Error 5 Fix

Script for performing Mojang 'Temporary Solution'

Wifey hit with known unfixed bug

So that's it. There's a bug in the latest iterations of the Minecraft Native Launcher for Windows that goes like this (https://bugs.mojang.com/browse/MCL-5291) :
When the launcher needs to update, it downloads a copy to "%MinecraftInstallDir%\tmp\tmpLauncher.tmp". This is actually the new version of MinecraftLauncher.exe.
It then attempts to replace the current launcher at "\%MinecraftInstallDir%MinecraftLauncher.exe" with the downloaded temp file.
This is when users are seeing an 'Error 5' (Which, to me is 'Access Denied', but may be completely unrelated).

A lot of sites suggest that you should delete the temp file and try again, which some users have reported success with but what I'm reading in the updateLog.txt doesn't seem to match. The one solution that I find definitely works is to manually copy over the file as suggested in the "temporary" solution from Mojang.

This issue has reoccurred nearly every update and the bug doesn't seem to be going away, so..,

No Darling, I said 'Batch... it...'

Copy and paste this into notepad and save.
Rename .bat instead of .txt. 
When the error displays, close and 'Run as administrator' on batch file.

@echo off
:: Admin Test
"%systemroot%\system32\reg.exe" query "HKU\S-1-5-19\Environment" > NUL
If Not %ERRORLEVEL% EQU 0 (
Cls & Echo You must have administrator rights to continue.
Echo Please close and restart this batch file by
Echo right-clicking and select 'Run as administrator'.
Pause & Exit
)

:: Set Paths
if exist "%PROGRAMFILES(x86)%" (
Set MCLPath="%PROGRAMFILES(x86)%\Minecraft\MinecraftLauncher.exe"
Set MCTPath="%PROGRAMFILES(x86)%\Minecraft\tmp\tmpLauncher.tmp"
) else (
Set MCLPath="%PROGRAMFILES%\Minecraft\MinecraftLauncher.exe"
Set MCTPath="%PROGRAMFILES%\Minecraft\tmp\tmpLauncher.tmp"
)

::Check files exist
if NOT exist %MCLPath% ( echo Minecraft.exe not found at %MCLPath% & pause & exit )
if NOT exist %MCTPath% ( echo tmpLauncher.tmp not found at %MCTPath% & pause & exit )

:Copy and overwrite
copy /y %MCTPath% %MCLPath%

:Launch
%MCLPath%

exit

Sunday, 7 May 2017

Block YouTube Domain List

Kids losing life on net, YouTube took a hit

Kids watching bollocks on YouTube modified their attitudes and wound me up to the point it got blocked. Modified host files in androids and set access control on the router.

Block List

www.youtube.com
m.youtube.com
youtubei.googleapis.com
youtube.googleapis.com
www.youtube-nocookie.com
youtube.com
youtu.be
ytimg.l.google.com
youtube.l.google.com
s.ytimg.com
ytimg.com
googlevideo.com

Caveats

In the Android KitKat devices, the host file is stored in read-only partition '/etc/hosts' so root access is needed. '/system/etc/hosts is just a reflection of this file. The line endings need to have Unix style EOLs (Use Notepad++ if unsure) and there should be an empty line at the end.

Monday, 24 April 2017

Check if AD user has associated Exchange mailbox GUID in Powershell

You want me to set up their mailbox in Outlook? Do they have one to configure?

Just one of the routine things I'm asked to do is set up a new users' e-mail profile in Outlook on thier desk which is usually a quick 30 seconds, one time per user job. Thing is, I'm not responsible for (read: not allowed near) the Exchange server (a common security setup). Now I can either go back to my desk and check the Active Directory for the user through RSAT, or (because we have one) use the shiny Intranet site from any PC connected in the local branch. Still, usernames, passwords, clicky and not fast enough for me...

ADSI I say aye...

I'm becoming fond of the [ADSISearcher] type accelerator and notation Looks nice and clean, dates back to early Powershell. I find it a great way to check for a username with a LDAP filter to do the fine tuning. Pass in a logon (samaccountname) name and you should get your result.
Another nicety is that the return object for the FindOne() function has all the attributes for the found object. So a quick check for an associated mailbox GUID should quickly tell me if the users' AD object has an associated mailbox.

Function C-FM($n){
$q=[ADSISearcher]"(&(objectCategory=person)(objectClass=user)(|(samaccountname=$n)(displayname=$n)))"
try{$r=$q.FindOne()}catch{'Cannot access domain';return}
if($r){$r.Properties.samaccountname;if($r.Properties.msexchmailboxguid){'User has mailbox'}else{'No mailbox found'}}else{'User not found'}
}
'-- Check if user has mailbox --'
do{$i=Read-Host 'User';if($i-notlike'exit'){C-FM $i}}while($i-notlike'exit')


Wrap it up

Boom. That's it. You can investigate the quirks of the ADSISearcher type accelerator in your own time. I've also noticed that I've been losing some code text on some posts due to HTML formatting. I'll see if this HTML converter helps...

Tuesday, 11 April 2017

Remembering T586A, B and crossover - Beware my thought process

Clip back, B is current, Orange with Orange, Green with Blue, Blue with Green, Brown with Brown. One to Three, Two to Six. Straight to A, Cross to cross.

This may sound like madness, but it's my process of remembering T586A, B and crossover wiring using twisted pair ethernet cables.

So let's break it down:

"Clip back"
When looking at the Ethernet cable plug, the clip to secure it in a socket should be at the back (facing away from you), then looking at the lines, pin one is on your left.

"B is current, Orange with Orange, Green with Blue, Blue with Green, Brown with Brown."

Pin| T586B (The T586B standard is the current standard mainly in use)
---|--------
 1 |Orange White (Orange with)
 2 |Orange
 3 |Green White (Green with)
 4 |Blue
 5 |Blue White  (Blue with)
 6 |Green
 7 |Brown White  (Brown with)
 8 |Brown

So not too complicated, just keeping the flow. The next part might lose some of you...
"One to Three, Two to Six."

At this point, I need to visualise the eight pins of the older standard (T586A) side by side with this and draw a line between pin one to pin three and two to six from both sides to the other.

Which allows us to move on to "Straight to A, Cross to cross."

If the final intended end is T586A, go pin to pin down the remaining pins.

Or if the final intended end is a T586B crossover cable, cross the lines in a parallel fashion.
With this pattern in mind, I can remember how to wire any T586A/B/Crossover cable.

I've deliberately left out any pictures of connectors in this article to avoid focusing on the physical and encourage a reliance on the schematic of mnemonics in the mind that proves correct in all situations. Small information is beautiful.

Developments in code compression and execution in PowerShell derived from malware

New stuff, new stuff...

I've read a lot of interesting articles recently about how Powershell can be used in a variety of execution methods spanning various mediums. This lead me to grabbing hold of some malware and unwrapping the venom inside. Interestingly enough, there are some great features of PowerShell that can be used in a benign and even helpful way,

Good stuff, great stuff

Unwrapping the code was an interesting process in itself as the AV kept trying to bash the deobfuscated code as soon as it hit harddisk, but from the code, I found some really great functions for compression and obfuscation of scripts I thought were worthy of mention as I have used them myself:

String to Base64, Base64 to String Obfuscation

Function S-B64([parameter(ValueFromPipeline=$True, Mandatory=$True)]$i){[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($i))}
#Export-ModuleMember -Function S-B64
Function B64-S([parameter(ValueFromPipeline=$True, Mandatory=$True)]$i){[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i))}
#Export-ModuleMember -Function B64-S

For example:
S-B64 'net' produces 'bmV0'
S-B64 'user' produces 'dXNlcg=='
S-B64 'Administrator' produces 'QWRtaW5pc3RyYXRvcg=='
S-B64 'secretPass' produces 'c2VjcmV0UGFzcw=='

In your script:
Function B64-S([parameter(ValueFromPipeline=$True, Mandatory=$True)]$i){[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($i))}

& (B64-S 'bmV0') (B64-S 'dXNlcg==') (B64-S 'QWRtaW5pc3RyYXRvcg==') (B64-S 'c2VjcmV0UGFzcw==')

This is written as two functions, but if used just once in a script, you wrap can the second function around that base string to present the results to a variable or function.

Invoke-Expression "[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('V3JpdGUtSG9zdCAnV2Fzc3VwLCBidWQ/Jw=='))"

File to Base64? Yes we can.

function F-B64([string]$p=''){
  if(!(Test-Path $p)){throw 'F-B64: File path invalid'}
  try{$b=[IO.File]::ReadAllBytes($p)}catch{throw 'F-B64: File Read Fail'}
  if($b){[Convert]::ToBase64String($b)}else{throw 'F-B64: No data received'}
}
#Export-ModuleMember -Function B64-S
function B64-F([parameter(ValueFromPipeline=$True, Mandatory=$True)]$d,[string]$p=''){
  if($p){if(Test-Path $p){throw 'B64-F: File already exists'}}else{throw 'B64-F: File path empty'}
  $a=[Convert]::FromBase64String($d)
  try{[IO.File]::WriteAllBytes($p,$a)}catch{throw 'B64-F: Write Access Fail'}
}
#Export-ModuleMember -Function B64-F

Simple as, read the file as bytes, convert to base64. Read out in the same way. Now binaries can be implanted into script files as strings. Nice and easy way to carry tools without AVs sniping them off your USB sticks. The resultant Base64 string is huge... Actually much larger in size than the original executable, but this is heavily mitigated by the next set of functions.

Compressing it further...

function S-CB64([parameter(ValueFromPipeline=$True,Mandatory=$True)]$d){
  $m=New-Object IO.MemoryStream
  $g=New-Object IO.Compression.GZipStream($m,[IO.Compression.CompressionMode]::Compress)
  $s=New-Object IO.StreamWriter($g)
  $s.Write($d)
  $s.Close()
  [Convert]::ToBase64String($m.ToArray())
}
#Export-ModuleMember -Function S-CB64

function CB64-S([parameter(ValueFromPipeline=$True,Mandatory=$True)]$d){
    $a=[Convert]::FromBase64String($d)
    $m=New-Object IO.MemoryStream
    $m.Write($a,0,$a.Length)
    $null=$m.Seek(0,0)
    $g=New-Object IO.Compression.GZipStream($m,[IO.Compression.CompressionMode]::Decompress)
    $s=New-Object IO.StreamReader($g)
    $s.readtoend()
}
#Export-ModuleMember -Function CB64-S

OK, so now, we push that one stage further and realise that, with Invoke-Expression we can run entire scripts (to unnaturally powerful effects), how can we reduce the size of the payload? The malware already had the answer. Use a Gzip compression stream. Push the script in one side and grab the Base64 of the compressed script, then spit it into IEX on the user side. This will turn large scripts into quite tiny strings and compressed B64 strings from files are a near match to the original executable size.

What I learned???

PowerShell is living up to its name. By leveraging COM, .Net, Win32API and whatever else it can get into with all their varying execution methods, PowerShell can execute any data however it wants. There's good and bad to this, but this just amplifies the very name of it. It takes me much longer to produce a refined C# application that can be powerful than it does to slap out a quick script with the limited tools available, but Powershell brings me both at once, which is very powerful indeed.

Footnote

This dissemination of code and the understanding of it was made massively easier by using ISESteroids for contextual help, window shuffling, code cleaning and generally a better overall Powershell experience. Still raving about this.