Image of sitting at the computer dealing with ASRmageddon

ASRmageddon: What Happened and How We Responded

On January 13th, Microsoft deployed a new definitions update for Microsoft Defender. This caused an issue for some users, as Defender began to flag and delete shortcuts in the “C:\ProgramData\Microsoft\Windows\Start Menu\Programs” folder on devices with the Attack Surface Reduction (ASR) rule “Block Win32 API calls from Office macro” set to Block. The widespread nature of this problem prompted the nickname “ASRmageddon” among technology providers and those affected.

Upon discovering the issue, Infused Innovations quickly took action to mitigate the effects of the problem. We first changed the problematic ASR rule to Audit only for our clients and reached out to make them aware of the situation. This helped to prevent further damage and gave us the opportunity to assess the impact of the issue on our clients’ systems. 

Once the rule was updated, we created and pushed out a script to restore the most common applications used by our clients, focusing on trying to get them back up and running quickly. Our goal was to minimize disruption to our clients’ operations and help them to return to normal as soon as possible. 

Through our efforts, we were able to restore the majority of our clients’ shortcuts before Microsoft released their first remediation script. At the time, Microsoft’s script fixed fewer shortcuts than ours, and we provided our clients with a more comprehensive solution. 

We continued to iterate on our script to restore as much as possible for our clients, while keeping an eye on any new developments from Microsoft and the IT community at large. Our team worked continuously to ensure that our clients were not negatively impacted by this issue for long. 

Overall, the incident serves as a reminder of the importance of staying up to date on the latest updates and developments in the IT industry. It also highlights the importance of having a well-trained and responsive IT team that can quickly respond to and resolve issues as they arise

At Infused, we pride ourselves on providing comprehensive IT solutions and exceptional customer service. We will continue to monitor the situation and work closely with our clients to ensure that their systems are fully restored and that any future issues are avoided. 

We would like to thank our clients for their patience and understanding during this incident and assure them that we are always available to answer any questions or concerns they may have. 

For those who are interested or still need to fix this bug, see our script below.

ASRmageddon: What Happened and How We Responded 1

Script to Restore Shortcuts Deleted by Defender ASR Bug MO497128 #ASRmageddon

<#
.SYNOPSIS
    Script to restore shortcuts deleted by Defender ASR Bug MO497128 #ASRmageddon

.DESCRIPTION
    This script will check to see if a Volume Shadow Service restore point is available from before the deletions happened. 
    If a restore point is available, it will try to restore the shortcuts to the start menu and users' desktop folders.
    If there's no available restore points, it will check to see if there's a payload deployed alongside the script with shortcuts included, 
    if there is, it will restore the missing shortcuts included in the payload for programs that are installed.

    To create the payload, include any shortcuts you want to deploy in a archive named "Programs.zip"

.NOTES
    Written by Alex Clayton - 1/2023

#>

$StartMenuFolder = "$env:ProgramData\Microsoft\Windows\Start Menu\Programs"

$ShadowCopyMountPath = "C:\VSSShortcutRestore"

$DateTime = [datetime]'01/13/2023 09:00:00'

$ToTimeZone = [System.TimeZoneInfo]::FindSystemTimeZoneById((Get-Timezone).Id)

$ConvertedTime = ([System.TimeZoneInfo]::ConvertTimeFromUtc($DateTime ,$ToTimeZone))

$ShadowCopies = Get-CIMInstance -Class Win32_ShadowCopy | Where-Object {$_.InstallDate -lt $ConvertedTime} | Sort-Object -Property InstallDate -Descending

Function Mount-VolumeShadowCopy {

<#

.SYNOPSIS

Mount a volume shadow copy.

.DESCRIPTION

Mount a volume shadow copy.

.PARAMETER ShadowPath

Path of volume shadow copies submitted as an array of strings

.PARAMETER Destination

Target folder that will contain mounted volume shadow copies

.EXAMPLE

Get-CimInstance -ClassName Win32_ShadowCopy |

Mount-VolumeShadowCopy -Destination C:\VSS -Verbose

#>

[CmdletBinding()]

Param(

[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]

[ValidatePattern('\\\\\?\\GLOBALROOT\\Device\\HarddiskVolumeShadowCopy\d{1,}')]

[Alias("DeviceObject")]

[String[]]$ShadowPath,

[Parameter(Mandatory)]

[ValidateScript({

Test-Path -Path $_ -PathType Container

}

)]

[String]$Destination

)

Begin {

Try {

$null = [mklink.symlink]

}

Catch {

Add-Type @"

using System;

using System.Runtime.InteropServices;

namespace mklink

{

public class symlink

{

[DllImport("kernel32.dll")]

public static extern bool CreateSymbolicLink(string lpSymlinkFileName, string lpTargetFileName, int dwFlags);

}

}

"@

}

}

Process {

$ShadowPath | ForEach-Object -Process {

if ($($_).EndsWith("\")) {

$sPath = $_

}

else {

$sPath = "$($_)\"

}

$tPath = Join-Path -Path $Destination -ChildPath (

'{0}-{1}' -f (Split-Path -Path $sPath -Leaf), [GUID]::NewGuid().Guid

)

try {

if (

[mklink.symlink]::CreateSymbolicLink($tPath, $sPath, 1)

) {

Write-Verbose "`tSuccessfully mounted $sPath to $tPath"

return $tPath

}

else {

Write-Warning "[!] Failed to mount $sPath"

}

}

catch {

Write-Warning "[!] Failed to mount $sPath because $($_.Exception.Message)"

}

}

}

End {}

}

Function Dismount-VolumeShadowCopy {

<#

.SYNOPSIS

Dismount a volume shadow copy.

.DESCRIPTION

Dismount a volume shadow copy.

.PARAMETER Path

Path of volume shadow copies mount points submitted as an array of strings

.EXAMPLE

Get-ChildItem -Path C:\VSS | Dismount-VolumeShadowCopy -Verbose

#>

[CmdletBinding()]

Param(

[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)]

[Alias("FullName")]

[string[]]$Path

)

Begin {

}

Process {

$Path | ForEach-Object -Process {

$sPath = $_

if (Test-Path -Path $sPath -PathType Container) {

if ((Get-Item -Path $sPath).Attributes -band [System.IO.FileAttributes]::ReparsePoint) {

try {

[System.IO.Directory]::Delete($sPath, $false) | Out-Null

Write-Verbose "`tSuccessfully dismounted $sPath"

}

catch {

Write-Warning "[!] Failed to dismount $sPath because $($_.Exception.Message)"

}

}

else {

Write-Warning "[!] The path $sPath isn't a reparsepoint"

}

}

else {

Write-Warning "[!] The path $sPath isn't a directory"

}

}

}

End {}

}

function Restore-StartMenuShortcuts {

[CmdletBinding()]

param (

[Parameter(ValueFromPipeline)]

$InputObject

)

process {

$InputObject | ForEach-Object {

$relativePath = ($_.fullname -split('Programs'))[1]

If(test-path ($StartMenuFolder + $relativepath)) {

"$($_.name) already exist in start menu"

}

else {

"$($_.name) not found in start menu - checking if program pointed to by shortcut exist"

$sh = New-Object -ComObject WScript.Shell

if(Test-Path($sh.CreateShortcut($_.FullName).TargetPath)){

"Program exists - copying $($_.Name) into start menu folder"

Copy-Item -Path $_.FullName -Destination "$StartMenuFolder$relativepath" -Force

}

else {

"Did not find $($sh.CreateShortcut($_.FullName).TargetPath) - will not copy $($_.name)"

}

}

}

}

}

function Restore-DesktopShortcuts {

[CmdletBinding()]

param (

[Parameter(ValueFromPipeline)]

$InputObject

)

process {

$InputObject | ForEach-Object {

$relativePath = ($_.FullName -split('Users'))[1]

If(test-path ("C:\users$relativepath")) {

"$RelativePath already exists"

}

else {

"$RelativePath not found checking if program pointed to by shortcut exist"

$sh = New-Object -ComObject WScript.Shell

if(Test-Path($sh.CreateShortcut($_.FullName).TargetPath)){

"Program exists - copying $RelativePath to users' desktop"

Copy-Item -Path $_.FullName -Destination "C:\users$relativepath" -ErrorAction:Continue

}

else {

"Did not find $($sh.CreateShortcut($_.FullName).TargetPath) - will not copy $($_.name)"

}

}

}

}

}

If ($null -ne $ShadowCopies) {

New-Item -Path $ShadowCopyMountPath -ItemType Directory -Force | Out-Null

If ($ShadowCopies -is [array]) {

$ShadowCopy = $ShadowCopies[0] | Mount-VolumeShadowCopy -Destination $ShadowCopyMountPath

} elseif ($ShadowCopies -is [object]) {

$ShadowCopy = $ShadowCopies | Mount-VolumeShadowCopy -Destination $ShadowCopyMountPath

} else {

Remove-Item -Path $ShadowCopyMountPath -Recurse -Force | Out-Null

Write-Warning -Message "Error with parsing the available shadow copies. Exiting."

Exit;

}

$ShortCuts = Get-ChildItem -Path "$ShadowCopy\ProgramData\Microsoft\Windows\Start Menu\Programs" -Recurse -File

$ShortCuts | Restore-StartMenuShortcuts

$UserProfiles = Get-ChildItem -Path "$ShadowCopy\Users"

$UserProfiles | ForEach-Object {

Get-ChildItem $_.FullName -Filter "*Desktop" -directory -Depth 1 | ForEach-Object {

$DesktopShortcutsLNK = Get-ChildItem -Path $_.FullName -File -Filter "*.lnk"

$DesktopShortcutsURL = Get-ChildItem -Path $_.FullName -File -Filter "*.url"

$DesktopShortcutsLNK | Restore-DesktopShortcuts

$DesktopShortcutsURL | Restore-DesktopShortcuts

}

}

Get-ChildItem -Path $ShadowCopyMountPath | Dismount-VolumeShadowCopy -Verbose

Remove-Item -Path $ShadowCopyMountPath -Recurse -Force | Out-Null

} ElseIf (Test-Path -Path "$PSScriptRoot\Programs.zip") {

Expand-Archive -Path "$PSScriptRoot\Programs.zip" -DestinationPath "$PSScriptRoot" -Force

$ShortCuts = Get-ChildItem "$PSScriptRoot\Programs" -Recurse -File

$ShortCuts | Restore-StartMenuShortcuts

} Else {

Write-Warning -Message "No restore points found. Exiting."

Exit;

}

Credits:

2 Comments

  1. Gary Baer on January 19, 2023 at 6:29 pm

    First of all, I’m not sure what is wrong with MS script, but on about half our 2k plus devices, it finds no shadow copies, and therefore does not work when trying to pull from VSS.

    Yours, on the other hand, worked like a charm on the ones that MS script reports no shadow copies older than 1/12/23 10pm. However, I’m trying to edit yours for Intune to include these three things:

    C:\Users\Public\Desktop\*.lnk

    Include a verbose log file I can check the output

    Include a detection file that I can use for a Win32App detection logic.

    Any help you can provide, would be great! I don’t want to get in there and mess it up… 😉

  2. Gary baer on January 20, 2023 at 12:46 am

    One other comment/question. I am loving the results I’m getting with this script. However, I’ve noticed that when I run it from an elevated command prompt, “powershell.exe -executionpolicy bypass -file “yourscriptpath” it finds the shadow copies. But when I bundle it into an Intune Win32 app and use the command, but it runs in “system” context, it doesn’t find any shadow copies. Any idea why?

Leave a Comment