420 lines
14 KiB
Plaintext
420 lines
14 KiB
Plaintext
///////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Microsoft Research Singularity
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// Note: Anti-virus scanner.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading;
|
|
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.SingSharp.Reflection;
|
|
using Microsoft.Singularity.Applications;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Configuration;
|
|
using Microsoft.Singularity.Crypto;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.Email.Contracts;
|
|
using Microsoft.Singularity.Endpoint;
|
|
using Microsoft.Singularity.Extending;
|
|
using Microsoft.Singularity.Io;
|
|
using Microsoft.Singularity.Security;
|
|
using Microsoft.Singularity.V1.Services;
|
|
|
|
[assembly: Transform(typeof(ApplicationResourceTransform))]
|
|
[assembly: ApplicationPublisherAttribute("singularity.microsoft.com")]
|
|
[assembly: AssertPrivilegeAttribute("$register-privilege.localhost")]
|
|
|
|
namespace Microsoft.Singularity.Email
|
|
{
|
|
// VirusDef
|
|
//
|
|
// This class holds the information from the virus definition files,
|
|
// such as the virus name, pattern, and type of file to which the
|
|
// definition applies. Some virus definitions refer to MD5 hashes,
|
|
// in which case the pattern represents the hash.
|
|
|
|
class VirusDefError : ApplicationException { }
|
|
|
|
class VirusDef
|
|
{
|
|
public VirusDef(string name, int length, Pattern pattern)
|
|
{
|
|
Name = name;
|
|
Type = TargetType.TYPE_ANY;
|
|
IsMD5 = true;
|
|
AnyOffset = false;
|
|
Offset = 0;
|
|
Length = length;
|
|
Pattern = pattern;
|
|
}
|
|
|
|
public VirusDef(string name, int type, string! offset, Pattern pattern)
|
|
{
|
|
Name = name;
|
|
Type = (TargetType)type;
|
|
IsMD5 = false;
|
|
Length = 0;
|
|
Pattern = pattern;
|
|
|
|
if (offset == "*") {
|
|
AnyOffset = true;
|
|
Offset = 0;
|
|
} else if (offset.Length >= 4 && offset.Substring(0, 4) == "EOF-") {
|
|
AnyOffset = false;
|
|
Offset = Int32.Parse(offset.Substring(3));
|
|
} else if (Type != TargetType.TYPE_PE && Type != TargetType.TYPE_ELF) {
|
|
AnyOffset = false;
|
|
Offset = Int32.Parse(offset);
|
|
} else {
|
|
AnyOffset = false;
|
|
Offset = 0;
|
|
}
|
|
}
|
|
|
|
public enum TargetType
|
|
{
|
|
TYPE_ANY = 0,
|
|
TYPE_PE = 1,
|
|
TYPE_OLE = 2,
|
|
TYPE_HTML = 3,
|
|
TYPE_MAIL = 4,
|
|
TYPE_GRAPHICS = 5,
|
|
TYPE_ELF = 6,
|
|
}
|
|
|
|
public string Name;
|
|
public TargetType Type;
|
|
public bool IsMD5;
|
|
public bool AnyOffset;
|
|
public int Offset;
|
|
public int Length;
|
|
public Pattern Pattern;
|
|
}
|
|
|
|
// AntiVirus
|
|
//
|
|
// This class contains the main appliation logic for the virus scanner.
|
|
// We load three virus definition files from disk, set up the pattern
|
|
// matcher, and then wait for incoming requests for virus checking.
|
|
|
|
[ConsoleCategory(HelpMessage="Anti-Virus Service", DefaultAction=true)]
|
|
internal class Parameters
|
|
{
|
|
[InputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Exp:READY> Stdin;
|
|
|
|
[OutputEndpoint("data")]
|
|
public readonly TRef<UnicodePipeContract.Imp:READY> Stdout;
|
|
|
|
[Endpoint]
|
|
public readonly TRef<DirectoryServiceContract.Imp:Start> nsRef;
|
|
|
|
[StringParameter("db", Mandatory=false, Default="/init/virus.db",
|
|
HelpMessage="File with universal virus signatures (.db).")]
|
|
internal string dbfile;
|
|
|
|
[StringParameter("hdb", Mandatory=false, Default="/init/virus.hdb",
|
|
HelpMessage="File with h virus signatures (.hdb).")]
|
|
internal string hdbfile;
|
|
|
|
[StringParameter("ndb", Mandatory=false, Default="/init/virus.ndb",
|
|
HelpMessage="File with n virus signatures (.ndb).")]
|
|
internal string ndbfile;
|
|
|
|
reflective internal Parameters();
|
|
|
|
internal int AppMain() {
|
|
return AntiVirus.AppMain(this);
|
|
}
|
|
}
|
|
|
|
class AntiVirus
|
|
{
|
|
internal static int AppMain(Parameters! config)
|
|
{
|
|
// Publish our service endpoint before loading virus definitions as
|
|
// they can take minutes to load.
|
|
DirectoryServiceContract.Imp ds = (config.nsRef).Acquire();
|
|
if (ds == null) {
|
|
throw new Exception("AntiVirus: Unable to acquire handle to Directory Service.");
|
|
}
|
|
ds.RecvSuccess();
|
|
|
|
ServiceProviderContract.Imp! nsImp;
|
|
ServiceProviderContract.Exp! nsExp;
|
|
ServiceProviderContract.NewChannel(out nsImp, out nsExp);
|
|
|
|
try {
|
|
ds.SendRegister(Bitter.FromString2(AntiVirusContract.ModuleName), nsImp);
|
|
|
|
switch receive {
|
|
case ds.AckRegister() :
|
|
// All is well.
|
|
break;
|
|
|
|
case ds.NakRegister(ServiceProviderContract.Imp:Start rejectedEP, error) :
|
|
// All is very much not well; abort.
|
|
Console.WriteLine("AntiVirus: Failed to register endpoint as {0}.",
|
|
AntiVirusContract.ModuleName);
|
|
delete nsExp;
|
|
delete rejectedEP;
|
|
return -1;
|
|
|
|
case ds.ChannelClosed():
|
|
Console.WriteLine("AntiVirus: ds channel closed");
|
|
delete nsExp;
|
|
return -1;
|
|
}
|
|
}
|
|
finally {
|
|
delete ds;
|
|
}
|
|
|
|
Console.WriteLine("AntiVirus: Loading virus definition files.");
|
|
LoadDefinitions(config.dbfile);
|
|
LoadDefinitions(config.hdbfile);
|
|
LoadDefinitions(config.ndbfile);
|
|
|
|
PrepareMatcher();
|
|
|
|
Console.WriteLine("AntiVirus: Ready at {0}", AntiVirusContract.ModuleName);
|
|
|
|
// Here is the set of client channels we service
|
|
|
|
for (;;) {
|
|
switch receive {
|
|
// ------------------------------- Requests for new connections
|
|
|
|
case nsExp.Connect(ServiceContract.Exp:Start! newEp):
|
|
// We expect people top give us AntiVirusContract.Exp instances
|
|
AntiVirusContract.Exp newClient = newEp as AntiVirusContract.Exp;
|
|
|
|
if (newClient == null) {
|
|
// Invalid contract type. Fail.
|
|
nsExp.SendNackConnect(newEp);
|
|
}
|
|
else {
|
|
// Signal ready and start servicing this contract
|
|
nsExp.SendAckConnect();
|
|
|
|
// Launch the thread to handle the incoming requests.
|
|
AntiVirus child = new AntiVirus(newClient);
|
|
Thread thread = new Thread(child.Run);
|
|
thread.Start();
|
|
}
|
|
break;
|
|
|
|
case nsExp.ChannelClosed():
|
|
// The namespace channel is closed so quit.
|
|
delete nsExp;
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
public AntiVirus([Claims]AntiVirusContract.Exp:Start! ep)
|
|
{
|
|
epRef = new TRef<AntiVirusContract.Exp:Start>(ep);
|
|
}
|
|
|
|
private TRef<AntiVirusContract.Exp:Start> epRef;
|
|
|
|
public void Run()
|
|
{
|
|
AntiVirusContract.Exp ep = epRef.Acquire();
|
|
|
|
if (ep == null) {
|
|
return;
|
|
}
|
|
|
|
ep.SendAntiVirusReady();
|
|
int arg;
|
|
|
|
for (;;) {
|
|
switch receive {
|
|
case ep.CheckFile(byte[]! in ExHeap buffer):
|
|
#if false
|
|
Console.WriteLine("AnitVirus: CheckFile");
|
|
#endif
|
|
// decide if the buffer contains a virus
|
|
// possible crack the MIME content.
|
|
string virus = CheckData(Bitter.ToByteArray(buffer));
|
|
|
|
delete buffer;
|
|
if (virus != null) {
|
|
ep.SendFileContaminated(Bitter.FromString2(virus));
|
|
}
|
|
else {
|
|
ep.SendFileClean();
|
|
}
|
|
break;
|
|
|
|
case ep.ChannelClosed():
|
|
delete ep;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
delegate VirusDef VirusDefParser(string! line);
|
|
|
|
static void LoadDefinitions(string! filename)
|
|
{
|
|
VirusDefParser parser = null;
|
|
|
|
string ext = GetExtension(filename);
|
|
switch (ext) {
|
|
case "db":
|
|
parser = LoadDB;
|
|
break;
|
|
case "hdb":
|
|
parser = LoadHDB;
|
|
break;
|
|
case "ndb":
|
|
parser = LoadNDB;
|
|
break;
|
|
default:
|
|
Console.WriteLine("Invalid definition file {0}.", filename);
|
|
break;
|
|
}
|
|
|
|
if (parser != null) {
|
|
FileStream fsInput = new FileStream(filename, FileMode.Open, FileAccess.Read);
|
|
StreamReader reader = new StreamReader(fsInput);
|
|
// StreamReader reader = new StreamReader(filename);
|
|
string line;
|
|
int linenum = 1;
|
|
|
|
while ((line = reader.ReadLine()) != null) {
|
|
try {
|
|
VirusDef def = parser(line);
|
|
if (def != null) {
|
|
defs.Add(def);
|
|
}
|
|
} catch (VirusDefError) {
|
|
Console.WriteLine("Invalid virus definition at {0}:{1}.", filename, linenum);
|
|
}
|
|
linenum++;
|
|
}
|
|
}
|
|
}
|
|
|
|
static string GetExtension(string! filename)
|
|
{
|
|
int dot = filename.LastIndexOf('.');
|
|
return (dot >= 0) ? filename.Substring(dot + 1) : null;
|
|
}
|
|
|
|
static VirusDef LoadDB(string! line)
|
|
{
|
|
VirusDef def = null;
|
|
|
|
string[] split = line.Split(new char[] { '=' });
|
|
if (split.Length == 2) {
|
|
string name = (!)split[0];
|
|
string pattern = (!)split[1];
|
|
def = new VirusDef(name, 0, "*", parser.Parse(name, pattern));
|
|
} else {
|
|
throw new VirusDefError();
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
static VirusDef LoadHDB(string! line)
|
|
{
|
|
VirusDef def = null;
|
|
|
|
string[] split = line.Split(new char[] { ':' });
|
|
if (split.Length == 3) {
|
|
string hash = (!)split[0];
|
|
string length = (!)split[1];
|
|
string name = (!)split[2];
|
|
def = new VirusDef(name, Int32.Parse(length), parser.Parse(name, hash));
|
|
} else {
|
|
throw new VirusDefError();
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
static VirusDef LoadNDB(string! line)
|
|
{
|
|
VirusDef def = null;
|
|
|
|
string[] split = line.Split(new char[] { ':' });
|
|
if (split.Length >= 4 && split.Length <= 6) {
|
|
string name = (!)split[0];
|
|
string type = (!)split[1];
|
|
string offset = (!)split[2];
|
|
string pattern = (!)split[3];
|
|
|
|
def = new VirusDef(name, Int32.Parse(type), offset, parser.Parse(name, pattern));
|
|
|
|
// Discard PE, ELF, and HTML definitions for now
|
|
if (def.Type == VirusDef.TargetType.TYPE_PE ||
|
|
def.Type == VirusDef.TargetType.TYPE_ELF ||
|
|
def.Type == VirusDef.TargetType.TYPE_HTML) {
|
|
def = null;
|
|
}
|
|
} else {
|
|
throw new VirusDefError();
|
|
}
|
|
|
|
return def;
|
|
}
|
|
|
|
static void PrepareMatcher()
|
|
{
|
|
// TODO: Handle Length & Type fields.
|
|
|
|
foreach (VirusDef! def in defs) {
|
|
if (def.IsMD5) {
|
|
Debug.Assert(!def.AnyOffset);
|
|
md5Matcher.AddPattern(def.Pattern, def.Offset);
|
|
} else {
|
|
if (def.AnyOffset) {
|
|
matcher.AddPattern(def.Pattern);
|
|
} else {
|
|
matcher.AddPattern(def.Pattern, def.Offset);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static string CheckData(byte[]! data)
|
|
{
|
|
byte[] hash = (!)hasher.Hash(data);
|
|
|
|
string found;
|
|
|
|
found = md5Matcher.Match(hash);
|
|
if (found == null) {
|
|
found = matcher.Match(data);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static private MD5 hasher = new MD5();
|
|
|
|
static private ArrayList defs = new ArrayList();
|
|
|
|
static private PatternParser parser = new PatternParser();
|
|
|
|
static private PatternMatcher matcher = new PatternMatcher();
|
|
static private PatternMatcher md5Matcher = new PatternMatcher();
|
|
}
|
|
}
|