垃圾堆——windowssky
人生就象一场旅行 不必在乎目的地 在乎的是沿途的风景以及看风景的心情
<2009年7月>
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678

留言簿(7)

随笔分类

随笔档案

文章档案

相册

NDIS IFS

内核研究

基础知识

安全技术

操作系统

病毒技术

网络技术

逆向工程

驱动开发

搜索

最新评论

阅读排行榜

评论排行榜

 
VC知识库BLOG   首页  新随笔  联系  聚合  登录 
  随笔-21 文章-0 评论-33 Trackbacks-0
2008年6月19日


Guest Article - Introduction to Registry Filtering in Vista (Part I)

(By: The NT Insider, Vol 15, Issue 1, March- April 2008 | Published: 28-May-08| Modified: 28-May-08)

By Jerry Kelley

The Registry Filtering Model (RFM) provides the developer with a framework to develop registry filters quickly and safely. Before the RFM, filter developers had to resort to potentially unsafe hacks to hook the registry service calls. With the RFM, a skeleton filter can be assembled in short order by anyone with any familiarity of filtering. This is the first of two articles which will present a whirlwind tour of the RFM in Vista. In this article, I'll show you how to put together a simple filter from which you can build upon to add functionality to suit your needs.

What can it do for me?
Let's get started by going over what the RFM actually does for the developer. It provides a framework whereby a driver can be developed that provides callbacks for registry IO that it's interested in. These callbacks are registered with the RFM and are called whenever specific registry IO is processed. All of the RFM interfaces that the driver uses are provided by the Configuration Manager (CM). In addition to managing the callbacks, the RFM also provides for three types of contexts that a driver can use to associate private information with an object or operation. Those who know the file system Filter Manager should already be acquainted with these features. The Filter Manager does provide a broader API than the RFM but, to be fair, it's been out there longer and file system filters are among the hardest drivers to develop so a lot of effort has gone into the Filter Manager to ease their burden.

Not exactly the new kid in town
You may have noticed that the RFM has been around since XP. The API was very limited in XP and expanded in Server 2003. The biggest change in Server 2003 was that the RFM added support for pre and post operation callbacks meaning the driver gets a callback before and after the CM processes an operation. (In XP, only a single operation callback is available which just announces the IO). Vista added callbacks for key flush, load, and unload as well as key security query and set.

Altitude is everything
Registry filters are assigned altitudes by the type of application they perform. An altitude arranges the layering of filters so that no matter when they load, they'll load within a pre-determined level based on their function. The altitude categories are the same as those for the file system minifilters and vendors who already have altitudes for their minifilters will use them for their registry filters as well. Altitudes can be requested from Microsoft via the WHDC Minifilter Altitude Allocation Web site.

Callbacks, of course
Before we discuss registering with CM for callbacks, let's take a look at the format of the callback routines. All of the callbacks are of the PEX_CALLBACK_FUNCTION type definition in WDM.H. The three input values represent, respectively, the registration context, the operation type, and a pointer to the operation's information structure. The PEX_CALLBACK_FUNCTION is shown in Figure 1.

typedef NTSTATUS (*PEX_CALLBACK_FUNCTION)(

       IN PVOID CallbackContext,

       IN PVOID Argument1,

       IN PVOID Argument2

       );

Figure 1 - Callback Routine Template

The callback context is really a registration context created when a filter registers with CM (This will be discussed further when we talk about registration). When a filter registers at an altitude it can set a private context that the filter can use to identify a specific registration. Argument1 is not really a pointer but an enumeration value of the REG_NOTIFY_CLASS type. This value indicates the specific registry operation. Sample enumeration values include RegNtPreDeleteKey, RegNtPostDeleteKey, RegNtPreCreateKeyEx, and RegNtPostCreateKeyEx. Be sure to treat it as an enumeration and not a pointer since it'll never hold a valid pointer value.

Argument2 is a pointer to an operation-specific structure. There are distinct pre-op and post-op structures that your filter will have to deal with. The pre-op structure depends on the operation. As an example, consider pre-create (CreateKeyEx), Argument2 is a REG_CREATE_KEY_INFORMATION pointer (See Figure 2).

typedef struct _REG_CREATE_KEY_INFORMATION {

       PUNICODE_STRING CompleteName;

       PVOID RootObject;

       PVOID ObjectType;

       ULONG CreateOptions;

       PUNICODE_STRING Class;

       PVOID SecurityDescriptor;

       PVOID SecurityQualityOfService;

       ACCESS_MASK DesiredAccess;

       ACCESS_MASK GrantedAccess;

       PULONG Disposition;

       PVOID *ResultObject;

       PVOID CallContext;

       PVOID Reserved;

} REG_CREATE_KEY_INFORMATION, REG_OPEN_KEY_INFORMATION,

*PREG_CREATE_KEY_INFORMATION, *PREG_OPEN_KEY_INFORMATION;

Figure 2 - REG_CREATE_KEY_INFORMATION Structure


In the pre-query key callback Argument2 provides a pointer to a REG_QUERY_KEY_INFORMATION structure (See Figure 3).

typedef struct _REG_QUERY_KEY_INFORMATION {

  PVOID Object;

  KEY_INFORMATION_CLASS KeyInformationClass;

  PVOID KeyInformation;

  ULONG Length;

  PULONG ResultLength;

  PVOID CallContext;

  PVOID ObjectContext;

  PVOID Reserved;

} REG_QUERY_KEY_INFORMATION, *PREG_QUERY_KEY_INFORMATION;

Figure 3 - REG_QUERY_KEY_INFORMATION Structure

You'll notice that REG_OPEN_KEY_INFORMATION is the same as REG_CREATE_KEY_INFORMATION. The pre-op structure for create-key and open-key are overloaded but the pre-op structures for the rest of the operations are not. These structures are defined in WDM.H and documented in the WDK documentation.

The pre-op structures all contain several common members that help the filter identify the target of the operation. Some contain only these values because nothing else is needed. Take delete-key for example. In Figure 4, the REG_DELETE_KEY _INFORMATION structure has only three members (ignoring Reserved) and this is all that's required to define the key to be deleted.

typedef struct _REG_DELETE_KEY_INFORMATION {

       PVOID Object;

       PVOID CallContext;

       PVOID ObjectContext;

       PVOID Reserved;

} REG_DELETE_KEY_INFORMATION, *PREG_DELETE_KEY_INFORMATION;

Figure 4 - REG_DELETE_KEY_INFORMATION Structure

The first three parameters are common to all pre-op structures except for create and open. To be exact, CallContext is used in all structures including create and open but Object does not exist until the create (or open) completes and ObjectContext is not valid unless it has been set on the registry object. Other pre-op structures may have more members but they all will have at least these three.

All post-op routines are passed a pointer to a REG_POST_OPERATION_INFORMATION structure providing common completion information regardless of the operation type. Figure 5 contains the definition of the REG_POST_OPERATION_INFORMATION structure.

typedef struct _REG_POST_OPERATION_INFORMATION {

       PVOID Object;

       NTSTATUS Status;

       PVOID PreInformation;

       NTSTATUS ReturnStatus;

       PVOID CallContext;

       PVOID ObjectContext;

       PVOID Reserved;

} REG_POST_OPERATION_INFORMATION,*PREG_POST_OPERATION_INFORMATION;

Figure 5 - REG_POST_OPERATION_INFORMATION Structure

Notice the three primary fields discussed previously - Object, CallContext, and ObjectContext. These give us the registry object and context information we need to properly identify the operation target. The Status field is the actual status from CM for the operation while ReturnStatus is what will be returned to the caller. Because a filter may choose to return a status other than Status the ReturnStatus member is where the filter can set the status it wants returned to the caller. This is covered in the section on completion processing. The PreInformation member is a pointer to the pre-op structure and provides the post-op routine with the original values defining the operation.

There's one more callback to cover, the context cleanup callback. This callback must be provided if your filter sets contexts on registry objects. This callback is executed when a registry object's handle is closed or the filter unregisters. In the unregister case, the filter will get a callback for every outstanding registry context assignment. This callback is where the filter releases any registry context resources. It is a PEX_CALLBACK_FUNCTION just like the rest of the other callbacks.

Callback templates
The callbacks have been discussed as far as their function prototype and structures passed so now we'll look at a template for a pre-op callback and then a post-op callback. For these examples I'm using CreateKeyEx but the template applies to any operation. The pre-op template shown in Figure 6 breaks down to enclosing the buffer access in a try/except block, casting Argument2 to a REG_CREATE_KEY_INFORMATION pointer, processing it and returning.

NTSTATUS PreCreateKeyExCallback(

  IN PVOID CallbackContext,

  IN PVOID Argument1,

  IN PVOID Argument2

  )

{

       __try

       {

              PREG_CREATE_KEY_INFORMATION pPreInfo =

                     (PREG_CREATE_KEY_INFORMATION)pInfo;

 

              //

              // perform pre-op processing

              //

       }

       __except(EXCEPTION_EXECUTE_HANDLER)

       {

              NTSTATUS Status = GetExceptionCode();

              //

              // handle the exception as required

              //

       }

 

       return STATUS_SUCCESS;

}

Figure 6 - Pre-Op Template

The post-op template in Figure 7 is similar to the pre-op template but adds an extra step where a pointer to the pre-op structure is extracted to a local as a shortcut. The processing is left to the filter implementation.

NTSTATUS PostCreateKeyExCallback(

  IN PVOID CallbackContext,

  IN PVOID Argument1,

  IN PVOID Argument2

  )

{

       NTSTATUS Status = STATUS_SUCCESS;

 

       __try

       {

              PREG_POST_OPERATION_INFORMATION pPostInfo =

                     (PREG_POST_OPERATION_INFORMATION)pInfo;

 

              PREG_CREATE_KEY_INFORMATION pPreInfo =

                     (PREG_CREATE_KEY_INFORMATION)pPostInfo->PreInformation;

 

              //

              // perform post-op processing

              //

       }

       __except(EXCEPTION_EXECUTE_HANDLER)

       {

              Status = GetExceptionCode();

              //

              // handle the exception as required

              //

       }

 

       return STATUS_SUCCESS;

}

Figure 7 - Post-Op Template

The pre and post-op callbacks have to be careful when using the buffer pointer in Argument2 since this can be a user-space pointer. With that in mind, all access to the buffer must take place in a try/except block. The processing depends on the filter's application, so just remember to use the Argument2 pointer safely.

The power of the registry IO handler
Now that we've looked at the callback templates and the structures used, we can take that a step further and talk about what a registry filter can do - or to be more precise - what a registry IO handler can do. In this case, the term handler refers to the pre-op and post-op callback pair. A handler considers the pre and post routines as a unit which processes a single registry IO operation. With that model in mind, we can quickly describe the primary functions of a registry filter; monitoring, blocking, and modifying.

The monitoring filter is passive in that it does not interfere with any of the registry IO other than to "sniff" it and possibly record it for an application. This is a common use of registry filtering and is found in many applications. The blocking filter uses criteria to determine if registry IO should be allowed. In this type of filter, the pre-op routine does the vast majority of the work since the idea is to stop IO before it can be passed on to CM. The criteria could be user, process, operation type, and so on. The final type of filter considered is one that modifies operations. There can be modifications made to the target of the operation or to the data itself. A typical modification is to the target in order to redirect the operation somewhere else in the registry. Another example is modifying the data of a value to encrypt or decrypt it.

What the Ex?
CreateKeyEx (and OpenKey by reference) were used in previous examples and we are discussing the Vista RFM implementation but let's diverge just for a moment to see just how different the "Ex" versions are from their predecessors. The "Ex" versions appeared in Server 2003 and replaced the "non-Ex" versions in XP. These "Ex" versions live up to their name by adding a wealth of new parameters describing the create or open. We've seen the REG_CREATE_KEY_I NFORMATION structure already; now let's look back at the REG_PRE_CREATE_KEY_INFORMATION used by CreateKey in Figure 8.

typedef struct _REG_PRE_CREATE_KEY_INFORMATION {

       PUNICODE_STRING CompleteName;

} REG_PRE_CREATE_KEY_INFORMATION, *PREG_PRE_CREATE_KEY_INFORMATION;

Figure 8 - REG_PRE_CREATE_KEY_INFORMATION Structure

It's quite a bit thinner than REG_CREATE_KEY_ INFORMATION and the same is true for the structure used by OpenKey when compared to OpenKeyEx. The other thing about these older routines is that they have their own post-op structures. Figure 9 shows the REG_POST_CREATE_KEY_ INFORMATION structure.

typedef struct _REG_POST_CREATE_KEY_INFORMATION {

       PUNICODE_STRING CompleteName;

       PVOID Object;

       NTSTATUS Status;

} REG_POST_CREATE_KEY_INFORMATION, *PREG_POST_CREATE_KEY_INFORMATION;

Figure 9 - _POST_CREATE_KEY_INFORMATION Structure

Compare REG_POST_CREATE_KEY_INFORMATION with REG_POST_OPERATION_INFORMATION and you'll again see that the "Ex" API's provide much more information to the filter.

Be sure to get registered
Once your callbacks have been defined you're almost ready to register with CM to start receiving notifications. The registration routine takes a PEX_CALLBACK_ FUNCTION that it calls for all registry IO sent to your filter. This callback is the central point from which you'll pass on the calls to your IO callbacks which we have discussed so far. Using a single dispatch callback is relatively easy to implement. To begin with, define an array of PEX_CALLBACK_ FUNCTION entries that'll contain pointers to each operation callback you have implemented. The array definition is shown in Figure 10.

PEX_CALLBACK_FUNCTION g_pCallbackFcns[MaxRegNtNotifyClass];

Figure 10 - Defining the Callback Array

Initialize the array such that each callback pointer is located based on its REG_NOTIFY_CLASS enumeration value. Figure 11 shows the initialization for a filter that only monitors creates, opens, and deletes. Your filter can use whatever combination of operation monitoring it needs for your application.

memset(g_pCallbackFcns, 0, sizeof(g_pCallbackFcns));

g_pCallbackFcns[RegNtPreCreateKeyEx]   = PreCreateKeyExCallback;

g_pCallbackFcns[RegNtPostCreateKeyEx]  = PostCreateKeyExCallback;

g_pCallbackFcns[RegNtPreOpenKeyEx]     = PreOpenKeyEx;

g_pCallbackFcns[RegNtPostOpenKeyEx]    = PostOpenKeyEx;

g_pCallbackFcns[RegNtPreDeleteKey]     = PreDeleteKeyCallback;

g_pCallbackFcns[RegNtPostDeleteKey]    = PostDeleteKeyCallback;

 

Figure 11 - Initializing the Callback Array

With the array defined, the dispatch callback simply has to cast Argument1 to a USHORT and use it as an index into the array to obtain the callback pointer. Be sure to check for a NULL entry in the array before attempting to use the callback. Figure 12 shows how the dispatch callback could be implemented.

NTSTATUS DispatchCallback(

  IN PVOID CallbackContext,

  IN PVOID Argument1,

  IN PVOID Argument2

  )

{

       USHORT Class = (USHORT)Argument1;

 

       if (NULL == g_pCallbackFcns[Class])

       {

              return STATUS_SUCCESS;

       }

 

       return (*(g_pCallbackFcns[Class]))(CallbackContext, Argument1, Argument2);

}

Figure 12 - The Dispatch Callback Function

If you don't handle a specific type of IO (the array entry is NULL) you must return STATUS_SUCCESS. This is required because the operation must continue and should not be interrupted just because your filter doesn't care about a particular type of IO.

We're now ready to notify CM that we want to filter registry IO. You can register your filter whenever makes the most sense for your application. Many filters will register in DriverEntry while others will register later. When you register, it's for all registry IO since you provide a single callback to CM. Therefore, the callback array should have NULL entries for any IO class (operation) you don't care about. This is why the dispatch callback must check for NULL entries (besides being a prudent practice anyway).

In addition to a pointer to the dispatch callback, the registration API takes a string description of the altitude, a pointer to the filter's driver object, a pointer to a registration context for the filter, and a pointer to a variable to receive a special cookie identifying the registration. The string description of the altitude is simply a UNICODE_STRING of the altitude. The registration context is optional and opaque to CM. It is a means for the filter to associate information regarding a particular registration. This context is passed into each callback in the CallbackContext parameter. The cookie is a LARGE_INTEGER that is opaque to the filter and used by CM to identify a registration. It is required by certain CM routines and thus the filter must maintain this value. A typical usage of CmRegisterCallbackEx is shown in Figure 13.

RtlInitUnicodeString(&altitudeString, L"360055");

 

NTSTATUS Status = CmRegisterCallbackEx(

       DispatchCallback,

       (PCUNICODE_STRING)&altitudeString,

       pDriver,

       &g_RegistrationContext1,

       &g_RegistrationContext1.Cookie,

       Null

       );

Figure 13 - Calling CmRegisterCallbackEx

A filter can register at more than one altitude provided it has been assigned multiple altitudes. It's up to the filter designer to determine how to differentiate IO at each altitude. You can still use the same dispatch callback and callback array but specify a different registration context for each altitude registered. When any of the callbacks are called, the altitude is determined by the callback using the registration context. Of course, a filter could define a separate dispatch callback and set of callbacks for each altitude but, depending on the application, this may result in a lot of duplicate code and more code to be debugged and maintained.

When the filter no longer needs to filter registry IO it must unregister with CM. This would typically be done in the driver's unload routine but depending on the functionality of the filter it could occur anywhere. A filter unregisters by calling CmUnRegisterCallback. This routine takes only the cookie that the filter received when it registered with CM. Continuing our example from Figure 13; Figure 14 depicts unregistering the same filter.

NTSTATUS Status = CmUnRegisterCallback(g_RegistrationContext1.Cookie)

Figure 14 - Calling CmUnRegisterCallback

As covered so far, there are several types of contexts available to a filter. Contexts allow a driver to associate private information with a distinct object or operation that is typically managed by another subsystem such as a service in the operating system. This means, for example, a driver can allocate and initialize a private blob of information that it attaches to an object, like a registry object in this case, and (nearly) every time the driver gets the object from operating system the context comes with it. So why is this useful? Well, those of us who developed file system filters prior to the Filter Manager can attest to the magnitude of overhead necessary to, say, track all open file and stream objects. Once we had context support it was so much easier to just allocate our private contexts and attach them to file system objects. Anytime we processed IO we just had to retrieve our contexts and we had the information that we used to have to track ourselves in trees or lists.

There are three types of contexts available to a registry filter:

  • Registration
  • Registry object
  • Registry operation

When we looked at registration earlier you'll recall we could pass a pointer to our initialized registration context. A filter uses this context to store per-registration information. This context is passed as the first parameter to every callback. This gives all callbacks access to the filter's private registration context to use as determined by the filter.

A registry filter can set a context on a registry object with a call to CmSetCallbackObjectContext (See Figure 15 for the prototype). This is very useful because registry objects are the central focus of registry IO (surprise) and with a context on each object the filter can easily track per-object information and status. For example, a registry object context could store the root and path, create/open options and the process and thread that created/opened it. The context is typically set in the create/open post-op if the create/open was successful. However, CmSetCallbackObjectContext can be called anytime from post-create (or post-open) to prior to the handle close pre-op to set the context. Once the context has been successfully set on the object, the filter will receive a RegNtCallbackObjectContextCleanup notification when the object's handle has been closed or the filter has unregistered. Therefore, if you're going to use object contexts you must register a context cleanup callback that releases your resources for the context.

NTSTATUS CmSetCallbackObjectContext(

  IN OUT PVOID Object,

  IN PLARGE_INTEGER Cookie,

  IN PVOID NewContext,

  OUT OPTIONAL PVOID *OldContext

  );

Figure 15 - CmSetCallbackObjectContext Prototype

The filter can track the context but it is passed to every pre-op callback in the ObjectContext member of the REG_Xxx_KEY_INFORMATION structure. If a filter registers at more than one altitude, different contexts for the same registry object can be assigned for each registration by using the cookies to delineate the assignments. CmSetCallbackObjectContext also lets you replace a context on a registry object. If you call CmSetCallbackObjectContext and a context already exists, it will remove the old context, attach the new one, and return a pointer to the old one in OldContext. If OldContext comes back non-NULL the filter is responsible for cleaning it up.

The registry operation context covers the scope between the pre-op and the post-op callbacks. It is passed from the pre-op to the post-op of a single operation. It does not span across operations and is not propagated beyond the operation by CM. The filter uses this context to pass private information from a pre-op to a post-op routine. This means the filter will allocate a context in the pre-op and free it in the post-op. The context is set in the CallContext member of REG_Xxx_KEY_ INFORMATION in the pre-op and retrieved from the CallContext member of REG_POST_OPERATION_ INFORMATION in the post-op.

Modification of Registry IO Calls
We're almost done with this discussion of registry filters but we have an important subject left to cover: modifying registry IO calls. There are generally four distinct areas of modifications and they can be combined:

  • Input parameters
  • Output parameters
  • Return value
  • Completion processing

The filter's pre-op callback can modify input parameters in the REG_Xxx_KEY_INFORMATION structure and pass the new value(s) on. Likewise, the post-op callback can alter values in REG_POST_OPERATION_INFORMATION and return the modified information (Think of redirection as an example). In the case where the filter's post-op wants to change the return value to the caller, it sets the ReturnStatus member of REG_POST_OPERATION_INFORMATION with the status value that it wants the caller to receive and returns STATUS_CALLBACK_BYPASS. This halts further processing on the operation and immediately returns ReturnStatus to the caller. The caveat here is that the filter is responsible for cleanup of any CM objects if the status is changed from a success to a failure. If it is changed from failure to success the filter will need to provide proper parameter values in REG_POST_OPERATION_ INFORMATION. If the filter wants to fail the operation, it sets ReturnStatus to the error status to be returned to the caller and returns an error status other than STATUS_CALLBACK_BYPASS (STATUS_CALLBACK_ BYPASS is defined as an error value by the way).

A filter can handle the operation completely and bypass the intended operation path. To do this, the pre-op callback processes the operation however it wants and returns STATUS_CALLBACK_BYPASS. The post-op callback is not called in this case because CM will immediately return STATUS_SUCCESS to the caller without calling any other registry filters or CM routines beyond what is necessary to complete the operation. The filter must set the corresponding REG_Xxx_KEY_INFORMATION structure with proper parameters to be returned to the caller.

More to Come
This article has covered a lot of material in a relatively short amount of space. The WDK provides fairly good coverage of this information but there are some crucial elements left out - I know because I had to fill in a lot of these holes to get my first registry filter working. I have tried to provide enough coverage for a developer to become familiar with the concepts and components of a registry filter. The next article will cover transactions and how to build a non-durable resource manager since the RFM doesn?t provide one (Your filter will need one to support transactions and there is no known public documentation on how to build one).

 

Jerry Kelley is a file system filter driver developer working on software virtualization systems for a major security software corporation in the U.S. He has twenty-five years of development experience including embedded systems with the last nine years in filter development. He can be reached at jerryjkelley@msn.com.

发表于 2008-06-19 23:42 垃圾一堆 阅读(1192) | 评论 (0)编辑 收藏
2008年6月1日
前段时间无聊时,简单逆向了XX游戏保护的驱动;本来不准备写这篇文章,但搜索资料看到类似代码,想做个简单的比较,纯属娱乐,各位看官见笑

一 xx游戏保护驱动的实现:

 TSDeviceControl()
  IOCTL_TS_INIT(初始化游戏保护器):
   1) Irp->AssociatedIrp.SystemBuffer输入参数格式
       struct{dword dwMajorVersion;
              dword dwMinorVersion;
              dword dwServicePackMajor;
              dword dwServicePackMinor;
              dword dwNum;     //需保护进程数目
              dword dwPID[32]; //需保护进程列表
              dword decode;    //密钥
              }InitParam;

    2)TSDecodeControlParam();//对输入参数进行解密


    3)TSInitGuardProcess();//初始化需保护的进程列表
      {
       if (InitParam.dwNum <= 0)
       {
          return;
       }
       
       for(int i=0; i<InitParam.dwNum && i<LIMIT_GUARD_PROCESS_NUM; i++)
       {
         if (PsLookupProcessByProcessId(InitParam.dwPID[i], g_GuardEProcess[g_GuardEProcessNum]) == STATUS_SUCCESS)
         {
              g_GuardEProcessNum++;
         }
       }
     }
     #define  LIMIT_GUARD_PROCESS_NUM 32
     dword    g_GuardEProcessNum;
     EPROCESS g_GuardEProcess[LIMIT_GUARD_PROCESS_NUM];
 
   
    4)TSInitINTNumber();
      (1)初始化GDI(win32k.sys)中要detour的服务中断号
      (2)动态加载PsGetProcessImageFileName


    5)TSSetKernelHook();
      (1)动态加载ObOpenObjectByPointer和NtOpenProcess

      (2)查找NtOpenProcess中调用ObOpenObjectByPointer的地址(内置了一个小的反汇编引擎)
         mov     ecx, g_NtOpenProcess     ; 
         cmp     al, 0E8h                 ; is relative call
         jnz     short Loc_NoRelativeCall ;
         mov     eax, [ecx+1]             ;
         mov     g_CallObOpenObjectByPointerAddr, eax ;
         lea     eax, [eax+ecx+5];                    ; 相对转跳的位置
         cmp     eax, g_ObOpenObjectByPointer         ; 是否是ObOpenObjectByPointer的地址

         如果成功找到;detour后NtOpenProcess的实现就是:
         NTSTATUS NtOpenProcess(...)
         {
          ...
          call NewObOpenObjectByPointer();
          test eax, eax
          jz   Loc_exit;
          ...
         }

        
       (3)如果上述任何(1)/(2)步骤失败:
          (a)从ntoskrnel.exe文件中读取KeServiceDescriptorTable的service列表(服务的地址)

          (b)根据不同版本的os版本,获得NtReadVirtualMemory/NtWriteVirtualMemory的服务中断号

          (c)并分析出NtReadVirtualMemory/NtWriteVirtualMemory的地址([文件偏移->内存偏移]+ntoskrnel baseAddr)

           上述步骤成功;detour NtReadVirtualMemory/NtWriteVirtualMemorydetour后的实现就是:
           NTSTATUS NtReadVirtualMemory/NtWriteVirtualMemorydetour()
           {
            mov eax, NewFunAddr;
            jmp eax;
            .....
           }
         
   6)TSSetGDIHook()
      (1)查找KeServiceDescriptorTableShadow的地址(KeAddSystemServiceTable和KeServiceDescriptorTable匹配法)
         代码到处都有就不贴了
        (PS 方法一:GUI KTHREAD.ServiceTable; 方法二:上下空间内搜索-2k上xp下; 方法三:KeAddSystemServiceTable法)
         
      (2)根据TSInitINTNumber()获得要detour的GDI(win32k.sys中)函数地址
         (1) NtUserGetDCIndex
         (2) NtUserGetDCExIndex
         (3) NtUserBuildHwndListIndex
         (4) NtUserFindWindowExIndex
         (5) NtUserGetForegroundWindowIndex
         (6) NtUserWindowFromPointIndex
         (7) NtUserQueryWindow(此函数不detour,只为了在NewNtUserxxx中使用)

      (3)ssdt hook 上述GDI(win32k.sys中)函数

   7)附加一些保护和检查机制就不说了
     TSAntiWindbg()


二 Cheat Engine
   困了,明天再说~
  
发表于 2008-06-01 04:18 垃圾一堆 阅读(3648) | 评论 (7)编辑 收藏
2007年11月23日


卡巴主动防御中检测隐藏进程的方法:
    Hook掉系统的SwapContext,这种方法是在2003年被提出的;这个函数被KiSwapThread调用,负责线程调度;下面这部分代码就是卡巴Detour SwapContext的汇编代码的分析(后面附上对应的c代码):

(PS:突破这种方式来隐藏进程的方法也在随后就出来了,自己实现线程调度,详见:http://hi-tech.nsys.by/33/)

一:KlifSetSwapContextHook

.text:0002DE60 KlifSetSwapContextHook proc near      
.text:0002DE60
.text:0002DE60 var_5C          = dword ptr -5Ch
.text:0002DE60 var_58          = dword ptr -58h
.text:0002DE60 var_54          = dword ptr -54h
.text:0002DE60 var_50          = dword ptr -50h
.text:0002DE60 var_48          = dword ptr -48h
.text:0002DE60 var_38          = dword ptr -38h
.text:0002DE60 var_34          = dword ptr -34h
.text:0002DE60 var_30          = word ptr -30h
.text:0002DE60 var_24          = dword ptr -24h
.text:0002DE60 var_20          = dword ptr -20h
.text:0002DE60 var_1C          = dword ptr -1Ch
.text:0002DE60 var_18          = dword ptr -18h
.text:0002DE60 var_10          = dword ptr -10h
.text:0002DE60 var_4           = dword ptr -4
.text:0002DE60
.text:0002DE60                 push    ebp
.text:0002DE61                 mov     ebp, esp
.text:0002DE63                 push    0FFFFFFFFh
.text:0002DE65                 push    offset dword_13158
.text:0002DE6A                 push    offset sub_2E46C
.text:0002DE6F                 mov     eax, large fs:0
.text:0002DE75                 push    eax
.text:0002DE76                 mov     large fs:0, esp
.text:0002DE7D                 sub     esp, 4Ch
.text:0002DE80                 push    ebx
.text:0002DE81                 push    esi
.text:0002DE82                 push    edi
.text:0002DE83                 mov     [ebp+var_18], esp
.text:0002DE86                 mov     ecx, g_NtoskrnlAddr
.text:0002DE8C                 cmp     word ptr [ecx], 5A4Dh ; Ms dos stub - MZ
.text:0002DE91                 jnz     Routine_Error
.text:0002DE97                 mov     eax, [ecx+3Ch]  ; e_lfanew
.text:0002DE9A                 add     eax, ecx
.text:0002DE9C                 cmp     dword ptr [eax], 4550h ; PE
.text:0002DEA2                 jnz     Routine_Error
.text:0002DEA8                 xor     edx, edx
.text:0002DEAA                 mov     dx, [eax+14h]      ; SizeOfOptionalHeader
.text:0002DEAE                 lea     eax, [edx+eax+18h] ; 指向第一Section(.text)
.text:0002DEB2                 mov     esi, [eax+0Ch]
.text:0002DEB5                 add     esi, ecx           ;.text区的内存地址
.text:0002DEB7                 mov     eax, [eax+10h]     ;.text区的内存大小
.text:0002DEBA                 xor     ebx, ebx
.text:0002DEBC                 mov     [ebp+var_20], ebx
.text:0002DEBF                 mov     [ebp+var_4], ebx
.text:0002DEC2                 mov     ecx, ds:NtBuildNumber
.text:0002DEC8                 mov     cx, [ecx]
.text:0002DECB                 cmp     cx, 2600        ; 1575 - 2195 Win2K
.text:0002DECB                                         ; 2202 - 2600 WinXP
.text:0002DECB                                         ; 3501 - 3790 Win2003
.text:0002DED0                 jle     Is2kORxp
.text:0002DED6                 mov     [ebp+var_1C], 5
.text:0002DEDD                 cmp     cx, 3790
.text:0002DEE2                 jl      short Is2003Release
.text:0002DEE4                 mov     ecx, offset g_2003CharCode ; 2003的特征码
.text:0002DEE9                 mov     [ebp+var_58], ecx
.text:0002DEEC                 mov     edx, 9
.text:0002DEF1                 mov     [ebp+var_54], edx
.text:0002DEF4                 xor     edi, edi
.text:0002DEF6                 jmp     short Is2003
.text:0002DEF8
.text:0002DEF8 Is2003Release:
.text:0002DEF8                 cmp     cx, 3604
.text:0002DEFD                 jle     short Is2003Beta3
.text:0002DEFF                 mov     ecx, offset g_ReleaseCharCode ; 2003Beat3-Release的特征码
.text:0002DF04                 mov     [ebp+var_58], ecx
.text:0002DF07                 mov     edx, 8
.text:0002DF0C                 mov     [ebp+var_54], edx
.text:0002DF0F                 mov     edi, 0Ah
.text:0002DF14                 jmp     short Is2003
.text:0002DF16
.text:0002DF16 Is2003Beta3:
.text:0002DF16                 mov     ecx, offset g_Bete3CharCode ; 2003的Beta-Beta3特征码
.text:0002DF1B                 mov     [ebp+var_58], ecx
.text:0002DF1E                 mov     edx, 7
.text:0002DF23                 mov     [ebp+var_54], edx
.text:0002DF26                 mov     edi, 9
.text:0002DF2B
.text:0002DF2B Is2003:
.text:0002DF2B                 mov     [ebp+var_50], edi
.text:0002DF2E                 push    edx
.text:0002DF2F                 push    ecx
.text:0002DF30                 push    eax
.text:0002DF31                 push    esi
.text:0002DF32                 call    KilfGetAddrByCharCode ; arg_c 特征码的长度
.text:0002DF32                                               ; arg_8 特征码的地址
.text:0002DF32                                               ; arg_4 搜索的最大范围
.text:0002DF32                                               ; arg_0 搜索的起始地址
.text:0002DF37                 mov     [ebp+var_24], eax
.text:0002DF3A                 cmp     eax, 0FFFFFFFFh
.text:0002DF3D                 jz      Search_Failed
.text:0002DF43                 add     eax, edi
.text:0002DF45                 add     esi, eax
.text:0002DF47                 cmp     dword ptr [esi], 0FF1043FFh
.text:0002DF4D                 jnz     short Search_Failed
.text:0002DF4F                 mov     ebx, esi
.text:0002DF51                 mov     [ebp+var_20], ebx
.text:0002DF54                 mov     [ebp+var_4], 0FFFFFFFFh
.text:0002DF5B                 jmp     SetSwapContextHook
.text:0002DF60
.text:0002DF60 Is2kORxp:
.text:0002DF60                 push    5
.text:0002DF62                 push    offset g_2kCharCode ; 2k的特征码
.text:0002DF67                 push    eax
.text:0002DF68                 push    esi
.text:0002DF69                 call    KilfGetAddrByCharCode ; arg_c 特征码的长度
.text:0002DF69                                               ; arg_8 特征码的地址
.text:0002DF69                                               ; arg_4 搜索的最大范围
.text:0002DF69                                               ; arg_0 搜索的起始地址
.text:0002DF6E                 mov     [ebp+var_24], eax
.text:0002DF71                 cmp     eax, 0FFFFFFFFh
.text:0002DF74                 jz      short Search_Failed
.text:0002DF76                 mov     edx, ds:NtBuildNumber
.text:0002DF7C                 cmp     word ptr [edx], 2195
.text:0002DF81                 jle     short Is2k
.text:0002DF83
.text:0002DF83 Isxp:
.text:0002DF83                 add     esi, eax
.text:0002DF85                 mov     [ebp+var_5C], esi
.text:0002DF88                 mov     [ebp+var_1C], 7
.text:0002DF8F                 push    4
.text:0002DF91                 push    offset g_XpCharCode ; xp的特征码
.text:0002DF96                 push    100h
.text:0002DF9B                 push    esi
.text:0002DF9C                 call    KilfGetAddrByCharCode ; arg_c 特征码的长度
.text:0002DF9C                                               ; arg_8 特征码的地址
.text:0002DF9C                                               ; arg_4 搜索的最大范围
.text:0002DF9C                                               ; arg_0 搜索的起始地址
.text:0002DFA1                 mov     [ebp+var_24], eax
.text:0002DFA4                 cmp     eax, 0FFFFFFFFh
.text:0002DFA7                 jz      short Search_Failed
.text:0002DFA9                 lea     ebx, [esi+eax+2]
.text:0002DFAD                 mov     [ebp+var_20], ebx
.text:0002DFB0                 mov     [ebp+var_4], 0FFFFFFFFh
.text:0002DFB7                 jmp     short SetSwapContextHook
.text:0002DFB9
.text:0002DFB9 Is2k:
.text:0002DFB9                 lea     ebx, [eax+esi]
.text:0002DFBC                 mov     [ebp+var_20], ebx
.text:0002DFBF                 mov     [ebp+var_1C], 5
.text:0002DFC6
.text:0002DFC6 Search_Failed:
.text:0002DFC6                 mov     [ebp+var_4], 0FFFFFFFFh
.text:0002DFCD                 jmp     short SetSwapContextHook
.text:0002DFCF
.text:0002DFCF SEH_Routine:    
.text:0002DFCF                 mov     eax, 1
.text:0002DFD4                 retn
.text:0002DFD5
.text:0002DFD5 SEH_Routine2:      
.text:0002DFD5                 mov     esp, [ebp-18h]
.text:0002DFD8                 mov     dword ptr [ebp-4], 0FFFFFFFFh
.text:0002DFDF                 mov     ebx, [ebp-20h]
.text:0002DFE2
.text:0002DFE2 SetSwapContextHook:
.text:0002DFE2                 test    ebx, ebx
.text:0002DFE4                 jz      Routine_Error
.text:0002DFEA                 mov     eax, 90909090h
.text:0002DFEF                 mov     [ebp+var_38], eax
.text:0002DFF2                 mov     [ebp+var_34], eax
.text:0002DFF5                 mov     [ebp+var_30], ax
.text:0002DFF9                 mov     byte ptr [ebp+var_38], 0E9h ; JMP
.text:0002DFFD                 mov     ecx, offset KlifSwapContext
.text:0002E002                 sub     ecx, ebx        ; EBX是SwapContext+offset的地址
.text:0002E004                 sub     ecx, 5
.text:0002E007                 mov     [ebp+var_38+1], ecx
.text:0002E00A
.text:0002E00A Klif_Store_SC_Addr:                     ; 保存原来的指令
.text:0002E00A                 mov     ecx, [ebp+var_1C]
.text:0002E00D                 lea     edx, [ecx+ebx]
.text:0002E010                 mov     g_SwapContextAddr, edx
.text:0002E016                 mov     esi, ebx
.text:0002E018                 mov     edi, offset g_SwapContextOpcode
.text:0002E01D                 mov     eax, ecx
.text:0002E01F                 shr