Day21 保护操作系统
文章目录
- 1. 保护操作系统1(harib18c)
- 2. 保护操作系统2(harib18d)
- 3. 对异常的支持(harib18e)
- 4. 保护操作系统4(harib18g)
1. 保护操作系统1(harib18c)
先假设我们可以使用C语言编写应用程序,然后做了一个这样的应用程序,直接往0x00102600地址写0。这就是一个很强的破坏操作系统的应用软件,所以需要对操作系统加保护,即防止应用软件访问由操作系统管理的内存空间。
void HariMain(void)
{*((char *) 0x00102600) = 0;return;
}
2. 保护操作系统2(harib18d)
为应用程序提供专用的内存空间,并告诉应用程序“此处以外不可访问”。因此需要创建应用程序专用数据段,并在应用程序运行期间,将DS(数据段寄存器)和SS(栈段寄存器)指向该段地址。
// console.c 文件
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{/* 省略 */char *p, *q;/* 省略 */if (finfo != 0) {/* 找到该文件 */p = (char *) memman_alloc_4k(memman, finfo->size);q = (char *) memman_alloc_4k(memman, 64 * 1024); // 应用程序专用64K空间*((int *) 0xfe8) = (int) p;file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);set_segmdesc(gdt + 1004, 64 * 1024 - 1, (int) q, AR_DATA32_RW); // 应用程序专用64K空间if (finfo->size >= 8 && strncmp(p + 4, "Hari", 4) == 0) {// 修改应用程序可执行文件的前6字节p[0] = 0xe8;p[1] = 0x16;p[2] = 0x00;p[3] = 0x00;p[4] = 0x00;p[5] = 0xcb;}start_app(0, 1003 * 8, 64 * 1024, 1004 * 8); // 之前只是调用了far-CALLmemman_free_4k(memman, (int) p, finfo->size);memman_free_4k(memman, (int) q, 64 * 1024);cons_newline(cons);return 1;
}
# naskfunc.nas 文件
_start_app: # void start_app(int eip, int cs, int esp, int ds);PUSHAD # 将32位寄存器的值全部保存(共8个),所以取函数传参需要ESP多加32MOV EAX,[ESP+36] # 应用程序用EIPMOV ECX,[ESP+40] # 应用程序用CSMOV EDX,[ESP+44] # 应用程序用ESPMOV EBX,[ESP+48] # 应用程序用DS/SSMOV [0xfe4],ESP # 操作系统用ESPCLI # 在切换过程到应用程序中禁止中断请求MOV ES,BX # 把ES,SS,DS,FS,GS都赋值为应用程序的DS/SSMOV SS,BXMOV DS,BXMOV FS,BXMOV GS,BXMOV ESP,EDX # 将应用函数栈顶赋值给ESPSTI # 切换完恢复中断请求PUSH ECX # 用于far-CALL的PUSH(cs)PUSH EAX # 用于far-CALL的PUSH(eip)CALL FAR [ESP] # 调用应用程序,跳到[cs:eip]位置,应用程序的代码段# 应用程序结束后返回此处MOV EAX,1*8 # 操作系统用DS/SSCLI # 切换回操作系统用DS/SS,禁止中断请求MOV ES,AXMOV SS,AXMOV DS,AXMOV FS,AXMOV GS,AXMOV ESP,[0xfe4]STI # 切换完恢复中断请求POPAD # 恢复函数最初保存的寄存器值RET
PUSHAD之后的栈空间:
高地址
±----------+
| 参数 ds | ← ESP + 16 (调用前)
±----------+
| 参数 esp | ← ESP + 12
±----------+
| 参数 cs | ← ESP + 8
±----------+
| 参数 eip | ← ESP + 4
±----------+
| 返回地址 | ← ESP (调用前) ← call 指令压入
±----------+
| 旧 EAX | ← ESP - 4 ← PUSHAD 压入
±----------+
| 旧 ECX | ← ESP - 8
±----------+
| 旧 EDX | ← ESP - 12
±----------+
| 旧 EBX | ← ESP - 16
±----------+
| 旧 ESP (快照) | ← ESP - 20
±----------+
| 旧 EBP | ← ESP - 24
±----------+
| 旧 ESI | ← ESP - 28
±----------+
| 旧 EDI | ← ESP - 32 ← 当前 ESP 位置
±----------+
低地址
hrb_api
函数是C语言编写的操作系统内部函数,因此如果不将段地址设回操作系统用的段就无法正常工作,于是修改_asm_hrb_api
。
# naskfunc.nas 文件
# 中断40时,会调用到 asm_hrb_api
_asm_hrb_api:PUSH DSPUSH ESPUSHADMOV EAX,1*8MOV DS,AX # 先仅将DS设定位操作系统用MOV ECX,[0xfe4] # 操作系统的ESPADD ECX,-40MOV [ECX+32],ESP # 保存应用程序的ESPMOV [ECX+36],SS # 保存应用程序的SS# 将PUSHAD的值复制到系统栈MOV EDX,[ESP ]MOV EBX,[ESP+ 4]MOV [ECX ],EDX # 复制传递给hrb_apiMOV [ECX+ 4],EBX # 复制传递给hrb_apiMOV EDX,[ESP+ 8]MOV EBX,[ESP+12]MOV [ECX+ 8],EDX # 复制传递给hrb_apiMOV [ECX+12],EBX # 复制传递给hrb_apiMOV EDX,[ESP+16]MOV EBX,[ESP+20]MOV [ECX+16],EDX # 复制传递给hrb_apiMOV [ECX+20],EBX # 复制传递给hrb_apiMOV EDX,[ESP+24]MOV EBX,[ESP+28]MOV [ECX+24],EDX # 复制传递给hrb_apiMOV [ECX+28],EBX # 复制传递给hrb_apiMOV ES,AX # 将剩余的段寄存器也设为操作系统用MOV SS,AXMOV ESP,ECXSTI # 恢复中断请求CALL _hrb_api # 调用hrb_apiMOV ECX,[ESP+32] # 取出应用程序的ESPMOV EAX,[ESP+36] # 取出应用程序的SSCLIMOV SS,AXMOV ESP,ECXPOPADPOP ESPOP DSIRETD # 自动执行STI,恢复中断请求
类似的需要修改每一个中断的汇编函数(_asm_inthandler20,_asm_inthandler21等),因为中断产生后会调用_inthandler20等操作系统内部的C语言函数,因此也需要对DS和SS进程切换。
其实CPU本身就具有自动进行复杂段切换的功能,最终并不会像本节这样对中断产生时调用到的函数做手动的DS/SS切换。
3. 对异常的支持(harib18e)
在x86架构规范中,当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,就会产生0x0d中断,因此该中断也被称为“异常”。
# naskfunc.nas 文件
_asm_inthandler0d:STIPUSH ESPUSH DSPUSHADMOV AX,SSCMP AX,1*8JNE .from_app
# 当操作系统活动时,产生中断的情况和之前类似MOV EAX,ESPPUSH SS # 保存中断时的SSPUSH EAX # 保存中断时的ESPMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler0dADD ESP,8POPADPOP DSPOP ESADD ESP,4 # INT 0x0d 中需要使用的IRETD
.from_app:
# 当应用程序活动时产生中断CLIMOV EAX,1*8MOV DS,AX # 先仅将DS设定为操作系统用MOV ECX,[0xfe4] # 操作系统的ESPADD ECX,-8MOV [ECX+4],SS # 保存产生中断时的SSMOV [ECX ],ESP # 保存产生中断时的ESPMOV SS,AXMOV ES,AXMOV ESP,ECXSTICALL _inthandler0dCLICMP EAX,0JNE .killPOP ECXPOP EAXMOV SS,AX # 将SS恢复为应用程序用MOV ESP,ECX # 将ESP恢复为应用程序用POPADPOP DSPOP ESADD ESP,4 # INT 0x0d 需要使用IRETD
.kill:
# 将应用程序强制结束MOV EAX,1*8 # 操作系统用的DS/SSMOV ES,AXMOV SS,AXMOV DS,AXMOV FS,AXMOV GS,AXMOV ESP,[0xfe4] # 强制返回到start_app时的ESPSTI # 切换完成后恢复中断请求POPAD # 恢复事先保存的寄存器值RET
// console.c 文件
int inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n"); // 一般保护异常return 1; /* 强制结束程序 */
}
该函数类似于_asm_inthandler20,主要在于其添加了STI/CLI这样的控制中断请求禁止和恢复的指令,和根据inthandler0d的执行结果来强制结束应用程序的操作。0x0d对应的中断其实就是一般保护异常,还有一些特殊的异常是由0x0d以外的中断处理。
// dsctbl.c 文件
void init_gdtidt(void)
{/* IDT的设置 */set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32);
}
现在在命令行执行crack1应该会爆出“General Protected Exception.”错误。
4. 保护操作系统4(harib18g)
当前可以防止C语言应用程序恶意访问操作系统的段内存,但是还不能防止汇编程序直接直接向DS存入操作系统的段地址。如下:
# crack2.nas 文件
[INSTRSET "i486p"]
[BITS 32]MOV EAX,1*8 # 操作系统用的段号MOV DS,AX # 将其存入DSMOV BYTE [0x102600],0RETF
x86存在一个功能,在段定义的地方,如果将访问权限加上0x60,就可以将段设置为应用程序用。当CS的段地址为应用程序用段地址时,CPU会认为“当前正在运行应用程序”,这是如果存入操作系统用段地址的话,就会产生异常。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{char *p, *q;struct TASK *task = task_now();/* 省略 */set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);set_segmdesc(gdt + 1004, 64 * 1024 - 1, (int) q, AR_DATA32_RW + 0x60);/* 省略 */start_app(0, 1003 * 8, 64 * 1024, 1004 * 8, &(task->tss.esp0));/* 省略 */
}
参考之前的方法,需要在start_app的函数定义中far-CALL应用程序的段,但是x86中禁止操作系统far-CALL或far-JMP应用程序。此时可以使用RETF,就像是被CALL过一样,先将地址PUSH到栈中,然后执行RETF,就可以成功启动应用程序了。
RETF的本质就是从栈中将地址POP出来,并JMP到该地址,因此可以用RETF代替far-JMP功能。
# naskfunc.nas 文件
_start_app: ; void start_app(int eip, int cs, int esp, int ds, int *tss_esp0);PUSHAD # 将32位寄存器的值全部保存MOV EAX,[ESP+36] # 应用程序用EIPMOV ECX,[ESP+40] # 应用程序用CSMOV EDX,[ESP+44] # 应用程序用ESPMOV EBX,[ESP+48] # 应用程序用DS/SSMOV EBP,[ESP+52] # tss.esp0的地址MOV [EBP ],ESP # 保存操作系统的ESPMOV [EBP+4],SS # 保存操作系统的SSMOV ES,BXMOV DS,BXMOV FS,BXMOV GS,BX
# 调整栈,以免用RETF跳转到应用程序OR ECX,3 # ECX是应用程序段号OR EBX,3 # EBX是应用程序段号PUSH EBX # 应用程序用的SSPUSH EDX # 应用程序用的ESPPUSH ECX # 应用程序用的CSPUSH EAX # 应用程序用的EIPRETF
# 应用程序结束后不会回到这里
由于并不是通过far-CALL调用应用程序,因此应用程序执行完成之后无法用RETF的方式结束并返回。
# naskfunc.nas 文件
_asm_hrb_api:STIPUSH DSPUSH ESPUSHAD # 用于保存PUSHAD # 向hrb_api传值MOV AX,SSMOV DS,AX # 将操作系统段地址存入DS和ESMOV ES,AXCALL _hrb_apiCMP EAX,0 # 当EAX不为0时调用end_app,结束程序JNE end_appADD ESP,32POPADPOP ESPOP DSIRETD
end_app:
# EAX为tss.esp0的地址MOV ESP,[EAX]POPADRET # 返回cmd_app
当_hrb_api返回0时,继续运行程序;返回非0值时,则把返回值当作tss.esp0处理,JNE end_app强制结束应用程序。所以需要修改hrb_api函数,配置当EDX为4时结束程序。
// console.c 文件
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{struct TASK *task = task_now();/* 省略 */if (edx == 4) {return &(task->tss.esp0);}return 0;
}
同时修改异常中断0x0d:
// console.c 文件
int *inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");return &(task->tss.esp0); /* 让程序强制结束 */
}
由于把麻烦的栈切换全部由CPU处理了,因此_asm_inthandler20,_asm_inthandler21,_asm_inthandler2c等系列的函数也都需要恢复原来的样子。但是处理异常中断的_asm_inthandler0d恢复为原来的样子之外还需要关注EAX的值(类似_asm_hrb_api中当EAX不等于0的时候需要调用end_app)。
配置asm_hrb_api作为0x40中断时也需要声明“其可用于应用程序作为API来调用”。
// dsctbl.c 文件
void init_gdtidt(void)
{/* 省略 */set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32 + 0x60);/* 省略 */
}
在应用程序中(hello.nas, hello2.nas, crack2.nas)则需要在结束时添加程序退出:
MOV EDX, 4
INT 0x40
对于c语言写的应用程序(a.c, crack1.c, hello3.c),则需要在最后调用定义在a_nask.nas文件中的汇编代码函数api_end
:
# a_nask.nas 文件
_api_end: # void api_end(void);MOV EDX,4INT 0x40