垃圾堆——windowssky
人生就象一场旅行 不必在乎目的地 在乎的是沿途的风景以及看风景的心情
<2012年5月>
293012345
6789101112
13141516171819
20212223242526
272829303112
3456789

留言簿(14)

随笔分类

随笔档案

文章档案

相册

NDIS IFS

内核研究

基础知识

安全技术

操作系统

病毒技术

网络技术

逆向工程

驱动开发

搜索

最新评论

阅读排行榜

评论排行榜

 
VC知识库BLOG   首页  新随笔  联系  聚合  登录 
  随笔-21 文章-0 评论-25 Trackbacks-1
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 垃圾一堆 阅读(2342) | 评论 (1)编辑 收藏
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 垃圾一堆 阅读(9782) | 评论 (16)编辑 收藏
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     ecx, 2
.text:0002E022                 rep movsd
.text:0002E024                 mov     ecx, eax
.text:0002E026                 and     ecx, 3
.text:0002E029                 rep movsb
.text:0002E02B                 lea     ecx, [ebp+var_48]
.text:0002E02E                 push    ecx
.text:0002E02F                 push    1
.text:0002E031                 push    ebx
.text:0002E032                 call    KlifSetInterruptStauts
.text:0002E037                 test    al, al
.text:0002E039                 jz      short Routine_Error
.text:0002E03B                 mov     ecx, offset g_kernelLock
.text:0002E040                 call    KlifClearInterruptAndLock
.text:0002E045
.text:0002E045 Klif_Set_SC_Hook:                         ; detour开始
.text:0002E045                 mov     ecx, [ebp+var_1C] ; 覆盖的Opcodes Num
.text:0002E048                 lea     esi, [ebp+var_38] ; Jmp Klif+0x????
.text:0002E04B                 mov     edi, ebx
.text:0002E04D                 mov     edx, ecx
.text:0002E04F                 shr     ecx, 2
.text:0002E052                 rep movsd
.text:0002E054                 mov     ecx, edx
.text:0002E056                 and     ecx, 3
.text:0002E059                 rep movsb
.text:0002E05B                 mov     edx, eax
.text:0002E05D                 mov     ecx, offset g_kernelLock
.text:0002E062                 call    KlifUnLock
.text:0002E067                 lea     eax, [ebp+var_48]
.text:0002E06A                 push    eax
.text:0002E06B                 mov     ecx, [ebp+var_48]
.text:0002E06E                 push    ecx
.text:0002E06F                 push    ebx
.text:0002E070                 call    KlifSetInterruptStauts
.text:0002E075                 mov     al, 1
.text:0002E077                 mov     ecx, [ebp+var_10]
.text:0002E07A                 mov     large fs:0, ecx
.text:0002E081                 pop     edi
.text:0002E082                 pop     esi
.text:0002E083                 pop     ebx
.text:0002E084                 mov     esp, ebp
.text:0002E086                 pop     ebp
.text:0002E087                 retn
.text:0002E088 Routine_Error:                         
.text:0002E088                 xor     al, al
.text:0002E08A                 mov     ecx, [ebp+var_10]
.text:0002E08D                 mov     large fs:0, ecx
.text:0002E094                 pop     edi
.text:0002E095                 pop     esi
.text:0002E096                 pop     ebx
.text:0002E097                 mov     esp, ebp
.text:0002E099                 pop     ebp
.text:0002E09A                 retn
.text:0002E09A KlifSetSwapContextHook endp


二:KlifSwapContext

.text:0002DE10 KlifSwapContext:                      
.text:0002DE10                 pushf
.text:0002DE11                 pusha
.text:0002DE12                 mov     ebp, esp
.text:0002DE14                 sub     esp, 10h
.text:0002DE17                 add     esi, dword_2FC90
.text:0002DE1D                 add     edi, dword_2FC90
.text:0002DE23                 mov     eax, [esi]      ; Address of next thread
.text:0002DE25                 mov     [ebp-8], eax
.text:0002DE28                 mov     eax, [edi]      ; Address of previous thread
.text:0002DE2A                 mov     [ebp-0Ch], eax
.text:0002DE2D                 mov     ecx, offset g_kernelLock
.text:0002DE32                 call    KlifClearInterruptAndLock
.text:0002DE37                 mov     [ebp-4], eax
.text:0002DE3A                 mov     eax, [ebp-8]
.text:0002DE3D                 push    eax             ; Address of next thread
.text:0002DE3E                 call    KlifSwapContextMain ;//MAIN!!!
.text:0002DE43                 mov     edx, [ebp-4]
.text:0002DE46                 mov     ecx, offset g_kernelLock
.text:0002DE4B                 call    KlifUnLock
.text:0002DE50                 add     esp, 10h
.text:0002DE53                 popa
.text:0002DE54                 popf
.text:0002DE55                 jmp     g_OldSwapContext
.text:0002DE5B                 align 10h


三:KlifSetSwapContextHook 对应C代码

PVOID g_NtoskrnlAddr;
char  g_2003CharCode[8]    = {0xFF, 0x43, 0x10, 0xFF, 0x33, 0x83, 0x7B, 0x08};
char  g_ReleaseCharCode[8] = {0x80, 0x7E, 0x5D, 0x00, 0x74, 0x04, 0xF3, 0x90};
char  g_Bete3CharCode[8]   = {0xF7, 0x46, 0x24, 0x01, 0x00, 0x00, 0x00, 0x00};
char  g_2kCharCode[8]      = {0x26, 0xC6, 0x46, 0x2D, 0x02, 0x00, 0x00, 0x00};
char  g_xpCharCode[4]      = {0x8B, 0x0B, 0x83, 0xBB};
int   g_SwapContextOpcode[4] = {0x90909090, 0x90909090, 0x9090FF25, g_SwapContextAddr};
int   g_SwapContextAddr;
char  g_kernelLock;

BOOL KlifSetSwapContextHook()
{
 METUEX ProtectMutex;        //这个不大准确
 int    nOffset          = 0;//特征码距Ntoskrnl的偏移移
 int    nReplaceNum      = 0;//要替换SwapContext几个字节
 char*  pSwapContextAddr = 0;//要替换的地址
 
 int   nCompareNum  = 0;
 char* pCompareCode = 0;
 
 PIMAGE_DOS_HEADER     pImageDos =  (PIMAGE_DOS_HEADER)g_NtoskrnlAddr;
 PIMAGE_NT_HEADERS     pImageNt  =  (PIMAGE_NT_HEADERS)(g_NtoskrnlAddr+pImageDos->e_lfanew);
 PIMAGE_SECTION_HEADER pImageSec =  (PIMAGE_SECTION_HEADER )((char*)pImageNt+pImageNt->FileHeader.SizeOfOptionalHeader);
 
 char* pSearchAddr = (char*)g_NtoskrnlAddr + pImageSec->VirtualAddress;
 int   nSearchArea = pImageSection->SizeOfRawData;
 
 if (*(WORD*)g_NtoskrnlAddr               == 0x5A4D &&
     *(WORD*)((char*)g_NtoskrnlAddr+0x3C) == 0x4550)
 {
  return FALSE;
 }
 
 //Windows 2003
 if (*NtBuildNumber > 2600)
 {
  nReplaceNum = 5;

  int nOrgOffset = 0;
  if (*NtBuildNumber >= 3790)
  {
   nCompareNum = 9;
   pCompareCode = g_2003CharCode;
   
  }else
  {
   if (*NtBuildNumber > 3604)
   {
    nOrgOffset   = 10;
    nCompareNum  = 8;
    pCompareCode = g_ReleaseCharCode;
   }else
   {
    nOrgOffset   = 9;
    nCompareNum  = 7;
    pCompareCode = g_Bete3CharCode;
     
   }
  }
  
  nOffset = KilfGetAddrByCharCode(nCompareNum, 
                                  pCompareCode,
                                  nSearchArea,
                                  pSearchAddr);
  if (nOffset == -1)
  {
   return FALSE;
  }
  
  pSwapContextAddr = pSearchAddr + nOffset + nOrgOffset;
  
  if (*((int*)pSwapContextAddr) != 0xFF1043FF)
  {
   return FALSE;
  }
  
 }else
 {
  //Win2K
  nOffset = KilfGetAddrByCharCode(5,
                                  g_2kCharCode,
                                  nSearchArea,
                                  pSearchAddr);
  if (nOffset == -1)
  {
   return FALSE;
  }
  
  //WinXP
  if (*NtBuildNumber > 2395)
  {
   nOffset = KilfGetAddrByCharCode(4,
                                   g_xpCharCode,
                                   nSearchArea,
                                   pSearchAddr);   
   if (nOffset == -1)
   {
    return FALSE;
   }
   nReplaceNum      = 7;
   pSwapContextAddr = pSearchAddr + nOffset + 2;

  }else
  {
   nReplaceNum      = 5;
   pSwapContextAddr = pSearchAddr + nOffset;
  }
 }

 if (!pSwapContextAddr)
 {
  return FALSE;
 }

 char NewSwapContext[9] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};

 NewSwapContext[0]           = 0xE9;
 *((int*)&NewSwapContext[1]) = (int)KlifSwapContext - (int)pSwapContextAddr - 5;


 //保存原来的指令
 g_SwapContextAddr = pSwapContextAddr + nReplaceNum;//跳回来的地址

 for(int i = nReplaceNum; i>0; i--)
 {
  g_SwapContextOpcode[i] = pSwapContextAddr[i];
 }
 

 if (!KlifSetInterruptStauts(pSwapContextAddr, 1, &ProtectMutex))
 {
  return FALSE;
 }

 KlifClearInterruptAndLock(g_kernelLock);

 //覆盖原来指令
 for(i = nReplaceNum; i>0; i--)
 {
  pSwapContextAddr[i] = NewSwapContext[i];
 }
 KlifUnLock(g_kernelLock);

 KlifSetInterruptStauts(pSwapContextAddr, ProtectMutex, &ProtectMutex);

 return TRUE;
}

阐述中存在错误,望指教,谢谢!转载请注明地址 :)

发表于 2007-11-23 18:37 垃圾一堆 阅读(6507) | 评论 (10)编辑 收藏
2007年9月25日

A FAT file system volume is composed of four basic regions
which are laid out in this order on the volume:

0 – Reserved Region
1 – FAT Region
2 – Root Directory Region (doesn’t exist on FAT32 volumes)
3 – File and Directory Data Region

DBR就是我们的Boot扇区:

DBR的格式:
 jmp  $+$BPB_FAT32_END;
 _emit NOP;

 BPB  label   byte
 ......
 BPB  end

 FAT32 label  byte
 .....
 FAT32 end
 
$BPB_FAT32_END:
 xor     ax, ax
 mov     ss, ax
 mov     sp, 7c00h
 ....
 检查int13/int13扩展中断是否可用
 读取BPB/FAT32信息
 通过int13/int13扩展中断读取ntldr文件描述信息
 再读取ntldr文件数据
 jmp ntldr-OEP

 org   510
 dw    55h,AAh

BPB/FAT32中的主要信息(本机):
1个簇有           16个扇区(8192个字节)
1个磁道有         63个扇区
保留扇区          32个扇区(for DBR)
FAT               10235个扇区


所以根目录起始扇区 10235*2+32 = 20502, 起始簇号 2

/* FAT12/16中 数据区的开始簇号是2,
/* FAT32中的没有根目录的概念就在此,
/* 只是把根目录算做数据区了

/* int13中断只能读取到8.4GB的数据, 再大就
/* 读不到了,int13扩展中断就是解决这个问题


            [FAT 32 Byte Directory Entry Structure]
Name  Offset (byte) Size (bytes)   Description
DIR_Name         0         11          Short name.
DIR_Attr         11(0BH)   1           File attributes:
                                            ATTR_READ_ONLY  0x01
                                            ATTR_HIDDEN     0x02
                                            ATTR_SYSTEM     0x04
                                            ATTR_VOLUME_ID  0x08
                                            ATTR_DIRECTORY  0x10
                                            ATTR_ARCHIVE    0x20

                                            ATTR_LONG_NAME |ATTR_READ_ONLY | ATTR_HIDDEN |
                                            ATTR_SYSTEM | ATTR_VOLUME_ID The upper two bits
                                            of the attribute byte are reserved and should
                                            always be set to 0 when a file is created and
                                            never modified or looked at after that.
DIR_NTRes        12(0CH)    1           Reserved for use by Windows NT. Set value to 0 when
                                        a file is created and never modify or look at it
                                        after that.                                       
DIR_CrtTimeTenth 13(0DH)    1           Millisecond stamp at file creation time. This field 
                                        actually contains a count of tenths of a second.
                                        The granularity of the seconds part of DIR_CrtTime is 2 
                                        seconds so this field is a count of tenths of a second 
                                        and its valid value range is 0-199 inclusive.
DIR_CrtTime      14(0EH)    2           Time file was created.
DIR_CrtDate      16(10H)    2           Date file was created.
DIR_LstAccDate   18(12H)    2           Last access date. Note that there is no last access time,
                                        only a date. This is the date of last read or write.
                                        In the case of a write, this should be set to the same
                                        date as DIR_WrtDate.
DIR_FstClusHI    20(14H)    2           High word of this entry’s first cluster number
                                        (always 0 for a FAT12 or FAT16 volume).
DIR_WrtTime      22(16H)    2           Time of last write. Note that file creation is
                                        considered a write.
DIR_WrtDate      24(18H)    2           Date of last write. Note that file creation is
                                        considered a write.
DIR_FstClusLO    26(1AH)    2           Low word of this entry’s first cluster number.
DIR_FileSize     28(1CH)    4           32-bit DWORD holding this file’s size in bytes.

[公式:
     1 根目录的位置 = 保留扇区 + FAT表所占的扇区 * FAT表个数  (扇区)

     2 指定簇所在的位置 = 根目录所在扇区 + (簇号 - 根目录所在簇号)* 簇/扇数 (扇区)

     3 指定簇在FAT表中的位置 = 保留扇区 * 扇区/字节 + 簇号*(簇在FAT表中描述所占的字节数) (字节)
]


(下面表述中 簇号用10进制; 扇区号用10进制; 逻辑偏移用16进制; 工具WINHEX)

eg: 查找c:\windows\system32\ntoskrnl.exe的过程:

    1) 定位根目录的位置: FAT*2 + Reserved = 10235*2+32 = 20502(扇区)  ------ 0x000A02C00(逻辑偏移)
    2) 读根目录中的数据(INT13),找到windows目录的描述

       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       000A036A0   57 49 4E 44 4F 57 53 20  20 20 20 10 00 46 F7 B1   WINDOWS    ..F鞅
       000A036B0   21 37 2B 37 00 00 F8 B1  21 37 03 14 00 00 00 00   !7+7..!7......

       /* 对照上表,0BH位是0x10----是一个目录
       /* 我感兴趣的只有它的簇号(14H & 1AH):0x00001403


    3) 找到Windows目录中的目录项所在的第一个簇号: 0x00001403 = 5123(簇)
       对应扇区号: 20502 + (5123 - 2) * 16 = 102438(扇区)  ------ 0x003204C00(逻辑偏移)
       为啥要减2? 因为20502对应的2号簇!
     
       对应FAT表中的位置: Reserved*512 + 5123*4 = 0x0900C(逻辑偏移)
       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       000009000                                        64 46 00 00               dF..
       /* Windows目录表中的目录项所在的下个簇号: 0x00004664  = 18020(簇)
      

       下个簇号对应FAT表中的位置: Reserved*512 + 18020*4 = 0x015990(逻辑偏移)
       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       000015990   FF FF FF 0F                                        .
       /* 再下一下簇号是? 0x0FFFFFFF表示最后一个簇了

     
       /* 在上述两个簇中找System32的描述(运气真好,在第一簇中就找到了)
       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       003204C40   53 59 53 54 45 4D 33 32  20 20 20 10 08 49 F7 B1   SYSTEM32   ..I鞅
       003204C50   21 37 33 37 00 00 F8 B1  21 37 04 14 00 00 00 00   !737..!7......
       /* 对照上表,0BH位是0x10----是一个目录
       /* 我感兴趣的只有它的簇号(14H & 1AH): 0x00001404 = 5123(簇)


    4) 找到System32目录中的目录项所在第一个簇号: 0x00001404 = 5124(簇)
       对应扇区号: 20502 + (5124 - 2) * 16 = 102454(扇区)  ------ 0x003206C00(逻辑偏移)
     
       对应FAT表中的位置: Reserved*512 + 5124*4 = 0x09010(逻辑偏移)
       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       000009010   7A 46 00 00                                        zF..
       /* System32目录表中的目录项所在的下个簇号: 0x0000467A  = 18042(簇)
   
       在FAT表中继续找下去......
       System32目录表中的目录项所在的所有簇是:5124 18042 24684 47193 55087 62856 65459 54377 5884 5166
    
    
     5) 我们在18042簇中找到了Ntosrkrnl.exe的文件描述信息

       Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
       01F510700   4E 54 4F 53 4B 52 4E 4C  45 58 45 20 18 00 00 80   NTOSKRNLEXE ...€
       01F510710   9E 33 32 37 07 00 3C 00  61 36 5E 8F 00 96 20 00   ?27..<.a6^??.
       /* 对照上表,0BH位是0x20----是一个存档文件
       /* 我感兴趣的还有它的簇号(14H & 1AH):0x00078F5E


     6) 找到Ntoskrnl.exe文件数据所在的第一个簇号: 0x00078F5E = 495454(簇)
        对应扇区号: 20502 + (495454 - 2) * 16 = 7947734(扇区)  ------ 0x0F28BAC00(逻辑偏移)

        对应FAT表中的位置: Reserved*512 + 495454*4 = 0x1E7D78(逻辑偏移)
        Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
        0001E7D70                            5F 8F 07 00 60 8F 07 00           _?.`?.
        0001E7D80   61 8F 07 00 62 8F 07 00  63 8F 07 00 64 8F 07 00   a?.b?.c?.d?.
        /* Ntosrkrnl.exe文件数据所在的下个簇号: 0x00078F5F  = 495455(簇)
        在FAT表中继续找下去......
 
        我们来看一下Ntoskrnl.exe第一个簇中的数据:
        Offset       0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F
        0F28BAC00   4D 5A 90 00 03 00 00 00  04 00 00 00 FF FF 00 00   MZ?..........
        0F28BAC10   B8 00 00 00 00 00 00 00  40 00 00 00 00 00 00 00   ?......@.......
        0F28BAC20   00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00   ................
        0F28BAC30   00 00 00 00 00 00 00 00  00 00 00 00 D8 00 00 00   ............?..
        0F28BAC40   0E 1F BA 0E 00 B4 09 CD  21 B8 01 4C CD 21 54 68   ..?.???L?Th
        0F28BAC50   69 73 20 70 72 6F 67 72  61 6D 20 63 61 6E 6E 6F   is program canno
        0F28BAC60   74 20 62 65 20 72 75 6E  20 69 6E 20 44 4F 53 20   t be run in DOS
        0F28BAC70   6D 6F 64 65 2E 0D 0D 0A  24 00 00 00 00 00 00 00   mode....$.......
        0F28BAC80   FE 3C 69 99 BA 5D 07 CA  BA 5D 07 CA BA 5D 07 CA   ?i櫤].屎].屎].?

        0F28BAC90   79 52 5A CA BD 5D 07 CA  BA 5D 06 CA ED 5D 07 CA   yRZ式].屎].薯].?
        0F28BACA0   79 52 08 CA ED 5D 07 CA  79 52 59 CA BB 5D 07 CA   yR.薯].蕐RY驶].? 
        0F28BACB0   79 52 58 CA 3D 5F 07 CA  79 52 5B CA BB 5D 07 CA   yRX?_.蕐R[驶].?
 
      0F28BACC0   79 52 5D CA BB 5D 07 CA  52 69 63 68 BA 5D 07 CA   yR]驶].蔙ich篯.?
        0F28BACD0   00 00 00 00 00 00 00 00  50 45 00 00 4C 01 15 00   ........PE..L...

      7)  很熟悉吧? 以上六步所有数据结果都是通过计算器算出来了,不是把WINHEX中的东西直接贴出来的


      8)  现在知道为什么把ntldr放在系统根目录下的原因了吧?
          快速定位,要在512个字节(其实远小于这个啦 BPB&FAT描述信息&出错提示信息 也要占空间的)内搞定这些!!


(PS: 前段时间在网在看到一人说自己写了一个Rootkit,可以绕过所有anti-Rootkit软件的检测
     在别的系统下也看不到这个Rootkit, 只实现了FAT32系统下的, 估计就用到"隐藏扇区"的
     技术, 把此RootKit所在的簇在FAT表中的描述改为坏道——0x0FFFFFF7;  没拿到bin, 没
     有Disasm, 不敢断言)


参考资料   《FAT32规范》
          《细说IDE硬盘的容量限制》


洗澡睡觉~ windowssky by 20070920 02:17

 

发表于 2007-09-25 23:55 垃圾一堆 阅读(6181) | 评论 (59)编辑 收藏
2007年8月31日

 堆栈溢出在我们exploit时常用到,利用其一般方式是:JMP ESP 与 JMP EBX;
 昨天有一“同学”问我关于JMP EBX详细的问题,所以在此介绍一下:

1 JMP ESP

函数调用后的堆栈结构:

    L
    L -> child program of Local variable
    L -> 一般情况下此处保存的ebp
    R -> EIP for return parent funcion 
    P -> pass to child prgram variable
    P 
    P

exploit后的堆栈结构:

    N 
    N
    N -> NOP把原来的Local variable空间覆盖掉,导致它overflow
    R -> 系统dll中的Jmp esp的地址
    N -> NOP把原来pass过来的参数给覆盖掉 
    N
    N
    S -> Shellcode
    S
    S


解释:
  1) 函数返回时执行ret XXX指令; 要做的事: pop eip; add esp,XXX;
  2) 此时栈中的R已不是原来的返回地址了,而改成了系统dll中的Jmp esp的地址(eg:0x7FFA4512)
  3) ret指令执行完毕后,此时的EIP->Jmp esp的addr(eg:0x7FFA4512);ESP->我们的shellcode
  4) 程序继续执行,此时EIP里的内容是Jmp esp,系统执行Jmp esp,就跳到我们的ShellCode了

  注: 子过程是stdcall/pascal调用,才要用NOP把原来pass过来的参数给覆盖掉
      因为堆栈平衡在子过程中完成,通过ret XXX; 如果是cdecl或无参数就不要R下面的NOP了

 

2  JMP EBX

函数调用后的堆栈结构:

    L
    L  -> child program of Local variable
    L  -> 一般情况下此处保存的ebp
    R  -> EIP for return parent funcion 
    P  -> pass to child prgram variable
    P 
    P
    PL -> parent program of Local variable
    PL
    PL
    ...
    SEH.prev    ->前一个EXCEPTION_REGISTRATION结构
    SEH.handler ->异常处理例程入口


exploit后的堆栈结构:

    N
    N -> 
    N -> 
    N -> 
    N -> 
    N
    N
    N -> NOP把原来的栈上异常处理前的东西全覆盖掉,导致它overflow和Trap
    N
    N
    ...
    J -> 现在内容是0xeb 0x04 0x90 0x90(Jmp $+8);原来是前一个EXCEPTION_REGISTRATION结构
    E -> 系统dll中的Jmp ebx的地址
    S -> Shellcode
    S
    S


解释:
  1) 子过程返回,此时的返回地址已经改成Nop了,EIP->0x90909090;
  2) EIP所指向的地址不可访问,GP
  3) SEH被激活,但此时的处理地址是系统dll的Jmp ebx的地址
  4) SEH激活后EBX指向的是J的地址,就是前一个EXCEPTION结构的地址
  5) 程序继续执行Jmp ebx后,现在EIP指向J的地址
  6) 程序再继续执行Jmp$+8,到我们的shellcode了

  注: 关于SEH激活后EBX指向的是J的地址,我debug到ntdll中的KiRaiseUserExceptionDispatcher
      它会调用一个过程(0x77FACBB2),在这个过程中它主要处理了:
      a)它会调用一个过程(0x77FBB3BC)把SEH的最后一个EXCEPTION_REGISTRATION结构放到EBX中
      b)它会调用一个过程(0x77FBB23C)通过指针调用我们的SEH处理函数

      a)处对应代码解释
       77FACBC8    E8 EFE70000     CALL ntdll.77FBB3BC
       77FACBCD    8365 FC 00      AND DWORD PTR SS:[EBP-4],0
       77FACBD1    8BD8            MOV EBX,EAX
       
      
77FBB3BC    64:A1 00000000  MOV EAX,DWORD PTR FS:[0]
       77FBB3C2    C3              RETN

      b)处对应代码解释
      77FACC06    6A 10           PUSH 10
      77FACC08    53              PUSH EBX
      77FACC09    6A 00           PUSH 0
      77FACC0B    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
      77FACC0E    56              PUSH ESI
      77FACC0F    E8 2C7E0000     CALL ntdll.77FB4A40
      77FACC14    8945 08         MOV DWORD PTR SS:[EBP+8],EAX
      77FACC17    FF73 04         PUSH DWORD PTR DS:[EBX+4]; exploit.__except_handler3
      77FACC1A    8D45 F0         LEA EAX,DWORD PTR SS:[EBP-10]
      77FACC1D    50              PUSH EAX
      77FACC1E    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
      77FACC21    53              PUSH EBX
      77FACC22    56              PUSH ESI
      77FACC23    E8 14E60000     CALL ntdll.77FBB23C

      77FBB23C    BA 86B2FB77     MOV EDX,ntdll.77FBB286
      77FBB241    EB 09           JMP SHORT ntdll.77FBB24C
      77FBB243    90              NOP
      77FBB244    BA ADB2FB77     MOV EDX,ntdll.77FBB2AD
      77FBB249    8D49 00         LEA ECX,DWORD PTR DS:[ECX]
      77FBB24C    55              PUSH EBP
      77FBB24D    8BEC            MOV EBP,ESP
      77FBB24F    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
      77FBB252    52              PUSH EDX
      77FBB253    64:FF35 0000000>PUSH DWORD PTR FS:[0]
      77FBB25A    64:8925 0000000>MOV DWORD PTR FS:[0],ESP
      77FBB261    FF75 14         PUSH DWORD PTR SS:[EBP+14]
      77FBB264    FF75 10         PUSH DWORD PTR SS:[EBP+10]
      77FBB267    FF75 0C         PUSH DWORD PTR SS:[EBP+C]
      77FBB26A    FF75 08         PUSH DWORD PTR SS:[EBP+8]
      77FBB26D    8B4D 18         MOV ECX,DWORD PTR SS:[EBP+18]
      77FBB270    FFD1            CALL ECX ; exploit.__except_handler3
      77FBB272    64:8B25 0000000>MOV ESP,DWORD PTR FS:[0]
      77FBB279    64:8F05 0000000>POP DWORD PTR FS:[0]
      77FBB280    8BE5            MOV ESP,EBP
      77FBB282    5D              POP EBP
      77FBB283    C2 1400         RETN 14



 

Windows SEH的整个处理并不是想象的那么简单,涉及到KiTrap/KiUserTrapHandler

具体参见:
 
http://linux.insigma.com.cn/jszl.asp
 http://linux.insigma.com.cn/jszl.asp?docid=157542564

 

阐述中也许存在错误,望提醒和谅解,谢谢!

发表于 2007-08-31 12:59 垃圾一堆 阅读(5070) | 评论 (6)编辑 收藏
2007年8月23日


PART 1: Kernel Object

//每一位表示对应这个特权级的队列中是否有线程(主要在KiSwapThread用,详细代码见PART3)
ULONG KiReadySummary = 0 

 - Referenced by KeSetAffinityThread(),KiFindReadyThread(),KiReadyThread(),
   KiScanReadyQueues(),  KiSetPriorityThread(), and NtYieldExecution().

//
LIST_ENTRY  KeBugCheckCallbackListHead


//
LIST_ENTRY KiDispatcherReadyListHead[MAXIMUM_PRIORITY] 

  - Referenced by KeSetAffinityThread(), KiFindReadyThread(), KiInitSystem(),
    KiReadyThread(), KiScanReadyQueues(), KiSetPriorityThread(),
    and NtYieldExecution().


//
LIST_ENTRY KiProfileListHead 

  - Referenced by KeStartProfile(), and KiInitSystem().


LIST_ENTRY KiProfileSourceListHead 

  - Referenced by KeStartProfile(), KeStopProfile(), and KiInitSystem().


//
LIST_ENTRY KiProcessOutSwapListHead 

  - Referenced by KeDetachProcess(), KeSwapProcessOrStack(), KeTerminateThread(),
    KeUnstackDetachProcess(), KiInitSystem(), KiOutSwapKernelStacks(), and
    KiOutSwapProcesses().


LIST_ENTRY KiProcessInSwapListHead 

  - Referenced by KeSwapProcessOrStack(), KiAttachProcess(), KiInitSystem(), 
    KiInSwapProcesses(), KiOutSwapProcesses(), and KiReadyThread().

 

LIST_ENTRY KiStackInSwapListHead 

  - Referenced by KeSwapProcessOrStack(), KiInitSystem(), KiInSwapKernelStacks(),
    and KiReadyThread().


//
LIST_ENTRY KiTimerTableListHead[TIMER_TABLE_SIZE] 

  - Referenced by KeCheckForTimer(), KeSetSystemTime(), KiInitSystem(),
    KiInsertTimerTable(), KiTimerExpiration(), and VerifierKeInitializeTimerEx().

 

//
LIST_ENTRY KiWaitInListHead 

  - Referenced by KiInitSystem(), and KiOutSwapKernelStacks().


LIST_ENTRY KiWaitOutListHead 

  - Referenced by KiInitSystem(), and KiOutSwapKernelStacks().

 

 

PART 2: Kernel Object of KPROCESS

LIST_ENTRY _KPROCESS::ThreadListHead
 
  - Referenced by ExpGetProcessInformation(), KeDetachProcess(), KeFreezeAllThreads(), 
    KeTerminateThread(), KeThawAllThreads(), and KeUnstackDetachProcess().

 

LIST_ENTRY _KPROCESS::SwapListEntry 

  - Referenced by KeDetachProcess(), KeTerminateThread(), KeUnstackDetachProcess(),
    KiOutSwapKernelStacks(), KiOutSwapProcesses(), and KiReadyThread().

 

LIST_ENTRY _KPROCESS::ReadyListHead 

  - Referenced by KiInSwapProcesses(), KiOutSwapProcesses(), and KiReadyThread().

 


PART 3:

/*------------------------- MmInitSystem -------------------------

- MmInitSystem启动两个线程: KeBalanceSetManager 和 KeSwapProcessOrStack;

- 平衡集管理器(balance set manager)

- 交换管理器(KeSwapProcessOrStack)

- 其实它还启动了MiModifiedPageWriter(将某些页面置入pagefile中)

-----------------------------------------------------------------*/
01337         //
01338         // Start the modified page writer.
01339         //
01340
01341         InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
01342
01343         if (!NT_SUCCESS(PsCreateSystemThread(
01344                         &ThreadHandle,
01345                         THREAD_ALL_ACCESS,
01346                         &ObjectAttributes,
01347                         0L,
01348                         NULL,
01349                         MiModifiedPageWriter,
01350                         NULL
01351                         ))) {
01352             return FALSE;
01353         }
01354         ZwClose (ThreadHandle);
01355
01356         //
01357         // Start the balance set manager.
01358         //
01359         // The balance set manager performs stack swapping and working
01360         // set management and requires two threads.
01361         //
01362
01363         KeInitializeEvent (&MmWorkingSetManagerEvent,
01364                            SynchronizationEvent,
01365                            FALSE);
01366
01367         InitializeObjectAttributes( &ObjectAttributes, NULL, 0, NULL, NULL );
01368
01369         if (!NT_SUCCESS(PsCreateSystemThread(
01370                         &ThreadHandle,
01371                         THREAD_ALL_ACCESS,
01372                         &ObjectAttributes,
01373                         0L,
01374                         NULL,
01375                         KeBalanceSetManager,
01376                         NULL
01377                         ))) {
01378
01379             return FALSE;
01380         }
01381         ZwClose (ThreadHandle);
01382
01383         if (!NT_SUCCESS(PsCreateSystemThread(
01384                         &ThreadHandle,
01385                         THREAD_ALL_ACCESS,
01386                         &ObjectAttributes,
01387                         0L,
01388                         NULL,
01389                         KeSwapProcessOrStack,
01390                         NULL
01391                         ))) {
01392
01393             return FALSE;
01394         }

 

 

/*---------------------- KeBalanceSetManager ---------------------

-  KeBalanceSetManager也一直循环着并等待着一个MmWorkingSetManagerEvent事件
(当内存低时调整工作集的大小)和另一个定时器.

-  定时器事件处理程序周期性地将KiStackOutSwapRequest设置为TRUE,
并且触发KiSwapEvent信号通知KeSwapProcessOrStack线程, KeSwapProcessOrStack线程
不得不将长时间等待某个东西的线程的内核堆栈交换出去.

-  KeBalanceSetManager也调用KiScanReadyQueues
来提高在就绪队列中线程(KiDispatcherReadyListHead数组)的优先级.

-  对于每一个提高了优先级的线程, KiReadyThread将会被调用,
所以马上将PRCB.NextThread设置为提高了优先级的线程也是很有可能的
(KiReadyThread 会抢占原先的NextThread).

-----------------------------------------------------------------*/

00141 VOID
00142 KeBalanceSetManager (
00143     IN PVOID Context
00144     )
00145
00146 /*++
00147
00148 Routine Description:
00149
00150     This function is the startup code for the balance set manager. The
00151     balance set manager thread is created during system initialization
00152     and begins execution in this function.
00153
00154 Arguments:
00155
00156     Context - Supplies a pointer to an arbitrary data structure (NULL).
00157
00158 Return Value:
00159
00160     None.
00161
00162 --*/
00163
00164 {
00165
00166     LARGE_INTEGER DueTime;
00167     KTIMER PeriodTimer;
00168     KIRQL OldIrql;
00169     ULONG StackScanPeriod;
00170     ULONG ExecutionTimeLimitPeriod;
00171     NTSTATUS Status;
00172     KWAIT_BLOCK WaitBlockArray[MaximumObject];
00173     PVOID WaitObjects[MaximumObject];
00174
00175     //
00176     // Raise the thread priority to the lowest realtime level.
00177     //
00178
00179     KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY);
00180
00181     //
00182     // Initialize the periodic timer, set it to expire one period from
00183     // now, and set the stack scan period.
00184     //
00185
00186     KeInitializeTimer(&PeriodTimer);
00187     DueTime.QuadPart = - PERIODIC_INTERVAL;
00188     KeSetTimer(&PeriodTimer, DueTime, NULL);
00189     StackScanPeriod = STACK_SCAN_PERIOD;
00190     ExecutionTimeLimitPeriod = EXECUTION_TIME_LIMITS_PERIOD;
00191     //
00192     // Compute the stack protect time based on the system size.
00193     //
00194
00195     if (MmQuerySystemSize() == MmSmallSystem) {
00196         KiStackProtectTime = SMALL_SYSTEM_STACK_PROTECT_TIME;
00197
00198     } else {
00199         KiStackProtectTime = STACK_PROTECT_TIME;
00200     }
00201
00202     //
00203     // Initialize the wait objects array.
00204     //
00205
00206     WaitObjects[TimerExpiration] = (PVOID)&PeriodTimer;
00207     WaitObjects[WorkingSetManagerEvent] = (PVOID)&MmWorkingSetManagerEvent;
00208
00209     //
00210     // Loop forever processing balance set manager events.
00211     //
00212
00213     do {
00214
00215         //
00216         // Wait for a memory management memory low event, a swap event,
00217         // or the expiration of the period timout rate that the balance
00218         // set manager runs at.
00219         //
00220
00221         Status = KeWaitForMultipleObjects(MaximumObject,
00222                                           &WaitObjects[0],
00223                                           WaitAny,
00224                                           Executive,
00225                                           KernelMode,
00226                                           FALSE,
00227                                           NULL,
00228                                           &WaitBlockArray[0]);
00229
00230         //
00231         // Switch on the wait status.
00232         //
00233
00234         switch (Status) {
00235
00236             //
00237             // Periodic timer expiration.
00238             //
00239
00240         case TimerExpiration:
00241
00242             //
00243             // Attempt to initiate outswaping of kernel stacks.
00244             //
00245
00246             StackScanPeriod -= 1;
00247             if (StackScanPeriod == 0) {
00248                 StackScanPeriod = STACK_SCAN_PERIOD;
00249                 KiLockDispatcherDatabase(&OldIrql);
00250                 if (KiStackOutSwapRequest == FALSE) {
00251                     KiStackOutSwapRequest = TRUE;
00252                     KiUnlockDispatcherDatabase(OldIrql);
00253                     KeSetEvent(&KiSwapEvent, 0, FALSE);
00254
00255                 } else {
00256                     KiUnlockDispatcherDatabase(OldIrql);
00257                 }
00258             }
00259
00260             //
00261             // Adjust the depth of lookaside lists.
00262             //
00263
00264             ExAdjustLookasideDepth();
00265
00266             //
00267             // Scan ready queues and boost thread priorities as appropriate.
00268             //
00269
00270             KiScanReadyQueues();
00271
00272             //
00273             // Execute the virtual memory working set manager.
00274             //
00275
00276             MmWorkingSetManager();
00277
00278             //
00279             // Enforce execution time limits
00280             //
00281
00282             ExecutionTimeLimitPeriod -= 1;
00283             if (ExecutionTimeLimitPeriod == 0) {
00284                 ExecutionTimeLimitPeriod = EXECUTION_TIME_LIMITS_PERIOD;
00285                 PsEnforceExecutionTimeLimits();
00286                 }
00287
00288             //
00289             // Set the timer to expire at the next periodic interval.
00290             //
00291
00292             KeSetTimer(&PeriodTimer, DueTime, NULL);
00293             break;
00294
00295             //
00296             // Working set manager event.
00297             //
00298
00299         case WorkingSetManagerEvent:
00300
00301             //
00302             // Call the working set manager to trim working sets.
00303             //
00304
00305             MmWorkingSetManager();
00306             break;
00307
00308             //
00309             // Illegal return status.
00310             //
00311
00312         default:
00313             KdPrint(("BALMGR: Illegal wait status, %lx =\n", Status));
00314             break;
00315         }
00316
00317     } while (TRUE);
00318     return;
00319 }

 

 

/*---------------------- KeSwapProcessOrStack ---------------------

-  将内核堆栈交换出去(由BOOLEAN KiStackOutSwapRequest指定)
-  将进程交换出去 (需要交换出去的进程存放在KiProcessOutSwapListHead中)
-  将进程交换进来  (需要交换出去的进程存放在KiProcessInSwapListHead中)
-  将内核堆栈交换进来(需要交换进来的线程存放在KiStackInSwapListHead中).

-----------------------------------------------------------------*/

00321 VOID
00322 KeSwapProcessOrStack (
00323     IN PVOID Context
00324     )
00325
00326 /*++
00327
00328 Routine Description:
00329
00330     This thread controls the swapping of processes and kernel stacks. The
00331     order of evaluation is:
00332
00333         Outswap kernel stacks
00334         Outswap processes
00335         Inswap processes
00336         Inswap kernel stacks
00337
00338 Arguments:
00339
00340     Context - Supplies a pointer to the routine context - not used.
00341
00342 Return Value:
00343
00344     None.
00345
00346 --*/
00347
00348 {
00349
00350     KIRQL OldIrql;
00351     NTSTATUS Status;
00352
00353     //
00354     // Raise the thread priority to the lowest realtime level + 7 (i.e.,
00355     // priority 23).
00356     //
00357
00358     KeSetPriorityThread(KeGetCurrentThread(), LOW_REALTIME_PRIORITY + 7);
00359
00360     //
00361     // Loop for ever processing swap events.
00362     //
00363
00364     do {
00365
00366         //
00367         // Wait for a swap event to occur.
00368         //
00369
00370         Status = KeWaitForSingleObject(&KiSwapEvent,
00371                                        Executive,
00372                                        KernelMode,
00373                                        FALSE,
00374                                        NULL);
00375
00376         //
00377         // Raise IRQL to dispatcher level and lock dispatcher database.
00378         //
00379
00380         KiLockDispatcherDatabase(&OldIrql);
00381
00382         //
00383         // Loop until all of the four possible actions cannot be initiated.
00384         //
00385
00386         do {
00387
00388             //
00389             // If a request has been made to out swap kernel stacks, then
00390             // attempt to outswap kernel stacks. Otherwise, if the process
00391             // out swap list is not empty, then initiate process outswapping.
00392             // Otherwise, if the process inswap list is not empty, then start
00393             // process inswapping. Otherwise, if the kernal stack inswap list
00394             // is not active, then initiate kernel stack inswapping. Otherwise,
00395             // no work is available.
00396             //
00397
00398             if (KiStackOutSwapRequest != FALSE) {
00399                 KiStackOutSwapRequest = FALSE;
00400                 KiOutSwapKernelStacks(OldIrql);
00401                 continue;
00402
00403             } else if (IsListEmpty(&KiProcessOutSwapListHead) == FALSE) {
00404                 KiOutSwapProcesses(OldIrql);
00405                 continue;
00406
00407             } else if (IsListEmpty(&KiProcessInSwapListHead) == FALSE) {
00408                 KiInSwapProcesses(OldIrql);
00409                 continue;
00410
00411             } else if (IsListEmpty(&KiStackInSwapListHead) == FALSE) {
00412                 KiInSwapKernelStacks(OldIrql);
00413                 continue;
00414
00415             } else {
00416                 break;
00417             }
00418         } while (TRUE);
00419
00420         //
00421         // Unlock the dispatcher database and lower IRQL to its previous
00422         // value.
00423         //
00424
00425         KiUnlockDispatcherDatabase(OldIrql);
00426     } while (TRUE);
00427     return;
00428 }

 

 

/*------------------------ KiSwapThread -------------------------

-----------------------------------------------------------------*/
;++
;
; VOID
; KiSwapThread (
;    VOID
;    )
;
; Routine Description:
;
;    This routine is called to select the next thread to run on the
;    current processor and to perform a context switch to the thread.
;
; Arguments:
;
;    None.
;
; Return Value:
;
;    Wait completion status (eax).
;
;--

cPublicFastCall KiSwapThread, 0
.fpo (0, 0, 0, 4, 1, 0)

;
; N.B. The following registers MUST be saved such that ebp is saved last.
;      This is done so the debugger can find the saved ebp for a thread
;      that is not currently in the running state.
;

        sub     esp, 4*4
        mov     [esp+12], ebx           ; save registers
        mov     [esp+8], esi            ;
        mov     [esp+4], edi            ;
        mov     [esp+0], ebp            ;

        mov     ebx, PCR[PcSelfPcr]     ; get address of PCR
        mov     edx, [ebx].PcPrcbData.PbNextThread ; get next thread address
        or      edx, edx                ; check if next thread selected
        jnz     Swt140                  ; if nz, next thread selected

;
; Find the highest nibble in the ready summary that contains a set bit
; and left justify so the nibble is in bits <31:28>
;

        mov     ecx, 16                 ; set base bit number
        mov     edi, _KiReadySummary    ; get ready summary
        mov     esi, edi                ; copy ready summary
        shr     esi, 16                 ; isolate bits <31:16> of summary
        jnz     short Swt10             ; if nz, bits <31:16> are nonzero
        xor     ecx, ecx                ; set base bit number
        mov     esi, edi                ; set bits <15:0> of summary
Swt10:  shr     esi, 8                  ; isolate bits <15:8> of low bits
        jz      short Swt20             ; if z, bits <15:8> are zero
        add     ecx, 8                  ; add offset to nonzero byte
Swt20:  mov     esi, edi                ; isolate highest nonzero byte
        shr     esi, cl                 ;
        add     ecx, 3                  ; adjust to high bit of nibble
        cmp     esi, 10h                ; check if high nibble nonzero
        jb      short Swt30             ; if b, then high nibble is zero
        add     ecx, 4                  ; compute ready queue priority
Swt30:  mov     esi, ecx                ; left justify ready summary nibble
        not     ecx                     ;
        shl     edi, cl                 ;
        or      edi, edi                ;

;
; If the next bit is set in the ready summary, then scan the corresponding
; dispatcher ready queue.
;

Swt40:  js      short Swt60             ; if s, queue contains an entry
Swt50:  sub     esi, 1                  ; decrement ready queue priority
        shl     edi, 1                  ; position next ready summary bit
        jnz     short Swt40             ; if nz, more queues to scan


;
; If the next bit is set in the ready summary, then scan the corresponding
; dispatcher ready queue.
;

Swt40:  js      short Swt60             ; if s, queue contains an entry
Swt50:  sub     esi, 1                  ; decrement ready queue priority
        shl     edi, 1                  ; position next ready summary bit
        jnz     short Swt40             ; if nz, more queues to scan

发表于 2007-08-23 17:28 垃圾一堆 阅读(6234) | 评论 (12)编辑 收藏
2007年8月8日

注一:
File system recognizer 文件系统识别器(下文简称为recognizer)
File system            文件系统     (下文简称为fs)
File system filter     文件系统过滤器(下文简称为filter)

文件系统识别器是一个标准的NT内核模式驱动程序;它只实现一项功能:检查物理介质设备,如果它能够识别存储介质的格式便加载相应的文件系统驱动程序,利用它主要是为节约系统内存,文件系统驱动程序没用到时为什么让他在内存中呢?


               文件系统过滤驱动的一般处理流程(参照sfilter):

                     DriverEntry
                        ||
                        ||初始化dispatch表中的IRP_MJ_FILE_SYSTEM_CONTROL
                        ||routine
                        ||         
    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl ...
                        ||
                        ||注册一个SfFsNotification;当文件系统或文件系统识别器
                        ||registers or unregisters时被调用
                        ||
    IoRegisterFsRegistrationChange(DriverObject, SfFsNotification)
                        ||
                        ||
                        \/
     SfFsNotification(PDEVICE_OBJECT DeviceObject, BOOLEAN FsActive)
                        ||
                        || FsActive
        TRUE <----------\/------------> FALSE
         ||                              ||
         ||                              ||
IoAttachDeviceToDeviceStack         IoDetachDevice
         ||
         ||
 /-------\/------->Attach to (Filesystem)-----------------\IRP_MJ_FILE_SYSTEM_CONTROL
 |                                                        |
 |                                                        |MN:IRP_MN_MOUNT_VOLUME
 \---------------->Attach to (recognizer)                 |你就尽管Mount,只要fs
                        ||                                |处理状态为STATUS_SUCCESS;
                        ||MJ:IRP_MJ_FILE_SYSTEM_CONTROL   |fs中关于卷的Mount处理
                        \/                                |参见FatMountVolume函数
    SfFsControl (PDEVICE_OBJECT DeviceObject, PIRP Irp)   |
                        ||                                /\filter其他处理参见sfilter
                        ||MN:    
         /--------------\/-----------------------------\
         |                                             |
         |IRP_MN_MOUNT_VOLUME                          |
         |                                             |
 filter等待recognizer处理完成                          
         ||                                            |
         ||                                            |
   /-----\/-->STATUS_FS_DRIVER_REQUIRED(1)---|\        |
   |                                         ||       
   \-------->STATUS_UNRECOGNIZED_VOLUME(2)---||       
                                             ||        |
                                             ||        |
                                             \/        |
不论结果是哪一个,filter什么动作也没有;                 
但IO管理器收到recognizer处理结果后,处理各不相同:          
(1)表示recognizer认识这个卷,但对应的fs还没启动             |
(2)表示recognizer不认识这个卷,对应的fs不能处理            ||
 如果是第一种情况I/O管理器就会向这个设备发送---------------||IRP_MN_LOAD_FILE_SYSTEM
                                                      ||
                                                      ||原来我们一直挂接在recognizer上的
                                                      \/
                                                 IoDetachDevice(recognizer)
                                                      ||
                                                      ||
                                             wait recognizer load fs status
                                                      ||
                                                      ||
                              Other status<-----------\/----------->STATUS_SUCCESS
                                 ||                                     
                                 ||recognizer加载对应的fs失败
                                 ||只好还挂到recognizer上
                                 ||
                                 \/
                  IoAttachDeviceToDeviceStack(recognizer)

 

注二:IoRegisterFsRegistrationChange函数

在winxp,win2003下会对已加载的文件系统重新枚举一遍
而nt4.0和win2000下则不会,具体原因代码见...
(留意一下IopNotifyAlreadyRegisteredFileSystems的作用和实现)

//win2000
NTSTATUS
IoRegisterFsRegistrationChange(
    IN PDRIVER_OBJECT DriverObject,
    IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine
    )

/*++

Routine Description:

    This routine registers the specified driver's notification routine to be
    invoked whenever a file system registers or unregisters itself as an active
    file system in the system.

Arguments:

    DriverObject - Pointer to the driver object for the driver.

    DriverNotificationRoutine - Address of routine to invoke when a file system
        registers or unregisters itself.

Return Value:

    The return status is the final value of the function.

--*/

{
    PNOTIFICATION_PACKET nPacket;

    PAGED_CODE();

    //
    // Begin by attempting to allocate storage for the shutdown packet.  If
    // one cannot be allocated, simply return an appropriate error.
    //

    nPacket = ExAllocatePoolWithTag( PagedPool,
                                     sizeof( NOTIFICATION_PACKET ),
                                     'sFoI' );
    if (!nPacket) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Initialize the notification packet and insert it onto the tail of the
    // list.
    //

    nPacket->DriverObject = DriverObject;
    nPacket->NotificationRoutine = DriverNotificationRoutine;

    ExAcquireResourceExclusive( &IopDatabaseResource, TRUE );
    InsertTailList( &IopFsNotifyChangeQueueHead, &nPacket->ListEntry );
    ExReleaseResource( &IopDatabaseResource );

    //
    // Increment the number of reasons that this driver cannot be unloaded.
    //

    ObReferenceObject( DriverObject );

    return STATUS_SUCCESS;
}

 

//win2003
NTSTATUS
IoRegisterFsRegistrationChange(
    IN PDRIVER_OBJECT DriverObject,
    IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine
    )

/*++

Routine Description:

    This routine registers the specified driver's notification routine to be
    invoked whenever a file system registers or unregisters itself as an active
    file system in the system.

Arguments:

    DriverObject - Pointer to the driver object for the driver.

    DriverNotificationRoutine - Address of routine to invoke when a file system
        registers or unregisters itself.

Return Value:

    STATUS_DEVICE_ALREADY_ATTACHED -
                Indicates that the caller has already registered
                last with the same driver object & driver notification

    STATUS_INSUFFICIENT_RESOURCES
    STATUS_SUCCESS

--*/

{
    PNOTIFICATION_PACKET nPacket;

    PAGED_CODE();

    ExAcquireResourceExclusiveLite( &IopDatabaseResource, TRUE );

    if (!IsListEmpty( &IopFsNotifyChangeQueueHead )) {

        //
        // Retrieve entry at tail of list
        //

        nPacket = CONTAINING_RECORD( IopFsNotifyChangeQueueHead.Blink, NOTIFICATION_PACKET, ListEntry );

        if ((nPacket->DriverObject == DriverObject) &&
            (nPacket->NotificationRoutine == DriverNotificationRoutine)) {

            ExReleaseResourceLite( &IopDatabaseResource);
            return STATUS_DEVICE_ALREADY_ATTACHED;
        }
    }

    //
    // Begin by attempting to allocate storage for the shutdown packet.  If
    // one cannot be allocated, simply return an appropriate error.
    //

    nPacket = ExAllocatePoolWithTag( PagedPool|POOL_COLD_ALLOCATION,
                                     sizeof( NOTIFICATION_PACKET ),
                                     'sFoI' );
    if (!nPacket) {

        ExReleaseResourceLite( &IopDatabaseResource );
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Initialize the notification packet and insert it onto the tail of the
    // list.
    //

    nPacket->DriverObject = DriverObject;
    nPacket->NotificationRoutine = DriverNotificationRoutine;

    InsertTailList( &IopFsNotifyChangeQueueHead, &nPacket->ListEntry );

    IopNotifyAlreadyRegisteredFileSystems(&IopNetworkFileSystemQueueHead, DriverNotificationRoutine, FALSE);
    IopNotifyAlreadyRegisteredFileSystems(&IopCdRomFileSystemQueueHead, DriverNotificationRoutine, TRUE);
    IopNotifyAlreadyRegisteredFileSystems(&IopDiskFileSystemQueueHead, DriverNotificationRoutine, TRUE);
    IopNotifyAlreadyRegisteredFileSystems(&IopTapeFileSystemQueueHead, DriverNotificationRoutine, TRUE);

    //
    // Notify this driver about all already notified filesystems
    // registered as an active file system of some type.
    //


    ExReleaseResourceLite( &IopDatabaseResource );

    //
    // Increment the number of reasons that this driver cannot be unloaded.
    //

    ObReferenceObject( DriverObject );

    return STATUS_SUCCESS;
}


VOID
IopNotifyAlreadyRegisteredFileSystems(
    IN PLIST_ENTRY  ListHead,
    IN PDRIVER_FS_NOTIFICATION DriverNotificationRoutine,
    IN BOOLEAN SkipRaw
    )
/*++

Routine Description:

    This routine calls the driver notification routine for filesystems
    that have already been registered at the time of the call.

Arguments:

    ListHead - Pointer to the filesystem registration list head.
    DriverNotificationRoutine - Pointer to the routine that has to be called.

Return Value:

    None.

--*/
{
    PLIST_ENTRY entry;
    PDEVICE_OBJECT fsDeviceObject;

    entry = ListHead->Flink;
    while (entry != ListHead) {

        //
        // Skip raw filesystem notification
        //
        if ((entry->Flink == ListHead) && (SkipRaw)) {
            break;
        }

        fsDeviceObject = CONTAINING_RECORD( entry, DEVICE_OBJECT, Queue.ListEntry );
        entry = entry->Flink;
        DriverNotificationRoutine( fsDeviceObject, TRUE );
    }
}

注三:IoRegisterFileSystem函数

是文件系统在DriverEntry中最后要调用的函数,主要是向IO管理器中注册一下,以后有卷需要Mount时通知我...

VOID
IoRegisterFileSystem(
    IN OUT PDEVICE_OBJECT DeviceObject
    )

/*++

Routine Description:

    This routine inserts the device object for the file system which the device
    object represents into the list of file systems in the system.

Arguments:

    DeviceObject - Pointer to device object for the file system.

Return Value:

    None.


--*/

{
    PNOTIFICATION_PACKET nPacket;
    PLIST_ENTRY entry;

    PAGED_CODE();

    //
    // Allocate the I/O database resource for a write operation.
    //

    (VOID) ExAcquireResourceExclusive( &IopDatabaseResource, TRUE );

    //
    // Insert the device object into the appropriate file system queue based on
    // the driver type in the device object.  Notice that if the device type is
    // unrecognized, the file system is simply not registered.
    //

    if (DeviceObject->DeviceType == FILE_DEVICE_NETWORK_FILE_SYSTEM) {
        InsertHeadList( &IopNetworkFileSystemQueueHead,
                        &DeviceObject->Queue.ListEntry );
    } else if (DeviceObject->DeviceType == FILE_DEVICE_CD_ROM_FILE_SYSTEM) {
        InsertHeadList( &IopCdRomFileSystemQueueHead,
                        &DeviceObject->Queue.ListEntry );
    } else if (DeviceObject->DeviceType == FILE_DEVICE_DISK_FILE_SYSTEM) {
        InsertHeadList( &IopDiskFileSystemQueueHead,
                        &DeviceObject->Queue.ListEntry );
    } else if (DeviceObject->DeviceType == FILE_DEVICE_TAPE_FILE_SYSTEM) {
        InsertHeadList( &IopTapeFileSystemQueueHead,
                        &DeviceObject->Queue.ListEntry );
    }

    //
    // Ensure that this file system's device is operable.
    //

    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;

    //
    // Notify all of the registered drivers that this file system has been
    // registered as an active file system of some type.
    //

    //PS:看到了吧,如果你的filter调用了IoRegisterFsRegistrationChange,
    //文件系统注册时就要通过你啦,TRUE-Load/FALSE-unload.
    entry = IopFsNotifyChangeQueueHead.Flink;
    while (entry != &IopFsNotifyChangeQueueHead) {
        nPacket = CONTAINING_RECORD( entry, NOTIFICATION_PACKET, ListEntry );
        entry = entry->Flink;
        nPacket->NotificationRoutine( DeviceObject, TRUE );
    }

    //
    // Release the I/O database resource.
    //

    ExReleaseResource( &IopDatabaseResource );

    //
    // Increment the number of reasons that this driver cannot be unloaded.
    //

    ExInterlockedAddUlong( &DeviceObject->ReferenceCount, 1, &IopDatabaseLock );
}

注四:
   有兴趣的话可以看看文件识别器,IO管理器,文件系统的实现.



以前写的,今天整理了一下,阐述中有错误望指教,谢谢!


发表于 2007-08-08 16:55 垃圾一堆 阅读(4858) | 评论 (12)编辑 收藏
2007年7月30日

  对于加壳软件的开发者,掌握PE Loader的实现是最基本的技术;因为壳运行结束后,你要仿照PE加载器去Load映象体,我曾看过UPX的开源代码,全手工打造PE,实现的也是极其复杂,是学习加壳,脱壳和开发加壳软件的上乘资料.

  在ReatOs和Nt4.0上找到了其实现,前者实现的比较清晰,不过还是看看Nt4.0的实现吧

//
KiThreadStartup->向Kernel APC插入LdrInitializeThunk->向User APC插入
//LdrInitializeThunk;详请参见PspCreateThread的实现
//
//注:Kernel APC与User APC的区别:
//           1)前者是在APC_LEVEL上运行,后者是在PASSIVE_LEVEL上运行
//           2)只有KTHREAD中的AlertAble或KTHREAD-ApcState-UserApcPending

//               为1的情况下,User APC中的Routine才会被执行
//
//ring0下启动ring3下的程序就是通过kernel下操作
User APC实现的(向Explorer.exe中插入一个User APC)
//详请参考:
http://www.codeproject.com/useritems/KernelExec.asp


//扯远了,还是来看看LdrInitializeThunk和LdrInitialize的实现吧
;++
;
; VOID
; LdrInitializeThunk(
;    IN PVOID NormalContext,
;    IN PVOID SystemArgument1,
;    IN PVOID SystemArgument2
;    )
;
; Routine Description:
;
;    This function computes a pointer to the context record on the stack
;    and jumps to the LdrpInitialize function with that pointer as its
;    parameter.
;
; Arguments:
;
;    NormalContext - User Mode APC context parameter (ignored).
;
;    SystemArgument1 - User Mode APC system argument 1 (ignored).
;
;    SystemArgument2 - User Mode APC system argument 2 (ignored).
;
; Return Value:
;
;    None.
;
;--

cPublicProc _LdrInitializeThunk , 4

NormalContext   equ [esp + 4]
SystemArgument1 equ [esp + 8]
SystemArgument2 equ [esp + 12]
Context         equ [esp + 16]

        lea     eax,Context             ; Calculate address of context record
        mov     NormalContext,eax       ; Pass as first parameter to
if DEVL
        xor     ebp,ebp                 ; Mark end of frame pointer list
endif
IFDEF STD_CALL
        jmp     _LdrpInitialize@12      ; LdrpInitialize
ELSE
        jmp     _LdrpInitialize         ; LdrpInitialize
ENDIF

stdENDP _LdrInitializeThunk

_TEXT   ends
        end



VOID
LdrpInitialize (
    IN PCONTEXT Context,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2
    )

/*++

Routine Description:

    This function is called as a User-Mode APC routine as the first
    user-mode code executed by a new thread. It's function is to initialize
    loader context, perform module initialization callouts...

Arguments:

    Context - Supplies an optional context buffer that will be restore
              after all DLL initialization has been completed.  If this
              parameter is NULL then this is a dynamic snap of this module.
              Otherwise this is a static snap prior to the user process
              gaining control.

    SystemArgument1 - Supplies the base address of the System Dll.

    SystemArgument2 - not used.

Return Value:

    None.

--*/

{
    NTSTATUS st, InitStatus;
    PPEB Peb;
    PTEB Teb;
    UNICODE_STRING UnicodeImageName;
    MEMORY_BASIC_INFORMATION MemInfo;
    BOOLEAN AlreadyFailed;
    LARGE_INTEGER DelayValue;

    SystemArgument2;

    AlreadyFailed = FALSE;
    Peb = NtCurrentPeb();
    Teb = NtCurrentTeb();

    if (!Peb->Ldr) {
#if defined(MIPS) || defined(_ALPHA_)
        ULONG temp;
#if defined(MIPS)
        Peb->ProcessStarterHelper = (PVOID)LdrProcessStarterHelper;
#endif
        //
        // Set GP register
        //
        LdrpGpValue =(ULONG)RtlImageDirectoryEntryToData(
                Peb->ImageBaseAddress,
                TRUE,
                IMAGE_DIRECTORY_ENTRY_GLOBALPTR,
                &temp
                );
        if (Context != NULL) {
            LdrpSetGp( LdrpGpValue );
#if defined(_MIPS_)
            Context->XIntGp = (LONG)LdrpGpValue;
#else
            Context->IntGp = LdrpGpValue;
#endif
        }
#endif // MIPS || ALPHA

        NtGlobalFlag = Peb->NtGlobalFlag;
#if DBG
        if (TRUE)
#else
        if (Peb->BeingDebugged || Peb->ReadImageFileExecOptions)
#endif
        {
            PWSTR pw;

            pw = (PWSTR)Peb->ProcessParameters->ImagePathName.Buffer;
            if (!(Peb->ProcessParameters->Flags & RTL_USER_PROC_PARAMS_NORMALIZED)) {
                pw = (PWSTR)((PCHAR)pw + (ULONG)(Peb->ProcessParameters));
                }
            UnicodeImageName.Buffer = pw;
            UnicodeImageName.Length = Peb->ProcessParameters->ImagePathName.Length;
            UnicodeImageName.MaximumLength = UnicodeImageName.Length;

            st = LdrQueryImageFileExecutionOptions( &UnicodeImageName,
                                                    L"GlobalFlag",
                                                    REG_DWORD,
                                                    &NtGlobalFlag,
                                                    sizeof( NtGlobalFlag ),
                                                    NULL
                                                  );
            if (!NT_SUCCESS( st )) {
                UnicodeImageName.Length = 0;

                if (Peb->BeingDebugged) {
                    NtGlobalFlag |= FLG_HEAP_ENABLE_FREE_CHECK |
                                    FLG_HEAP_ENABLE_TAIL_CHECK |
                                    FLG_HEAP_VALIDATE_PARAMETERS;
                    }
                }
        }

#if DBG && FLG_HEAP_PAGE_ALLOCS

        if ( NtGlobalFlag & FLG_HEAP_PAGE_ALLOCS ) {

            //
            //  Turn on BOOLEAN RtlpDebugPageHeap to indicate that
            //  new heaps should be created with debug page heap manager
            //  when possible.  Also force off other heap debug flags
            //  that can cause conflicts with the debug page heap
            //  manager.
            //

            RtlpDebugPageHeap = TRUE;

            NtGlobalFlag &= ~( FLG_HEAP_ENABLE_TAGGING |
                               FLG_HEAP_ENABLE_TAG_BY_DLL
                             );

            }

#endif // DBG && FLG_HEAP_PAGE_ALLOCS

    }
#if defined(MIPS) || defined(_ALPHA_)
    else
    if (Context != NULL) {
        LdrpSetGp( LdrpGpValue );
#if defined(_MIPS_)
        Context->XIntGp = (LONG)LdrpGpValue;
#else
        Context->IntGp = LdrpGpValue;
#endif
    }
#endif // MIPS || ALPHA

    ShowSnaps = (BOOLEAN)(FLG_SHOW_LDR_SNAPS & NtGlobalFlag);

    //
    // Serialize for here on out
    //

    Peb->LoaderLock = (PVOID)&LoaderLock;
    if ( !RtlTryEnterCriticalSection(&LoaderLock) ) {
        if ( LoaderLockInitialized ) {
            RtlEnterCriticalSection(&LoaderLock);
            }
        else {

            //
            // drop into a 30ms delay loop
            //

            DelayValue.QuadPart = Int32x32To64( 30, -10000 );
            while ( !LoaderLockInitialized ) {
                NtDelayExecution(FALSE,&DelayValue);
                }
            RtlEnterCriticalSection(&LoaderLock);
            }
        }

    if (Teb->DeallocationStack == NULL) {
        st = NtQueryVirtualMemory(
                NtCurrentProcess(),
                Teb->NtTib.StackLimit,
                MemoryBasicInformation,
                (PVOID)&MemInfo,
                sizeof(MemInfo),
                NULL
                );
        if ( !NT_SUCCESS(st) ) {
            LdrpInitializationFailure(st);
            RtlRaiseStatus(st);
            return;
            }
        else {
            Teb->DeallocationStack = MemInfo.AllocationBase;
        }
    }

    InitStatus = STATUS_SUCCESS;
    try {
        if (!Peb->Ldr) {
            LdrpInLdrInit = TRUE;
#if DBG
            //
            // Time the load.
            //

            if (LdrpDisplayLoadTime) {
                NtQueryPerformanceCounter(&BeginTime, NULL);
            }
#endif // DBG

            try {
                //
                //This function initializes the loader for the process.
                //This includes:

                //    - Initializing the loader data table

                //    - Connecting to the loader subsystem

                //    - Initializing all staticly linked DLLs

                InitStatus = LdrpInitializeProcess( Context,
                                                    SystemArgument1,
                                                    UnicodeImageName.Length ? &UnicodeImageName : NULL
                                                  );
                }
            except ( EXCEPTION_EXECUTE_HANDLER ) {
                InitStatus = GetExceptionCode();
                AlreadyFailed = TRUE;
                LdrpInitializationFailure(GetExceptionCode());
                }
#if DBG
            if (LdrpDisplayLoadTime) {
                NtQueryPerformanceCounter(&EndTime, NULL);
                NtQueryPerformanceCounter(&ElapsedTime, &Interval);
                ElapsedTime.QuadPart = EndTime.QuadPart - BeginTime.QuadPart;
                DbgPrint("\nLoadTime %ld In units of %ld cycles/second \n",
                    ElapsedTime.LowPart,
                    Interval.LowPart
                    );

                ElapsedTime.QuadPart = EndTime.QuadPart - InitbTime.QuadPart;
                DbgPrint("InitTime %ld\n",
                    ElapsedTime.LowPart
                    );
                DbgPrint("Compares %d Bypasses %d Normal Snaps %d\nSecOpens %d SecCreates %d Maps %d Relocates %d\n",
                    LdrpCompareCount,
                    LdrpSnapBypass,
                    LdrpNormalSnap,
                    LdrpSectionOpens,
                    LdrpSectionCreates,
                    LdrpSectionMaps,
                    LdrpSectionRelocates
                    );
            }
#endif // DBG

            if (!NT_SUCCESS(InitStatus)) {
#if DBG
                DbgPrint("LDR: LdrpInitializeProcess failed - %X\n", InitStatus);
#endif // DBG
            }

        } else {
            if ( Peb->InheritedAddressSpace ) {
                InitStatus = LdrpForkProcess();
                }
            else {

#if defined (WX86)
                if (Teb->Vdm) {
                    InitStatus = LdrpInitWx86(Teb->Vdm, Context, TRUE);
                    }
#endif

                 //    This function is called by a thread that is terminating cleanly.
                 //It's purpose is to call all of the processes DLLs to notify them
                 //that the thread is detaching.

                LdrpInitializeThread();
                }
        }
    } finally {
        LdrpInLdrInit = FALSE;
        RtlLeaveCriticalSection(&LoaderLock);
        }

    NtTestAlert();

    if (!NT_SUCCESS(InitStatus)) {

        if ( AlreadyFailed == FALSE ) {
            LdrpInitializationFailure(InitStatus);
            }
        RtlRaiseStatus(InitStatus);
    }


}

详细实现参见:
 nt4\private\ntos\dll\ldrinit.c
 nt4\private\ntos\dll\ldrapi.c
 nt4\private\ntos\dll\ldrsnap.c

reatos实现参见:
 ReactOS030\dll\ntdll\ldr\startup.c
 ReactOS030\dll\ntdll\ldr\utils.c 
 ReactOS030\dll\ntdll\ldr\elf.c

发表于 2007-07-30 10:36 垃圾一堆 阅读(11255) | 评论 (13)编辑 收藏
2007年7月16日

什么叫rootkit?
  它是由有用的小型程序组成的工具包,使得攻击者能够保持访问计算机上具有最高权限的用户“root”.rootkit是能够持久或可靠地、无法检测地存在于计算机上的一组程序和代码.

rootkit主要分为下列大类:
1 进程隐藏
2 文件隐藏
3 端口隐藏
4 注册表隐藏
5 驱动服务隐藏


Part I: 进程隐藏
一:序言
  下列情况不在讨论之中(没进程)
  1 通过CreateRemoteThread Inject代码到另一个进程(有种病毒就用这种方法,实现内存感染的;其实还有更多应用)
  2 通过CreateRemoteThread LoadLibray一dll到另一个进程(屏蔽Ctrl+Alt+Del,就是通过这种方法和SetWindowLog实现)


二:进程隐藏
  1 Hook/InlineHook Api NtQuerySystemInformation(taskmgr.exe就是用这个函数得到Process list)
  2 Hook/InlineHook Api Process32Next
  3 把要隐藏的进程的EPROCESS从LIST_ENTRY中摘除
    a)ring0下驱动实现,注意:Nt/2000/xp/2003中PID和FLINK在EPROCESS中的offset不尽相同
    b)ring3下利用call gate结合\Device\PhysicalMemory内核对象实现
 

二:检测进程隐藏
  我们重要讨论一下杀毒软件Kaspersky和rootkit检测工具Icesword的两种方法:
 
  1 kaspersky的方法:kaspersky从6.0中加入了主动防御功能,它detour了SwapContext.

lkd> u KiSwapThread L20
nt!KiSwapThread:
804dd66e 8bff             mov     edi,edi
804dd670 56               push    esi
804dd671 57               push    edi
804dd672 3ea120f0dfff     mov     eax,ds:[ffdff020]
804dd678 8bf0             mov     esi,eax
804dd67a 8b4608           mov     eax,[esi+0x8]
804dd67d 85c0             test    eax,eax
804dd67f 8b7e04           mov     edi,[esi+0x4]
804dd682 0f8557ba0000     jne     nt!KiSwapThread+0x16 (804e90df)
804dd688 53               push    ebx
804dd689 0fbe5e10         movsx   ebx,byte ptr [esi+0x10]
804dd68d 33d2             xor     edx,edx
804dd68f 8bcb             mov     ecx,ebx
804dd691 e86bffffff       call    nt!KiFindReadyThread (804dd601)
804dd696 85c0             test    eax,eax
804dd698 0f843e990000     je      nt!KiSwapThread+0x2e (804e6fdc)
804dd69e 5b               pop     ebx
804dd69f 8bc8             mov     ecx,eax
804dd6a1 e80cf7ffff       call    nt!KiSwapContext (804dcdb2)
804dd6a6 84c0             test    al,al
804dd6a8 8a4f58           mov     cl,[edi+0x58]
804dd6ab 8b7f54           mov     edi,[edi+0x54]
804dd6ae 8b3570864d80     mov     esi,[nt!_imp_KfLowerIrql (804d8670)]
804dd6b4 0f85d10a0100     jne     nt!KiSwapThread+0x56 (804ee18b)
804dd6ba ffd6             call    esi
804dd6bc 8bc7             mov     eax,edi
804dd6be 5f               pop     edi
804dd6bf 5e               pop     esi
804dd6c0 c3               ret

lkd> u KiSwapContext L20
nt!KiSwapContext:
804dcdb2 83ec10           sub     esp,0x10
804dcdb5 895c240c         mov     [esp+0xc],ebx
804dcdb9 89742408         mov     [esp+0x8],esi
804dcdbd 897c2404         mov     [esp+0x4],edi
804dcdc1 892c24           mov     [esp],ebp
804dcdc4 8b1d1cf0dfff     mov     ebx,[ffdff01c]
804dcdca 8bf1             mov     esi,ecx
804dcdcc 8bbb24010000     mov     edi,[ebx+0x124]
804dcdd2 89b324010000     mov     [ebx+0x124],esi
804dcdd8 8a4f58           mov     cl,[edi+0x58]
804dcddb e8d9000000       call    nt!SwapContext (804dceb9)
804dcde0 8b2c24           mov     ebp,[esp]
804dcde3 8b7c2404         mov     edi,[esp+0x4]
804dcde7 8b742408         mov     esi,[esp+0x8]
804dcdeb 8b5c240c         mov     ebx,[esp+0xc]
804dcdef 83c410           add     esp,0x10
804dcdf2 c3               ret


lkd> u SwapContext L10
nt!SwapContext:
804dceb9 0ac9             or      cl,cl
804dcebb 26c6462d02       mov     byte ptr es:[esi+0x2d],0x2
804dcec0 9c               pushfd
804dcec1 8b0b             mov     ecx,[ebx]
804dcec3 e948cfa077       jmp     f7ee9e10(注意:这个地址不在NTOSKRNL.EXE范围中,落在klif.sys范围中,<它用了相对转跳,这样可以节约两个字节,cs:08>)
804dcec8 90               nop
804dcec9 90               nop
804dceca 51               push    ecx
804dcecb 0f8534010000     jne     nt!SwapContext+0x14d (804dd005)
804dced1 833d8c29568000 cmp dword ptr [nt!PPerfGlobalGroupMask (8056298c)],0x0
804dced8 0f85fe000000     jne     nt!SwapContext+0x124 (804dcfdc)
804dcede 0f20c5           mov     ebp,cr0
804dcee1 8bd5             mov     edx,ebp
804dcee3 8a4e2c           mov     cl,[esi+0x2c]
804dcee6 884b50           mov     [ebx+0x50],cl
804dcee9 fa               cli

考虑到机器的效率,SwapContext是用汇编代码实现的,看看它具体功能(实现自己看代码吧:)):
;++
;
; Routine Description:
;
;    This routine is called to swap context from one thread to the next.
;    It swaps context, flushes the data, instruction, and translation
;    buffer caches, restores nonvolatile integer registers, and returns
;    to its caller.
;
;    N.B. It is assumed that the caller (only caller's are within this
;         module) saved the nonvolatile registers, ebx, esi, edi, and
;         ebp. This enables the caller to have more registers available.
;
; Arguments:
;
;    cl - APC interrupt bypass disable (zero enable, nonzero disable).
;    edi - Address of previous thread.
;    esi - Address of next thread.
;    ebx - Address of PCR.
;
; Return value:
;
;    al - Kernel APC pending.
;    ebx - Address of PCR.
;    esi - Address of current thread object.
;
;--

(虽然悬挂或等待的线程,不会获得cpu时间,但在SwapContext的时候仍然要检测,知道Thread了,得到对应Process也就容易了)
注:这个方法最初是J. Butler提出的,参见:
http://ieeexplore.ieee.org/xpl/freeabs_all.jsp?arnumber=1232409

 
  2 IceSword的方法:以前的方法是检测EPRCOESS,后来改成了PspCidTable
  
  a)全局变量PspCidTable是一个HANDLE_TABLE的指针,这个变量并没有被NTOSKRNL导出,这个HANDLE_TABLE的表保存着所有进程和线程对象的指针.

  b)PID(进程ID)和 ThreadID(线程ID)就是在这个句柄表中的索引,这个HANDLE_TABLE不属于任何进程,也没有链在HANDLE_TABLE链上.

  c)PspCidTable在PsLookupProcessByProcessId中被用到,所以可以在此函数中搜索PspCidTalbe变量以定位其地址.

  d)得到PspCidTable这个句柄表地址后,IceSword调用ExEnumHandleTable.

这个函数的函数原形是:
BOOLEAN ExEnumHandleTable(
IN PHANDLE_TABLE HandleTable,
IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
IN PVOID EnumParameter,
OUT PHANDLE Handle OPTIONAL)

参数说明:
HandleTable        : 句柄表,可以用PspCidTable做参数.
EnumHandleProcedure: 类型为BOOLEAN (*EX_ENUMERATE_HANDLE_ROUTINE)(HANDLE_TALBE_ENTRY*,DWORD PID,PVOID Param)函数指针.
EnumParameter      : 传送给EnumHandleProcedure函数的参数.
Handle             : 此函数返回True时此参数才有效,为停止枚举前所枚举的句柄(可选).

功能说明:
调用ExEnumHandleTable函数的时,在每次枚举到表中的一个句柄时都会调用一次回调函数;
回调函数返回值为FALSE时继续枚举句柄表,返回TRUE时则停止枚举.

我们来看看他的具体实现吧!

BOOLEAN
ExEnumHandleTable(
    IN PHANDLE_TABLE HandleTable,
    IN EX_ENUMERATE_HANDLE_ROUTINE EnumHandleProcedure,
    IN PVOID EnumParameter,
    OUT PHANDLE Handle OPTIONAL
    )

/*++

Routine Description:

    This function enumerates all the valid handles in a handle table.
    For each valid handle in the handle table, the specified eumeration
    function is called. If the enumeration function returns TRUE, then
    the enumeration is stopped, the current handle is returned to the
    caller via the optional Handle parameter, and this function returns
    TRUE to indicated that the enumeration stopped at a specific handle.

Arguments:

    HandleTable - Supplies a pointer to a handle table.

    EnumHandleProcedure - Supplies a pointer to a fucntion to call for
        each valid handle in the enumerated handle table.

    EnumParameter - Supplies an uninterpreted 32-bit value that is passed
        to the EnumHandleProcedure each time it is called.

    Handle - Supplies an optional pointer a variable that receives the
        Handle value that the enumeration stopped at. Contents of the
        variable only valid if this function returns TRUE.

Return Value:

    If the enumeration stopped at a specific handle, then a value of TRUE
    is returned. Otherwise, a value of FALSE is returned.

--*/

{

    PHANDLE_ENTRY HandleEntry;
    BOOLEAN ResultValue;
    PHANDLE_ENTRY TableEntries;
    PHANDLE_ENTRY TableBound;
    ULONG TableIndex;

    PAGED_CODE();

    ASSERT(HandleTable != NULL);

    //
    // Lock the handle table exclusive and enumerate the handle entries.
    //

    ResultValue = FALSE;
    ExLockHandleTableShared(HandleTable);
    TableBound = HandleTable->TableBound;
    TableEntries = HandleTable->TableEntries;
    HandleEntry = &TableEntries[1];
    while (HandleEntry < TableBound) {
        if (ExIsEntryUsed(TableEntries, TableBound, HandleEntry)) {
            TableIndex = HandleEntry - TableEntries;
            if ((*EnumHandleProcedure)(HandleEntry,
                                        INDEX_TO_HANDLE(TableIndex),
                                        EnumParameter)) {

                if (ARGUMENT_PRESENT(Handle)) {
                    *Handle = INDEX_TO_HANDLE(TableIndex);
                }

                ResultValue = TRUE;
                break;
            }
        }

        HandleEntry += 1;
    }

    ExUnlockHandleTableShared(HandleTable);
    return ResultValue;
}

(PS:IceSword的检测方法部分参见匿名用户的文章,非常感谢!)

发表于 2007-07-16 18:41 垃圾一堆 阅读(4879) | 评论 (0)编辑 收藏
2007年7月5日
(ps:很老的文章,只是今天在rootkit上找东西,又看到了,所以收藏到blog中)
原文章  Part1:
http://www.rootkit.com/newsread.php?newsid=219
            Part2: http://www.rootkit.com/newsread.php?newsid=253
相关文章ktcp: http://www.rootkit.com/newsread.php?newsid=591

Hooking into NDIS and TDI (Part 1)


This is the fist part in a series of 2 articles on how to hook into the NDIS and TDI layer. In this first one, we will discuss where and how to hook in to the NDIS layer.

In the second, we will do the same for TDI.

First, lets take a quick look at a quite simplicit view of the network stack in kernel space:
TDI
NDIS protocol layer
NDIS Intermediate layer
Miniport layer
Hardware

To be able to control data flow in NDIS, we have 3 potential points where we can either add a device / driver or hook into existing. First, we have the miniport layer, these are the drivers controlling the NIC hardware, which is a bit to low of a level for what we want at this time. Next, we have the intermediate layer. This layer would be perfect for this purpose, since it would allow us to contol the dataflow to all NDIS protocol drivers. But, it has a major drawback: To be able to add a driver to this layer, it has to be signed on a default install.



Depending on what system and under what circumstances we are installing this code, it might not be possible to get past this problem in an easy manner. Last, we have the protocol layer. Adding a driver to this layer would be easy, software such as WinPcap does that. However, in that case we will not be able to control what a user would see through for software relaying on these types of drivers, such as Ethereal. So, is there any way we can get around the driver signing issue in the intermediate layer and at the same time control data in the protocol layer and above? Yes! We can virually add a layer in between intermediate and protocol by hooking all NDIS protocol drivers and their protocol functions.

When an NDIS protocol is registered, it is recorded in a linked list. Each element in this linked list carries a pointer to a structure named NDIS_OPEN_BLOCK. This structure carries the pointers for all registered function pointers for the protocol. The linked list elements are made out of a structure looking something like the following:


typedef struct _NDIS_LINKED_LIST {
  PNDIS_OPEN_BLOCK pOpenBlock;
  PVOID p;
  REFERENCE ref;
  struct _NDIS_LINKED_LIST *Next;
} NDIS_LINKED_LIST,*PNDIS_LINKED_LIST;


It was a year or so since I played with this code, so I can actually not remember the real name of this struct. Interested persons can find it through google. This will also reflect in the source code later on, since it is relying on absolute offsets instead of the typedef'ed struct.

To be able to hook into all registered NDIS protocols, we need to find the first element in this linked list. This is actually returned by NdisRegisterProtocol as the NDIS_HANDLE. So, what we have to do is to register a bogus NDIS protocol, save the pointer and then remove the protocol. This will give us the ability to walk through the list of registered NDIS protocols and exchange existing function points to functions we control.

First, we register the bogus protocol to get the pointer. To make sure the registration does not fail, the protocol we register needs to have a ReceiveHandler:


NDIS_STATUS DummyNDISProtocolReceive(
    IN NDIS_HANDLE ProtocolBindingContext,
    IN NDIS_HANDLE MacReceiveContext,
    IN PVOID HeaderBuffer,
    IN UINT HeaderBufferSize,
    IN PVOID LookAheadBuffer,
    IN UINT LookAheadBufferSize,
    IN UINT PacketSize)
{
    return NDIS_STATUS_NOT_ACCEPTED;
}

NDIS_HANDLE RegisterBogusNDISProtocol(void)
{
    NTSTATUS Status = STATUS_SUCCESS;
    NDIS_HANDLE hBogusProtocol = NULL;
    NDIS_PROTOCOL_CHARACTERISTICS BogusProtocol;
    NDIS_STRING ProtocolName;

    NdisZeroMemory(&BogusProtocol,sizeof(NDIS_PROTOCOL_CHARACTERISTICS));
    BogusProtocol.MajorNdisVersion = 0x04;
    BogusProtocol.MinorNdisVersion = 0x0;

    NdisInitUnicodeString(&ProtocolName,L"BogusProtocol");
    BogusProtocol.Name = ProtocolName;
    BogusProtocol.ReceiveHandler = DummyNDISProtocolReceive;

    NdisRegisterProtocol(&Status,&hBogusProtocol,&BogusProtocol,
        sizeof(NDIS_PROTOCOL_CHARACTERISTICS));

    if(Status == STATUS_SUCCESS) return hBogusProtocol;
    else return NULL;
}


Once we have the pointer, we can deregister the protocol again:


void DeregisterBogusNDISProtocol(NDIS_HANDLE hBogusProtocol)
{
    NTSTATUS Status;

    NdisDeregisterProtocol(&Status,hBogusProtocol);
}


Once we start walking the linked list and overwriting function pointers, we need to save the old pointers to be able to call them from our functions. There are atleast 2 ways of doing this:

1. Create a linked list of "hooked instances", holding the old pointers for each protocol. When our NDIS functions are called, the linked list has to be searched for the right element.

2. Allocate one instance of our functions for each protocol we hook and write the old pointer directly into the code of the function. This is slighly more work during hooking, but should be faster during run-time than searching through a linked list for every packet.

When this code was written, I never thought of option number 2, but that is probably the option I would use today. So, enjoy option 1, it works well and I haven't seen any major performance hits from it.

For every element in the NDIS registered protocol linked list, I allocate one element in my own list and save all important pointers together with 2 context handles. The handles values are later used to find the right element for the current protocol. Relevant pointers are then overwritten to point to my versions of the send and receive functions. We also save a pointer to the NDIS_OPEN_BLOCK itself to make unhooking easy. The code walking the list and hooking into the protocol would look something like this:


NTSTATUS HookExistingNDISProtocols(void)
{
    UINT *ProtocolPtr;
    NDIS_HANDLE hBogusProtocol = NULL;
    PNDIS_OPEN_BLOCK OpenBlockPtr = NULL;
    PNDIS_PROTOCOL_HOOK pNode;

    hBogusProtocol = RegisterBogusNDISProtocol();
    if(hBogusProtocol == NULL) return STATUS_UNSUCCESSFUL;

    ProtocolPtr = (UINT*)hBogusProtocol;
    ProtocolPtr = (UINT*)((PBYTE)ProtocolPtr + sizeof(REFERENCE) + 8);
    ProtocolPtr = (UINT*)(*ProtocolPtr);

    while(ProtocolPtr != NULL) {
        OpenBlockPtr = (PNDIS_OPEN_BLOCK)(*ProtocolPtr);
        if(OpenBlockPtr != NULL) {
            pNode = NewNDISNode();
            if(pNode != NULL) {
                pNode->ProtocolBindingContext = OpenBlockPtr->ProtocolBindingContext;
                pNode->MacBindingContext = OpenBlockPtr->MacBindingHandle;
                pNode->OpenBlockPtr = OpenBlockPtr;
                pNode->RealSendHandler = OpenBlockPtr->SendHandler;
                //How about WanSendHandler?
                pNode->RealPostNt31ReceiveHandler = OpenBlockPtr->PostNt31ReceiveHandler;
                
                InsertNDISNode(pNode);

                OpenBlockPtr->SendHandler = NDISSendHandler;
                //How about WanSendHandler?
                OpenBlockPtr->PostNt31ReceiveHandler = NDISPostNt31ReceiveHandler;
            }
        }

        ProtocolPtr = (UINT*)((PBYTE)ProtocolPtr + sizeof(REFERENCE) + 8);
        ProtocolPtr = (UINT*)(*ProtocolPtr);
    }

    DeregisterBogusNDISProtocol(hBogusProtocol);

    return STATUS_SUCCESS;
}


There are more functions in the NDIS_OPEN_BLOCK that might be of interest to hook, but if you only want to control network traffic, send and receive are enough. Another thing worth mentioning is that the NDIS_OPEN_BLOCK changes with OS versions. It looks different in Win2K compared to XP, mostly due to member names changing.

The next thing to do now is to implement send and recieve functions which searches through the linked list to find the original function pointers and then calls them if the traffic is to be passed on. If the traffic is to be altered, that is performed before calling the real protocol function. If the traffic is supposed to be dropped, we can just skip calling the real function and return with the appropriate status:


NDIS_STATUS NDISSendHandler(
    IN NDIS_HANDLE MacBindingHandle,
    IN PNDIS_PACKET Packet)
{
    PNDIS_PROTOCOL_HOOK Node;

    Node = FindNDISNode(MacBindingHandle,2);
    if(Node == NULL) return NDIS_STATUS_SUCCESS;

    return Node->RealSendHandler(MacBindingHandle,Packet);
}

NDIS_STATUS NDISPostNt31ReceiveHandler(
    IN NDIS_HANDLE ProtocolBindingContext,
    IN NDIS_HANDLE MacReceiveContext,
    IN PVOID HeaderBuffer,
    IN UINT HeaderBufferSize,
    IN PVOID LookAheadBuffer,
    IN UINT LookAheadBufferSize,
    IN UINT PacketSize)
{
    PNDIS_PROTOCOL_HOOK Node;

    Node = FindNDISNode(ProtocolBindingContext,1);
    if(Node == NULL) return NDIS_STATUS_SUCCESS;

    return Node->RealPostNt31ReceiveHandler(ProtocolBindingContext,MacReceiveContext,
        HeaderBuffer,HeaderBufferSize,LookAheadBuffer,LookAheadBufferSize,PacketSize);
}


Now, there is only one thing left, unhooking. We do this by walking our linked list of "hooked instances" and replace all pointers:


NTSTATUS ReleaseExistingNDISProtocols(void)
{
    PNDIS_PROTOCOL_HOOK CurrentNode;
    PNDIS_OPEN_BLOCK OpenBlockPtr = NULL;

    CurrentNode = GetFirstNDISNode();
    if(CurrentNode == NULL) return STATUS_UNSUCCESSFUL;

    while(CurrentNode != NULL) {
        OpenBlockPtr = CurrentNode->OpenBlockPtr;
        if(OpenBlockPtr != NULL) {
            OpenBlockPtr->SendHandler = CurrentNode->RealSendHandler;
            OpenBlockPtr->PostNt31ReceiveHandler = CurrentNode->RealPostNt31ReceiveHandler;
        }
        CurrentNode = GetNextNDISNode(CurrentNode);
    }

    return STATUS_SUCCESS;
}


What is left to be done? The code does not hook into NDIS protocol being registred after NDIS is hooked into. This is left up to the reader to figure out, one way to do it can be found in the win32 version of sebek.

Does the code work? Sure, I use it in a win32 version of knockd called sesame that can be found at
.http://www.toolcrypt.org/.




Hooking into NDIS and TDI (Part 2)

This is the second and last article on how to hook into the NDIS and TDI
layer. The approach we will use will be slightly different from the NDIS
case. However, a neat side effect is that this method can be used to hook
into any device chain, for example the keyboard to sniff key strokes. It all boils down to getting a pointer to the device object and replace all major functions with our own dispatch function.

To be able to fully control the TDI layer, we need access to the IRP both
before and after the original driver has processed it. If we have that, we
can choose what the original driver should process and we can also alter
results before they are returned to user-space. The "before filtering" can
be accomplished in our own, new dispatch function and the "after filtering" can be accomplished in a completion routine.

First, to be able to overwrite and insert our own dispatch function, we need a pointer to the driver object we are going to hook into. An easy way to get this pointer is to call ObReferenceObjectByName with the appropriate driver name. Then we only have to save all old function pointers and overwrite the existing ones with our own. The code to do this would look something like the following:


DRIVER_OBJECT RealTDIDriverObject;

NTSTATUS HookTDI(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;



    RtlInitUnicodeString(&usDriverName,L"\\Driver\\Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++) {
        RealTDIDriverObject.MajorFunction[i] =
DriverObjectToHookPtr->MajorFunction[i];
        DriverObjectToHookPtr->MajorFunction[i] = TDIDeviceDispatch;
    }

    return STATUS_SUCCESS;
}




RealTDIDriverObject is a DRIVER_OBJECT where we save the original
information to both be able to call the old functions and also be able to
unhook once we are done. The orignal driver gets all its major functions
overwritten with a pointer to our own dispatch function, TDIDeviceDispatch.

We now have control over the IRPs before TDI can process them. But, we still have to make sure we can also control them once TDI is done with it but before it is returned to the IO handler and user-space. We will solve this in our dispatch function with the help of a completion routine. It is not as straight forward as it sounds, since we might be hooking the last entity in the chain, we can't just insert a completion routine with
IoSetCompletionRoutine (see the DDK docs), since it in that case never will be called. Completion routines are set in the next IRP stack location, not the current. If we are the last entity, there will be no next stack location in the IRP. Searching through the header files reveal IoSetCompletionRoutine as a macro which only gets the next IRP stack location and sets the CompletionRoutine pointer together with the Control element. Following the same principcal, we can set our own completion routine to regain control over the IRP with the following dispach function:


NTSTATUS TDIDeviceDispatch(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp)
{
    NTSTATUS Status;
    PIO_STACK_LOCATION StackLocationPtr;

    if(Irp == NULL) return STATUS_SUCCESS;

    StackLocationPtr = IoGetCurrentIrpStackLocation(Irp);
    if(StackLocationPtr->CompletionRoutine != NULL) StackLocationPtr->Context =
StackLocationPtr->CompletionRoutine;
    else StackLocationPtr->Context = NULL;
    StackLocationPtr->CompletionRoutine =
(PIO_COMPLETION_ROUTINE)TDICompletionRoutine;
    StackLocationPtr->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR |
SL_INVOKE_ON_CANCEL;

    Status =
RealTDIDriverObject.MajorFunction[StackLocationPtr->MajorFunction](DeviceObject,Irp);

    return Status;
}


What we actually do is faking a scenario where the layer above set the
completion routine for us. We also save a potentially already existing
completion routine in the Context element of the IRP. Control is set to
invoke the completion routine in all cases. There are 2 potential issues
with this code. First, we overwrite whatever is in the Context element.
Second, we never save the Control element, so we don't know when to invoke
an already existing completion routine. So far, I have not seen any
side-effects from doing this.

The completion routine would look something like:


NTSTATUS TDICompletionRoutine(PDEVICE_OBJECT DeviceObject,PIRP Irp,PVOID
Context)
{
    COMPLETIONROUTINE RealCompletionRoutine = (COMPLETIONROUTINE)Context;

    if(Context != NULL) return RealCompletionRoutine(DeviceObject,Irp,NULL);
    else return STATUS_SUCCESS;
}


It invokes a potential completion routine as soon as it is done and returns the status from it. Finally, unhooking the driver is just a question of restoring the pointers we overwrote in the hooking function:


NTSTATUS ReleaseTDIDevices(void)
{
    NTSTATUS Status;
    UNICODE_STRING usDriverName;
    PDRIVER_OBJECT DriverObjectToHookPtr;
    UINT i;

    RtlInitUnicodeString(&usDriverName,L"\\Driver\\Tcpip");

    Status =
ObReferenceObjectByName(&usDriverName,OBJ_CASE_INSENSITIVE,NULL,0,IoDriverObjectType,KernelMode,NULL,&DriverObjectToHookPtr);
    if(Status != STATUS_SUCCESS) return Status;

    for(i = 0;i < IRP_MJ_MAXIMUM_FUNCTION;i++)
        DriverObjectToHookPtr->MajorFunction[i] =
RealTDIDriverObject.MajorFunction[i];

    return STATUS_SUCCESS;
}



There is another way to accomplish the same result which utilizes a more
offically supported mode of operation. It is based upon attaching to the
device chain with GetDeviceObject and AttachToDevice, which will allow us to process all IRPs before the real device. Once in the dispatch function we contruct a new IRP and add a completion routine to regain control of the IRP before it is returned to the IO system and user-space.

One last important thing to mention; This code is quite untested. It seems
to work as intended but it has never been used in any major applications, so use it on your own risk. With that said, hope you have enjoyed this little article series.

发表于 2007-07-05 17:46 垃圾一堆 阅读(3793) | 评论 (2)编辑 收藏