Windows内核实验:中断现场(Interrupt Handling)

Windows内核实验:中断现场(Interrupt Handling)

!!!windbg调试器给我们的信息可能是不准确的,中断断下来后,调试子系统需要通过串口发送给调试器,这期间有些寄存器的值就变了,比较准确的方法就是在r0里写汇编,返回给我们数据

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_eax[2], g_ebx[2], g_ecx[2], g_edx[2], g_esi[2], g_edi[2], g_esp[2], g_ebp[2];
WORD g_cs[2], g_ds[2], g_es[2], g_fs[2], g_gs[2], g_ss[2];
DWORD g_eflags,g_eflags0;
void __declspec(naked) IdtEntry() {
__asm {
pushfd
pop[g_eflags]
mov[g_eax + 4], eax
mov[g_ebx + 4], ebx
mov[g_ecx + 4], ecx
mov[g_edx + 4], edx
mov[g_esi + 4], esi
mov[g_edi + 4], edi
mov[g_esp + 4], esp
mov[g_ebp + 4], ebp
mov[g_cs + 2], cs
mov[g_ds + 2], ds
mov[g_es + 2], es
mov[g_fs + 2], fs
mov[g_gs + 2], gs
mov[g_ss + 2], ss
iretd
}
}
void interrupt() {
__asm {
pushfd
pop [g_eflags0]
mov[g_eax], eax
mov[g_ebx], ebx
mov[g_ecx], ecx
mov[g_edx], edx
mov[g_esi], esi
mov[g_edi], edi
mov[g_esp], esp
mov[g_ebp], ebp
mov[g_cs], cs
mov[g_ds], ds
mov[g_es], es
mov[g_fs], fs
mov[g_gs], gs
mov[g_ss], ss
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
printf("eax:%x\t ebx:%x\t ecx:%x\t edx:%x\t esi:%x\t edi:%x\t esp:%x\t ebp:%x\t\n",
g_eax[0], g_ebx[0], g_ecx[0], g_edx[0], g_esi[0], g_edi[0], g_esp[0], g_ebp[0]);

printf("eax:%x\t ebx:%x\t ecx:%x\t edx:%x\t esi:%x\t edi:%x\t esp:%x\t ebp:%x\t\n",
g_eax[1], g_ebx[1], g_ecx[1], g_edx[1], g_esi[1], g_edi[1], g_esp[1], g_ebp[1]);

printf("cs:%x\t ds:%x\t es:%x\t fs:%x\t gs:%x\t ss:%x\t\n",
g_cs[0], g_ds[0], g_es[0], g_fs[0], g_gs[0], g_ss[0]);

printf("cs:%x\t ds:%x\t es:%x\t fs:%x\t gs:%x\t ss:%x\t\n",
g_cs[1], g_ds[1], g_es[1], g_fs[1], g_gs[1], g_ss[1]);
printf("efalgs0:0x%x\nefalgs0x%x",g_eflags0,g_eflags);
system("pause");
}
eax:401040       ebx:7ffd9000    ecx:b2ede300    edx:7c92eb94    esi:419cf0
edi:154d58 esp:12ff7c ebp:12ffc0
eax:401040 ebx:7ffd9000 ecx:b2ede300 edx:7c92eb94 esi:419cf0
edi:154d58 esp:b2cdfdcc ebp:12ffc0
cs:1b ds:23 es:23 fs:3b gs:0 ss:23
cs:8 ds:23 es:23 fs:3b gs:0 ss:10
efalgs0:0x246
efalgs0x46

中断前后变得只有,eflags:0x246–>0x46 esp:12ff7c->b2d13dcc cs:1b–>8 ss:23–>10

1b(0001 1011) –> 8(1000)

23(0010 0011) –> 10(0001 0000)

ds为什么还是23,没有提升到内核?

kd> dg 23
P Si Gr Pr Lo
Sel Base Limit Type l ze an es ng Flags
---- -------- -------- ---------- - -- -- -- -- --------
0023 00000000 ffffffff Data RW Ac 3 Bg Pg P Nl 00000cf3

可以看到ds是一个平坦段,可以访问整个4gb,分段保护机制其实是在弱化,只是提供了进程隔离,具体访问保护是由分页机制来提供的

eflags变化?

第九位变为0了,也就是IF位置零,中断处理过程关中断

cs段选择子为什么会是8?

和我们构造的中断门有关系 他的16-31位为中断处理程序目标代码段选择子 即为下面的0008

eq 8003f500 0040ee00`00081040

dg x命令显示段选择子x所指向的段描述符

ss和sp是怎么来的呢?

从tss段里取出来的,TR寄存器中存放着指向当前任务TSS段的段选择子

str [g_tr] 把当前任务(任务状态段TSS)的选择器存储到指定的位置上看下是0x28 右移3位所以是gdt表第5个即为tss段描述符

image-20230528174545899

TYPE的B是指任务是否处于忙状态(当前正在执行或被挂起等待执行),忙为1

kd> r gdtr
gdtr=8003f000
kd> dq 8003f000+8*5 l1
8003f028 80008b04`200020ab

type为1011

image-20230528172940147

这个基地址就是任务状态段TSS(Task state segment)所在处,即为下面的结构

其中红色的叫做静态字段,在任务创建是设置,一般不会改变

绿色的叫做动态字段,时常更新

image-20230528175556760

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_eax[2], g_ebx[2], g_ecx[2], g_edx[2], g_esi[2], g_edi[2], g_esp[2], g_ebp[2];
WORD g_cs[2], g_ds[2], g_es[2], g_fs[2], g_gs[2], g_ss[2];
WORD g_tr;
DWORD g_80042004, g_80042008;
void __declspec(naked) IdtEntry() {
__asm {
mov[g_esp], esp
mov[g_ss], ss
mov eax, ds:[0x80042004]
mov[g_80042004], eax
mov eax, ds:[0x80042008]
mov[g_80042008],eax
iretd
}
}
void interrupt() {
__asm {
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
printf("esp: 0x%x\tss: 0x%x\n", g_esp[0], g_ss[0]);
printf("esp0: 0x%x\tss0: 0x%hx",g_80042004,g_80042008);
system("pause");
}
esp:  0xb29d3dcc         ss: 0x10
esp0: 0xb29d3de0 ss0: 0x10

可以验证到,就是从tss里面去取内核栈

为什么esp栈顶提升了0x14,栈里压了5个什么

#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
DWORD g_eax[2], g_ebx[2], g_ecx[2], g_edx[2], g_esi[2], g_edi[2], g_esp[2], g_ebp[2];
WORD g_cs[2], g_ds[2], g_es[2], g_fs[2], g_gs[2], g_ss[2];
DWORD g_eflags;
DWORD g_stack[5];
void __declspec(naked) IdtEntry() {
__asm {
mov eax, dword ptr ss:[esp]
mov dword ptr [g_stack], eax
mov eax, dword ptr ss:[esp+4]
mov dword ptr [g_stack + 4], eax
mov eax, dword ptr ss:[esp+8]
mov dword ptr [g_stack + 8], eax
mov eax, dword ptr ss:[esp+12]
mov dword ptr [g_stack +12], eax
mov eax, dword ptr ss:[esp+16]
mov dword ptr [g_stack+16], eax
iretd
}
}
void interrupt() {
__asm {
pushfd
pop [g_eflags]
mov [g_ss],ss
mov [g_esp],esp
mov [g_cs],cs
int 0x20
}
}
int main() {
if (0x401040 != IdtEntry) {
printf("IdtRntry address wrong");
system("pause");
exit(-1);
}
interrupt();
printf("cs: 0x%x\teflags: 0x%hx\t",g_cs[0],g_eflags);
printf("esp: 0x%x\tss: 0x%x\n", g_esp[0], g_ss[0]);
for (size_t i = 0; i < 5; i++) {
printf("stack[%d]:0x%x\n",i,g_stack[i]);
}
system("pause");
}
cs: 0x1b        eflags: 0x246   esp: 0x12ff78   ss: 0x23
stack[0]:0x4010aa
stack[1]:0x1b
stack[2]:0x246
stack[3]:0x12ff78
stack[4]:0x23
请按任意键继续. . .

栈里第一个是返回地址,后面分别是三环的cs eflags esp ss,和之前理论是一样的(出错码是可选的),iret时会弹出来

image-20230528225918572

栈顶的返回地址怎么来调试验证?

直接在IdtEntry里写上int3 ,

void __declspec(naked) IdtEntry() {
__asm {
int 3
iretd
}
}
void interrupt() {
__asm {
push eax
pop eax
int 0x20
}
}

不挂调试器会报错,挂了调试器会在int3处断下来

Break instruction exception - code 80000003 (first chance)
00401040 cc int 3
kd> r esp
esp=b2cf3dcc
kd> dps b2cf3dcc l5
b2cf3dcc 00401061
b2cf3dd0 0000001b
b2cf3dd4 00000246
b2cf3dd8 0012ff78
b2cf3ddc 00000023
kd> ub 00401061 l4
0040105b 756d jne 004010ca
0040105d 50 push eax
0040105e 58 pop eax
0040105f cd20 int 20h

返回地址确实是int 20h中断后的下一条指令

image-20230527235756049

cs和ss选择子的RPL(CPL)都变为0,代表当前正在执行的程序或任务的特权级别为内核权限

windbg

kd> r gdtr
gdtr=8003f000
kd> r gdtl
gdtl=000003ff
kd> dq 8003f000 l20
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`17000068 80008955`17680068
8003f060 00009302`2f30ffff 0000920b`80003fff
8003f070 ff0092ff`700003ff 80009a40`0000ffff
8003f080 80009240`0000ffff 00009200`00000000
8003f090 00000000`00000000 00000000`00000000
8003f0a0 8200891e`a9400068 00000000`00000000
8003f0b0 00000000`00000000 00000000`00000000
8003f0c0 00000000`00000000 00000000`00000000
8003f0d0 00000000`00000000 00000000`00000000
8003f0e0 f8009f71`a000ffff 00009200`0000ffff
8003f0f0 8000984f`ccd403b7 00009200`0000ffff

GDT中第一个表项(0号)的描述符保留不用,称为空描述符

image-20230528001616020

按照上图解析

image-20230528001515527