代码跨段跳转和调用门
文中如有错误,还请大佬指正
前言
已知的段寄存器有:ES, CS, SS, DS, FS, GS, LDTR, GDTR, IDTR, TR
每个寄存器都可以通过一些指令来修改他们的值。如ES, SS, DS, FS, GS可以通过mov
, LES
, LSS
, LDS
, LFS
, 'LGS’来修改,LDTR, GDTR, IDTR, TR可以通过 LLDT
, LGDT
, LIDT
, LTR
来修改
唯独CS的修改比较特殊。首先CS寄存器会直接影响到当前执行代码的位置 (当前执行代码的线性地址 = CS.base + EIP),所以只修改CS寄存器可能会使得程序跑偏了。所以没有提供只修改CS寄存器的指令,要修改CS寄存器,只能连同EIP一起修改,这样才能保证程序的正确性。
jmp far指令
与之前所看到的jmp有所不同,以前我们写的jmp是这样的: jmp EIP
,这个称为段间跳转,或者叫短跳;而现在jmp指令改写成这样jmp CS:EIP
,这个称为跨段跳转,或者叫长跳(jmp far)。
jmp far的执行顺序
首先有一条jmp far指令:jmp 0x1B:0x12345678
-
第一个地址是段选择子,所以要把0x1B按照段选择子的拆分方法进行分解,index = 3, TI = 0, RPL = 3
-
TI == 0,所以查GDT表index为3的位置(index要从0开始算起),而且段描述符所描述的段必须是代码段,否则就会进入异常处理
描述符要代表代码段:P == 1, S == 1, Type的最高位(第11位) == 1
(忘了可以看看前写的段描述符)
-
如果Type的E位(第10位) == 1代表非一致代码段,这时候要求 CPL == DPL && RPL <= CPL
如果Type的E位(第10位) == 0代表一致代码段,这时候要求 CPL >= DPL -
上面的检查全通过了之后才能将段选择子和段描述符加载到段寄存器
-
最后使用CS.base + EIP
小实验
首先通过windbg查询gdt表的位置,将一个空白的位置用index = 3 的描述符进行填充如图所示
eq 8003f048 00cffb00`0000ffff
(GDTR = 0x8003f000)
然后计算index的值,构造段选择子,index = (0x8003f048 - 0x8003f000) / 8== = 9
所以段选择子为 1001 0 11 = 0x4B
打开OD,修改指令为jmp 0x4B:1003E30
,并观察当前的CS寄存器的值
然后执行这条指令
观察CS的值,变成了 0x4B
我们尝试将DPL改成0试试,意思是需要0环权限才能访问此段
图中红线部分变成了9
eq 8003f048 00cf9b00 0000ffff
依旧是修改成这条指令,执行试试
进入了ntdll模块,不是我们想要的位置,这是个异常函数,不是我们想去的位置
证明3环不能直接跳到0环去执行
修改一些描述符,将其改成0环代码的一致代码段
eq 8003f048 00cf9f00`0000ffff
图中b改成 f
可以看到跳转成功了,验证了一直代码段的作用
并且看到CS的权限依旧是3环的,所以一致代码段并不能提升权限
总结
jmp far 只能跳转到 相同权限的地方 和 不同权限的一致代码段