@[TOC]

环境搭建

1.需要:vs2019+wdk
选择对应得wdk版本,成功安装后可以在vs上看到此模块
在这里插入图片描述

wdk链接

2.接下来找到windbg位置,在桌面创建一个快捷方式,在目标后加上

1
-b -k com:pipe,port=\\.\pipe\com_2,resets=0

在这里插入图片描述3.虚拟机打开xp,添加一个串行端口,填上

1
\\.\pipe\com_2

在这里插入图片描述
4.打开系统环境变量设置,添加如下环境变量

1
_NT_SYMBOL_PATH         SRV*D:\Myself_Software\Windows_soft\symbols* http://msdl.microsoft.com/download/symbols

在这里插入图片描述5.打开设置好的xp虚拟机,首先在文件夹选项里把隐藏受保护得文件这个选项关闭,你就可以在c盘得跟目录下看到boot.ini文件
在这里插入图片描述在boot.ini文件最后添加代码

1
multi(0)disk(0)rdisk(0)partition(1)\WINDOWS="Microsoft Windows XP Professional" /noexecute=optin /fastdetect /debug /debugport=com_2 /baudrate=115200

在这里插入图片描述
之后用win+r,输入msconfig 打开启动项 修改如下:
一般>有选择的启动
boot.ini>时间改为3秒
在这里插入图片描述在这里插入图片描述

对于xp虚拟机的配置选择单核单处理器,内存大小为256mb
在这里插入图片描述
这下在主机打开windbg,虚拟机重起xp,就会发现xp在重起阶段会弹出选项,选择调试模式,虚拟机就会在此断下,在windbg里按g即可正常开机
在这里插入图片描述至此双击调试和vs的环境就已经配好

中断提权

在配好上述环境后,这里在虚拟机里做第一个实验,中断提权。Intel的CPU将特权级别分为4个级别:RING0,RING1,RING2,RING3。Windows只使用其中的两个级别RING0和RING3,RING0只给操作系统用,RING3谁都能用。正常我们运行的程序都是在ring3层,这里通过中断的方法让我们的程序读取到0层的数据,从而完成提权。

1.先补充一些基础知识
IDT表就是中断描述符表,用来记录各种不同的中断,及对应的中断处理函数的地址,可以用pc_hunter这个软件进行查看,内核钩子>系统中断表
比如常见的除0异常序号就是00
在这里插入图片描述如果能把我们的函数加入到这个表里,当系统遇到对应的异常时,就可以跳到我们自己的函数去实现功能,而因为异常属于内核层面,所以当进入我们的函数时,就已经完成了从3层到0层的转换,从而可以读取任意的内存数据。在图表里可以看见从0x20开始就是空白的,所以等下我们填入的时候可以从0x20开始。

2.先用vs写一个简单的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
#include<stdlib.h>

void __declspec(naked)IdtEntry() {
__asm {
jmp eax
}
}

int main() {

printf("%p\n", IdtEntry);
return 0;
}

IdtEntry这个函数里我们用来写一些内敛汇编,__declspec(naked)是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码,尝试运行后发现会打印一个随机的地址
但我们修改代码后,这个地址会改变,为了实验方便,我们设置属性release,在属性>链接器>高级> 随即地址改成否 固定地址改成是
在这里插入图片描述
修改后就会发现地址变成固定的,即0x401040,记住这个地址
在这里插入图片描述
3 修改idt表
首先用双机调试环境打开,这里先补充一些常用的windbg命令

1
2
3
4
5
6
7
8
9
10
11
g  // Go(F5)
Ctrl+Break // 暂停正在运行的程序
p // 单步执行(F10) 【Step】
t // Step into(F11) 【Trace】
k // 显示当前调用堆栈
u // 反编译下8条指令
ub . // 反汇编当前ip寄存器地址的前8条指令
r // 显示所有寄存器信息及发生core所在的指令
.cls // 清除屏幕
dd dq//查看数据
eq //编辑数据

用windbg查看idt表
在这里插入图片描述
继续查看表的数据,可以观察到
在这里插入图片描述结合上面的图片,可以发现一个表项由8字节组成,地址就是前两字节和后两字节的拼接,对应到0x20项,即地址为0x803f500 也可以观察到大部分由0填充,那么我们可以在这里填充我们的函数

在这里插入图片描述编辑数据,至于中间的四字节如何写,可以直接int3上面的,至于中间的四字节是什么意思,可以查看inter的白皮书idt的格式,大致意思是区分0环和3环,
假如你填写的是00408e00`00081040 代表这个是0环的,但是你在代码里写的是int 0x20 这是一个3环的代码,当他要跳到0x401040时会先匹配是不是0环的,因为是3环的,所以实际上不会运行0x401040的代码,而直接报错。而我们平常遇见的除0异常是真正的除0异常,它是由硬件所产生的,属于0环的,就可以跳到其处理函数,因此这里的格式应该和int 3的格式一样,因为int 3本身就是一个3环的断点。
在这里插入图片描述

1
eq 8003f500 0040ee00`00081040

在这里插入图片描述
4.完善实验代码
在主函数里加入了一个判断地址的检测函数,新加入了一个go函数用来触发断点,随后会进入到IdEntry函数,并且切换到0环,尝试读取内存数据,如果读取成功,代表成功。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD go_tmp;

void __declspec(naked) IdtEntry() {
__asm {
mov eax, dword ptr ds : [0x8003f400] ;
mov go_tmp, eax;
iretd
}
}
void go() {
__asm {
int 0x20;
}
}

int main() {
if ((DWORD)IdtEntry != 0x401040) {
printf("wrong addr: %p", IdtEntry);
exit(-1);
}
printf("%p\n", IdtEntry);
go();
printf("%p\n", go_tmp);
system("pause");
return 0;
}

小端序,成功读入内核的数据
在这里插入图片描述
5 问题
如果在xp上运行发现不是有效的win32程序,需要配置vs环境
在vs里添加单个组件 支持xp的v141工具,修改我们的平台集环境,把“C/C++ - 语言 - 符合模式”改成否,在 Release配置 属性页下,把运行库改成“/MT”
可参考VS2019怎样编译出可以在WinXP上运行的exe?

多核复杂性

1 读取if标志位
继续利用上面的环境 编写代码 通过pushfd就可以把eflags读取出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD g_eflags;

void __declspec(naked) IdtEntry() {
__asm {
pushfd //寄存器压栈
pop eax //读取到eax
mov g_eflags,eax //转到全局变量
iretd
}
}
void go() {
__asm {
int 0x20;
}
}


int main() {
if ((DWORD)IdtEntry != 0x401040) {
printf("wrong addr: %p", IdtEntry);
exit(-1);
}
go();
printf("%p\n", g_eflags);
system("pause");
return 0;
}

在这里插入图片描述
在这里插入图片描述

IF——中断允许标志若IF=1则cpu可以响应外部可屏蔽中断请求;若IF=0,则cpu不允许响应中断请求。可以发现if为0 关中断。这样就可以在0环的时候防止中断嵌套。
2 死循环
0环关中断 这样写一个死循环后,虚拟机和windbg都会卡死

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD g_eflags;

void __declspec(naked) IdtEntry() {
__asm {
Label:
jmp Label
iretd

}
}
void go() {
__asm {
int 0x20;
}
}


int main() {
if ((DWORD)IdtEntry != 0x401040) {
printf("wrong addr: %p", IdtEntry);
exit(-1);
}
go();
printf("%p\n", g_eflags);
system("pause");
return 0;
}

但加入了汇编指令sti(开中断),那么就不会死循环,强制虚拟机蓝屏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD g_eflags;

void __declspec(naked) IdtEntry() {
__asm {
sti
Label:
jmp Label
iretd

}
}
void go() {
__asm {
int 0x20;
}
}


int main() {
if ((DWORD)IdtEntry != 0x401040) {
printf("wrong addr: %p", IdtEntry);
exit(-1);
}
go();
printf("%p\n", g_eflags);
system("pause");
return 0;
}

3 多核
编辑虚拟机,设置为双核
在这里插入图片描述windbg输入命令,可在0核和1核之间相互切换

1
2
~1
~0

在这里插入图片描述
因为多核有两个idt表,表的内容不同,行为就有可能发生变化
在这里还是和之前的环境一样,把0核的idt表作修改,而1核的idt表变成函数2的地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
DWORD g_id;

void __declspec(naked) IdtEntry() {
__asm {
mov eax,1
mov g_id,eax
iretd

}
}
void __declspec(naked) IdtEntry2() {
__asm {
mov eax, 2
mov g_id, eax
iretd

}
}
void go() {
__asm {
int 0x20;
}
}


int main() {
if ((DWORD)IdtEntry != 0x401041) {
printf("wrong addr: %p", IdtEntry);
printf("wrong addr: %p", IdtEntry2);
exit(-1);
}
go();
printf("%p\n", g_id);
system("pause");
return 0;
}

在这里插入图片描述

1
2
3
4
~1
r idtr
dq f9c30590 l40
eq f9c30690 0040ee00`00081050

多次运行后,就会发现结果不一样
在这里插入图片描述
4 cpu写保护
先设置单核环境
寻找一个内核模块,打开pchunter>驱动模块 第一个驱动即可 右键>定位到驱动模块 复制出来,用ida打开
把ida的基质调整和pchunter里显示的一样,然后找到地址0x8054520,这里是3环进入0环的入口
在这里插入图片描述
尝试在这里写入数据,会发现无法成功写入,这是因为此页的也属性不能写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>


void __declspec(naked) IdtEntry() {
__asm {
mov eax ,0x12345678
mov ds:[80542520],eax
iretd

}
}
void go() {
__asm {
int 0x20;
}
}


int main() {
if ((DWORD)IdtEntry != 0x401040) {
printf("wrong addr: %p", IdtEntry);
exit(-1);
}
go();
system("pause");
return 0;
}

可以通过windbg命令来查看页属性

1
!pte xxxxxx

有两种方法可以进行写,一种直接加入可写的属性,另一种是把cpu的写保护关闭
关闭cpu写保护的汇编代码

1
2
3
mov eax,cr0
and eax,not 10000h
mov cr0,eax

5 多核写内存稳定性
整合上面的汇编代码,在单核的条件下,会成功的写入数据,并陷入循环卡死中

1
2
3
4
5
6
7
8
9
		mov eax,cr0
and eax,not 10000h
mov cr0,eax

mov eax ,0x12345678
mov ds:[80542520],eax
L:
jmp L
iretd

而在切换到双核的条件下,会发现产生了蓝屏(在windbg调试的情况下,报错返回到调试器)
这是因为,0核会和上面一样成功写入数据,并且陷入死循环,而当1核想要进入0环时,发现入口代码已经被修改成无效代码,无法正常进入,而原本0核想要处理1核的异常却被陷入死循环,这样就会发生蓝屏