《Undocumented Windows 2000 Secrets》翻译 --- 第六章(4)

第六章  在用户模式下调用内核API函数

翻译:Kendiv( fcczj@263.net )

更新:Friday, May 06, 2005

 

声明:转载请注明出处,并保证文章的完整性,本人保留译文的所有权利。

 

通往用户模式的桥梁

现在,内核调用接口的演化已经缓慢的到达了终点----至少已经涉及内核模式(kernel-mode)。让我们总结一下到目前为止,我们已经获得了什么:

l         名为SpyCallEx()的函数(见列表6-3)将收到一个SPY_CALL_INPUT控制块,该控制块中包含目标地址和一些函数所需的参数。SpyCallEx()调用指定的地址,并且通过一个SPY_CALL_OUTPUT控制块将结果返回。

 

l         一种按名字查找导出的系统函数和变量的方法,该方法由SpyModuleSymbolEx()函数实现(见列表6-11)。

 

现在,最后一个问题是:“我们如何让用户模式下的应用程序访问这些资源?”回答当然是:“通过设备I/O控制(Device I/O Control)”。到现在为止,Spy device提供了一组IOCTL函数,6-1列出了这些函数。该表是第四章的4-2的摘要,4-2包含w2k_spy.sys提供的所有IOCTL函数。列表6-12给出了与SpyDispatcher()函数相关的部分,第四章的列表4-7给出了SpyDispatcher()函数的具体细节。

 

6-1最后一行中,名为SPY_IO_CALL的函数将作为通向用户模式的桥梁。我相信一旦Spy device可以访问这些极具价值的信息,它将很容易使用户模式的应用程序获取这些数据。就像在第四章和第五章中一样,下面我们将对新引入的IOCTL函数作一个简短的介绍。

 

6-1.   与内核调用接口相关的IOCTL函数

函数名

ID

IOCTL编码

   

SPY_IO_MODULE_INFO

19

0x 8000604C

返回有关已加载的系统模块的信息

SPY_IO_PE_HEADER

20

0x80006050

返回IMAGE_NT_HEADERS数据

SPY_IO_PE_EXPORT

21

0x80006054

返回IMAGE_EXPORT_DIRECTORY数据

SPY_IO_PE_SYMBOL

22

0x80006058

返回一个导出符号的地址

SPY_IO_CALL

23

0x8000E 05C

调用一个位于模块(已加载)内部的函数

 

 

NTSTATUS SpyDispatcher (PDEVICE_CONTEXT pDeviceContext,

                        DWORD           dCode,

                        PVOID           pInput,

                        DWORD           dInput,

                        PVOID           pOutput,

                        DWORD           dOutput,

                        PDWORD          pdInfo)

    {

    SPY_MEMORY_BLOCK smb;

    SPY_PAGE_ENTRY   spe;

    SPY_CALL_INPUT   sci;

    PHYSICAL_ADDRESS pa;

    DWORD            dValue, dCount;

    BOOL             fReset, fPause, fFilter, fLine;

    PVOID            pAddress;

    PBYTE            pbName;

    HANDLE           hObject;

    NTSTATUS         ns = STATUS_INVALID_PARAMETER;

 

    MUTEX_WAIT (pDeviceContext->kmDispatch);

 

    *pdInfo = 0;

 

    switch (dCode)

        {

        // unrelated IOCTL functions ommitted (cf. Listing 4-7)

        case SPY_IO_MODULE_INFO:

            {

            if ((ns = SpyInputPointer (&pbName,

                                       pInput, dInput))

                == STATUS_SUCCESS)

                {

                ns = SpyOutputModuleInfo (pbName,

                                          pOutput, dOutput, pdInfo);

                }

            break;

            }

        case SPY_IO_PE_HEADER:

            {

            if ((ns = SpyInputPointer (&pAddress,

                                       pInput, dInput))

                == STATUS_SUCCESS)

                {

                ns = SpyOutputPeHeader (pAddress,

                                        pOutput, dOutput, pdInfo);

                }

            break;

            }

        case SPY_IO_PE_EXPORT:

            {

            if ((ns = SpyInputPointer (&pAddress,

                                       pInput, dInput))

                == STATUS_SUCCESS)

                {

                ns = SpyOutputPeExport (pAddress,

                                        pOutput, dOutput, pdInfo);

                }

            break;

            }

        case SPY_IO_PE_SYMBOL:

            {

            if ((ns = SpyInputPointer (&pbName,

                                       pInput, dInput))

                == STATUS_SUCCESS)

                {

                ns = SpyOutputPeSymbol (pbName,

                                        pOutput, dOutput, pdInfo);

                }

            break;

            }

        case SPY_IO_CALL:

            {

            if ((ns = SpyInputBinary (&sci, SPY_CALL_INPUT_,

                                      pInput, dInput))

                == STATUS_SUCCESS)

                {

                ns = SpyOutputCall (&sci,

                                    pOutput, dOutput, pdInfo);

                }

            break;

            }

        }

    MUTEX_RELEASE (pDeviceContext->kmDispatch);

    return ns;

    }

列表6-12.  Spy driverHook Command Dispatcher(摘录)

 

IOCTL函数SPY_IO_MODULE_INFO

IOCTL函数SPY_IO_MODULE_INFO接收一个模块基地址,并返回一个SPY_MODULE_INFO结构(如果该地址指向了一个有效的PE Image)。列表6-13给出了该结构的定义以及与其相关的SpyOutputModuleInfo()帮助函数(列表6-12中的SpyDispatcher()将调用该函数)。SpyOutputModuleInfo()基于SpyModuleFind()函数(参见列表6-9),SpyModuleFind()函数返回它从ZwQuerySystemInformation()获取的MODULE_INFO数据。MODULE_INFO数据将被转换为SPY_MODULE_INFO格式后发送给调用者。

 

typedef struct _SPY_MODULE_INFO

    {

    PVOID pBase;

    DWORD dSize;

    DWORD dFlags;

    DWORD dIndex;

    DWORD dLoadCount;

    DWORD dNameOffset;

    BYTE  abPath [MAXIMUM_FILENAME_LENGTH];

    }

    SPY_MODULE_INFO, *PSPY_MODULE_INFO, **PPSPY_MODULE_INFO;

 

#define SPY_MODULE_INFO_ sizeof (SPY_MODULE_INFO)

 

// -----------------------------------------------------------------

NTSTATUS SpyOutputModuleInfo (PBYTE  pbModule,

                              PVOID  pOutput,

                              DWORD  dOutput,

                              PDWORD pdInfo)

    {

    SPY_MODULE_INFO smi;

    PMODULE_LIST    pml;

    PMODULE_INFO    pmi;

    DWORD           dIndex;

    NTSTATUS        ns = STATUS_INVALID_PARAMETER;

 

    if ((pbModule != NULL) && SpyMemoryTestAddress (pbModule) &&

        ((pml = SpyModuleFind (pbModule, &dIndex, &ns)) != NULL))

        {

        pmi = pml->aModules + dIndex;

 

        smi.pBase       = pmi->pBase;

        smi.dSize       = pmi->dSize;

        smi.dFlags      = pmi->dFlags;

        smi.dIndex      = pmi->wIndex;

        smi.dLoadCount  = pmi->wLoadCount;

        smi.dNameOffset = pmi->wNameOffset;

 

        strcpyn (smi.abPath, pmi->abPath, MAXIMUM_FILENAME_LENGTH);

 

        ns = SpyOutputBinary (&smi, SPY_MODULE_INFO_,

                              pOutput, dOutput, pdInfo);

 

        SpyMemoryDestroy (pml);

        }

    return ns;

    }

列表6-13.   SPY_IO_MODULE_INFO的实现方式

 

IOCTL函数SPY_IO_PE_HEADER

IOCTL函数SPY_IO_PE_HEADER只是一个简单的IOCTL外包函数,其核心部分是ntoskrnl.exe导出的RtlImageNtHeader()函数,如列表6-14所示。和SPY_IO_MODULE_INFO类似,SPY_IO_PE_HEADER也需要一个模块基地址。返回的数据是模块的IMAGE_NT_HEADER结构。

 

NTSTATUS SpyOutputPeHeader (PVOID  pBase,

                            PVOID  pOutput,

                            DWORD  dOutput,

                            PDWORD pdInfo)

    {

    PIMAGE_NT_HEADERS pinh;

    NTSTATUS          ns = STATUS_INVALID_PARAMETER;

 

    if ((pBase != NULL) && SpyMemoryTestAddress (pBase) &&

        ((pinh = RtlImageNtHeader (pBase)) != NULL))

        {

        ns = SpyOutputBinary (pinh, IMAGE_NT_HEADERS_,

                              pOutput, dOutput, pdInfo);

        }

    return ns;

    }

列表6-14.  SPY_IO_PE_HEADER的实现方式

 

IOCTL函数SPY_IO_PE_EXPORT

这个函数比上一个IOCTL函数有趣多了。该函数返回与调用者提供的模块基地址相关的IMAGE_EXPORT_DIRECTORY结构。仔细观察列表6-15给出的该函数的实现方式,你会发现它和列表6-10中的SpyModuleExport()极其相似。不过,SpyOutputPeExport()需要完成了更多的工作。这是因为IMAGE_EXPORT_DIRECTORY包含相对地址的缘故,这一点前面已经解释过。在数据被复制到独立的缓冲区之后,调用者还是无法使用这些偏移量,这是因为与这些偏移量相关的基地址已经改变了。在PE表头中没有其他附加的地址信息,因此不可能计算出一个新的与之匹配的基地址。为了减少调用者的工作,SpyOutputPeExport()将所有指向导出节内部的偏移量转换为相对于导出节起始位置的偏移量,这是通过减去它们在IMAGE_DATA_DIRECTORY结构中的VirtualAddress而得到的。地址数组中的数据项必须采用不同的方法进行处理,因为它们引用的是PE Image中的其他节区。因此,SpyOutputPeExport()将它们加上Image的基地址,从而将它们转换为绝对线性地址。

 

NTSTATUS SpyOutputPeExport (PVOID  pBase,

                            PVOID  pOutput,

                            DWORD  dOutput,

                            PDWORD pdInfo)

    {

    PIMAGE_NT_HEADERS       pinh;

    PIMAGE_DATA_DIRECTORY   pidd;

    PIMAGE_EXPORT_DIRECTORY pied;

    PVOID                   pData;

    DWORD                   dData, dBias, i;

    PDWORD                  pdData;

    NTSTATUS                ns = STATUS_INVALID_PARAMETER;

 

    if ((pBase != NULL) && SpyMemoryTestAddress (pBase) &&

        ((pinh = RtlImageNtHeader (pBase)) != NULL))

        {

        pidd = pinh->OptionalHeader.DataDirectory

               + IMAGE_DIRECTORY_ENTRY_EXPORT;

 

        if (pidd->VirtualAddress &&

            (pidd->Size >= IMAGE_EXPORT_DIRECTORY_))

            {

            pData = (PBYTE) pBase + pidd->VirtualAddress;

            dData = pidd->Size;

 

            if ((ns = SpyOutputBinary (pData, dData,

                                       pOutput, dOutput, pdInfo))

                == STATUS_SUCCESS)

                {

                pied  = pOutput;

                dBias = pidd->VirtualAddress;

 

                pied->Name                  -= dBias;

                pied->AddressOfFunctions    -= dBias;

                pied->AddressOfNames        -= dBias;

                pied->AddressOfNameOrdinals -= dBias;

 

                pdData = PTR_ADD (pied, pied->AddressOfFunctions);

 

                for (i = 0; i < pied->NumberOfFunctions; i++)

                    {

                    pdData [i] += (DWORD) pBase;

                    }

                pdData = PTR_ADD (pied, pied->AddressOfNames);

 

                for (i = 0; i < pied->NumberOfNames; i++)

                    {

                    pdData [i] -= dBias;

                    }

                }

            }

        else

            {

            ns = STATUS_DATA_ERROR;

            }

        }

    return ns;

    }

列表6-15.  SPY_IO_PE_EXPORT的实现细节

 

IOCTL函数SPY_IO_PE_SYMBOL

该函数使得用户模式下的应用程序可以访问内核调用接口的符号查找引擎。列表6-16给出了该函数的实现方式,但看起来并不是很让人兴奋,因为它只是列表6-11中的SpyModuleSymbolEx()的外包函数而已。调用者必须传入一个形如”module!symbol”的字符串或者一个”symbol”(如果该symbol属于ntoskrnl.exe)。如果该函数调用成功,函数将返回与符号相关的线性地址,如果调用者提供的symbol无效或者发生了其他错误,则函数返回NULL

 

NTSTATUS SpyOutputPeSymbol (PBYTE  pbSymbol,

                            PVOID  pOutput,

                            DWORD  dOutput,

                            PDWORD pdInfo)

    {

    PVOID    pAddress;

    NTSTATUS ns = STATUS_INVALID_PARAMETER;

 

    if ((pbSymbol != NULL) && SpyMemoryTestAddress (pbSymbol)

        &&

        ((pAddress = SpyModuleSymbolEx (pbSymbol, NULL, &ns))

         != NULL))

        {

        ns = SpyOutputPointer (pAddress,

                               pOutput, dOutput, pdInfo);

        }

    return ns;

    }

列表6-16.  SPY_IO_PE_SYMBOL的实现细节

 

IOCTL函数SPY_IO_CALL

最后是我们等待良久的SPY_IO_CALL函数了。列表6-17提供了该函数的实现细节。如果传入的字符串地址正确,此函数将调用SpyModuleSymbolEx(),如果传入的字符串可以被解析,则继续调用SpyCallEx()。和SPY_IO_PE_SYMBOL类似,此函数期望一个形如”module!symbol”或一个简单的”symbol”作为符号名,符号字符串将作为SPY_CALL_INPUT结构的一部分被初始化。如果成功,SPY_IO_CALL返回一个SPY_CALL_OUTPUT结构,该结构中将包含函数调用的结果(如果传入的符号引用的是一个API函数),或者目标变量的值(如果符号引用的是一个公共变量,如NtBuildNumberKeServiceDescriptorTable)。

 

如果SPY_IO_CALL调用失败,则将不会返回任何数据。调用者必须适当的处理这种情况。忽略这一错误意味着从内核调用接口返回的是无效数据。如果将这样的数据传递给其他内核函数,将会出现错误。如果你很走运,则错误将由SpyCallEx()内的异常处理例程捕获。如果你很不走运,则整个进程都将挂起在Spy deviceIOCTL调用中。而且,这里还存在着出现蓝屏的可能性。但是不要过于担心,在下一节中,将展示如何在用户模式的应用程序中恰当的使用内核调用接口。

 

NTSTATUS SpyOutputCall (PSPY_CALL_INPUT psci,

                        PVOID           pOutput,

                        DWORD           dOutput,

                        PDWORD          pdInfo)

    {

    SPY_CALL_OUTPUT sco;

    NTSTATUS        ns = STATUS_INVALID_PARAMETER;

 

    if (psci->pbSymbol != NULL)

        {

        psci->pEntryPoint =

            (SpyMemoryTestAddress (psci->pbSymbol)

             ? SpyModuleSymbolEx  (psci->pbSymbol, NULL, &ns)

             : NULL);

        }

    if ((psci->pEntryPoint != NULL)              &&

        SpyMemoryTestAddress (psci->pEntryPoint) &&

        ((ns = SpyCallEx (psci, &sco)) == STATUS_SUCCESS))

        {

        ns = SpyOutputBinary (&sco, SPY_CALL_OUTPUT_,

                              pOutput, dOutput, pdInfo);

        }

    return ns;

    }

列表6-17.  SPY_IO_CALL的实现细节

 

 

Next

    在下一节中,将讨论如何将内核调用接口封装到DLL中。

 

 

………….待续………..

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值