티스토리 뷰

프로그래밍Tip

Deviare quick start

이성권 2013. 7. 12. 16:29

[미완성]

튜토리얼 v.2.0

이 글은 Jovana Milutinovich 씨가 Webhostinggeeks.com에 올린 글을 번역한 글이다.


목차

1 소개

2 프로젝트 셋업 (Free COM)

3 프로젝트 셋업 (Non-Free COM)

4 Hooking Basics

5 Interception Handling

6 Tunning & hacks

7 Further research

소개

이 글은 Deviare를 사용해서 C# 프로그램에서 시스템 호출을 가로채는 기본적인 방법을 논의한다. 기본 준비 사항은 : 적절한  C# 컴파일러 및 에디터(VisualStudio 또는 SharpDevelop), 그리고 Deviare 2.0 를 다운받는다.


프로젝트는 Visual Studio 2008로 개발되었고, Visual Studio 2010으로 변환하는 것은 권고하지 않는다.


프로젝트 셋업 (Free COM)

우리는 C#으로 Deviare를 사용할 것이다. Visual Studio세션을 열고 C# Windows Forms project를 열도록 한다. 이름은  'DeviareTest'라고 명명한다.


프로젝트가 성공리에 셋업되면, 솔루션 플랫폼을 선택하고(x64 또는 x86), "Visual Studio hosting process" 를 disable 하고, "Create application without a manifest"를 선택하도록 한다. 끝으로,  프로젝트를 빌드하고 다음 파일들을 (Release\Debug) 디렉터리로 복사한다.:



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



그리고 "Add Reference"하여,  "Browse" 를 누르고 "Nektra.Deviare2.dll"를 include하도록 한다.


프로젝트 셋업 (Non-Free COM)

우리는 C#으로 Deviare를 사용할 것이다. Visual Studio 세션을 열고, 신규 C# Windows Forms project를 생성하도록 한다. 이름은  'DeviareTest'라고 명명한다.


프로젝트가 성공적으로 셋업되면 플랫폼을 선택하도록 한다.(x64 또는 x86)

x86:

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

그리고,  "Add Reference"를 눌러,  "COM"울 선택하고  "DeviareCOM 2.0"을 선택하도록 한다.


x64:

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

그리고,  "Add Reference"를 눌러,  "Browse"울 선택하고 "DeviareCOM64.dll"을 include하도록 한다.


후킹의 기초(Hooking Basics)

우리는  CreateFileW 시스템 호출을 후킹할 것이다.  이 경우에, 우리는 잘 알려진 응용 프로그램인  NOTEPAD.EXE를 우리의 예제에서 사용하도록 하겠다.


쓰레드 모델을 변경하도록 한다.

[STAThread] -> [MTAThread]


네임스페이스를 Include하도록 한다.

using Nektra.Deviare2;


임의의 Deviare 라이브러리 메서드를 하기 전에, 반드시 NktSpyMgr class를 초기화해야 한다. 이것은 Form constructor내에서 이뤄진다.:


_spyMgr = new NktSpyMgr();

_spyMgr.Initialize();

후킹하고자 하는 타겟 프로세스를 가져 오도록 한다.

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;

}

이제, 당신은 프로세스를 찾기 위해서 다음을 호출할 수 있다. (우리 예제에서는  Deviare 에게 어느 프로세스를 후킹할지를 얘기해 주어야 한다.).

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

 Form load 이벤트 핸들러에서, 우리는 신규 훅 오브젝트를 생성한다. 


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

우리의 핸들러는 후킹된 함수 실행 전에 불리어 진다는 것을 명심하라.  다른 옵션은  flgOnlyPostCall인데, 우리의 후킹된 함수가 종료된 후에 불리게 된다. 리턴 되자 마자(right after return), 리턴값을 살펴 볼 수 있다.


이 시스템 호출이 타겟 프로세스의 컨텍스트에서 실행되면, 우리는 그것을 우리의 이벤트 핸들러에서 잡을 수 있다.

_spyMgr.OnFunctionCalled += new DNktSpyMgrEvents_OnFunctionCalledEventHandler(OnFunctionCalled);

이제, Hook::Hook() 호출은 구성된 프로퍼티하에서  명세된 함수를 가로채는 메커니즘을 초기화한다.

hook.Hook(true);

hook.Attach(_process, true);

우리는 우리가 어태치할 프로세스를 명세해야 한다. 프로세스(_process )는 위에서 언급한 GetProcess 함수에서  리턴된다. 그리고, 우리의 예제 내에서 NOTEPAD 프로세스에 관한 정보를 포함한다.

결국 우리는 그 호출을 프로세스하기 위해 OnFunctionCalled 이벤트를 사용한다. 이 경우에 우리는 CreateFileW의 정보를 살펴볼 것이다.:

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

);

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.