• <li id="00i08"><input id="00i08"></input></li>
  • <sup id="00i08"><tbody id="00i08"></tbody></sup>
    <abbr id="00i08"></abbr>
  • 新聞中心

    EEPW首頁 > 嵌入式系統 > 設計應用 > Windows CE API機制初探

    Windows CE API機制初探

    作者: 時間:2011-02-28 來源:網絡 收藏
    創建時間: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 Toolswce420POCKET PC 2003IncludeArmv4kfuncs.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索引定義在PUBLICCOMMONOAKINCpsyscall.h中,如:HT_EVENT、HT_APISET、HT_SOCKET等。

    SystemAPISets共有32個CINFO結構的APIset,通過遍歷SystemAPISets成員,可以列出系統所有API。其中CINFO的結構在PRIVATEWINCEOSCOREOSNKINCkernel.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
    /PRIVATEWINCEOSCOREOSNKINCKernel.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: X acName: %.4s disp: %d type: %d cMethods: %d "
    "ppfnMethods: X pdwSig: X pServer: X %lsn",
    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: X sign Xn",
    j,
    l_pMethod,
    l_dwSign);
    }
    }
    }/for(int i = 0; i NUM_SYSTEM_SETS; i )
    }
    __except(1)
    {
    fprintf(fp, "Exception in DumpApisn");
    }

    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個地址,這些地址實際上就是內核系統調用的實現地址。它們的索引相對PRIVATEWINCEOSCOREOSNKKERNELkwin32.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的數據是什么。先看PUBLICCOMMONOAKINCpkfuncs.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比特被使用。再來看PRIVATEWINCEOSCOREOSNKKERNELARMmdram.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)在PRIVATEWINCEOSCOREOSNKINCkernel.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
    linux操作系統文章專題:linux操作系統詳解(linux不再難懂)


    評論


    相關推薦

    技術專區

    關閉
    主站蜘蛛池模板: 微博| 新平| 吉水县| 鹤庆县| 鸡西市| 台中县| 沙河市| 宜章县| 彰化市| 旬邑县| 伊吾县| 保德县| 巴彦淖尔市| 通许县| 新邵县| 抚顺市| 巴里| 当阳市| 怀集县| 科技| 交城县| 博罗县| 时尚| 平度市| 确山县| 雅安市| 五大连池市| 丽水市| 巴东县| 万宁市| 巴南区| 萝北县| 稻城县| 马尔康县| 会泽县| 吐鲁番市| 华坪县| 江达县| 晋宁县| 大港区| 将乐县|