代码改变世界

Windows Embedded CE 6.0 Internals (4) The Mechanism of API

2010-03-23 16:55  王克伟  阅读(...)  评论(... 编辑 收藏

引言

在调用系统提供的(System call)或者自己实现的DLL文件(有导出函数)中的API时,我很好奇其中的机制,也就是:我们为什么能调用这些API? 另外,安全和效率总是矛盾的,那么CE如何保证这两者? 现在的CE是不是不堪一击,用户态进程无意的操作是否就能让系统Crash,或者几行Shellcode就能瓦解它的安全体系?

这个问题让我想起来前几天看到的新闻,关于Windows桌面操作系统和Mac系统安全性的比较,就像查理·米勒所说的:

“Mac OS X就像是你生活在一所乡村里的,没有锁的房子,而Windows则是一间只有门闩的城市公寓。”

笔者尝试搞清楚其中的一些问题,欢迎交流,如发现文章错误或者欠妥的地方,请不吝赐教。

 

1.什么是系统调用?

“In computing, a system call is the mechanism by which a program requests a service from an operating system's kernel.”

简单的说就是系统内核提供给程序各种服务的一种方法。与系统调用类似的还有异常、中断(参加WindowsCE异常和中断服务程序初探或者《Windows Internals》)。

“On Unix, Unix-like and other POSIX-compatible Operating Systems, popular system calls are open, read, write, close, wait, exec, fork, exit, and kill. Many of today's operating systems have hundreds of system calls. For example, Linux has 319 different system calls. Similarly, FreeBSD has almost 330.”

系统调用机制可增加安全性,操作系统能更容易的控制应用程序的权限。但是为了提高效率,CE系统会根据调用者是内核态的还是用户态的来做不同的处理,这也是内核态驱动比用户态驱动快的一个原因。

另一方面除了这些直接的系统API可用外,我们还经常看到一些库,这些库处于程序和操作系统之间,比如C library,这些库实现更好的跨平台性(portability)。更高层的,比如.Net Framework,能够提供更方便、更快捷的开发环境。

 

2.系统调用的基本实现原理

我们一般使用 software interrupt 或者 trap 的方式实现CPU控制权的转移。那么当触发一个系统调用时,CPU的执行特权等级会改变(比如ring 3->0),如何实现?

在较早的X86系统中,我们使用call gate机制,而现代系统的使用SYSENTER/SYSEXIT(Intel)和SYSCALL/SYSRET(AMD):
SYSENTER——快速系统调用
sysenter/sysexit 指令
Implement system call with sysenter/sysexit

不知道Android系统调用机制如何(Linux的系统调用机制),期待看到这样的文章,这里有份简单的文档:
http://ubuntubd.files.wordpress.com/2009/12/linux-system-call-quick-reference.pdf

系统调用大致可以分为以下5个种类:

  1. Process Control.
    • end, abort
    • load, execute
    • create process, terminate process
    • get process attributes, set process attributes
    • wait for time
    • wait event, signal event
    • allocate and free memory
  2. File management.
    • create file, delete file
    • open, close
    • read, write, reposition
    • get file attributes, set file attributes
  3. Device Management.
    • request device, release device
    • read, write, reposition
    • get device attributes, set device attributes
    • logically attach or detach devices
  4. Information Maintenance.
    • get time or date, set time or date
    • get system data, set system data
    • get process, file, or device attributes
    • set process, file, or device attributes
  5. Communication.
    • create, delete communication connection
    • send, receive messages
    • transfer status information
    • attach or detach remote devices

 

3.CE中的具体实现

在CE中coredll.dll文件的作用比较类似于桌面操作系统kernel32.dll的作用。我们看看维基百科中的关于kernel32.dll的介绍:

Base Services[3]
Provide access to the fundamental resources available to a Windows system. Included are things like file systems, devices, processes and threads, and error handling. These functions reside in kernel.exe, krnl286.exe or krnl386.exe files on 16-bit Windows, and kernel32.dll on 32-bit Windows.

 更多请看这里:Windows API - Wikipedia, the free encyclopedia

另外我们注意到 k.coredll.dll 是内核版本的coredll.dll,更多请参考:Windows Embedded CE 6.0 Internals (1) Kernel Overview

“The kernel-mode servers are supported by a kernel-only version of COREDLL, named K.COREDLL.DLL. Any code that loads into the kernel but was linked against COREDLL.DLL, is automatically redirected to use K.COREDLL.DLL instead. ”

下文我们一直围绕着一个问题开展:从应用层发起的API调用首先会定位到coredll.dll中的位置,如何定位?

打开\PRIVATE\WINCEOS\COREOS\NK\INC\nkarm.h,我们看到一些结构:

/* High memory layout
 *
 * This structure is mapped in at the end of the 4GB virtual
 * address space.
 *
 *  0xFFFD0000 - first level page table (uncached) (2nd half is r/o)
 *  0xFFFD4000 - disabled for protection
 *  0xFFFE0000 - second level page tables (uncached)
 *  0xFFFE4000 - disabled for protection
 *  0xFFFF0000 - exception vectors
 *  0xFFFF0400 - not used (r/o)
 *  0xFFFF1000 - disabled for protection
 *  0xFFFF2000 - r/o (physical overlaps with vectors)
 *  0xFFFF2400 - Interrupt stack (1k)
 *  0xFFFF2800 - r/o (physical overlaps with Abort stack & FIQ stack)
 *  0xFFFF3000 - disabled for protection
 *  0xFFFF4000 - r/o (physical memory overlaps with vectors & intr. stack & FIQ stack)
 *  0xFFFF4900 - Abort stack (2k - 256 bytes)
 *  0xFFFF5000 - disabled for protection
 *  0xFFFF6000 - r/o (physical memory overlaps with vectors & intr. stack)
 *  0xFFFF6800 - FIQ stack (256 bytes)
 *  0xFFFF6900 - r/o (physical memory overlaps with Abort stack)
 *  0xFFFF7000 - disabled
 *  0xFFFFC000 - kernel stack
 *  0xFFFFC800 - KDataStruct
 *  0xFFFFCC00 - disabled for protection (2nd level page table for 0xFFF00000)
 */

typedef struct ARM_HIGH {
    ulong   firstPT[4096];      // 0xFFFD0000: 1st level page table
    char    reserved2[0x20000-0x4000];

    char    exVectors[0x400];   // 0xFFFF0000: exception vectors
    char    reserved3[0x2400-0x400];

    char    intrStack[0x400];   // 0xFFFF2400: interrupt stack
    char    reserved4[0x4900-0x2800];

    char    abortStack[0x700];  // 0xFFFF4900: abort stack
    char    reserved5[0x6800-0x5000];

    char    fiqStack[0x100];    // 0xFFFF6800: FIQ stack
    char    reserved6[0xC000-0x6900];

    char    kStack[0x800];      // 0xFFFFC000: kernel stack
    struct KDataStruct kdata;   // 0xFFFFC800: kernel data page
} ARM_HIGH;

我们看到KDataStruct这个结构(这个结构在CE 4.0和6.0上有所不同),其开始地址是固定的PUserKData(参考\PUBLIC\COMMON\SDK\INC\ kfuncs.h),对于ARM处理器是0xFFFFC800,而其它处理器是0x00005800。

// NOTE: only kernel and coredll should access PUserKData directly
//       or potentially BC Break once we move on to the next OS.
#if defined(_ARM_)
    #define PUserKData        ((LPBYTE)0xFFFFC800)
#else
    #define PUserKData        ((LPBYTE)0x00005800)
#endif
struct KDataStruct {

#include "kdata_common.h"

    // CPU Dependent part uses offset 0x2a0 - 0x300, and after aInfo
    ulong       dwArchitectureId;           // 0x2a0 architecture id
    LPVOID      pAddrMap;                   // 0x2a4 ptr to OEMAddressTable array
    DWORD       dwRead;                     // 0x2a8 - R/O, both kernel and user
    DWORD       dwWrite;                    // 0x2ac - R/W, both kernel and user
    DWORD       dwKrwUno;                   // 0x2b0 - Kernel R/W, user no access
    DWORD       dwKrwUro;                   // 0x2b4 - Kernel R/W, user no access
    DWORD       dwProtMask;                 // 0x2b8 - mask for all the permission bits
    DWORD       dwOEMInitGlobalsAddr;       // 0x2bc ptr to OAL entry point
    DWORD       dwTTBRCtrlBits;             // 0x2c0 - control bits for TTBR (see above TTBR Bit formats)
    DWORD       dwPTExtraBits;              // 0x2c4 - extra bits for PT entry
    long        alPad[14];                  // 0x2c8 - padding

    // aInfo MUST BE AT OFFSET 0x300
    DWORD       aInfo[32];      /* 0x300 - misc. kernel info */
                                /* 0x380 - interlocked api code */
                                /* 0x400 - end */
};  /* KDataStruct */

aInfo[32]中详细布局如下(参考\PUBLIC\COMMON\OAK\INC\pkfuncs.h):

        #define KINX_PROCARRAY  0    /* 0x300 address of process array */
        #define KINX_PAGESIZE   1    /* 0x304 system page size */
        #define KINX_PFN_SHIFT  2    /* 0x308 shift for page # in PTE */
        #define KINX_PFN_MASK   3    /* 0x30c mask for page # in PTE */
        #define KINX_PAGEFREE   4    /* 0x310 # of free physical pages */
        #define KINX_SYSPAGES   5    /* 0x314 # of pages used by kernel */
        #define KINX_KHEAP      6    /* 0x318 ptr to kernel heap array */
        #define KINX_SECTIONS   7    /* 0x31c ptr to SectionTable array */
        #define KINX_MEMINFO    8    /* 0x320 ptr to system MemoryInfo struct */
        #define KINX_MODULES    9    /* 0x324 ptr to module list */
        #define KINX_DLL_LOW   10    /* 0x328 lower bound of DLL shared space */
        #define KINX_NUMPAGES  11    /* 0x32c total # of RAM pages */
        #define KINX_PTOC      12    /* 0x330 ptr to ROM table of contents */
        #define KINX_KDATA_ADDR 13   /* 0x334 kernel mode version of KData */
        #define KINX_GWESHEAPINFO 14 /* 0x338 Current amount of gwes heap in use */
        #define KINX_TIMEZONEBIAS 15 /* 0x33c Fast timezone bias info */
        #define KINX_PENDEVENTS 16   /* 0x340 bit mask for pending interrupt events */
        #define KINX_KERNRESERVE 17  /* 0x344 number of kernel reserved pages */
        #define KINX_API_MASK 18     /* 0x348 bit mask for registered api sets */
        #define KINX_NLS_CP 19       /* 0x34c hiword OEM code page, loword ANSI code page */
        #define KINX_NLS_SYSLOC 20   /* 0x350 Default System locale */
        #define KINX_NLS_USERLOC 21  /* 0x354 Default User locale */
        #define KINX_HEAP_WASTE 22   /* 0x358 Kernel heap wasted space */
        #define KINX_DEBUGGER 23     /* 0x35c For use by debugger for protocol communication */
        #define KINX_APISETS 24      /* 0x360 APIset pointers */
        #define KINX_MINPAGEFREE 25  /* 0x364 water mark of the minimum number of free pages */
        #define KINX_CELOGSTATUS 26  /* 0x368 CeLog status flags */
        #define KINX_NKSECTION  27   /* 0x36c Address of NKSection */
        #define KINX_PWR_EVTS   28   /* 0x370 Events to be set after power on */

        #define KINX_NKSIG     31    /* 0x37c last entry of KINFO -- signature when NK is ready */
                                     /* 0x380 - interlocked api code */
                                     /* 0x400 - end */

偏移KINFO_OFFSET(即0x300)的是UserKInfo数组,里面保存了重要的系统数据,比如模块链表、内核堆、APIset pointers表(SystemAPISets)。

偏移0x324的aInfo[KINX_MODULES]是一个指向模块链表的指针,通过这个链表可以找到coredll.dll模块。(更详细的资料请参考:Windows CE初探 ,但是该文章介绍的是较早的CE 4.2,CE 6.0可能有所区别。)

APIset pointers表存放在UserKInfo[KINX_APISETS]处。从下图我们能更直观的看到内存映射,但是该图是CE 4.2的,CE 6.0在KINFO_OFFSET以上部分有所不同。

clip_image001


打开\PUBLIC\COMMON\SDK\INC\kfuncs.h我们发现:

//
// We now support 128 API sets. Where the API numbers are defined as follows
// 
//      0:      kernel WIN32 API set
//      1-63:   OS reserved Handle based API sets
//      64-79:  partner defined Handle based API sets
//      80-111: OS reserved non-handle based API sets
//      112-127:partner defined non-hnadle based API sets
//
// non-handle-based APIs (API set 80-127) will receive PSL notifications.
//
//
#define SH_FIRST_OS_HAPI_SET    1       // 1st OS, handle based API set
#define SH_FIRST_EXT_HAPI_SET   64      // 1st partner defined handle based API set
#define SH_FIRST_OS_API_SET     80      // 1st OS non-handle based API set
#define SH_FIRST_EXT_API_SET    112     // 1st partner defined non-handle based API set

#define SH_WIN32                0

#define SH_CURTHREAD            1
#define SH_CURPROC              2
#define SH_CURTOKEN             3

// Special handle indicies used for "typed" handle calls
#define HT_EVENT                4       // Event handle type
#define HT_MUTEX                5       // Mutex handle type
#define HT_APISET               6       // kernel API set handle type
#define HT_FILE                 7       // open file handle type
#define HT_FIND                 8       // FindFirst handle type
#define HT_DBFILE               9       // open database handle type
#define HT_DBFIND               10      // database find handle type
#define HT_SOCKET               11      // WinSock open socket handle type
#define HT_CRITSEC              12      // Critical section
#define HT_SEMAPHORE            13      // Semaphore handle type
#define HT_FSMAP                14      // mapped files
#define HT_WNETENUM             15      // Net Resource Enumeration
#define HT_AFSVOLUME            16      // file system volume handle type
#define HT_NAMESPACE            17      // namespace type
#define HT_POLICY               18      // policy type
#define HT_SECLOADER            19      // secure loader type

#define SH_LAST_NOTIFY          SH_FIRST_OS_API_SET    // Last set notified on Thread/Process Termination

#define SH_GDI                  (SH_LAST_NOTIFY+0)
#define SH_WMGR                 (SH_LAST_NOTIFY+1)
#define SH_WNET                 (SH_LAST_NOTIFY+2)      // WNet APIs for network redirector
#define SH_COMM                 (SH_LAST_NOTIFY+3)      // Communications not "COM"
#define SH_FILESYS_APIS         (SH_LAST_NOTIFY+4)      // File system APIS
#define SH_SHELL                (SH_LAST_NOTIFY+5)
#define SH_DEVMGR_APIS          (SH_LAST_NOTIFY+6)      // File system device manager
#define SH_TAPI                 (SH_LAST_NOTIFY+7)
#define SH_CPROG                (SH_LAST_NOTIFY+8)      // Cprog APIS
#define SH_SERVICES             (SH_LAST_NOTIFY+10)
#define SH_DDRAW                (SH_LAST_NOTIFY+11)
#define SH_GWEUSER              (SH_LAST_NOTIFY+13)
#define SH_CONNMGR_LEGACY       (SH_LAST_NOTIFY+14)     // ConnMgr legacy API
#define SH_DMSRV                (SH_LAST_NOTIFY+15)     // Device Management APIs
#define SH_INPUT                (SH_LAST_NOTIFY+16)     // Input API
#define SH_COMPOSITOR           (SH_LAST_NOTIFY+17)     // Window Composition
#define SH_NETCF                (SH_LAST_NOTIFY+18)     // .NET Compact Framework sever
#define SH_LASTRESERVED         (SH_FIRST_EXT_API_SET-1)

CE目前支持128个API集。

“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。”(引用自Windows CE API机制初探)

struct _APISET {
    CINFO   cinfo;      /* description of the API set */
    int     iReg;       /* registered API set index (-1 if not registered) */
};


其中CINFO的结构在\PRIVATE\WINCEOS\COREOS\NK\INC\apicall.h和\PRIVATE\WINCEOS\COREOS\NK\INC\kerncmn.h中定义:

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   *ppfnExtMethods; /* 08: ptr to array of methods (for external interface) */
    const PFNVOID   *ppfnIntMethods; /* 0C: ptr to array of methods (for internal interface) */
    const ULONGLONG *pu64Sig;        /* 10: ptr to array of method signatures */
    DWORD           dwServerId;      /* 14: server process id */
    PHDATA          phdApiSet;       /* 18: HDATA of API set */
    PFNAPIERRHANDLER pfnErrorHandler; /* 1C: ptr to the API set error handler (could be NULL) */
    const DWORD     *lprgAccessMask; /* 20: ptr to the access mask associated with every API in this API set; valid only for handle based API sets */
};

“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。”(引用自Windows CE API机制初探,在CE 6.0上这个还有待验证。)

了解了其中的原理之后,我们可以实现API的劫持,以便满足一些需求,比如文件监控,当然也能干些见不得人的事。我还存在的疑问是:

UserKInfo[KINX_APISETS]在什么时候初始化?怎么初始化?

coredll.dll如何对API进行包裹的?

疑问还非常多,这些问题以后慢慢挖掘吧,今天头已经大了。

 

更多资料

Dr. Dobb's | Spy: A Windows CE API Interceptor | October 1, 2003

API Spying Techniques for Windows 9x, NT and 2000

Hacking Windows CE

Details Emerge on the First Windows Mobile Virus

 

Windows Embedded CE 6.0 Internals (1) Kernel Overview

Windows Embedded CE 6.0 Internals (2) Memory

Windows Embedded CE 6.0 Internals (3) Memory Continuation