اسکن بدافزارها در حین آپلود فایلها در ASP.NET Core با استفاده از Windows AMSI: آخرین خط دفاعی
نویسنده: وحید نصیری
تاریخ: ۱۴۰۴/۰۷/۱۷ ۱۳:۳۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
| مزیت | توضیح |
| استفاده از آنتیویروس موجود | از Windows Defender یا هر آنتیویروس سازگار با AMSI که در حال حاضر نصب شده، بهره میبرد. |
| بهروزرسانی خودکار | امضاهای تشخیص بدافزار بهطور خودکار توسط Windows Update بهروز میشوند. |
| بدون هزینه اضافی | استفاده از آن در سیستمعاملهای ویندوز رایگان است و نیازی به مجوز جداگانه ندارد. |
| ادغام سبکوزن | دارای API ساده با سربار (Overhead) حداقلی. |
| اسکن محتوا-محور | محتوای واقعی فایل را تجزیه و تحلیل میکند، نه صرفاً امضاهای سطح بالای فایل را. |
| استاندارد صنعتی | از هوش تهدیدات گسترده مایکروسافت بهره میبرد. |
public sealed class MalwareScanValidationStep
{
private readonly ILogger _logger;
public MalwareScanValidationStep(ILogger logger)
{
_logger = logger;
}
public async Task<ValidationResult> ValidateAsync(IFormFile file, CancellationToken token)
{
// فرض کنید متد GetData() یک متد Extension برای خواندن محتوای IFormFile به صورت آرایه بایت است.
var payload = await file.GetData();
// 1. ایجاد یک Context جدید AMSI
using var application = AmsiContext.Create("DocumentStorage");
// 2. ایجاد یک Session جدید (برای مرتبط کردن درخواستهای اسکن)
using var session = application.CreateSession();
// 3. اسکن محتوای فایل
var isMalware = await session.IsPotentialMalwareAsync(payload, file.Name, token);
if (isMalware)
{
_logger.LogWarning("MalwareScan: File '{FileName}' potentially contains malware.", file.FileName);
// در صورت تشخیص بدافزار، عملیات را با خطا متوقف میکنیم.
return ValidationResult.Fail(new BadRequestObjectResult(new { message = "Document potentially contains malware." }));
}
return ValidationResult.Success();
}
}amsi.dll استفاده میکنیم.AmsiInitialize) و اتمام (AmsiUninitialize) رابط AMSI برای برنامه ما را بر عهده دارد و یک دسته (Handle) اصلی برای کل برنامه ایجاد میکند.public sealed class AmsiContext : IDisposable
{
private IntPtr _handle;
private AmsiContext(IntPtr handle)
{
_handle = handle;
}
public static AmsiContext Create(string appName)
{
var result = AmsiInitialize(appName, out var handle);
if (result != 0)
{
throw new InvalidOperationException($"Failed to initialize AMSI. Error code: {result}");
}
return new AmsiContext(handle);
}
public AmsiSession CreateSession()
{
var result = AmsiOpenSession(_handle, out var sessionHandle);
if (result != 0)
{
throw new InvalidOperationException($"Failed to open AMSI session. Error code: {result}");
}
return new AmsiSession(_handle, sessionHandle);
}
public void Dispose()
{
if (_handle != IntPtr.Zero)
{
AmsiUninitialize(_handle);
_handle = IntPtr.Zero;
}
}
[DllImport("amsi.dll", CharSet = CharSet.Unicode)]
private static extern int AmsiInitialize(string appName, out IntPtr amsiContext);
[DllImport("amsi.dll")]
private static extern int AmsiOpenSession(IntPtr amsiContext, out IntPtr session);
[DllImport("amsi.dll")]
private static extern void AmsiUninitialize(IntPtr amsiContext);
}AmsiOpenSession/AmsiCloseSession) ایجاد میکند و مهمترین متد، یعنی AmsiScanBuffer را فراخوانی میکند که دادههای باینری را برای اسکن به آنتیویروس ارسال میکند.public sealed class AmsiSession : IDisposable
{
private readonly IntPtr _amsiContext;
private IntPtr _sessionHandle;
internal AmsiSession(IntPtr amsiContext, IntPtr sessionHandle)
{
_amsiContext = amsiContext;
_sessionHandle = sessionHandle;
}
public async Task<bool> IsPotentialMalwareAsync(byte[] data, string filename, CancellationToken cancellationToken = default)
{
return await Task.Run(() => IsPotentialMalware(data, filename), cancellationToken);
}
private bool IsPotentialMalware(byte[] data, string filename)
{
var result = AmsiScanBuffer(_amsiContext, data, (uint)data.Length, filename, _sessionHandle, out var scanResult);
if (result != 0)
{
throw new InvalidOperationException($"Failed to scan buffer. Error code: {result}");
}
// AMSI_RESULT values:
// 0 = Clean
// 1-32767 = Detected (higher = more confident detection)
// 32768+ = Blocked
return scanResult >= 1;
}
public void Dispose()
{
if (_sessionHandle != IntPtr.Zero)
{
AmsiCloseSession(_amsiContext, _sessionHandle);
_sessionHandle = IntPtr.Zero;
}
}
[DllImport("amsi.dll")]
private static extern int AmsiScanBuffer(IntPtr amsiContext, byte[] buffer, uint length, string contentName, IntPtr session, out int result);
[DllImport("amsi.dll")]
private static extern void AmsiCloseSession(IntPtr amsiContext, IntPtr session);
}[Test]
public async Task ValidateAsync_WithEICARTestFile_ReturnsFail()
{
// EICAR test string - universally detected as malware
const string eicarString = @"X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*";
var eicarBytes = Encoding.UTF8.GetBytes(eicarString);
var mockFile = new Mock<IFormFile>();
mockFile.Setup(f => f.FileName).Returns("eicar.txt");
// Mock GetData() extension method
var memoryStream = new MemoryStream(eicarBytes);
mockFile.Setup(f => f.OpenReadStream()).Returns(memoryStream);
mockFile.Setup(f => f.Length).Returns(eicarBytes.Length);
var validator = new MalwareScanValidationStep(logger);
// Act
var result = await validator.ValidateAsync(mockFile.Object, CancellationToken.None);
// Assert
Assert.That(result.IsValid, Is.False);
Assert.That(result.ErrorResult, Is.InstanceOf<BadRequestObjectResult>());
}public async Task<bool> IsPotentialMalwareAsync(byte[] data, string filename,
CancellationToken cancellationToken = default)
{
return await Task.Run(() => IsPotentialMalware(data, filename), cancellationToken);
}