保护机制、段、页
保护机制
首先铺垫,在前面学过pe文件格式,且写过反射型注入,有一定知识前置
我们知道,一个软件在运行的时候,会分配一个内存中的基地址(虚拟地址),很好,那么明明是在内存中实体存在的为什么叫虚拟地址?我们还知道,地址是线性的0 1 2 …… 0xffffffffh,线性排布,那么问题来了,我的程序加载到内存中,假如说是0x87654321,欸?此时我有一个指令是mov dword ptr [0] , 1,那么会把内存开始地方的东西改动掉,但是内存最开始的东西肯定是很内核的东西,什么对程序的调用啊,什么的之前在反调试有接触过
自然是不能让你随便动他的,那么怎么才能很安全的做到呢
目前我学到的是分段分页,相当于在原本的0x55555555h的地方一刀斩断,后面又开始0x0h(应该如此,后续回来补充)
嗯不是这样的,是分成一段一段然后通过代码段,数据段来区分,这是段保护机制
页保护估计就是进程线程的吧。。。
windbg的一些指令
r 查看一些寄存器(r eax | r al | r gdtr)
d 查看地址(byte(db) | word(dw) | … ) | (L limit 限制要看多长) | (s 竖着看)
e 写内存(用法和d一致)
idt和gdt
idt和gdt结构前两字节是limit大小,后四字节是地址
idt和gdt的指令
sidt sgdt 用例就是把gdt表和idt表加载出来
lidt lgdt 赋值给他
门符框架
很好前面虽然很乱但是捋一下就清晰了,本篇文章只用来构造框架,具体的内容将在其他文章细记
门符
注意注意,门或符GDT表或IDT中每个单元的名称!!!
每个单元都有-P位 S位 Type位
P-决定该描述符有效
S-决定该描述符属于什么类型
T-决定改描述符更具体类型
声明1:门和xx段描述符感觉差不多一个意思
s=1-段描述符
s=1是段描述符,这时候的T遵循的图就是
t<8-数据段 GDT
t>7-代码段 GDT
数据段或代码段,同时无论是数据段还是代码段都解析的都是如下图
s=0-系统描述符
s=0是系统描述符,这时候的T遵循的图就是
t=c-调用门 GDT
调用门遵循下图
t=6 -中断门 IDT
int 3 int 2 int 1…遵循下图
t=5-任务门 IDT

段描述符
段选择符

长度一字节,比如CS里的1B值就是段选择符(0x18是打印cs值会打印出来的)
1B:0000 0000 0001 1 0 11
0011 查找表 请求权限
3,在表中的第3位 0=GDT Global Decsrctor Table(全局描述表)
1=LDT Local Decsrctor Table(本地描述表)
CS 1B 代码段
SS 23 栈段(栈,局部变量)
DS 23 数据段(堆,全局变量)
ES 23 扩展段(可以还原ds)
FS 上下文环境段 R3代表TEB R0代码是KPCR
1 | // study02.cpp : 定义控制台应用程序的入口点。 |
段描述符
也就是前面的GDT和LDT表的内容
举个例子:
kd> r gdtr
gdtr=80b98800
通过查找指令找到gdt表,gdt表是按照1字也就是两字节的形式为最小单位,所以查找的时候要用dq
kd> dq 80b98800
80b98800 00000000
00000000 00cf9b000000ffff80b98810 00cf9300
0000ffff 00cffb000000ffff80b98820 00cff300
0000ffff 80008bb98c0020ab80b98830 804093b9
b0004fff 0040f30000000fff80b98840 0000f200
0400ffff 000000000000000080b98850 800089b9
ad200067 800089b9acb0006780b98860 00000000
00000000 000000000000000080b98870 800092b9
880003ff 0000000000000000
按照上面索引第三个(注意,从0开始数) 00cffb00`0000ffff,和段选择符一样,要拆分分析

对照上表,高字节部分和低字节部分
31 24 19 16 12 8 0
1 | 00 c f f b 00 |
1100 1111
h: b gb0a l hd s t b
0000 ffff
l: b l
limit:f+ffff=0xfffff 段限制大小(sizeof(cs)):(h&l)+(l&l)
base:00+00+0000 基址大小(base):(h&b)+(h&b)+(l&b)
type:(h&t)b tpye作用见下表
s:(h&s)1
DPL:(h&d)3
p:(h&p)1 p用来证明该段是否有效,1有效,0无效
avl:(h&a)0
0:(h&0)0
D/B:(h&b)1
G:(h&g)1 若为1,则以页为单位(影响的是段限制的单位)(一个页4096字节) 转换为hex->0x1000
故真正限制大小:(limit+1)*(g) 按照上面的计算也就是(0xfffff+1)*0x1000=0x1 0000 0000,范围就是0-0xf ffff fff 也就是4个g
type作用:
练习代码
1 | // study02.cpp : 定义控制台应用程序的入口点。 |
再次总结一下,cs,ds这些段如果是cs:[x],那么会调用代码段中的x地址,但是如果只是cs,也就是cs中的值,并不是基地址,而是段选择符,也就是可以拆解的,有时候就会去对照gdt表,然后查看gdt里的东西
d/b
话解上题,通过windbg更改端段描述符cs,ds可以做到全局改变,d/b的作用是用来规定操作符大小的,比如正常push指令push的是一字节,但是如果d/b位改变,这里有可能只push一字等,也就是本来的push xx xx xx xx就会变成push xx xx
个人感觉就像是改变这个电脑是32位还是16位还是64位,存在用处也很明显,就是可以兼容低版本(这里注意改变的是段描述符里的d/b也就是说,该段相关改变,比如ss(栈)改了的话,call指令就会有问题,因为call相当于pop jmp,但是操作字节变少了,原本是call 0x12345678,pop出来就只有0x1234,jmp 0x1234,所以就会出现问题
win系统内部权限分化,R0和R3是内核层和应用层,这个倒是很好理解,去调用
判别当前代码是当前层的办法就是CS和SS
这里阐述一个观点,段的存在是划分硬件资源的,内存中是线性排列的一群1和0,是段来定位划分每一块的作用,最后cs,ss,ds诸如此类的段就是对这个划分块的描述
DPL: Descriptor Privilege Level 描述符特权级
CPL: Current Privilege Level 当前特权级 CS段描述符的DPL
RPL: Request Privilege Level 请求者特权级 CS DS SS,在段描述符的后三位

这里卡了有点久,起初我不是很理解gpt所说的
| 段类型 | DPL 规则 |
|---|---|
| 代码段 | CPL ≤ DPL |
| 数据段 | max(CPL, RPL) ≤ DPL |
| 栈段(SS) | RPL=CPL = DPL |
上述表格,但是结合上图就会发现,0才是内核层,假如现在代码是内核代码,那么这段代码的DPL就是0,如果你自己写了一段代码,那么大概率在R3,应用层,此时你的代码描述符cs里的CPL就会是3(DPL应该也是3)
此时如果你要调用内核层的代码,则会访问失败(但我不知道这个怎么调用)
以此类推,后两个也不难理解
提权跳转-符
嗯后面的我好像也懂了,补刚刚的坑,那么众所周知cs在应用层是23,那么
call 23h:0040100h就是跳转到当前程序的0x401000地址,并且跳跃过去的段权限对应是3,那么在gdt表中把一个无用位置改成CPL值为2的cs,然后手动call 48h:4010000h,48就是改变的gdt表导向,也就是call提权
那么跳转指令还有jmp\ret\retf等等,这些的跳转可以不可以提权呢
jmp 在调用门 只能同权限跳转
retf 只能同权限或向低权限跳转
call 同权限或提权
本来换下一节课了,听到调用门一愣,调用门似乎他没说,我不清楚为什么感觉容易漏东西,所以自己补了一下,网上找了一篇文章讲的还不错
我一看这个调用门描述符,和段描述符十分相似
所以段又描述符,调用门也有?后面看来好像是这样的,而且构造和段描述符差不多
我去,,,误会了我没看到这节课,好吧回来接着学
那么前面说的和提权跳转差不多,当然也有可能是这两个本来就是一样的,所以我决定好好复现这个程序
那么首先我们要获得cs的值,并且分解分析
1 | int _tmain(int argc, _TCHAR* argv[]) |
这里获取的值是1b ->0001 1011,也就是3的位置
r gdtr获取地址-80b98800
dp 80b98800后发现80b98848后的位置适合放更改DPL的位置
原值是0000ffff 00cffb00-f-1111-DPL-11
需要改成DPL-00-1001-9-0000ffff 00cf9b00
我突然发现有现成的1,其实可以改成0000 1011- b
那么接下来要获得函数的地址-0x0401000
1 | #include "stdafx.h" |
int 3
哈哈中断一下,因为确实这里中断了好几次,我当时自己写的代码一直都不能跑,会中断,我复现课里的内容也还是不可以,最后发现他改的是 00cffb00`0000ffff
欸?这不是没改嘛,对就是没改,那不对呀,没改怎么提权了,哎这里真的想了很久,因为RPL在段描述符中,他用的是48,0100 1000,RPL是0,R0层,哦对他也没说这个实验可以提权,而是申请对吧
。。所以这里就是这个啊。。。哎不记了接着看,反正也懂了,始终切记DPL在gdt里,RPL在段描述符里
系统段描述符
这里的门是系统段的门,也算是描述符,就像是指引路的传送门,比如调用门,门后的就是代码段,中断门,门后的是处理中断的代码段,任务门门后的是TSS任务段
还是看完再测试比较好
调用门
调用门作用是跳转提供基地址等一系列参数的
具体框架在另一篇文章,这里只阐述注意点
1.调用门即地址前寻址的cs ds ss,但是并不是段选择符的那些cs ds ss,call jmp等指令使用到调用门时便遵循调用门解析跳转
2.jmp xx:aaaa 遵循xx的地址跳转
3.提权跳转,先解析需跳转地址,构造出对应的调用门
4.调用门结构中Segment Selector(段选择符)就是前面的符,以符来作为基地址跳转,所以在这里可以选择R0或R3层,进行提权
5.jmp 在调用门 只能同权限跳转 | retf 只能同权限或向低权限跳转 | call 同权限或提权
中断门
中断门作用是int系列或中断指令触发时,决定跳转到哪个地方来解决
具体框架在另一篇文章,这里只阐述注意点
1.中断门即int 1/2/3,当程序运行上述命令时,遵循中断门的跳转,类调用门
2.int xx 遵循xx的地址跳转
3.查看idt表,会发现,0 1 2 3会有对应可分析的中断门,每个单元都是int后对应的中断门,所以自己如果要增加中断门在32元素位置,那么中断门就是int 32
4.中断门的跳转极其类似调用门,所以不多记
陷阱门
陷阱门≈中断门
陷阱门与中断门几乎一致,这里只阐述不同点
1.中断门清除eflag里的VM NT IF TF位
陷阱门不清楚IF位,所以中断门会造成阻塞,需要iretd来返回,或者在进入中断门之前sti,陷阱门不必
任务门
在不同段跳转的时候,比如R0跳R3这种跨段跳转的时候,栈(ss)环境和esp是会改变的,前面实验就能看出
所以任务门担任的就是承接作用 ps:框架越来越完善了
还是老规矩,这里只阐述注意点
基于中断门的hook int 3 hook
通过更改int 3对应cs段基地址然后触发int 3hook到自己的函数
101012
这里火哥的课就又开始”架空”起来了,很多新名词完全没有说过,看一下这篇文章-101012部分,火哥的课不行就跳过这一节,里面也说了”Cr3”
插个眼,这里! process 0 0这个指令还不太懂物理地址懂一半,之前搞项目那会学了一点,但是没太深入,我估计后面学页的时候可以相辅相成
前进一小步,文明一大步
——男厕
页の前章
线性地址对应物理地址
假如我在记事本中写了一串字符”helloworld”,然后用工具比如CE查找到该字符串在内存中的地址00484C58那么可以按照10-10-12的方式解析该虚拟地址的物理地址,所谓101012就是bit位排布,首先把000AE928换成二进制bit位形式0000 0000 0100 1000 0100 1100 0101 1000,然后划分
10 00 0000 0001 1 页目录 单位1*4byte PDE
10 00 1000 0100 84 页表项 单位1*4byte PTE
12 1100 0101 1000 C58 页内偏移 单位1byte
于是就可以找物理地址了,于是就弥补了前文的无知,cr3是什么?cr3就是改进程(书)地址的寄存器,代表了该书,用! process 0 0列举所有cr3的值找到notepad.exe,得到物理基地址03d75000
! dd 03d75000来获取目录物理地址的值,目录中存放的是指针,也不难想到指针指向的地方就是书本内容的地方,比如这个要找的就是! dd 03d75000+14->3cbc7867,这里要去掉867,这个是页属性得到3cbc7000,这就是当前页的起始地址,再接着加上844,得到3d25b867,同理3d25b000,于是就可以看里面的内容了
1 | kd> !db 3d25b000+C58 |
页属性
当直接查看CR3的时候,看到的后三位并不是0,其实这里代表的就是页属性
可以对照该表
P
0代表无效
US
1代表R3可访问,0代表R0才能访问
A
是否被改变过,改变1
G
如果是1的话,是不会刷新缓存值的
没什么可记的,看完课就好
页の终章
挂页
VirtrualAlloc申请地址在赋值前是不会挂物理页的,在memset等操作进行之后才会赋值,又众所周知,0地址是不可赋值的,因为0地址没有挂物理页,那么就会有一种程序,给0地址挂页
1 | // study05.cpp : 定义控制台应用程序的入口点。 |
页下
这个感觉不太好记,比较散,应该也不容易忘就不急了,记脑子里
TLB
虚拟页帧 物理页帧 attr属性 次数 PCID
本质是缓存,里面可以临时存储最近用过的联系?然后再次使用的时候可以先碰撞TLB,看看有没有现成的联系,没有再拆