在调用系统提供的(System call)或者自己实现的DLL文件(有导出函数)中的API时,我很好奇其中的机制,也就是:我们为什么能调用这些API? 另外,安全和效率总是矛盾的,那么CE如何保证这两者? 现在的CE是不是不堪一击,用户态进程无意的操作是否就能让系统Crash,或者几行Shellcode就能瓦解它的安全体系?
这个问题让我想起来前几天看到的新闻,关于Windows桌面操作系统和Mac系统安全性的比较,就像查理·米勒所说的:
“Mac OS X就像是你生活在一所乡村里的,没有锁的房子,而Windows则是一间只有门闩的城市公寓。”
笔者尝试搞清楚其中的一些问题,欢迎交流,如发现文章错误或者欠妥的地方,请不吝赐教。
“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,能够提供更方便、更快捷的开发环境。
我们一般使用 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个种类:
在CE中coredll.dll文件的作用比较类似于桌面操作系统kernel32.dll的作用。我们看看维基百科中的关于kernel32.dll的介绍:
另外我们注意到 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以上部分有所不同。
打开\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
Details Emerge on the First Windows Mobile Virus