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
};
这是黑客常用的缓冲攻击代码形式,这里的32为常数,都是ARM机器指令。
我就要想你展示的不是如何攻击WinCE内核,而是看看WinCE的系统调用是如何实现的。WinCE一个常用的API,KernelIoControl,它的实现体实在内核NK.exe,而function caller只是一个trusted的user级别的application。
你可以想象,这样的API的实现,必然要经历CPU从user processer mode切换到supervisor processer mode。
BOOL KernelIoControl(
DWORD dwIoControlCode,
LPVOID lpInBuf,
DWORD nInBufSize,
LPVOID lpOutBuf,
DWORD nOutBufSize,
LPDWORD lpBytesReturned
);
KernelIoControl超过了4个参数,那么在参数传递时,超过的部分使用堆栈完成的。IOCTL_HAL_REBOOT的参数都没什么用,所以这个我们忽略,传0就可以了,因此r1,r2,r3赋值0.
IOCTL_HAL_REBOOT的数值必须放在寄存器r0。
上面的代码中r4的内容会变为0xF000FE74,代码最后就是跳转到0xF000FE74,你可以看到这个地址很大,在整个内存空间的高地址。同时这个地址是经过编码得到,公式是:
0xf0010000-(256*apiset+apinr)*4
对于KernelIoControl,apiset是0,apinr是99。0xF000FE74是这样得到的。
当代码跳转的这样一个高地址时,会引发prefetch abort,这样exception会被内核 ,也就是NK.exe抓到。然后再对这个出错地址进行解析,就可以知道应用程序想访问那个system cal。
综上,WinCE并不是靠SWI这样的软中断实现system call,而是prefetch abort。
Windows CE内核启动分析
移植或者创建一个BSP,也许需要先熟悉Windows CE的内核启动过程.
目录
基于ARM的Windows CE内核启动分析1
1.startup.s2
2.KernelStart2
2.1 ARMInit()3
2.1.1 OALIntrInit3
2.1.2 OALTimerInit4
2.1.2.1 Variable Tick Scheduler4
2.2 KernelInit()4
2.3 FirstSchedule5
1.startup.s
内核入口点startup.S,内核从这里启动.因为内核经过bootloader加载,内核运行时候,已经由bootloader完成了硬件的基本初始化(关闭watchdog, pll设置等等)所以,startup.S的任务比较简单,只是将oemaddrtab_cfg.inc里面的g_oalAddressTable数组地址作为参数,传递给KernelStart,这个数组用来描述和实现物理地址到虚拟地址的映射.
(. + 8)是流水线处理.KernelStart()位于
PRIVATE\WINCEOS\COREOS\NK\KERNEL\ARM\armtrap.s
2.KernelStart
ARMInit()位于本目录的mdram.c文件.
KernelInit()位于PRIVATE\WINCEOS\COREOS\NK\KERNEL\kwin32.c中.
FirstSchedule()位于armtrap.s的一个label.
主要关注ARMInit()和KernelInit(),前一个进行目标板的初始化,后一个负责内核的初始化.FirstSchdule()开始调度第一个程序.
2.1 ARMInit()
先看看ARMInit()它的几个关键性动作如下:
KernelRelocate()是进行重定位.KernelFindMemory()是查找系统可用内存,并分成应用内存和object store两部分.这2个函数都已由MS自己实现.我们需要添加的函数是名字以OEM开头的函数.
OEMInitDebugSerial()初始化一个调试口,我们一般使用一个串口来作为调试口,这个函数需要自己实现,在 PLATFORM\SMDK2440A\Src\Kernel\Oal\debug.c中定义这个函数.比如可以将串口0设置为调试口,在这个函数中对串口0进行初始化.
OEMInit()是一个比较重要的函数,
OALCacheGlobalsInit()在PLATFORM\COMMON\SRC\ARM\COMMON\CACHE\init.s中实现,这部分代码以PQOAL的形式提供.
OALIntrInit()初始化中断.
OALTimerInit()初始化定时器TIMER4,作为系统时钟(tick),
configGPIO()初始化gpio口,设置相关寄存器.
InitDisplay()初始化LCD.有时候,我们希望在oal启动和内核加载期间显示一副等待图片或者显示LOGO,为达到这个目的,需要先初始化LCD.
OALKitlStart()准备启动KITL.
此外,在ARMInit还会通过调试口打印一些基本信息,开始时候打印”Windows CE Kernel for ARM….”字样, 中间打印处理器类型等等信息.结束时候打印” ARMInit done.”
2.1.1 OALIntrInit
调用OALIntrMapInit()初始化2个数组g_oalSysIntr2Irq,g_oalIrq2SysIntr,这2个数组表征irq和逻辑中断SysIntr的映射关系.
然后初始化中断寄存器,
最后,留一个接口给oem: BSPIntrInit(),如果oem需要在这个阶段初始化一些中断,可以定义这个函数并实现.
2.1.2 OALTimerInit
这个函数比较重要. 都知道所有WinCE系统都需要一个定时器来提供一个heartbeat,
g_oalTimer包含各种系统时钟相关的变量.
curridlehigh, curridlelow,这2个32位的DWORD变量合起来实现一个64位的计数器,反映了系统处于空闲模式(Idle mode)的时间。一般在OEMIdle()函数内更新。用户程序通过调用GetIdleTime()函数可以得到这个值。
初始化内核函数指针:pQueryPerformanceFrequency, pQueryPerformanceCounter.通过这两个函数实现高精度的计时器. 这两个函数的原型也已经由PQOAL实现.
初始化TIMER4作为系统时钟.TIMER4是一个16bit的定时器.此函数将TIMER4设置成为自动转载模式.
2.1.2.1 Variable Tick Scheduler
可变的系统时钟节拍,这个是WinCE5.0中增加的新的性能.
每一次定时器中断时候,内核分析所有线程后决定切换到哪个线程运行.假如所有线程都在等待状态,系统将进入idle状态.在这个状态的时候,任何中断都会唤醒系统重新开始调度.一般系统大部分时间是处于idle状态的,内核会调用OEMIdle()进入idle状态,我们已经知道这个状态会被任何中断唤醒. 在以前的版本中,系统中断(即上面的TIMER4中断)每毫秒产生一次,查看系统是否需要重新调度. 为了节电,不希望中断那么频繁.于是WinCE5.0中,在调用OEMIdle()之前会先调用pOEMUpdateRescheduleTime().通过这个函数重新设置侠义次系统时钟中断的时间.
2.2 KernelInit()
再看看KernelInit()函数
不过多关注KernelInit().
2.3 FirstSchedule
位于armtrap.s的一个label.开始第一个线程调度.整个内核开始运行.
//------------------------------------------------------------//
//
//-----------------------------------------------------------//
上一次写了一点critical section的用法,实际上还是比较肤浅,本来做得就不多。这两天主要是研究wince的线程调度。
wince调度的基本单位应该是线程,而且对每一个线程都有一个优先级。wince的优先级是0~255,其中0~247是实时优先级,248~255一般为应用程序的优先级。一个应用程序的优先级默认是251。创建线程的时候没有办法直接设置优先级,但是应用程序可以动态提升自己的线程的优先级,有两个API可用SetThreadPriority和CeSetThreadPriority。前者可在248~255范围内调节,后者可提升至实时线程。对于提升优先级,应用程序的优先级不要高于设备驱动,否则可能会有问题。
线程调度和时间片大小有很大关系,时间片大小一般在OEMinit()中初始化,也可以通过SetThreadQuantum来修改,当然也有对应的Get函数。一般默认是100ms。
wince的调度原则是高优先级线程可以实时抢占低优先级线程的cpu资源。所以在调用SetThreadPriority的时候需要多加注意。因为一旦提升了的线程的优先级高于本线程,本线程将无法得到执行。而把SetThreadPriority放入被提升的线程中也是会引发难以想象的问题。我用的一种办法是主调线程先提升自己的优先级,然后再创建新线程,并设CREATE_SUSPENDED,SetThreadPriority之后再ResumeThread。但是有些场合不太适合这样用。
对于同等级的线程,会轮流使用时间片。同一个进程的多个线程也会参与公平竞争。
对于windows的微内核结构,驱动程序都不是内核进程,device.exe会用一个线程执行驱动程序的函数。而线程的默认优先级就是251,不爽。呵。
去复习考试了,今晚又要少睡。其实从最近几个月来看,过去的几年是睡多了。
//------------------------------------Wince调度----------------//
1.CE线程调度信息
Windows CE调度是基于可抢占时间片轮转调度算法[1],系统根据线程的当前优先级BCPrio来确定哪个线程运行,一个正在运行的线程被抢占运行权后,其剩下的时间信息应保留。
?/P>
名称 | 含义 |
BBPrio | 基本的优先级 |
BCPrio | 当前优先级 |
DwQuantum | 线程所拥有的时间片 |
DwQuantleft | 线程所剩下的时间片 |
2.调度时机
x
x
x
x
3.关于调度的文件
C:\WINCE410\PRIVATE\WINCEOS\COREOS\NK\KERNEL\schedule.c
C:\WINCE410\PRIVATE\WINCEOS\COREOS\NK\INC\ schedule.h
C:\WINCE410\PRIVATE\WINCEOS\COREOS\NK\INC\kernel.h
//-----------------挖掘WinCE 5系统调用过程------------------------//
//网址:http://cpuwolf.blogspot.com/2008/07/wince-5.html
//说明:这个兄弟的博客都是写Wince的,相当有参考价值