Skip to main content

Blog

Go Search
Home
Blog
  

SOHO Technology Solutions, LLC > Blog
Creating and Using Managed Elevated COM objects

Why elevated COM you might ask.  Answer, because elevated COM is the ‘best’ way to allow an application that does not normally need administrator privileges to gain them for the unusual task.  Why managed you ask.  Because managed code in C# or VB allows the developer to deploy only one application to both x64 and x86 platforms.  As soon as you touch C++ you must compile the code for the specific mode you wish to run.  Further driving my quest for the use of managed code was the fact that I arrived here with minimal experience with COM.  In fact, I had never written any COM objects and did not relish the thought of learning ATL, or worse, at this point in my career.  Armed with the ‘knowledge’ that managed COM was easy, I set out on the development path.

There are several articles available that discuss some part of creating or access a managed elevated COM object.  I could not find one that filled in all the pieces.  I found Phil Wilson’s article, Build and Deploy a .NET COM Assembly, to be particularly helpful the uninitiated.  Christoph Wille has published three articles on the subject of managed elevated COM objects.  The last one, UAC Elevation in Managed Code: Guidance for Implementing COM Elevation, is the best resource and provides a good framework and it was my starting point.

I ran into several difficulties along my way to actually being able to access a managed COM object.  Perhaps the most insidious is the 64 bit versus 32 bit issue.  A COM object, managed or unmanaged, must be registered as 32 bit or 64 bit according to how it is going to be used.  The reality of this statement is that the object must be registered as both 32 bit and 64 bit when used on a 64 bit system.  Managed COM can be handled fairly easily by running both versions of REGASM.  (A 64 bit system has a second copy of REGASM in the Framework64 directory tree under the Microsoft.NET directory.)  Since either of these versions can be run from a 32 bit or 64 bit (elevated) command prompt, I created two bat files, REGASM32 and REGASM64, that call the correct REGASM and then I created a REGASM bat file that invokes both of these bat files.  That way I can conveniently invoke either or both versions of REGASM as needed.  (Check out the post at http://differentpla.net/content/2008/10/things-i-learnt-week-regsvr32exe-windows-x64 for information about registering 32 and 64 bit unmanaged COM objects.)  The other registration issue that I ran into is that the TYPELIB must be registered or activation will fail.  That may be written down somewhere or COM gurus may know this, but it was news to me.  The way that I have discovered that works and is easily done is to simply add the /tlb option to REGASM.  The third registration issue has to do with where the DLL actually is.  There are some people that believe that the DLL must be in the GAC to be registered.  That is not true.  If you add the /codebase option to REGASM, it will add the path to the registry and the COM object can be activated from any other path on the system.

In his third article Christoph discusses automatically adding the elevation information to the registry as the object is registered by using the ComRegFunction and ComUnregisterFunction attributes.  Well, again I ran into issues.  After much trial and error, I discovered that these attributes must be fully specified as [ComRegisterFunctionAttribute()] and [ComUnegisterFunctionAttribute()].  If you leave of the () or the word Attribute, you will not get a compile error, but the methods will not be called.  As a note, all the examples I found showed these two static functions to be public.  In reality, it is best practice that they be private.  Following’s Christoph’s lead I have reduced these two functions to a minimal and produced a helper class that does all the actual work.

[ComRegisterFunctionAttribute()]
private static void ComRegister (Type typeToRegister)
{
  ElevatedComRegistration.Register(typeToRegister.Assembly.Location,
    ClassGuid, AppIdGuid, 100);
}

[ComUnregisterFunctionAttribute()]
private static void ComUnregister (Type typeToUnRegister)
{
  ElevatedComRegistration.UnRegister(typeToUnRegister.Assembly.Location,
    ClassGuid, AppIdGuid);
}

ClassGuid and AppIdGuid are constants in the class to be registered.  Again I followed Christoph’s example, but made a few cosmetic changes.

#region Interface ISCUtility
[Guid(SCUtility.InterfaceGuid),
InterfaceType(ComInterfaceType.InterfaceIsDual),
ComVisible(true)]
public interface ISCUtility: IDisposable
{
  // Add COM accessible methods and properties here
}
#endregion

#region Class SCUtility
[GuidAttribute(SCUtility.ClassGuid),
ClassInterface(ClassInterfaceType.None),
ComVisible(true)]
public class SCUtility: ISCUtility, IDisposable
{
  public const string    ClassGuid = "DE177E01-C449-4987-A1E0-C98567EB3047";
  public const string    InterfaceGuid = "5C10AA8E-4A8F-464f-8882-2B95DA4A993E";
  private const string   AppIdGuid = "AD1C0896-F21B-42be-80E3-A31D0F031CEE";
...
}

As a note, by adding the ComVisible(true) to the interface and class definitions, you do not have to set ComVisible(true) for the entire assembly.  Of course, it should go without saying that you should not reuse my GUIDs in your projects.

A note.  As you read further it may appear that I am disparaging Christoph’s code.  That is far from my intent.  Without his groundbreaking work, I would not have completed this project and I would have turned in another direction.  I hope that I have simply built upon a predecessor's work in a constructive manner.

The code in the helper functions is similar to Christoph’s but with some simplification and additions.  In particular, his UnRegister function does not clean up enough information and leaves registry entries for items that have been unregistered.

public static void Register (string assemblyLocation, string classToElevate,
  string appId, int localizedStringId)
{
  RegistryKey    class_key;
  RegistryKey    elevation_key;
  RegistryKey    hkcr_app_id;
  RegistryKey    key;

/*
    Need a good check here for a system that requires UAC elevation
*/

  class_key = Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + classToElevate + "}", true);
/*
*    Add the appid and the prompt string to the class's entry
* */
  class_key.SetValue("AppId", "{" + appId + "}", RegistryValueKind.String);
  class_key.SetValue("LocalizedString", "@" + assemblyLocation + ",-" +
    localizedStringId.ToString(),RegistryValueKind.String);
/*
*    Enable elevation
* */
  elevation_key = class_key.CreateSubKey("Elevation");
  elevation_key.SetValue("Enabled", 1, RegistryValueKind.DWord);
  elevation_key.Close();
  class_key.Close();

  hkcr_app_id = Registry.ClassesRoot.OpenSubKey("AppID", true);
/*
*    Add the AppId key and tell it to use DLLSurrogate
* */
  key = hkcr_app_id.CreateSubKey("{" + appId + "}");
  key.SetValue(null, Path.GetFileNameWithoutExtension(assemblyLocation));
  key.SetValue("DllSurrogate", "", RegistryValueKind.String);
  key.Close();
/*
*    Add the AppId guid
* */
  key = hkcr_app_id.CreateSubKey(Path.GetFileName(assemblyLocation));
  key.SetValue("AppID", "{" + appId + "}", RegistryValueKind.String);
  key.Close();

  hkcr_app_id.Close();
}

public static void UnRegister (string assemblyLocation, string classToElevate, string appId)
{
  RegistryKey    class_key;
  RegistryKey    hkcr_app_id;

  class_key = Registry.ClassesRoot.OpenSubKey(@"CLSID\{" + classToElevate + "}", true);
  if (class_key != null)
  {
    try
    {
      class_key.DeleteSubKeyTree("Elevation");
    }
    catch
    {
    }
    class_key.DeleteValue("AppId",false);
    class_key.DeleteValue("LocalizedString",false);
  }
  hkcr_app_id = Registry.ClassesRoot.OpenSubKey("AppID", true);
  if (hkcr_app_id != null)
  {
    hkcr_app_id.DeleteSubKey("{" + appId + "}",false);
    hkcr_app_id.DeleteSubKey(Path.GetFileName(assemblyLocation),false);
  }
  hkcr_app_id.Close();
}

One last item of difficulty was the inclusion of the resource string required for registration.  This string is displayed to the user in the UAC prompt.  I found a method that works, but I would not propose it a seriously good method.  Maybe someone can give me a better method.

I took a .RC file from a CPP+ project and removed everything but four lines.   The result was

STRINGTABLE
BEGIN
    100 "Put the prompt message here"
END

The 100 is the 100 that is last parameter in the call to ElevatedComRegistration.Register.  I then added a Post Build event of rc $(ProjectDir)app.rc and then set the resource file on the Application tab to the full path of the resulting RES file.  Of course this does not handle alternate languages correctly, but it is a start.

Musings on Malware - Part Two

Why we are not safe or why virus scanners are not enough

I finished the last article with the rather dismal forecast that eventually we will all have our computers infected with a virus.  Many people may not agree with that prediction or may be asking why.  Well here is the why.

Most people know that they should have a virus scanner installed on their system.  All virus scanners use a set of files that contain virus signatures.  The exact contents of the virus signature files will vary according to vendor, but just know that the signature of a virus is the minimum information to identify it.  For instance the signature may contain information such as the size of the file, a hash code of the file and the file name.  The various anti‑virus product vendors will update their signature files on different schedules.  Some of them update daily, others update weekly and there is at least one that updates more than once daily.  If all other things are equal, the more frequent the update, the better the protection.

Most of the people that have a virus scanner also realize that they must update the signature files for the scanner to be effective.  The better products allow for an automatic update that can be run daily.  If you have an ‘always on’ connection such as DSL or cable, then configure your virus scanner to update the signature files in the early morning time.  (Around 04:00 05:00 AM appears to be a good time.)

Now, you are saying, “I update my signature files every day”.  ”I am safe”.  Well, I am sorry to say that you are not safe and in fact you may be less safe than someone that does not have a virus scanner.  To understand why, you need to look at the process that happens when a new virus is released.

The virus will usually make its debut in the wee hours of the morning.  (They often start in Europe or Asia.)  Someone at the anti-virus company will have to discover that there is a new virus.  This usually happens after several systems get infected with it.  Let us assume the new virus is released at 04:00 AM.  If we are lucky, the anti‑virus company will be aware of it by 06:00 AM, but many times it could take much longer.   The next step will be two or more hours of analyzing the virus so that it can be detected reliably.  Then the anti‑virus company can produce the new signature file, test it and post it on the web site for you to download.  By the time all of this is done it is at least six hours since the virus was released.  Many times it can be much longer.  It is even worse if the company only updates its signature files once a day or, unacceptably, once a week.  This process leaves a window of no protection that is a least one day and probably most of two days after a new virus is released.  If you take the attitude of “I am protected', you are being lulled into a false sense of security.  You should always be on the lookout for a virus in your email messages.

Musings on Malware - Part One

Worms, Viruses and Trojans

Trojan – A program, usually undesirable, that pretends to be desirable program.

Virus – A small program written specifically to cause problems in your computer.

Worm - A program that propagates itself over a network, reproducing itself as it goes. (http://wombat.doc.ic.ac.uk/foldoc/foldoc.cgi?query=Worm&action=Search)

Worms, viruses and trojans all fall into the heading of malware.  Malware could be described as any computer program that you don’t really want running on your system.  It includes the category of spy-ware and possibly even pop-ads and ad tracking software.

Trojans

Trojans may arrive on your system as an email attachment or they may be downloaded from a web site.  The best defense against trojans is to always know the source of the program that you are about to execute.  In the case of an email attachment, know the person that sent it to you and have them verify that they did actually send you the program and that it is what it is supposed to be.  Better yet, just do not run a program that comes to you as an email attachment.  When you go to download a program from a web site make sure you are dealing with a reputable web site.

Worms

Worms crawl around the internet.  They may arrive as an email attachment or they maybe programs that are running on another system that can access your system via a network (local or internet).  A worm will usually carry one or more programs with it, called the payload.  In general the payload of a worm will be a virus.

Viruses

We all know about viruses.  We all fear them and eventually we will all have our computers infected with one.  A virus can travel in many ways.  They may be disguised as a trojan, an attachment to an email or the payload of a worm.

 ‭(Hidden)‬ Admin Links