avatar

Catalog
代码跨段跳转(不提权)

之前的篇章中提到过,除了CS段寄存器外,均可使用MOV或LES,LSS,LDS,LFS,LGS指令进行修改;为什么CS不可以直接被修改呢?

CS是代码段的段寄存器,CS的改变意味着EIP的改变,所以无法使用上述指令进行修改

代码跳转指令

代码的跳转指令分为2种,一种是同时修改CS和EIP的指令,另一种,只修改EIP指令,具体如下:

  • 同时修改CS和EIP:JMP FAR/ CALL FAR / RETF / INT / IRETD
  • 只修改EIP:JMP / CALL / JCC / RET

本篇用到的是JMP FAR指令。

JMP FAR指令:和普通的JMP指令不同,JMP FAR实际是在JMP指令后写上6个字节,例如 JMP 0x4B: 0x00401456的形式。

其中0x4B是段选择子,0x00401456是跳转地址。

若能成功执行,0x4B会写入CS中,0x00401456会写入EIP中,代码发生跳转。

代码跳转流程

JMP 0x20:0x004183D7 CPU如何执行这行代码?

段选择子拆分

0x20 对应二进制 0x0000 0000 0010 0000

  • RPL:00
  • TI:0
  • Index:4

查表得到段描述符

  • TI = 0 所以查GDT表
  • 根据Index = 4 找到对应的段描述符
  • 四种情况可以跳转:代码段、调用门、TSS任务段、任务门(其中调用门,TSS任务段以及任务门,都属于系统段)

之前段描述符的篇章提到过,可以根据段描述符的属性判断属于哪个段,通过看属性的那16位:由于代码段和数据段的S位值为1,所以一般第12-15位为字节9/F,而代码段的Type域的第1位为1,所以代码段的Type域一定大于等于8。因而判断代码段描述符的第8-15位通常在98-9F或者F8-FF这个范围内。

权限检查

权限检查分为非一致代码段和一致代码段两种情况,下面我们分别来看

非一致代码段

非一致代码段,要求:CPL == DPL 并且 RPL <= DPL(数值上)

这个比较好理解,说白了,就是要求跳转必须在同级别进行,只能Ring0跳转到Ring0,或Ring3跳转Ring3,Ring0和Ring3相互之间不能跳转。

先看一个CPL == DPL的例子:

首先选定一个地址0x8003f048,挂上一个段描述符(这里使用了一个eq指令,这个指令和dq相似,d是查询内存,e就是往内存里写入,非常好理解),这个段描述符的属性为cffb,分析可知是一个DPL=3的非一致代码段

设置指令如下 JMP 0x4B:0x00401456(0x4B对应着8003f048处的段描述符,跳转地址是随便选的,选个近的方便查看)

执行后如下

发现跳转成功,同时CS段寄存器被改为自己设定的值,这说明满足条件,的确可以跳转。

下面来看一个CPL > DPL(数值上)的例子

这次我们修改一下段描述符,令属性变为c098,拆分可知,这是一个DPL = 0的非一致代码段

再次尝试执行:

结果跳转到了异常处理程序里,这说明了,在CPL > DPL的情况下,是不能跳转的。

至于RPL <= DPL的要求,可以参考数据段权限检查的例子,原因是相似的。

一致代码段

一致代码段,要求: CPL >= DPL

这条件很奇怪,为什么要求访问一致代码段反而需要权限更小才允许访问呢?

一致代码段又称作共享段,这是Windows给3环程序提供一些通用的功能,使得3环程序可以访问0环的一些代码段,这些功能并不会破坏内核,也不能提升3环程序的权限,因此可以让低权限的段访问。

下面我们来构造一个一致代码段的段描述符,具体如下,令属性为cf9f,是一个DPL = 0的一致代码段

单步执行

发现可以跳转,说明一致代码段是可以实现低权限程序进行跳转的。

加载段描述符

其实根据上面的实验,也都知道跳转流程后面的步骤了,这里还是简要说下。当段权限检查通过后,CPU会将段描述符加载到CS段寄存器中

代码执行

段描述符加载完后,CPU将 CS.Base + Offset(JMP FAR后面的那个跳转地址) 的值写入EIP 然后执行CS:EIP处的代码,段间跳转结束。

总结

  1. 为了对数据进行保护,普通代码段是禁止不同级别进行访问的。用户态的代码不能访问内核的数据,同样,内核态的代码也不能访问用户态的数据。
  2. 如果想提供一些通用的功能,而且这些功能并不会破坏内核数据,那么可以选择一致代码段,这样低级别的程序可以在不提升CPL权限等级的情况下即可以访问。
  3. 如果想访问普通代码段,只有通过“调用门”等提升CPL权限,才能访问。

参考教程:https://www.bilibili.com/video/av68700135?p=14

踩的一些坑

G位/Limit限制一致代码段

在一致代码段的实验过程中,我以为只要是满足属性是**9f/9e/9f/9c这样的一致代码段, 就一定可以跳转,结果并不是,而且很多都没有跳转。然后发现,实际上受到了G位或者Limit的限制,先说G位:

G位是粒度位,用来设置Limit的单位,当G=1时,Limit的单位是4KB,当G=0时,Limit的单位是字节;这是段描述符的知识。为什么说G位会有影响呢,举个简单的例子,比如代码的载入地址是0x401420,你想通过一致代码段跳到0x401456的位置,如下图:

假设段描述符是004f9f00`0000ffff,这是一个DPL = 0的一致代码段,Base=00000000,Limit=FFFFF,由于粒度G=0,所以Limit就只有这么大,因此能够访问的地址,仅仅只有0~FFFFF这么大的范围,超过了这个范围,就访问不到了,而你的载入地址是0x401420,你想跳转到0x401456,这两个地址都访问不到,不在范围内,因此跳转是不可能成功的。

接下来再说说Limit,是不是只要G位,置1就可以了?这也不是,说到底还是和Limit写入段寄存器的大小有关,就算G位置1,假设段描述符是00c09f00-000000ff,这时段描述符描述的是一个DPL = 0的一致代码段,且G=1,Base = 00000000,这时我们来算一下Limit,Limit = (FF+1)*4KB - 1 = 100000 - 1 = FFFFF,发现了吧,结果还是FFFFF,范围和刚刚的一样,你还是跳转不了。那怎么办,就继续加呗,把段描述符改成00c09f00-00000fff,这样Limit算下来就是FFFFFF,可以跳转的范围就是0~FFFFFF,而0x401420和0x401456显然都在这个范围内。

Base到底改没改?

这个问题困扰了大半个下午,试了半天没试出来个所以然,最后多亏Joney大哥(这是他的博客)解惑。那这是个什么情况呢?我们来康康:

根据代码跳转流程可以发现,在跳转执行前,会先将段描述符加载到段寄存器上,那也就是说,跳转前,CS的Base,Limit,Attribute,都来源于刚刚加载进来的段描述符。在上面这个坑中,已经阐述了G位和Limit对于一致代码段起到的限制作用,说明Attribute和Limit的确都是写入后的,那Base到底起没起作用呢?根据之前段属性探究的那篇可以知道:代码真正执行的地址其实是CS.Base+OFFSET,所以这次我们再次修改段描述符,这次修改成00cf9f00-0003ffff,这是一个DPL = 0的一致代码段,其中G=1,Limit=FFFFFFFF,Base=00000003。这样我们尝试执行代码JMP FAR 0x4B: 00401456,根据Base,猜测会跳到00401459的位置,结果:

还是跳到了0x00401456,难道段描述符没有修改Base?当我困惑的时候,Joney说,OD根本识别不出来Base到底改没改,所以一直默认CS.Base = 0,但其实已经改了!喔!原来如此!这是我们再添加两条指令,分别放在0x401456和0x401459的位置,然后单步:

不可思议的事情发生了!指令刚刚执行到0x401459说明前一个执行的指令是0x401456处的,但是观察右侧通用寄存器,修改的是ecx的值,而非eax的值,也就是说,刚刚执行的指令其实是位于0x401459处的add ecx, 1。这下终于搞清楚了,CS.Base的确得到了修改,只是OD没法正确的显示罢了。

同样,在执行完JMP FAR指令后,会发现,Limit = 0,长度=16bit的情况,这也是因为OD没法正确显示出跨段跳的结果。

以上就是在代码段跨段实验时遇到的两个坑,花费了些时间,但还是很有收获的,需要坚持下去。

Author: cataLoc
Link: http://cataloc.gitee.io/blog/2020/03/13/%E4%BB%A3%E7%A0%81%E8%B7%A8%E6%AE%B5%E8%B7%B3%E8%BD%AC/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
    微信
  • 支付寶
    支付寶