티스토리 뷰
[미완성]
튜토리얼 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.
'프로그래밍Tip' 카테고리의 다른 글
[tools] Strike/Counter-Strike RE(Reverse Engineering) (0) | 2014.11.27 |
---|---|
[Util] ELF Encryption (0) | 2014.11.27 |
WinMain vs main 그리고 유니코드(w~) (0) | 2013.07.10 |
Mongoose - 임베디드 웹서버 (0) | 2013.07.10 |
프로그래밍 하다 참조하기에 용이한 예제만 모은 사이트... (0) | 2013.07.09 |