A highly efficient, easy to use and flexible API for scanning files against maliciousness on Windows. Supports features like progress tracking, simplicity, async/await, buffering, and does not load the entire file into memory at once.
It is a direct wrapper for the AMSI.dll Windows library, which is the Antimalware Scan Interface provided by Microsoft. It allows you to scan files, buffers, and strings for malicious content using the underlying AMSI engine. Therefore, the security of these scans is provided by whichever Anti-Virus software is currently active on the system. By default, that's Microsoft Defender.
if (await Antimalware.QuickScanAsync("C:\\Suspicious.exe"))
{
// File is malicious
}
else
{
// File is clean
}To use this library, you'll need Microsoft Windows 10 1507 (July 2015) or newer. Newer operating systems like Windows 11 are supported, however, Windows 8.1 and older are not supported, as AMSI.dll was only introduced later.
The underlying AMSI engine is a powerful tool for detecting malicious files, but it can be a pain to work with directly. Dealing with pointers, unmanaged memory, and parameter checking is already a headache. The raw AMSI engine also doesn't support asynchronous operations, progress tracking, or buffering.
There do exist some AMSI-handling libraries on NuGet, but:
- Some of them load an entire file into memory at once, which can be a problem for large files.
- None are flexible enough to support async/await, progress tracking, and buffering together, let alone testing and notifying operations.
- AmsiSharp allows interoperability with other libraries that support AMSI, by giving you access to the underlying context handle.
- AmsiSharp balances between simplicity and flexibility, giving you the tools you need to get the job done without overwhelming you with unnecessary complexity.
- AmsiSharp just makes sense. You don't need to know how AMSI works. Every method here makes sense.
In your apps, AmsiSharp, when on Windows 10 or newer, can be used to make sure that you're not trying to process well-known unsafe content that can harm the user's computer.
If you just want to check if a file is malicious, Antimalware.QuickScanAsync will do the trick.
using AmsiSharp; // < IMPORTANT
if (await Antimalware.QuickScanAsync("C:\\Suspicious.exe"))
{
// File is malicious
}
else
{
// File is clean
}But if you want more flexibility, we got you covered.
You can hold an instance of Antimalware and use it to scan multiple files, track progress, and more.
using var antimalware = new Antimalware(); // Generates a random app name for you, but you can also specify one if you want
Console.WriteLine(antimalware.AppName); // Get the app name
if (await antimalware.IsMaliciousAsync("C:\\Suspicious.exe"))
{
// Malicious
}Or:
using var fs = File.OpenRead("C:\\Suspicious.exe");
AntimalwareScanResult asr = await antimalware.ScanAsync(fs);
if (Antimalware.IsMalicious(asr))
{
// Malicious
}
else
{
// Clean
}AmsiSharp gives you access to the underlying AMSI context, so you can use it with other libraries that support it.
IntPtr hAmsiContext = antimalware.Context; // Get the AMSI context handle
// InteropBy default, the new Antimalware() constructor generates a random app name for you, but you can also specify one if you want.
using var antimalware = new Antimalware("Peep");If you're unsure whether the AMSI engine is working, you can use the .TestAsync() method to test it with the EICAR test string.
if (await antimalware.TestAsync())
{
Console.WriteLine("AMSI engine is working!");
}
else
{
Console.WriteLine("AMSI engine is not working.");
}Under the hood, it feeds an EICAR string to the engine and checks if it detects it as malicious. If it does, then the engine is working.
You can set buffer size per scan, which is the amount of bytes that are read from the file and fed to the engine at once. The default buffer size is 1 MB, but you can change it if you want.
You can also change whether or not would you like the newly created context to be disposed when the Dispose() method is called (true by default).
antimalware.BufferSize = 1024 * 1024 * 2; // Set buffer size to 2 MB
antimalware.DisposeContext = false; // Don't dispose the context when Dispose() is calledIf you're interoperating with AMSI or other libraries supporting it, you can integrate your own context into AmsiSharp and use it to scan files.
IntPtr ctx = ...;
string appName = "Peep!";
using var antimalware = new Antimalware(ctx, appName, disposeContext: false);Alternatively, you can also create an instance of Antimalware with a random application name:
IntPtr ctx = ...;
using var antimalware = new Antimalware(ctx, Antimalware.NewAppName(), disposeContext: false);You can scan strings for maliciousness directly without having to write them to a file first. This is especially useful for scanning user input or data from the network.
string eicar = Antimalware.EicarString;
if (Antimalware.IsMalicious(await antimalware.ScanStringAsync(eicar)))
{
// Malicious
}
else
{
// Clean
}If you want to get the EICAR test string as bytes, you can use the Antimalware.EicarBytes property.
ImmutableArray<byte> eicarBytes = Antimalware.EicarBytes;These bytes are stored under the ASCII encoding. You can also get the raw string like this:
string eicarString = Antimalware.EicarString;Use StartBackgroundScan to start a background scan and track its progress.
IAntimalwareScanProgress progress = antimalware.StartBackgroundScan(
stream: myStream,
contentName: null,
progress: new Progress<long>((totalBytesRead) =>
{
// ...
}),
progressByPercentage: new Progress<decimal>((percentageFrom0to100) =>
{
// ...
}),
onFinish: (status) =>
{
if (Antimalware.IsMalicious(status))
{
// Malware
}
});
// Super basic timeout
Thread.Sleep(2000);
if (progress.IsAvailable)
{
progress.Stop();
}Background scan only updates every time the buffer is filled and fed to the engine, so the progress updates are not super smooth, but they are efficient. The progress callbacks are optional, so you can choose to only track total bytes read, or only track percentage, or both, or neither.
The background scan runs in the background thread. If you're willing to modify UI state from within the progress callbacks, make sure to marshal the calls to the UI thread using:
.Dispatcher(WPF).Invoke()(WinForms)
You can notify the engine about certain operations using the NotifyOperationAsync methods. You
can either use a raw pointer to the start of data (IntPtr) along with length (uint) overload,
or you can use the overload that accepts a byte array, or an overload that accepts text with a specific
encoding, which, when not supplied, defaults to UTF-8.
string notifyOperation = "Example operation, this will do nothing";
await antimalware.NotifyOperationAsync(notifyOperation, System.Text.Encoding.ASCII);Warning
Modern versions of Windows no longer export this function from AMSI anymore, so you'll get errors if you try to use it. It is only there if you'd like to try it on older versions of Windows, but it is recommended to avoid using it altogether, as it is deprecated and may not work on all versions of Windows.
Check out the TryAmsiSharp program. It is a simple WinForms app that demonstrates
the use of the library.
AmsiSharp is licensed to you under the MIT License. See LICENSE for more details.
We welcome contributions to AmsiSharp! If you have an idea for a new feature or have found a bug, please open an issue or submit a pull request. We will review it as soon as possible.