Quickstart v.2.0

Introduction


This quick start will show the basic capabilities for intercepting system calls with Deviare from a C# program. Requeriments are: a proper C# compiler/editor (VisualStudio or SharpDevelop), and Deviare 2.0

Projects were developed under Visual Studio 2008, conversion to Visual Studio 2010 is not advisable.

Project Setup (Free COM)

We'll use Deviare with C#. Open a Visual Studio session and create a new C# Windows Forms project. Name it as 'DeviareTest'.

With the project successfully setup, select solution platform (x64 or x86), disable "Visual Studio hosting process" and "Create application without a manifest". Finally build the project and copy the next files to (Release\Debug) directory:

x86:

Nektra.Deviare2.dll

DeviareCOM.dll

DeviareCOM.X.manifest

DvAgent.dll

Deviare32.db

rename: DeviareTest32.exe.manifest -> DeviareTest.exe.manifest

x64:

Nektra.Deviare2.dll

DeviareCOM64.dll

DeviareCOM64.X.manifest

DvAgent.dll

DvAgent64.dll

Deviare32.db

Deviare64.db

rename: DeviareTest64.exe.manifest -> DeviareTest.exe.manifest

Then "Add Reference", go to "Browse" and include "Nektra.Deviare2.dll"

Project Setup (Non-Free COM)

We'll use Deviare with C#. Open a Visual Studio session and create a new C# Windows Forms project. Name it as 'DeviareTest'.

With the project successfully setup, select solution platform (x64 or x86)

x86:

regsvr32 "D:\My path\DeviareCOM.dll"

Then "Add Reference", go to "COM" and include "DeviareCOM 2.0"

x64:

regsvr32 "D:\My path\DeviareCOM64.dll"

Then "Add Reference", go to "Browse" and include "DeviareCOM64.dll"

Hooking Basics

Our task will be to hook CreateFileW system calls. In this case, we'll use a known application such as NOTEPAD.EXE as a target for our example.

Modify thread model

[STAThread] -> [MTAThread]

Include namespace

using Nektra.Deviare2;

Prior to call any Deviare library methods, we need to initialize the NktSpyMgr class. This can be done in the Form constructor:

_spyMgr = new NktSpyMgr();
_spyMgr.Initialize();

Get our target process

private NktProcess GetProcess(string proccessName)
{
    NktProcessesEnum enumProcess = _spyMgr.Processes();
    NktProcess tempProcess = enumProcess.First();
    while (tempProcess != null)
    {
        if (tempProcess.Name.Equals(proccessName, StringComparison.InvariantCultureIgnoreCase))
        {
            return tempProcess;
        }
        tempProcess = enumProcess.Next();
    }
    return null;
}

Then you can do a call like the following one to find a process (in our example to tell Deviare which process we want to target for hooking).

NktProcess _process = GetProcess("notepad.exe");

On the Form load event handler, we create a new Hook object passing the system call to be intercepted in module!function format to the _spyMgr.CreateHook function

NktHook hook = _spyMgr.CreateHook("kernel32.dll!CreateFileW", (int)(eNktHookFlags.flgRestrictAutoHookToSameExecutable | eNktHookFlags.flgOnlyPreCall));

Note that our handler will be called *before* hooked function execution begins. Other option is flgOnlyPostCall, which calls our handler when the hooked function terminates

(right after return), making possible to inspect return values.

When this system call is executed in the context of the target process, we catch it in our event handler

_spyMgr.OnFunctionCalled += new DNktSpyMgrEvents_OnFunctionCalledEventHandler(OnFunctionCalled);

Now, Hook::Hook() call initiates interception mechanism for the specified functions under the configured properties.

hook.Hook(true);
hook.Attach(_process, true);

We specify the processes we want to attach. _process was returned from the GetProcess function above, and in our example contains information about NOTEPAD process.

At last we use the OnFunctionCalled event to process the call. In this case we will inspect CreateFileW information:

void OnFunctionCalled(NktHook hook, INktProcess proc, INktHookCallInfo callInfo)
{
    INktParamsEnum pms = callInfo.Params();
    INktParam p;
    string filename;
    uint access;

    //how to read a string
    p = pms.GetAt(0); //get the first param (LPCWSTR lpFileName)
    if (p.IsNullPointer == false)
        filename = p.ReadString();
    else
        filename = "";

    //how to read a simple value
    p = pms.GetAt(1); //get the second param (DWORD dwDesiredAccess)
    access = p.ULongVal; //you can freely analyze dwDesiredAccess flags
    
    //how to read a structure
    p = pms.GetAt(3); //get the fourth param (LPSECURITY_ATTRIBUTES lpSecurityAttributes)
    if (p.IsNullPointer == false)
    {
        INktParam pC;
        uint len;

        //if not null, analyze it
        p = p.Evaluate(); //now p becomes the struct itself not anymore a pointer to

        //now we want to read the length parameter
        pC = p.GetField(0); //get the first field (DWORD nLength)
        len = pC.ULongVal; //store length

        pC = p.GetField(1); //get the second field (LPVOID lpSecurityDescriptor)
        if (len > 0 && pC.IsNullPointer == false)
        {
            //at this point we have that the "lpSecurityDescriptor" points to the data and its length is "len" bytes
            //so we will read it into a byte array. To accomplish this task we will use some c# helpers and the INktProcessMemory interface

            INktProcessMemory procMem = _spyMgr.ProcessMemoryFromPID(proc.Id); //get the memory reader/writer

            var buffer = new byte[len]; //create our local buffer

            GCHandle pinnedBuffer = GCHandle.Alloc(buffer, GCHandleType.Pinned); //use C# pin mechanism to avoid garbage collection
            IntPtr pDest = pinnedBuffer.AddrOfPinnedObject(); //get the real address of our byte buffer
            procMem.ReadMem(pDest, pC.PointerVal, (IntPtr)len); //copy the data from the hooked process
            pinnedBuffer.Free(); //unpin the buffer

            //at this point, "buffer" has the copy of the security descriptor data

        }
    }

}

Interception Handling

Now let's see how we can implement a simple reporting routine for every CreateFileW call.

private void OnFunctionCalled(NktHook hook, NktProcess process, NktHookCallInfo hookCallInfo)
{
...
...
}

As an example, we want to report the CreateFileW function parameters for each interception. It's prototype is:

HANDLE WINAPI CreateFile(
  __in      LPCTSTR lpFileName,
  __in      DWORD dwDesiredAccess,
  __in      DWORD dwShareMode,
  __in_opt  LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  __in      DWORD dwCreationDisposition,
  __in      DWORD dwFlagsAndAttributes,
  __in_opt  HANDLE hTemplateFile
);

We can easily traverse those parameters using the hookCallInfo.Params() enumerator:

string strCreateFile = "CreateFile(\"";

INktParamsEnum paramsEnum = hookCallInfo.Params();

//lpFileName
INktParam param = paramsEnum.First();
strCreateFile += param.ReadString() + "\", ";

//dwDesiredAccess
param = paramsEnum.Next();
if ((param.LongVal & 0x80000000) == 0x80000000)
    strCreateFile += "GENERIC_READ ";
else if ((param.LongVal & 0x40000000) == 0x40000000)
    strCreateFile += "GENERIC_WRITE ";
else if ((param.LongVal & 0x20000000) == 0x20000000)
    strCreateFile += "GENERIC_EXECUTE ";
else if ((param.LongVal & 0x10000000) == 0x10000000)
    strCreateFile += "GENERIC_ALL ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwShareMode
param = paramsEnum.Next();
if ((param.LongVal & 0x00000001) == 0x00000001)
    strCreateFile += "FILE_SHARE_READ ";
else if ((param.LongVal & 0x00000002) == 0x00000002)
    strCreateFile += "FILE_SHARE_WRITE ";
else if ((param.LongVal & 0x00000004) == 0x00000004)
    strCreateFile += "FILE_SHARE_DELETE ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//lpSecurityAttributes
param = paramsEnum.Next();
if (param.PointerVal != IntPtr.Zero)
{
    strCreateFile += "SECURITY_ATTRIBUTES(";

    INktParamsEnum paramsEnumStruct = param.Evaluate().Fields();
    INktParam paramStruct = paramsEnumStruct.First();

    strCreateFile += paramStruct.LongVal.ToString();
    strCreateFile += ", ";

    paramStruct = paramsEnumStruct.Next();
    strCreateFile += paramStruct.PointerVal.ToString();
    strCreateFile += ", ";

    paramStruct = paramsEnumStruct.Next();
    strCreateFile += paramStruct.LongVal.ToString();
    strCreateFile += ")";
}
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwCreationDisposition
param = paramsEnum.Next();
if (param.LongVal == 1)
    strCreateFile += "CREATE_NEW ";
else if (param.LongVal == 2)
    strCreateFile += "CREATE_ALWAYS ";
else if (param.LongVal == 3)
    strCreateFile += "OPEN_EXISTING ";
else if (param.LongVal == 4)
    strCreateFile += "OPEN_ALWAYS ";
else if (param.LongVal == 5)
    strCreateFile += "TRUNCATE_EXISTING ";
else
    strCreateFile += "0";
strCreateFile += ", ";

//dwFlagsAndAttributes
strCreateFile += param.LongVal;
strCreateFile += ", ";

//hTemplateFile
strCreateFile += param.LongLongVal;
strCreateFile += ");\r\n";

Output(strCreateFile);

With this code, we get an output like:

CreateFile("C:\file.dat", GENERIC_READ , FILE_SHARE_READ , SECURITY_ATTRIBUTES(24, 1700896, 0), OPEN_EXISTING , 3, 0);
CreateFile("D:\Music\desktop.ini", GENERIC_READ , FILE_SHARE_READ , 0, OPEN_EXISTING , 3, 0);
CreateFile("D:\Links\Downloads.lnk", GENERIC_READ , FILE_SHARE_READ , 0, OPEN_EXISTING , 3, 0);

Tunning & hacks

In order to assist development process, we added some configuration that can be set up with the registry editor.

Open Registry Editor (with administrative privileges on Windows Vista and later) and create the a subkey named "Deviare2" inside the following key:

HKEY_LOCAL_MACHINE\Software\Nektra\Deviare2

NOTE: If using a 64-bit o.s., use the x64 version of regedit.exe and do not create the key under HKEY_LOCAL_MACHINE\Wow3264Node\Software\Nektra\Deviare2 because it will be ignored no matter you are using the 32 or 64 bit version of Deviare.

Inside Deviare2 key you can create the following values:

DebugLevelMask: A REG_DWORD value that enables/disables debug output of different modules.

The following bit flags are available:
  • Tools = 0x00000008
  • Transport = 0x00000010
  • Engine = 0x00000020
  • Agent = 0x00000040
  • HookEngine = 0x00000080
  • HookEnginePreCall = 0x00000100
  • HookEnginePostCall = 0x00000200
  • Error = 0x00000400
  • Warning = 0x00000800
  • dlInformation = 0x00002000
The recommended bitmask is: Tools|Transport|Engine|Agent|HookEngine|Error|Warning = 0xCF8

In order to see the output, we recommend to attach WinDbg to the desired process.

MemMgrPoolMaxAge and MemMgrPoolCheckFrequency: REG_DWORD values that control memory pool management.

Becuase Deviare do many allocations and deallocation of blocks of memory, internally, it uses some pools of memory to minimize fragmentation. When a block of memory is freed, it will remain in the pool for a while instead of being released to the operating system in order to recycle it and avoid calling o.s. memory management functions that are slow.

The MemMgrPoolMaxAge value indicates how much time a free block will remain in memory until released to the o.s. The MemMgrPoolCheckFrequency controls the frequency where the LRU blocks are checked and discarded.

The default values are:

MemMgrPoolMaxAge: 10000 (ms)
MemMgrPoolCheckFrequency: 128 (every 128 free call the pool is trimmed)

SpyMgrMaxTransportMessageCount, SpyMgrTransportMessageDelay, AgentMaxTransportMessageCount, AgentTransportMessageDelay and MaxTransportFreeListMessageCount: REG_DWORD values that control some communication settings.

To avoid saturation and memory consumption when too many messages are being sent between the SpyManager and the agent(s), you can tune up these items.

SpyMgrMaxTransportMessageCount and AgentMaxTransportMessageCount sets the maximum message count to send/receive before starting the delay operation. When the limit is reached, SpyMgr and/or Agent will begin to add some delays to message delivery in order to keep memory usage low until more buffers became available.

The delay time can be set using the SpyMgrTransportMessageDelay and AgentTransportMessageDelay values respectively. They are specified in milliseconds.

Also MaxTransportFreeListMessageCount specifies the number of free message buffers to mantain in memory and recycle when allocation is done.

The default values are the following:

SpyMgrMaxTransportMessageCount: 1000
AgentMaxTransportMessageCount: 500
SpyMgrTransportMessageDelay: 50 (50 ms)
AgentTransportMessageDelay: 50 (50 ms)
MaxTransportFreeListMessageCount: 500

Further research

Feel free to investigate and modify the C++/C# Deviare samples available, along with examining the API documentation provided.
Comments