长调用与短调用

短调用

指令格式

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 3mov ebx, 0x10mov val, ebxretf}
}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 3mov ebx, 0x10mov val, ebxretf}
}int main() {printf("%X\n",&test);system("pause");__asm{push 0x10push 0x12ff2cpush 0x48lea eax, testpush eaxretf}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∼3116\sim3116∼31 位 +++ 门描述符低四字节的 0∼150\sim150∼15 位

关于调用门不稳定问题

在前面长调用提权实验中,构造的调用门在返回时会出现异常,定位到错误原因是 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 fscall 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 3mov ebx, 0x10mov val, ebxretf 0x10}
}int main() {unsigned char buf[] = {0,0,0,0,0x4B,0};printf("%X\n",&test);system("pause");__asm{push fspush 1push 2push 3push 4call 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)  Branchnt!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)  Branchnt!RtlInitAnsiString+0x27:
804da296 b9ffff0000      mov     ecx,0FFFFhnt!RtlInitAnsiString+0x2c:
804da29b 66894a02        mov     word ptr [edx+2],cx
804da29f 49              dec     ecx
804da2a0 66890a          mov     word ptr [edx],cxnt!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 3pushadpushfdpush fsmov ax,0x30mov fs,axpush bufstrlea eax,strpush eaxcall initStrFunctionpop fspopfdpopadretf}
}int main() {unsigned char buf[]={0,0,0,0,0x48,0};printf("%X\n",&test);system("pause");__asm{push fspushadpushfdcall fword ptr ds:[buf]popfdpopadpop 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"

Windows保护模式(三)长调用与短调用调用门相关推荐

  1. Windows保护模式学习笔记(三)—— 长调用/短调用/调用门

    Windows保护模式学习笔记(三)-- 长调用/短调用/调用门 要点回顾 长调用与短调用 一.短调用 二.长调用(跨段不提权) 三.长调用(跨段并提权) 长调用执行时: 执行返回(RETF)时: 总 ...

  2. Windows保护模式学习笔记(十三)—— PWTPCD

    Windows保护模式学习笔记(十三)-- PWT&PCD 要点回顾 CPU缓存 CPU缓存与TLB的区别 PWT(Page Write Through) PCD(Page Cache Dis ...

  3. Windows保护模式学习笔记(十二)—— 控制寄存器

    Windows保护模式学习笔记(十二)-- 控制寄存器 控制寄存器 Cr0寄存器 Cr2寄存器 Cr4寄存器 控制寄存器 描述: 控制寄存器有五个,分别是:Cr0 Cr1 Cr2 Cr3 Cr4 Cr ...

  4. Windows保护模式学习笔记(十)—— TLB

    Windows保护模式学习笔记(十)-- TLB 地址解析 10-10-12分页 2-9-9-12分页 TLB TLB结构 TLB种类 练习1:体验TLB的存在 第一步:运行代码 第二步:设置中断门描 ...

  5. Windows保护模式学习笔记(九)—— 2-9-9-12分页

    Windows保护模式学习笔记(九)-- 2-9-9-12分页 要点回顾 10-10-12分页 原理 环境配置 2-9-9-12分页 原理 PDPTE PDE PTE XD/NX标志位 环境配置 实验 ...

  6. Windows保护模式学习笔记(八)—— 页目录表基址/页表基址

    Windows保护模式学习笔记(八)-- 页目录表基址/页表基址 要点回顾 一.页目录表基址 实验:拆分线性地址C0300000,并查看其对应的物理页 第一步:打开一个进程,获得它的Cr3 第二步:查 ...

  7. Windows保护模式学习笔记(七)—— PDEPTE

    Windows保护模式学习笔记(七)-- PDE&PTE Cr3 PDE(页目录表项) PTE(页表项) 物理页的属性 10-10-12分页的补充 实验1:证明PTE的特征1 第一步:选择一个 ...

  8. Windows保护模式学习笔记(六)—— 10-10-12分页

    Windows保护模式学习笔记(六)-- 10-10-12分页 基本概念 4GB内存空间 有效地址-线性地址-物理地址 有效地址与线性地址 物理地址 控制寄存器:Cr3 10-10-12分页 实验:通 ...

  9. Windows保护模式学习笔记(五)—— 任务段任务门

    Windows保护模式学习笔记(五)-- 任务段&任务门 要点回顾 任务段 TSS (Task-state segment ) TR段寄存器 TR段寄存器的读写 TSS段描述符 实验:加载自定 ...

  10. Windows保护模式学习笔记(四)—— 中断门陷阱门

    Windows保护模式学习笔记(四)-- 中断门&陷阱门 要点回顾 中断描述符表(IDT) 一.中断门 实验:构造一个中断门 第一步:初步构造参数 第二步:确定 Offset in Segme ...

最新文章

  1. 王者又连跪了?快让 AI 帮你上分!
  2. dataset__getitem___PyTorch源码解析与实践(1):数据加载Dataset,Sampler与DataLoader
  3. Lombok 安装、入门 - 消除冗长的 java 代码
  4. 注意服务器系统日期对防病毒软件的影响
  5. Python字符串的两种方式——百分号方式,format的方式
  6. JQuery 自动触发 a 标签的 click事件
  7. kadane算法_使用KADANE的算法求最大子阵列和
  8. xdebug+webgrind
  9. 激光雷达(LiDAR)简介-森林资源调查应用
  10. 前端基础知识体系之项目经验篇
  11. ReportMachine通过嵌套表达式计算某个值。
  12. Java调用regester命令
  13. 传奇3单机服务器怎么修改器,自己是GM并架设了传奇3单机版,如何改变装备属性?...
  14. html页面跳转闪屏,闪屏页.html
  15. 每页都有的表头和打印分页
  16. 2029年会实现通用人工智能吗?
  17. python练习题:输入某年某月某日,判断这一天是这一年的第几天?
  18. 微商的微信营销互动方法
  19. BLAST背后的知识(一点原理)
  20. 对excel的完美操作

热门文章

  1. oauth2-authorization-server;oauth2-resource-server;oauth2-client
  2. 使用最小二乘法和最大似然法估计非线性模型
  3. TreeMap实现原理 红黑树
  4. Home Assistant 接入 Lifesmart(云起智能)家居的途径。
  5. LinkedList类
  6. Python爬虫 scrapy框架爬取某招聘网存入mongodb解析
  7. 基于 TCP协议和sqlite3数据库的网络电子词典(个人项目)
  8. 真正厉害的人,都在延迟满足
  9. javaWeb新闻发布展示(分页)
  10. 题目:在单处理器多到分时系统中,有三道作业依次提交, 其提交时刻及运行时间分别为