《UEFI内核导读》第0篇SEC Reset Vector

不少介绍软件开发的书籍都喜欢用“第一行代码学xx”开始,我们也是大俗人,所以也采用“第一条指令讲UEFI”的模式,但与之不同的是一般介绍软件的书籍都是从源码的角度来剖析第一行代码怎么执行的,第一个“HelloWorld“是怎么输出的,这种方式虽容易入手但也有以下缺陷:

  1. 直接讲源码并不能理解在CPU视角下第一条指令是怎样被取指并执行的

  2. UEFI源码构建的过程比较复杂,从源码角度很难理解CPU是如何查找并跳转到第一条指令。

        本文尝试以最底层CPU的视角,介绍从处理器完成硬上电件初始化之后如何开始它的第一条指令的寻址、并执行第一条BIOS代码的过程。因此我们采取一种“伪逆向“的方式,从最终构建生成的二进制文件着手,试图从最底层视角去了解我们CPU是如何在硬件reset之后从我们的BIOS ROM里面寻找到第一条指令、查找到SEC Core的位置并跳转到SEC Core代码的过程。

        所谓SEC就是CPU刚完成硬件初始化的时候执行的与CPU体系架构息息相关的代码最基础的初始化代码,主要为后续CPU以及Chipset初始化代码所需的必备的环境做准备。以下以X86架构为例大概总结了SEC阶段主要的任务:

  1. RestVector的初始化,以及异常向量的初始化

  2. CPU工作模式的切换

  3. Enable Memory map PCI-E Config Space,RCBA,MCHBAR,GPIOBASE,PMBASE,HPEC等

  4. MicroCode的加载(或FIT table)

  5. 侦测系统中的CPU的数量等BIST

  6. 初始化NEM(至此我们基本从渺无人烟的荒漠跳到了小绿洲了)

  7. 跳转到PEICore至此SEC阶段结束(继续在这片贫瘠的小绿洲上辛勤耕耘,亟待解救)

  8. PEICore初始化内存(这次算是从绿洲迁移到土地肥沃富庶的中原地带了)

  9. 从PEI跳到DXECore(在核心的中原腹地大展拳脚,攻城略地)

  10. 从DXE历经BDS引导操作系统,交由OS来掌控大权。(功成身退,为避兔死狗烹,正式进行权利交接,BIOS退出历史舞台,到后台享清福去)

ResetVector

        使用RW来读取memory的0xfffffff0位置就像下面截图1一样,不过在做这个之前,下确认下你的主板的BIOS是否是UEFI模式,本人的NB是intel的HM86用的是AMI Aptio的core。这里仅仅以intel的板子为例,AMD的可自行研究,但是方法一样。

        上面的90 90就是CPU上电之后执行的第一条指令,可以查intel的文档,这两个字节其实是表示两条指令,NOP NOP意思是CPU什么也不做,等待两个指令周期。

        然后下一条指令是E9 C3F8 这里的意思是一条跳转指令,意思是JMP 0xF8C3,这是一条段内相对跳转指令,CPU只改变IP地址,不改变段地址。跳转的目标地址是:0xfffffff5+0xF8C3 =?这地址是多少,会算吗?这里有好几个陷阱需要我们注意:

  1. JMP指令是基于当前的下一条地址往另外一个地址做段内跳转,所以是0xfffffff5

  2. X86是小端模式,所以目标的偏移地址是0xF8C3,而不是0xC3F8

  3. 0xF8C3是一个负数,所以跳转地址是在0xfffffff5基础上往回跳,否则IP已经指向了0xfffffff5,再跳就跳出了4G的了,所以最终的结果是:0xfffffff5+0xF8C3 =0xfffff8B8

图片

图1

        下面2就是CPU完成了ResetVector跳转之后执行的第一条有意义的指令,我们通过查询intel的手册:FINIT指令(0xdb,0xe3)用来清除浮点数据寄存器栈和异常,初始化FPU,为程序提供一个“干净”的初始状态。否则遗留在浮点寄存器栈中的数据可能会产生堆栈溢出。至此第一条指令的讲解到此为止,具体的命令可以查看intel的IA32/X64指令集文档有详细的介绍。

图片

图2

SEC Core

      刚刚说完了第一条指令,下面来说重点SEC Core。那么说了半天SECCore到底在哪里呢,其实刚刚我们看到的第一条指令就是SECCore的入口,经过刚刚的一番折腾其实我们已经进入到了SECCore,那么现在我们来看看真正的SECCore。

        这回还是使用RW,从下面的这张图可以看到在0xffffff60的位置有如下的一系列数字,这个看似无序的数字其实是有着特殊的意义的,相信高手一眼就能可能出端倪来。

        仔细看看能看到什么?没错这个从offset 00~0F其实是一个很特殊的数字,我们把他称之为top file GUID当然它还有一个更为官方的名字叫做Volume Top File (VTF),从这里开始到我们刚刚的ResetVector结束这一段所有的内容就是我们的SECCore。看起来很诡异是吧,其实不然,这个都是有文档规定的,细节可以参考intel的大叔们写的那一堆的文档。

        接着往下看图3,Offset 10~17,这里是SECCore的FFS file header,意思是说,SECCore文件的校验和是(0xAA5e),文件是0x03(SECCore),属性是0x00(1 Byte对齐),文件大小是0xaa0字节,当前文件状态是0xF8(可查看相关文档具体信息);再往下就是offset 18~ 1b,这里表示FFS file section大小是0x0a44字节,Section类型是0x10(PE32类型),这里我们找到了第一个的FFS file section;

        如图4接着刚刚的步骤递归的往下找,我们可以找到第二个section在0xffffffbc的位置,其大小为0x44字节,类型为0x19(RAW类型),其实深究的话你会发现这个部分并不是我们的汇编语言编译产生的,而是在我们的代码编译完成之后使用特殊的工具来修改我们的SECcore代码,把这部分的二进制的文档强行插入进来的。然后修改部分的地址定位和文件大小的偏移。细看下底下的第二幅图4和最上面的图1是否有些相似,是的,其实这两个就是同一内容,只不过是从不同的方式去找到他们并且读出来而已。

图片

图3

图片

图4

        现在我接着看图3,来看下PE32 section里面有什么东西。我们先紧接着(3)来看,仔细看图3的Offset 1c~1d看能看到什么,“MZ”没错,这个就是传说中的幻数,说起这个幻数,我还查了一下资料,这是是MS的一位大叔Mark Zbikowski的名字的缩写,牛人就是牛人,让所有的KB的程序猿都记住他的大名了,更可恨的是这辈子咱们离不开它,你不用他还不行。

        看到了幻数我们就距离真相不远了,看下下面的数据结构,偏移量为0x3d的位置是0xB8(e_lfanew),然后我们再看offset 0xB8的位置,看到了什么 "PE",没错看到了熟悉的字符,这个就表示这里是这个PE32 格式section的文件头,剩下的就是一些关于PE32/PE32+格式的一些头,大家可以自行研究,再附上一张图5展示PE的格式。方便进行之后的分析。

typedef struct {

  UINT16  e_magic;    // Magic number

  UINT16  e_cblp;     // Bytes on last page of file

  UINT16  e_cp;       // Pages in file

  UINT16  e_crlc;     // Relocations

  UINT16  e_cparhdr;  // Size of header in paragraphs

  UINT16  e_minalloc; // Minimum extra paragraphs needed

  UINT16  e_maxalloc; // Maximum extra paragraphs needed

  UINT16  e_ss;       // Initial (relative) SS value

  UINT16  e_sp;       // Initial SP value

  UINT16  e_csum;     // Checksum

  UINT16  e_ip;       // Initial IP value

  UINT16  e_cs;       // Initial (relative) CS value

  UINT16  e_lfarlc;   // File address of relocation table

  UINT16  e_ovno;     // Overlay number

  UINT16  e_res[4];   // Reserved words

  UINT16  e_oemid;    // OEM identifier (for e_oeminfo)

  UINT16  e_oeminfo;  // OEM information; e_oemid specific

  UINT16  e_res2[10]; // Reserved words

  UINT32  e_lfanew;   // File address of new exe header//偏移量为0x3d

} EFI_IMAGE_DOS_HEADER;

图片

图5

        本文以X86平台为例介绍SEC Reset Vector,不同的体系架构有自己独特的实现方式,如ARM架构的第一条指令就是从0x00000000开始取指。而且不同架构其指令集有不同的安全启动模式如:Trustzone、BootGuard等,但是其目的都是一样的那就是完成从硬件到第一条软件指令的交接过程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值