Amsi(Antimalware Scan Interface)基礎知識
The Windows Antimalware Scan Interface (AMSI) is a versatile interface
standard that allows your applications and services to integrate with
any antimalware product that's present on a machine. AMSI provides
enhanced malware protection for your end-users and their data,
applications, and workloads.AMSI is agnostic of antimalware vendor; it's designed to allow for the
most common malware scanning and protection techniques provided by
today's antimalware products that can be integrated into applications.
It supports a calling structure allowing for file and memory or stream
scanning, content source URL/IP reputation checks, and other
techniques.
AMSI also supports the notion of a session so that antimalware vendors
can correlate different scan requests. For instance, the different
fragments of a malicious payload can be associated to reach a more
informed decision, which would be much harder to reach just by looking
at those fragments in isolation.
從前面微軟的文檔中我們可以知道Windows防病毒掃描界面(AMSI)是一個通用的接口標准,是開放給防病毒產品調用的一個接口。
這個接口支持允許不單單可以用來掃描腳本、代碼、命令或者cmdlet,也可以用來掃描任何文件、內存或者數據流,如字符串、即時消息、圖像或者視頻等等
我認為這個Amsi(Antimalware Scan Interface)主要是針對腳本來說的,目前來說腳本逐漸成為針對性攻擊的武器,諸如JavaScript,VBScript和PowerShell之類的腳本為我們攻擊提供了巨大的好處。它們是系統合法的軟件,並且執行文件不落地在內存中加載。
腳本語言比exe等等Pe文件要好混淆,可以從遠端或注冊表加載執行,並且腳本的動態特性允許攻我們容易地逃避反惡意軟件和類似的端點防護產品的檢測與分析。並且執行文件可以不落地直接在內存中加載。
那么 Windows 10可以通過反惡意軟件掃描接口(AMSI)提供查殺腳本的功能,還可以以腳本解釋程序的相同方式查看腳本內容 ,這樣的話就可以未加密和未混淆的形式來查看我們的攻擊腳本。
目前在微軟的文檔中我們可以知道
AMSI功能已集成到Windows 10的這些組件中。
用戶帳戶控制或UAC(EXE,COM,MSI或ActiveX)
PowerShell(腳本,交互使用和動態代碼評估)
Windows腳本主機(wscript.exe和cscript.exe)
JavaScript和VBScript
Office VBA宏
看到網上有python amsi的gist:https://gist.github.com/masthoon/8ada1d19e6e217481aa881fab16b5970
AMSI Interface in Python amsi.py import sys import windows import windows.winproxy import windows.generated_def as gdef class AMSIProxy(windows.winproxy.ApiProxy): APIDLL = "Amsi" default_error_check = staticmethod(windows.winproxy.no_error_check) """ HRESULT WINAPI AmsiInitialize( _In_ LPCWSTR appName, /*_In_ DWORD coInit, REMOVED */ _Out_ HAMSICONTEXT *amsiContext ); """ AmsiInitializePrototype = gdef.WINFUNCTYPE(gdef.BOOL, gdef.LPCWSTR, gdef.POINTER(gdef.PVOID)) AmsiInitializeParams = ((1, 'appName'),(1, 'amsiContext')) @AMSIProxy('AmsiInitialize', deffunc_module=sys.modules[__name__]) def AmsiInitialize(appName, amsiContext): return AmsiInitialize.ctypes_function(appName, amsiContext) """ HRESULT WINAPI AmsiOpenSession( _In_ HAMSICONTEXT amsiContext, _Out_ HAMSISESSION *session ); """ AmsiOpenSessionPrototype = gdef.WINFUNCTYPE(gdef.BOOL, gdef.PVOID, gdef.POINTER(gdef.PVOID)) AmsiOpenSessionParams = ((1, 'amsiContext'),(1, 'session')) @AMSIProxy('AmsiOpenSession', deffunc_module=sys.modules[__name__]) def AmsiOpenSession(amsiContext, session): return AmsiOpenSession.ctypes_function(amsiContext, session) """ HRESULT WINAPI AmsiScanBuffer( _In_ HAMSICONTEXT amsiContext, _In_ PVOID buffer, _In_ ULONG length, _In_ LPCWSTR contentName, _In_opt_ HAMSISESSION session, _Out_ AMSI_RESULT *result ); """ AmsiScanBufferPrototype = gdef.WINFUNCTYPE(gdef.BOOL, gdef.PVOID, gdef.PVOID, gdef.ULONG, gdef.LPCWSTR, gdef.PVOID, gdef.POINTER(gdef.ULONG)) AmsiScanBufferParams = ((1, 'amsiContext'),(1, 'buffer'),(1, 'length'),(1, 'contentName'),(1, 'session'),(1, 'result')) @AMSIProxy('AmsiScanBuffer', deffunc_module=sys.modules[__name__]) def AmsiScanBuffer(amsiContext, buffer, length, contentName, session, result): return AmsiScanBuffer.ctypes_function(amsiContext, buffer, length, contentName, session, result) """ typedef enum AMSI_RESULT { AMSI_RESULT_CLEAN = 0, AMSI_RESULT_NOT_DETECTED = 1, AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384, AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479, AMSI_RESULT_DETECTED = 32768 } AMSI_RESULT; BOOL AmsiResultIsMalware( _In_ AMSI_RESULT r ); """ AMSI_RESULT_CLEAN = gdef.EnumValue("_AMSI_RESULT", "AMSI_RESULT_CLEAN", 0x0) AMSI_RESULT_NOT_DETECTED = gdef.EnumValue("_AMSI_RESULT", "AMSI_RESULT_NOT_DETECTED", 0x1) AMSI_RESULT_BLOCKED_BY_ADMIN_START = gdef.EnumValue("_AMSI_RESULT", "AMSI_RESULT_BLOCKED_BY_ADMIN_START", 16384) AMSI_RESULT_BLOCKED_BY_ADMIN_END = gdef.EnumValue("_AMSI_RESULT", "AMSI_RESULT_BLOCKED_BY_ADMIN_END", 20479) AMSI_RESULT_DETECTED = gdef.EnumValue("_AMSI_RESULT", "AMSI_RESULT_DETECTED", 32768) class _AMSI_RESULT(gdef.EnumType): values = [AMSI_RESULT_CLEAN, AMSI_RESULT_NOT_DETECTED, AMSI_RESULT_BLOCKED_BY_ADMIN_START, AMSI_RESULT_BLOCKED_BY_ADMIN_END, AMSI_RESULT_DETECTED] mapper = {x:x for x in values} AMSI_RESULT = _AMSI_RESULT class AMSIScanner(object): def __enter__(self): self.context = gdef.PVOID() self.session = gdef.PVOID() if AmsiInitialize("TestEngine", self.context) != 0: print("AmsiInitialize failed!") if AmsiOpenSession(self.context, self.session) != 0: print("AmsiOpenSession failed!") return self def __exit__(self, exc_type, exc_val, exc_tb): pass # TODO release the AMSI engine def scan(self, buffer): result = AMSI_RESULT() AmsiScanBuffer(self.context, buffer, len(buffer), "fakefile.bin", self.session, result) return result if __name__ == '__main__': with AMSIScanner() as scanner: print("Scanning ABC:") res = scanner.scan('ABC') print("\t{}".format(res)) print("Scanning EICAR:") res = scanner.scan('X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*') print("\t{}".format(res))
也有人實現了python的amsi接口調用:
amsi.py #!/usr/bin/env python3 #> ------------------------------------ # Antimalware Scan Interface #> ------------------------------------ import sys from enum import IntEnum from ctypes import HRESULT, POINTER, windll, byref from ctypes.wintypes import HANDLE, LPCWSTR, UINT, LPCSTR from comtypes.hresult import S_OK #> ------------------------------------------------------------------------------ # The AMSI_RESULT enumeration specifies the types of results returned by scans #> ------------------------------------------------------------------------------ """ typedef enum AMSI_RESULT { AMSI_RESULT_CLEAN, AMSI_RESULT_NOT_DETECTED, AMSI_RESULT_BLOCKED_BY_ADMIN_START, AMSI_RESULT_BLOCKED_BY_ADMIN_END, AMSI_RESULT_DETECTED } ; """ class AMSI_RESULT(IntEnum): """ AMSI Results Class """ AMSI_RESULT_CLEAN = 0 AMSI_RESULT_NOT_DETECTED = 1 AMSI_RESULT_BLOCKED_BY_ADMIN_START = 16384 AMSI_RESULT_BLOCKED_BY_ADMIN_END = 20479 AMSI_RESULT_DETECTED = 32768 class Amsi(object): """ AMSI API Class """ def __init__(self): # Inialize the context and session to utilize self.amsi_ctx = self.amsi_initialize() self.amsi_session = self.amsi_open_session() #> ------------------------- # Initialize the AMSI API #> ------------------------- """ HRESULT AmsiInitialize( LPCWSTR appName, HAMSICONTEXT *amsiContext ); """ def amsi_initialize(self): AmsiInitialize = windll.amsi.AmsiInitialize AmsiInitialize.argtypes = [LPCWSTR, POINTER(HANDLE)] # Specify the argument data types AmsiInitialize.restype = HRESULT amsi_ctx = HANDLE(0) # Return this context amsi_hres = AmsiInitialize("amsi-test", byref(amsi_ctx)) # If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code if amsi_hres != S_OK: print(f"[!]\tAmsiInitialize Error: {amsi_hres}") sys.exit() # Exit if initialization fails return amsi_ctx #> ---------------------------------------------------------------------------------- # Remove the instance of the AMSI API that was originally opened by AmsiInitialize #> ---------------------------------------------------------------------------------- """ void AmsiUninitialize( HAMSICONTEXT amsiContext ); """ def amsi_uninitialize(self): AmsiUninitialize = windll.amsi.AmsiUninitialize AmsiUninitialize.argtypes = [HANDLE] AmsiUninitialize(self.amsi_ctx) return None #> ----------------------------------------------------------------------- # Opens a session within which multiple scan requests can be correlated #> ----------------------------------------------------------------------- """ HRESULT AmsiOpenSession( HAMSICONTEXT amsiContext, HAMSISESSION *amsiSession ); """ def amsi_open_session(self): AmsiOpenSession = windll.amsi.AmsiOpenSession AmsiOpenSession.argtypes = [HANDLE, POINTER(UINT)] # Specify attribute data types AmsiOpenSession.restype = HRESULT amsi_session = UINT(0) # Return this session amsi_hres = AmsiOpenSession(self.amsi_ctx, amsi_session) # If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code if amsi_hres != S_OK: print(f"[!]\tAmsiOpenSession Error: {amsi_hres}") sys.exit() # Exit if session creation fails return amsi_session #> ---------------------------------------------------- # Close a session that was opened by AmsiOpenSession #> ---------------------------------------------------- """ void AmsiCloseSession( HAMSICONTEXT amsiContext, HAMSISESSION amsiSession ); """ def amsi_close_session(self): AmsiCloseSession = windll.amsi.AmsiCloseSession AmsiCloseSession.argtypes = [HANDLE, UINT] # Specify attribute data types AmsiCloseSession.restype = HRESULT amsi_hres = AmsiCloseSession(self.amsi_ctx, self.amsi_session) return None #> ---------------------------- # Scans a string for malware #> ---------------------------- """ HRESULT AmsiScanString( HAMSICONTEXT amsiContext, LPCWSTR string, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT *result ); """ def amsi_scan_string(self, data): AmsiScanString = windll.amsi.AmsiScanString AmsiScanString.argtypes = [HANDLE, LPCWSTR, LPCWSTR, UINT, POINTER(UINT)] # Specify attribute data types AmsiScanString.restype = HRESULT amsi_res = UINT(0) # Return this scan result amsi_hres = AmsiScanString(self.amsi_ctx, data, "string-data", self.amsi_session, byref(amsi_res)) # If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code if amsi_hres != S_OK: print(f"[!]\tAmsiScanString Error: {amsi_hres}") sys.exit() # Exit if scan fails return amsi_res #> -------------------------------------------- # Scans a buffer-full of content for malware #> -------------------------------------------- """ HRESULT AmsiScanBuffer( HAMSICONTEXT amsiContext, PVOID buffer, ULONG length, LPCWSTR contentName, HAMSISESSION amsiSession, AMSI_RESULT *result ); """ def amsi_scan_buffer(self, data): AmsiScanBuffer = windll.amsi.AmsiScanBuffer AmsiScanBuffer.argtypes = [HANDLE, LPCSTR, UINT, LPCWSTR, UINT, POINTER(UINT)] # Specify attribute data types AmsiScanBuffer.restype = HRESULT amsi_res = UINT(0) # Return this scan result amsi_hres = AmsiScanBuffer(self.amsi_ctx, data, len(data), "buffer-data", self.amsi_session, byref(amsi_res)) # If this function succeeds, it returns S_OK. Otherwise, it returns an HRESULT error code if amsi_hres != S_OK: print(f"[!]\tAmsiScanBuffer Error: {amsi_hres}") sys.exit() # Exit if scan fails return amsi_res #> --------------------------------------------------------------------------------- # Determines if the result of a scan indicates that the content should be blocked #> --------------------------------------------------------------------------------- """ void AmsiResultIsMalware( r ); """ def amsi_result_is_malware(self, amsi_res): # List of potential detection result codes AMSI_RESULTS = { AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_END: 'BLOCKED_BY_ADMIN_END', AMSI_RESULT.AMSI_RESULT_BLOCKED_BY_ADMIN_START: 'BLOCKED_BY_ADMIN_START', AMSI_RESULT.AMSI_RESULT_DETECTED: 'DETECTED' } if amsi_res.value in AMSI_RESULTS.keys(): return True else: return False
Amsi(Antimalware Scan Interface)的工作流程。
As an application developer, you can actively participate in malware
Specifically, you can help protect your customers from
script-based malware, and from non-traditional avenues of
cyberattack.
By way of an example, let's say that your application is scriptable:
it accepts arbitrary script, and executes it via a scripting engine.
At the point when a script is ready to be supplied to the scripting
engine, your application can call the Windows AMSI APIs to request a
scan of the content. That way, you can safely determine whether or not
the script is malicious before you decide to go ahead and execute it.This is true even if the script was generated at runtime. Script
(malicious or otherwise), might go through several passes of
de-obfuscation. But you ultimately need to supply the scripting engine
with plain, un-obfuscated code. And that's the point at which you
invoke the AMSI APIs.
從微軟的文檔中我們可以看到Amsi中的工作流程。
如果有一個應用程序接受任意腳本,並通過腳本引擎執行該腳本。
那么殺毒軟件可以調用amsi來查殺腳本。
可能有同學有疑問:殺毒軟件不是也能不靠amsi查殺腳本嗎?不不不 amsi的意義在於:無論我們的惡意腳本是經過多次模糊處理還是遠程執行amsi都可以在腳本注入內存前檢測到。而普通的靜態殺毒軟件是沒辦法的。
我們可以看微軟文檔給出的工作流程圖來理解:
其實不難理解,首先我們要知道我們的惡意腳本是如何注入內存執行的
bypass 殺毒軟件時我們的腳本一定是模糊處理的,但是無論我們什么樣模糊處理到注入內存執行的時候一定是純凈,清晰的代碼,不然腳本引擎無法理解和執行我們的惡意腳本。
那么問題就是在這里,amsi在腳本解密到注入內存之前去掃描查殺。這才是調用amsi的意義。
我們可以調用powershell來執行我們的惡意代碼來更好理解Amsi
AMSI的一個簡單測試是在PowerShell提示符–中鍵入AMSI旁路中常用的字符串amsiutils。如果端點安全產品支持AMSI,並且檢測到該字符串,那么PowerShell提示符將顯示錯誤,表明輸入的命令是惡意的。
PS C:\Users\(123223Li)> amsiutils
所在位置 行:1 字符: 1
+ amsiutils
+ ~~~~~~~~~
此腳本包含惡意內容,已被你的防病毒軟件阻止。
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
+ FullyQualifiedErrorId : ScriptContainedMaliciousContent
很好Amsi查殺並阻止了power shell執行命令。
我們來處理一下amsiutils,比如我們常用的base64加密和XOR編碼技術。
$bytes = [convert]::FromBase64String('YW1zaXV0aWxz') # YW1zaXV0aWxz為amsiutils的base64編碼 $string = -join ($bytes | % {[char] ($_ -bxor 0x33)}) #進行XOR編碼 iex $string #執行命令
然后我們使用powershell ISE 執行來模擬我們在實戰中的無文件落地直接內存加載執行的手法
毫不意外,amsi檢測到並攔截了powershell去執行我們的腳本。
我們可以使用下面命令來查看amsi中的查殺結果
Get-WinEvent 'microssoft-windows-windows defender/operational' | Where-Object id -EQ 1116 | format-list
當然amsi也可以檢測vba和Java script
在實戰中,使用宏攻擊(釣魚)也是我們常用的手法,所以我們要知道amsi對宏文件的檢測流程
在微軟文檔中我們可以看到
The user receives a document containing a (malicious) macro, which
evades static antivirus software scans by employing techniques such as
obfuscation, password-protected files, or other. The user then opens
the document containing the (malicious) macro. If the document opens
in Protected View, then the user clicks Enable Editing to exit
Protected View. The user clicks Enable Macros to allow macros to run.
As the macro runs, the VBA runtime uses a circular buffer to log [1]
data and parameters related to calls to Win32, COM, and VBA APIs. When
specific Win32 or COM APIs that are considered high risk (also known
as triggers) [2] are observed, macro execution is halted, and the
contents of the circular buffer are passed to AMSI. The registered
AMSI anti-malware service provider responds with a verdict to indicate
whether or not the macro behavior is malicious. If the behavior is
non-malicious, then macro execution proceeds. Otherwise, if the
behavior is malicious, then Microsoft Office closes the session in
response to the alert [3], and the AV can quarantine the file.
微軟文檔中也給出了一個流程圖
通過閱讀理解微軟文檔我們可以知道amsi對宏的檢測查殺流程:
1.word等等釣魚文件加載宏
2.VBA宏運行時,運行時會有一個循環的緩沖區中記錄數據和參數調用Win32,COM, VBA等等api的情況。
3.amsi監控着緩沖區中的情況,一旦我們的宏調用了一些敏感的API或一些敏感的數據交互,就會觸發amsi的觸發器。
4.amsi停止宏執行並從循環緩沖區取出內容傳遞。
5.amsi從循環緩沖區取出內容傳遞給殺毒軟件。
6.殺毒軟件拿到數據后判斷宏是否為惡意的。
6.如果行為是無惡意的,那么宏可以執行。否則,關閉宏會話並發出響應警報和處理惡意文件。
我們可以看一個例子來理解amsi檢測查殺vba的流程:
跟powershell一樣我們也使用遠程加載powershell惡意代碼。這樣更貼近實戰。
1.使用cobat Strike生成我們的惡意代碼
2.使用宏遠程加載我們的惡意代碼
#使用宏調用powershell遠程加載ps1
Sub AutoOpen()
Call Shell("powershell -Sta -Nop -Window Hidden -EncodedCommand shell")
End Sub
在沒有開amsi的情況下可以執行上線!
在開了amsi的情況下無法執行了
amsi是所有殺毒軟件都可以調用嗎?並不是!
amsi是在Windows 10 和Windows Server 2016 之后才有的,然后並不是所有的殺毒軟件都可以調用amsi接口。國內的基本不可以哈哈哈。
在github上有一個項目記錄了可以調用amsi的殺毒軟件
https://github.com/subat0mik/whoamsi/wiki/Which-Endpoint-Protection-is-Using-AMSI%3F
殺軟對抗是一個動態對抗的過程,殺毒有新技術,免殺就有新手法。只有熟透殺軟才能更好免殺。
amsi解決的是遠程加載執行惡意腳本無文件落地的攻擊手法,過兩天看看跟大家學習一下bypass AMsi的手法。
我們學習了Antimalware Scan Interface(AMSI)的一些知識。作為滲透測試的我們了解殺軟是為了更好地進行免殺。
在對抗殺毒軟件的手法中,在大層面來說:
一就是破壞殺毒軟件,使其無法正常運行;
二是繞過殺毒軟件,使其無法檢測到。
從小的層面來說:
靜態免殺
動態免殺
沙盒對抗
等等
前面我們說到AMSI是windows開放的一個接口,注冊的殺毒軟件可以通過調用amsi接口來完成監控查殺。
既然是接口,那么我們是不是可以使殺毒軟件無法調用這個amsi接口來bypass?
首先我們得知道殺毒軟件是如何調用amsi的:
在微軟文檔中給出了2種調用amsi接口的方法
There are two ways in which you can interface with AMSI in your application.
By using the AMSI Win32 APIs. By using the AMSI COM interfaces.
分別是:
1.通過使用AMSI Win32 API
Functions that your application can call to request a scan. AMSI provides the following functions.
#函數,應用程序可以通過調用amsi,amsi提供以下函數
Function #函數 Description #功能
AmsiCloseSession Close a session that was opened by AmsiOpenSession.#關閉打開的會話
AmsiInitialize Initialize the AMSI API.#初始化AMSI API。
AmsiOpenSession Opens a session within which multiple scan requests can be correlated.#打開一個會話中關聯多個掃描請求。
AmsiResultIsMalware Determines if the result of a scan indicates that the content should be blocked.# 確定掃描的結果表明,內容應該是屏蔽
AmsiScanBuffer Scans a buffer-full of content for malware. # 掃描惡意軟件的緩沖區已滿的內容。
AmsiScanString Scans a string for malware.#惡意軟件掃描一個字符串。
AmsiUninitialize Remove the instance of the AMSI API that was originally opened by AmsiInitialize.#刪除的實例AMSI API,
其中每個函數的調用方法有興趣可以去微軟種查看。
https://docs.microsoft.com/en-us/windows/win32/amsi/antimalware-scan-interface-functions
2.通過使用AMSI COM接口
這個中調用的是IAmsiStream接口(amsi.h)代表一個流被掃描,微軟文檔中給出了示例,
https://docs.microsoft.com/en-us/windows/win32/api/amsi/nf-amsi-iamsistream-getattribute
Inheritance The IAmsiStream interface inherits from the IUnknown
interface. IAmsiStream also has these types of members:
Methods Methods The IAmsiStream interface has these methods.
METHODS Method Description IAmsiStream::GetAttribute Returns a
requested attribute from the stream. IAmsiStream::Read Requests a
buffer-full of content to be read. Requirements REQUIREMENTS
Minimum supported client Windows 10 [desktop apps only]
Minimum supported server Windows Server 2016 [desktop apps only] Target
Platform Windows Header amsi.h
然后上面我們知道了如何調用amsi,但是調用amsi還要注冊才能調用,所以我們也得看看amsi是如何注冊的:
As a creator of antimalware products, you can choose to author and
register your own in-process COM server (a DLL) to function as an AMSI
That AMSI provider must implement the IAntimalwareProvider
interface, and it must run in-process.Note that, after Windows 10, version 1709 (the Fall 2017 Creators'
Update), your AMSI provider DLL may not work if it depends upon other
DLLs in its path to be loaded at the same time. To prevent DLL
hijacking, we recommend that your provider DLL load its dependencies
explicitly (with a full path) using secure LoadLibrary calls, or equivalent.
We recommend this instead of relying on the LoadLibrary search behavior.The section below shows how to register your AMSI provider. For full
sample code showing how to author your own AMSI provider DLL, see the
IAntimalwareProvider interface sample application.Register your provider DLL with AMSI To begin with, you need to ensure
that these Windows Registry keys exist.HKLMSOFTWAREMicrosoftAMSIProviders HKLMSOFTWAREClassesCLSID An
AMSI provider is an in-process COM server. Consequently, it needs to
register itself with COM. COM classes are registered in
HKLMSOFTWAREClassesCLSID.
當然在微軟文檔中也給出了示例,但是我們是要做免殺,理解一些必要的東西就行。
在微軟的文檔中我們可以知道
Note that, after Windows 10, version 1709 (the Fall 2017 Creators'
Update), your AMSI provider DLL may not work if it depends upon other
DLLs in its path to be loaded at the same time. To prevent DLL
hijacking, we recommend that your provider DLL load its dependencies
explicitly (with a full path) using secure LoadLibrary calls, orequivalent.
We recommend this instead of relying on the LoadLibrarysearch behavior.search behavior.
微軟中說:
也就是說存在dll劫持的例子。