Windows保护模式(三)长调用与短调用&调用门

长调用与短调用

短调用

指令格式

CALL 立即数 / 寄存器 / 内存

堆栈变化

在这里插入图片描述

发生改变的寄存器

ESP EIP

长调用(跨段不提权)

指令格式

CALL CS:EIP(如果是通过调用门则 EIP 是废弃的,真正的 EIP 存储在门中)

堆栈变化

在这里插入图片描述

发生改变的寄存器

ESP EIP CS

验证

测试代码如下:

#include<stdio.h>

void test(){
	puts("This is test.");
}

int main(){
	unsigned char buf[]={0,0,0,0,0x48,0};
	*(unsigned int*)(&buf[0])=(unsigned int)&test;
	__asm {
		call fword ptr ds:[buf]
	}
	return 0;
}

在 0x90 处构造 DPL = 3 的非一致代码段。

kd> dq gdtr
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00000000`00000000
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff
kd> eq 8003f048 00cffb00`0000ffff
kd> dq gdtr
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00cffb00`0000ffff
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff

运行代码,调用前栈顶指针 esp 指向 0x12FF2C 处。
在这里插入图片描述
调用后栈顶指针 esp 指向 0x12FF24 处,并将原来的 cs 和 返回地址压栈,请求的 0 环权限获取,CPL 仍然为 3 。
在这里插入图片描述
继续执行后出错,但是 puts 函数成功调用。根据报错位置 checkesp.c 可以确定是堆栈不平衡导致。即 test 函数的 ret 只将返回值从栈中弹出,未将之前压入的 cs 弹出。
在这里插入图片描述
将代码做如下修改:将 test 更改为裸函数并将函数返回改为 retf 。由于 puts 函数调用会将 cs 修改为 0x1B ,因此将 puts 去掉。

#include<stdio.h>

void __declspec(naked) test(){
	__asm{
		retf
	}
}

int main(){
	unsigned char buf[]={0,0,0,0,0x48,0};
	*(unsigned int*)(&buf[0])=(unsigned int)&test;
	__asm {
		call fword ptr ds:[buf]
	}
	return 0;
}

返回前的栈状态(图中多出来的跳转与调试状态有关,对结果无影响):
在这里插入图片描述
返回后栈中的返回地址和 cs 均被弹出,cs 被修改为正确的值。
在这里插入图片描述
如果将 retf 改为 ret 4 同样可以平衡堆栈,但是返回后 cs 没有修复。同样,也可以在函数调用后手动 add esp, 4 来平衡堆栈。
在这里插入图片描述

长调用(跨段并提权)

指令格式:CALL CS:EIP(EIP是废弃的)

调用阶段堆栈变化

在这里插入图片描述

返回阶段堆栈变化

在这里插入图片描述

发生改变的寄存器

ESP EIP CS SS

验证

运行如下代码:

#include<stdio.h>
#include<stdlib.h>

int val = 0;
void __declspec(naked) test() {
	__asm {
		int 3
		mov ebx, 0x10
		mov val, ebx
		retf
	}
}

int main() {
	unsigned char buf[] = {0,0,0,0,0x48,0};
	printf("%X\n",&test);
	system("pause");
	__asm{
		call fword ptr ds:[buf]
	}
	return 0;
}

首先打印出 test 函数的地址 0x40100A:

在这里插入图片描述
构造调用门提权(稍后会介绍调用门):

kd> dq gdtr
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 00cffb00`0000ffff
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff
kd> eq 8003f048 0040ec00`0008100a
kd> dq gdtr
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 0040ec00`0008100a
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff

继续运行代码,成功在 test 函数断下来,并且 cs 和 ss 均已被提升为 0 环权限。
观察栈结构发现 0 环的栈上已被依次压入了 3 环的 ss,esp,cs,eip。
在这里插入图片描述
执行 retf 时完成了 eip,cs,esp,ss值的修复。

kd> p
0040105c cb              retf
kd> dd esp
b0f58dd0  004010b2 0000001b 0012ff2c 00000023
b0f58de0  00000000 00000000 00000000 00000000
b0f58df0  0000027f 7c930000 00000000 00000000
b0f58e00  00000000 00000000 00001f80 23222120
b0f58e10  27262524 00380178 00380188 00000002
b0f58e20  37363534 3b3a3938 00380000 003823a0
b0f58e30  003804e8 0012f8f0 00380178 0012f8f0
b0f58e40  7c930981 00380608 000000bc 00000040
kd> p
001b:004010b2 33c0            xor     eax,eax
kd> dd esp
0012ff2c  00241fe4 0012f7bc 7ffd6000 cccccccc
0012ff3c  cccccccc cccccccc cccccccc cccccccc
0012ff4c  cccccccc cccccccc cccccccc cccccccc
0012ff5c  cccccccc cccccccc cccccccc cccccccc
0012ff6c  cccccccc cccccccc cccccccc 00000000
0012ff7c  cccc0048 0012ffc0 004012e9 00000001
0012ff8c  00430e70 00430da0 00241fe4 0012f7bc
0012ff9c  7ffd6000 00000006 b0f58d04 0012ff94
kd> r cs
cs=0000001b
kd> r ss
ss=00000023

探究 RETF 指令是否能提权

前面实验发现:retf 指令会将保存在栈中的 cs 和 ss 赋值给 cs 和 ss 寄存器,使得权限由 3 环转变为 0 环。因此于是考虑尝试设定栈中的值使得 retf 指令可以提权。
执行如下代码:

#include<stdio.h>
#include<stdlib.h>

int val = 0;
void __declspec(naked) test() {
	__asm {
		int 3
		mov ebx, 0x10
		mov val, ebx
		retf
	}
}

int main() {
	printf("%X\n",&test);
	system("pause");
	__asm{
		push 0x10
		push 0x12ff2c
		push 0x48
		lea eax, test
		push eax
		retf
	}
	return 0;
}

在执行 retf 之前,栈中设置了合理的提权数据。
在这里插入图片描述
继续运行触发异常,说明 retf 只能在同级上跳转或者降低权限,不能利用 retf 提权。
在这里插入图片描述

总结

  • 跨段调用时,一旦有权限切换,就会切换堆栈

  • CS的权限一旦改变,SS的权限也要随着改变,CS与SS的等级必须一样

  • 如果不是一致代码段或者利用 TSS ,JMP FAR 只能同级跳转,但CALL FAR可以通过调用门提权,提升CPL的权限

  • RETF,IRETD 只能在同级跳转,或者只能降低权限

  • SS 与 ESP 的值来自与 TSS 段

  • RETF 指令根据 CS 指向的段描述符是否为们以及是否提权来确定调用时是压入的 4 个值还是 2 个值

调用门

指令格式

CALL CS:EIP(EIP是废弃的)

执行步骤

  • 根据CS的值查GDT表,找到对应的段描述符 这个描述符是一个调用门
  • 在调用门描述符中存储另一个代码段的段选择子
  • 段选择子指向的段 段.Base + 偏移地址 就是真正要执行的地址
    在这里插入图片描述

门描述符

结构图:
在这里插入图片描述
注意:

  • S位(第12位)必须为0,只有当S位为0时,段描述符才是系统段描述符;此时,当Type域为1100时,该描述符是门描述符
  • 低四字节的16~31位是决定 调用的代码存在于哪个段 的 段选择子
  • 当长调用执行时:真正要执行的代码地址 = = = 门描述符中段选择子所指向的代码段的Base + + + 门描述符高四字节的 16 ∼ 31 16\sim31 1631 + + + 门描述符低四字节的 0 ∼ 15 0\sim15 015

关于调用门不稳定问题

在前面长调用提权实验中,构造的调用门在返回时会出现异常,定位到错误原因是 FS 寄存器的段选择子变为 0 导致。
在这里插入图片描述
通过调试发现,在经过调用门提权后 fs 寄存器的值也提权为 0x30,而 retf 返回时并没有恢复 fs 寄存器,而是把 fs 寄存器置为 0 。

kd> g
Break instruction exception - code 80000003 (first chance)
00401050 cc              int     3
kd> p
00401051 bb10000000      mov     ebx,10h
kd> p
00401056 891db8374200    mov     dword ptr ds:[4237B8h],ebx
kd> p
0040105c cb              retf
kd> p
001b:004010b2 33c0            xor     eax,eax
kd> r fs
fs=00000000
kd> g
Break instruction exception - code 80000003 (first chance)
00401050 cc              int     3
kd> p
00401051 bb10000000      mov     ebx,10h
kd> p
00401056 891db8374200    mov     dword ptr ds:[4237B8h],ebx
kd> p
0040105c cb              retf
kd> r fs
fs=00000030
kd> p
001b:004010b2 33c0            xor     eax,eax
kd> r fs
fs=00000000

因此在调用门提权前后需要手动保存和恢复 fs 寄存器的值。

	__asm{
		push fs
		call fword ptr ds:[buf]
		pop fs
	}

另外,调用门不稳定的另一个原因是因为操作系统经常修改 GDT 表,这会导致构造出的门被操作系统修改造成错误,因此最好用 GDT 表中偏移 0x90 以上的元素构造门。

堆栈变化(含参数)

将 0x48 位置的门描述符的参数个数修改为 4 。

kd> dq gdtr
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 0040ec00`0008100a
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff
kd> eq 8003f048 0040ec04`0008100a
kd> dq gdtr 
8003f000  00000000`00000000 00cf9b00`0000ffff
8003f010  00cf9300`0000ffff 00cffb00`0000ffff
8003f020  00cff300`0000ffff 80008b04`200020ab
8003f030  ffc093df`f0000001 0040f300`00000fff
8003f040  0000f200`0400ffff 0040ec04`0008100a
8003f050  80008955`23800068 80008955`23e80068
8003f060  00009302`2f40ffff 0000920b`80003fff
8003f070  ff0092ff`700003ff 80009a40`0000ffff

执行如下代码:

#include<stdio.h>
#include<stdlib.h>

int val = 0;
void __declspec(naked) test() {
	__asm {
		int 3
		mov ebx, 0x10
		mov val, ebx
		retf 0x10
	}
}

int main() {
	unsigned char buf[] = {0,0,0,0,0x4B,0};
	printf("%X\n",&test);
	system("pause");
	__asm{
		push fs
		push 1
		push 2
		push 3
		push 4
		call fword ptr ds:[buf]
		pop fs
	}
	return 0;
}

成功在 test 函数断下来。

kd> g
00401050 cc              int     3
kd> uf eip
00401050 cc              int     3
00401051 bb10000000      mov     ebx,10h
00401056 891db8374200    mov     dword ptr ds:[4237B8h],ebx
0040105c ca1000          retf    10h
kd> dd esp
b0cacdc0  0040d73c 0000001b 00000004 00000003
b0cacdd0  00000002 00000001 0012ff18 00000023
b0cacde0  00000000 00000000 00000000 00000000
b0cacdf0  0000027f 7c930000 00000000 00000000
b0cace00  00000000 00000000 00001f80 23222120
b0cace10  27262524 00380178 00380188 00000002
b0cace20  37363534 3b3a3938 00380000 003823a0
b0cace30  003804e8 0012f8f0 00380178 0012f8f0

可以看出,调用门在传参后栈的结构为下图所示:
在这里插入图片描述
retf 指令后面跟的数字指定了参数的总字节数,从而确保返回时能用正确的平衡堆栈。
注意:retf 平衡的是用户态的堆栈,即回到用户态后将 esp 减去 参数个数 × \times × 4 ,使得进入调用门前压入用户态的堆栈的参数弹出,而内核态的堆栈的 esp 每次调用都利用 TSS 中保存的值初始化,因此返回用户态前不需要平衡堆栈。

权限检查

经过反复试验,总结出如下规律:

  • 指令中 RPL 不参与整个过程
  • 最终的 CPL 取决于代码段的 DPL
  • CPL ≤ \le 门的 DPL,否则没有权限使用门
  • 门的 RPL ≤ \le 代码段的 DPL,否则蓝屏

注意:上述规律可能会受环境不同的影响,比如第 4 条在我的环境中不成立
在这里插入图片描述

总结

  • 当通过门,权限不变的时候,只会PUSH两个值:CS 和 返回地址,新的CS的值由调用门决定
  • 当通过门,权限改变的时候,会PUSH四个值:SS、ESP、CS、返回地址,新的CS的值由调用门决定,新的SS和ESP由TSS提供
  • 通过门调用时,要执行哪行代码由调用门决定;但使用RETF返回时,由堆栈中压入的值决定(只要改变堆栈里面的值就可以想去哪去哪)
  • 可以在调用门中再建个门出去呢,也就是再用CALL出去

利用调用门在3环调0环函数

首先在 Windbg 查看要调用的 0 环函数 RtlInitAnsiString 的地址

kd> uf RtlInitAnsiString
nt!RtlInitAnsiString:
804da26f 57              push    edi
804da270 8b7c240c        mov     edi,dword ptr [esp+0Ch]
804da274 8b542408        mov     edx,dword ptr [esp+8]
804da278 c70200000000    mov     dword ptr [edx],0
804da27e 897a04          mov     dword ptr [edx+4],edi
804da281 0bff            or      edi,edi
804da283 741e            je      nt!RtlInitAnsiString+0x34 (804da2a3)  Branch

nt!RtlInitAnsiString+0x16:
804da285 83c9ff          or      ecx,0FFFFFFFFh
804da288 33c0            xor     eax,eax
804da28a f2ae            repne scas byte ptr es:[edi]
804da28c f7d1            not     ecx
804da28e 81f9ffff0000    cmp     ecx,0FFFFh
804da294 7605            jbe     nt!RtlInitAnsiString+0x2c (804da29b)  Branch

nt!RtlInitAnsiString+0x27:
804da296 b9ffff0000      mov     ecx,0FFFFh

nt!RtlInitAnsiString+0x2c:
804da29b 66894a02        mov     word ptr [edx+2],cx
804da29f 49              dec     ecx
804da2a0 66890a          mov     word ptr [edx],cx

nt!RtlInitAnsiString+0x34:
804da2a3 5f              pop     edi
804da2a4 c20800          ret     8

在 0x48 处构造调用门,然后执行如下代码

#include<stdlib.h>

typedef struct _STRING {
    unsigned short Length;
    unsigned short MaximumLength;
	char* Buffer;
} STRING;

typedef void(__stdcall* RtlInitAnsiString)(STRING* DestinationString,const char* SourceString);

RtlInitAnsiString initStrFunction=(RtlInitAnsiString)0x804da26f;

STRING str;
const char* bufstr="123456789";

void __declspec(naked) test() {
	__asm {
		int 3
		pushad
		pushfd
		push fs
		mov ax,0x30
		mov fs,ax
		push bufstr
		lea eax,str
		push eax
		call initStrFunction
		pop fs
		popfd
		popad
		retf
	}
}

int main() {
	unsigned char buf[]={0,0,0,0,0x48,0};
	printf("%X\n",&test);
	system("pause");
	__asm{
		push fs
		pushad
		pushfd
		call fword ptr ds:[buf]
		popfd
		popad
		pop fs
	}
	return 0;
}

在 test 函数中断下来后用 WinDbg 调试发现成功调用 RtlInitAnsiString 函数并返回 3 环。

kd> p
0040d3d9 ff15941b4200    call    dword ptr ds:[421B94h]
kd> dd esp
b1609da0  004227c8 004200f0 00000030 00000202
b1609db0  0012ff80 0012f7bc 0012ff80 b1609dd0
b1609dc0  7ffde000 003808dc 00000000 00000000
b1609dd0  0040b4a6 0000001b 0012ff04 00000023
b1609de0  00000000 00000000 00000000 00000000
b1609df0  0000027f 7c930000 00000000 00000000
b1609e00  00000000 00000000 00001f80 23222120
b1609e10  27262524 00380178 00380188 00000002
kd> dt _STRING 004227c8
nt!_STRING
 ""   +0x000 Length           : 0
   +0x002 MaximumLength    : 0
   +0x004 Buffer           : (null) 
kd> p
0040d3df 0fa1            pop     fs
kd> dt _STRING 004227c8
nt!_STRING
 "123456789"
   +0x000 Length           : 9
   +0x002 MaximumLength    : 0xa
   +0x004 Buffer           : 0x004200f0  "123456789"
kd> p
0040d3e1 9d              popfd
kd> p
0040d3e2 61              popad
kd> p
0040d3e3 cb              retf
kd> p
001b:0040b4a6 9d              popfd
kd> dt _STRING 004227c8
nt!_STRING
 "123456789"
   +0x000 Length           : 9
   +0x002 MaximumLength    : 0xa
   +0x004 Buffer           : 0x004200f0  "123456789"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_sky123_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值