摘錄—Windows CE API機制初探

來源:互聯網
上載者:User

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

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.