数据段代码段权限规则
首先认识一下裸函数,可以看见,当函数中什么也没写编译器也仍然会进行栈的初始化操作,而如果是裸函数,是不会有这些初始化操作的。


不过裸函数中需要至少一个ret返回,不然当程序进行到这里会直接异常结束,因为这里并没有指令。
1.段权限规则 代码段与数据段 堆栈段

段是硬件资源的划分
如何判断运行在3环下还是0环下呢?
要看cs段与ss段,只有两个同时处在0环下才是内核环境,但由于cs段与ss段的权限总是一致的,所以一般看cs就可以

DPL:descriptor privilege level
CPL:current......(cs段描述符的dpl)
RPL:request......(cs ds ss后面两位)

举例:当ds=23 rpl=3,那就是一个三环的请求权限
如果是20,就是一个0环的请求来访问,但接不接受不一定。
普通的数据段下,是可以随意给的
我们来进行实验:


可以发现这种情况下(rpl为0环,而dpl为3环)是可以正常运行的
那如果rpl与dpl都是0环呢?


可以发现这里直接报错了,因为本来cs是三环的,而ds想提权到0环,能否提权要看cs,如果在三环下跑代码是不能提权到0环下的
因此普通数据段下rpl是没有意义的,那么ss段呢?我们继续进行试验

将dpl改回三环


这里可以看到若ss的rpl是0环,但dpl与cpl是3环的,这里出错了。而当ss的rpl与dpl都是3环的,仍然可以正常运行。因此cs的rpl是不能随意赋值的,是有意义的。
堆栈段rpl==cpl==dpl才能更改
那么在代码段下呢?

这里发现48被修改成4b为三环,因此,在代码段上rpl也是没有用的,cpl==dpl
2.跨段跳转不提权 jmp和call

这种写法是不支持的,如果要实现对于cs的修改要间接jmp如:

但由于我们是jmp到test函数的,那该怎样回来呢?如果要用ret的话需要我们push构建
我们可以选择标签:
编译器在编译时,记下标签这个位置的内存地址,名字就叫heihei,等到链接完成,它就变成一个实实在在的地址值。采用这种方式就可以成功ret回来,但这种方式会有一种瑕疵,因为段选择子并没有被改回来



可以看到以call far的形式调用esp减少了8字节,但一般的call一般只会减少4字节的地址大小。我们查看一下堆栈中的信息

可以看到这里除了返回地址还压栈了cs原本的值1b
这种情况下ret是否能返回呢?
Ret的原理是从原有的堆栈里取出eip,并且esp-4,所以在这种情况下是可以ret的,但堆栈会不平衡,因为还有4字节的1b在堆栈中。单纯的ret4虽然可以正常运行,但修改的cs也并没有被改回去,这里就要用到retf

Retf专门应对这种call far,并且cs的值也会被还原

作业:在jmp far bufcode的情况下从test回到main且将cs改回去。

这里主要的修改是在push heihei前push了一个cs,这样就可以直接在堆栈构造出一个类似于bufcode的间接跳转,这样直接在test中jmp far [esp]即可,由于vs8并不支持这种写法所以采用了机器码的方式。下图为堆栈和结果


代码:
void __declspec(naked) test(){
__asm{
//jmp far [esp]
_emit 0FFh
_emit 02Ch
_emit 024h
}
}
int main(){
char bufcode[6]={0, 0, 0, 0, 0x48, 0};
*(int*)&bufcode[0]=(int)test;
__asm{
push cs
push heihei
jmp far bufcode
heihei:
add esp,0x6
}
}

浙公网安备 33010602011771号