吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6533|回复: 17
收起左侧

[系统底层] 保护模式笔记八 调用门提权(无参+有参)

  [复制链接]
lyl610abc 发表于 2021-6-6 11:24
本帖最后由 lyl610abc 于 2021-6-7 21:25 编辑

前言

所有保护模式索引链接:保护模式笔记一 保护模式介绍

在先前的保护模式笔记七 CALL 长调用与短调用中没有具体分析长调用的实例,接下来补充其需要的相关知识:调用门

调用门

调用门的作用

调用门可以用来提权,通过提权可以实现访问高地址空间等RING0(内核)层才能进行的操作


调用门和长调用关系

回顾先前的笔记可知:

长调用CALL FAR CS:EIP指令要调用的地址是由CS段选择子查GDT得到的调用门 段描述符得来的,后面的EIP不发挥作用


调用门执行流程

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

调用门描述符

对比段描述符

2


调用门描述符结构

image-20210605100534276


当一个段描述符是一个调用门描述符时,有以下特征:

  • S位为0,表示该段描述符为系统段描述符(调用门描述符属于系统段描述符)
  • Type域为1100,表示该段描述符为32位调用门
  • 低16位到31位由原本的基地址变为存储一个段选择子,该段选择子才和代码真正要调用的地址相关
  • 真正要调用的地址 = 段选择子所指向的段.Base + 32位的段中偏移 (段中偏移分为两部分:高位31-16位和低位15-0位)
  • 段.Base默认为0,故真正要调用的地址 = 32位的段中偏移

给出段描述符和调用门描述符各部分的对比(上半部分为段描述符,下半部分为调用门描述符):

数据位 31-24 23 22 21 20 19-16 15 14-13 12 11-8 7-0
含义 Base G D/B 0 AVL Seg.Limit P DPL S Type Base
解释 基地址 粒度 默认操作大小 固定为0 用于系统软件使用 段大小限制 有效位 特权等级 描述符类型 段类型 基地址
数据位 31-16 15 14-13 12 11-8 7-5 4-0
含义 offset P DPL S Type param.count
解释 段中偏移 有效位 特权等级 值为0 值为1100 值为000 参数计数

数据位 31-16 15-0
含义 Base Adress Segment Limit
解释 基地址 段大小限制
数据位 31-16 15-0
含义 selector offset
解释 段选择子 段中偏移

构造无参调用门描述符

了解了调用门描述符的结构后,尝试自己构造一个无参的调用门描述符,如下:

数据位 31-16 15 14-13 12 11-8 7-5 4-0
含义 offset P DPL S Type param
解释 段中偏移 有效位 特权等级 值为0 值为1100 值为000 参数
值(二进制) 0 1 11 0 1100 000 0000
数据位 31-16 15-0
含义 selector offset
解释 段选择子 段中偏移
值(十六进制) 0x0008 0

得到调用门描述符为:0000EC00`00080000

段中偏移暂时不明确要调用的代码段,先置0


示例代码

接下来给出一段演示代码:

#include <Windows.h>
#include <stdio.h>

 __declspec(naked) void callGate(){
         _asm{
                 int 3                                //软中断
                 retf                                //注意这里长调用对应长返回
         }
 }

int main(){

        char buff[6];
        //*(DWORD*)&buff[0]= 0x12345678;        //低地址32位为0x12345678,EIP已废弃,故随便填即可
        //*(DWORD*)&buff[4]=0x48;                        //高地址16位为0x48,段选择子

        //也可以换作这种写法
        _asm{
                mov dword ptr ds:[buff],0x12345678        //低32位赋值废弃EIP
                lea eax,dword ptr ds:[buff]                        //将buff地址给EAX
                add eax,4                                                        //地址+4,即得到高地址
                mov word ptr ds:[eax],0x48                        //高16位赋值段选择子selector
        }

        //使用 调用门
        _asm{
                call fword ptr ds:[buff]                        //fword 数据宽度为6字节
        }
        return 0;
}

代码说明

代码十分简单,主要分为两部分:

  • callGate:调用门真正要调用的函数,先软中断,然后长返回
  • main:先构造一个CS:EIP,这里为0x48:0x12345678,然后使用调用门

关于构造CS:EIP,可以观察到赋值后buff在内存中的存储情况:

image-20210605204546405

可以看到CS:EIP在内存中由高地址到低地址存储,为0x004812345678


将门描述符写入GDT

在代码中,CS的也就是段选择子的值为0x48,该选择子指向的GDT的地址为要写入的地址

关于Selector和GDT地址的对应关系在保护模式笔记三 段描述符和段选择子中已经说明过了,这里不再赘述


确定门描述符

在写入GDT前,还需要确定要写入的值,前面已经构造好了的门描述符为:0x0000EC00`00080000

但其段中偏移还未确定,于是使用VC++ 6.0查看要调用的代码的地址:

进入debug模式,中断后,选中callGate函数,然后右键→Go to Disassembly(查看反汇编)

image-20210605213307518


image-20210605213346663

可以得到要调用的函数的地址为0x00401020


将得到的要调用的函数地址填入门描述符中对应的offset得到:

  • 原:0000EC00`00080000
  • 现:0040EC00`00081020

于是得到确定的门描述符为0040EC00`00081020


写入门描述符

确定完门描述符和要写入的地址后,就可以将其写入GDT了,操作如下:

指令如下:

r gdtr                                查看gdtr
dq 0x8003f000                以qword查看地址,这里的地址为上面得到的gdtr地址
eq 8003f048 0040EC00`00081020                写入门描述符
dq 0x8003f000                查看是否写入成功

过程图如下:

image-20210605215524725


执行代码

执行代码结果如下:

Windbg获取到了代码中的int 3断点

image-20210605214030314

可以看到此时中断的地址正是门描述符中的偏移地址(要调用的地址 = 段.Base+Offset,Base默认为0,故要调用的地址就直接等于门描述符中的offset)


原本的Ring3(应用)层的int 3断点不会被Windbg所捕获,但这里通过门描述符提权后变为了Ring0(内核层)权限,故会引起Windbg的捕获

可以查看此时的寄存器情况:

r

image-20210605214356746

此时的CS正是前面构造的门描述符中的selector(选择子)


接下来继续单步执行

t

image-20210605215154023

可以看到int 3的下一行代码位retf,也就是callGate函数里的代码中的下一行,由此可以确定调用成功


对比执行前后寄存器

前面只提到了CS段寄存器的变化,现在来总览对比执行前后寄存器的变化:

执行前寄存器情况

在使用调用门的汇编语句处下断点,断下后得到:

image-20210605215924146


得到此时的寄存器情况:

寄存器
ESP 12FF2C
EBP 12FF80
CS 1B
DS 23
ES 23
SS 23
FS 3B
GS 0

执行后寄存器情况

image-20210605224727368



得到此时的寄存器情况:

寄存器
ESP B1026DD0
EBP 12FF80
CS 08
DS 23
ES 23
SS 10
FS 30
GS 0

变化对比
寄存器 执行前值 执行后值 是否变化
ESP 12FF2C B1026DD0
EBP 12FF80 12FF80 ×
CS 1B 08
DS 23 23 ×
ES 23 23 ×
SS 23 10
FS 3B 30
GS 0 0 ×

可以得出变化的寄存器有:ESP、CS、SS、FS

通过调用门提权后,前后寄存器的变化涉及到TSS,这里先记录下变化,具体细节留作之后


构造有参调用门描述符

示例代码

#include <Windows.h>
#include <stdio.h>

int a,b,c;

 __declspec(naked) void callGate(){
         _asm{
                pushad                                                        //将所有32位通用寄存器压入堆栈
        pushfd                                                        //将32位标志寄存器EFLFAGS压入堆栈
        //关于为何是通过ESP+XXX寻址详见后续的堆栈情况说明
                mov eax,[esp+0x24+0x8+0x8]                //从堆栈中取出第一个参数
                mov dword ptr ds:[a],eax                //将取出的参数赋值给全局变量a
                mov eax,[esp+0x24+0x8+0x4]                //从堆栈中取出第二个参数
                mov dword ptr ds:[b],eax                //将取出的参数赋值给全局变量b
        mov eax,[esp+0x24+8+0]                        //从堆栈中取出第三个参数
                mov dword ptr ds:[c],eax                //将取出的参数赋值给全局变量c
                popfd                                                        //将所有32位通用寄存器出栈
                popad                                                        //将所有32位标志寄存器EFLFAGS出栈
                retf 0xC//注意这里长调用对应长返回,堆栈平衡 0xC=12=3*4=参数个数*参数的数据宽度(单位字节)
         }
 }

int main(){

        char buff[6];
        //*(DWORD*)&buff[0]= 0x12345678;        //低地址32位为0x12345678,EIP已废弃,故随便填即可
        //*(DWORD*)&buff[4]=0x48;                        //高地址16位为0x48,段选择子

        //也可以换作这种写法
        _asm{
                mov dword ptr ds:[buff],0x12345678        //低32位赋值废弃EIP
                lea eax,dword ptr ds:[buff]                        //将buff地址给EAX
                add eax,4                                                        //地址+4,即得到高地址
                mov word ptr ds:[eax],0x48                        //高16位赋值段选择子selector
        }

        //使用 调用门
        _asm{
                push 1
                push 2
                push 3
                call fword ptr ds:[buff]                        //fword 数据宽度为6字节
        }
        printf("%X\t%X\t%X\n",a,b,c);
        return 0;
}

代码说明

与构造无参调用门描述符相比,主要变化为:

  • 在使用调用门前压入了三个参数:1、2、3
  • 调用代码作用为:①保护现场(压入所有通用寄存器和标志寄存器);②从堆栈中取出对应的参数;③将取出的参数赋值给对应的全局变量
  • 调用代码最后要平衡堆栈,ret 0xC         0xC=12=3*4=参数个数*参数的数据宽度(单位字节)
  • 在调用结束后,输出调用后被赋值的全局变量,验证参数是否成功传递

堆栈情况说明

堆栈调用情况按执行流程顺序依次说明:

执行前(压入参数后)

image-20210605235129290

记录下此时的堆栈情况:

地址 相对栈顶地址 说明
0012FF20 ESP 3 压入的第三个参数
0012FF24 ESP+4 2 压入的第二个参数
0012FF28 ESP+8 1 压入的第一个参数

切换到调用代码后

image-20210605235714407

记录下此时的堆栈情况:

地址 相对栈顶地址 说明
B9CAFDC4 ESP 0040D4E8 执行后要返回的地址
B9CAFDC8 ESP+0x4 1B 执行后要恢复的段选择子:CS
B9CAFDCC ESP+0x8 3 压入的第三个参数
B9CAFDD0 ESP+0xC 2 压入的第二个参数
B9CAFDD4 ESP+0x10 1 压入的第一个参数
B9CAFDD8 ESP+0x14 0012FF20 执行后要恢复的堆栈寄存器:ESP
B9CAFDDC ESP+0x18 23 执行后要恢复的段选择子:SS

保存通用寄存器组后

image-20210606000436166

记录下此时的堆栈情况:

地址 相对栈顶地址 说明
B9CAFDA4~B9CAFDC0 ESP~ESP+0x1C 通用寄存器组
B9CAFDC4 ESP+0x20 0040D4E8 执行后要返回的地址
B9CAFDC8 ESP+0x20+0x4 1B 执行后要恢复的段选择子:CS
B9CAFDCC ESP+0x20+0x8 3 压入的第三个参数
B9CAFDD0 ESP+0x20+0xC 2 压入的第二个参数
B9CAFDD4 ESP+0x20+0x10 1 压入的第一个参数
B9CAFDD8 ESP+0x20+0x14 0012FF20 执行后要恢复的堆栈寄存器:ESP
B9CAFDDC ESP+0x20+0x18 23 执行后要恢复的段选择子:SS

保存标志寄存器后

image-20210606095806445

记录下此时的堆栈情况:

地址 相对栈顶地址 说明
B9CAFDA0 ESP 0x202 标志寄存器
B9CAFDA4~B9CAFDC0 ESP+0x4~ESP+0x4+0x1C 通用寄存器组
B9CAFDC4 ESP+0x4+0x20 0040D4E8 执行后要返回的地址
B9CAFDC8 ESP+0x4+0x20+0x4 1B 执行后要恢复的段选择子:CS
B9CAFDCC ESP+0x4+0x20+0x8 3 压入的第三个参数
B9CAFDD0 ESP+0x4+0x20+0xC 2 压入的第二个参数
B9CAFDD4 ESP+0x4+0x20+0x10 1 压入的第一个参数
B9CAFDD8 ESP+0x4+0x20+0x14 0012FF20 执行后要恢复的堆栈寄存器:ESP
B9CAFDDC ESP+0x4+0x20+0x18 23 执行后要恢复的段选择子:SS

将门描述符写入GDT

确定门描述符

先确定段中偏移:进入debug模式,中断后,选中callGate函数,然后右键→Go to Disassembly(查看反汇编)

image-20210606100712344


image-20210606100827620

可以得到要调用的函数的地址为0x0040D480


将得到的要调用的函数地址填入门描述符中对应的offset得到:

  • 原:0000EC00`00080000
  • 现:0040EC00`0008D480

因为此次调用门描述符需要传递三个参数,故修改为:

0040EC03`0008D480

PS:修改了门描述符结构中的param.count,如不熟悉可回顾上面的 调用门描述符结构


于是得到确定的门描述符为0040EC03`0008D480


写入门描述符

确定完门描述符和要写入的地址后,就可以将其写入GDT了,操作如下:

指令如下:

r gdtr                                查看gdtr
dq 8003f000                以qword查看地址,这里的地址为上面得到的gdtr地址
eq 8003f048 0040EC03`0008D480                写入门描述符
dq 8003f000                查看是否写入成功

过程图如下:

image-20210606104637625


执行代码

执行代码结果如下:

image-20210606105331633

代码执行后,能够正确地输出三个参数,构造有参调用门描述符成功


总结

  1. 当通过门,权限不变的时候,只会PUSH两个值:①CS(新的CS的值由调用门决定) ;②返回地址
  2. 当通过门,权限改变的时候,会PUSH四个值:①SS;② ESP;③ CS ;④ 返回地址   (新的CS的值由调用门决定  新的SS和ESP由TSS提供
  3. 通过门调用时,要执行代码的地址由调用门中的选择子决定;使用RETF返回时,由堆栈中压入的返回地址决定

关于TSS的内容留作之后的笔记(* ̄3 ̄)╭
PS:写得比较匆忙,可能会有谬误之处,欢迎指出

免费评分

参与人数 10吾爱币 +11 热心值 +10 收起 理由
ICEY + 2 + 1 用心讨论,共获提升!
舒默哦 + 1 + 1 用心讨论,共获提升!
Noyi + 1 + 1 用心讨论,共获提升!
Higher-Stark + 1 谢谢@Thanks!
爱你小吉君 + 1 我很赞同!
qianshang666 + 2 + 1 用心讨论,共获提升!
Chenda1 + 1 + 1 我很赞同!
正己 + 1 + 1 我来了
sam喵喵 + 1 终于更新了
苏紫方璇 + 3 + 1 用心讨论,共获提升!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

932588089 发表于 2023-11-3 17:39
构造用门描述符前后堆栈变化中 FS从3B变成了30,是由于callGate() 中IN3 造成的,INT3会改变FS值
莫莫 发表于 2021-6-6 11:40
fan329218 发表于 2021-6-6 11:50
忆魂丶天雷 发表于 2021-6-6 16:23
汉字分开我都认识,连在一起我就看不懂了
wildfire_810 发表于 2021-6-6 17:01
有图有真相。感谢分享:)
张海洋 发表于 2021-6-7 08:30
根据CS的值 查GDT,找到对应的段描述符 这个描述符是一个调用门
race_002 发表于 2021-6-7 15:30
这是什么,高深,都是大佬啊
travishua 发表于 2021-6-8 00:16
思路清晰 条理妥妥
ab1582820166 发表于 2021-6-8 11:32
谢谢分享
jsncy 发表于 2021-7-16 16:53
挺好用的
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-15 14:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表