操作系统|30天自制操作系统——第二十天保护操作系统
在实现保护操作系统之前,我们先来实现两个功能。一个是使用API显示字符串,另一个是用C语言编写应用程序。
使用API显示字符串 显示字符串有两种方法,一种是显示一串字符,当遇到字符编码0时结束。另一种是先指定要显示的字符串的长度,然后进行显示。
实现方式如下:
console.c节选:
void cons_putstr0(struct CONSOLE *cons, char *s)
{ for (;
*s != 0;
s++) {cons_putchar(cons, *s, 1);
}
return;
}void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{ int i;
for (i = 0;
i < l;
i++) {cons_putchar(cons, s[i], 1);
}
return;
}
我们把上面两个函数变成API,用之前的方法就是给每个函数分配一个INT。可是如果每次都给新的函数分配一个INT,IDT(只能设置256个)很快就会用完。因此我们学习BIOS中的方法,用EDX来存放功能号,这样就能够设置上亿个API函数了。
naskfunc.nas节选
_asm_hrb_api:
STI
PUSHAD ;
用于保存寄存器值的PUSH
PUSHAD ;
用于向hrb_api的PUSH
CALL _hrb_api
ADDESP,32
POPAD
IRETD
需要注意的是,hrb_api并不知道需要显示的代码段起始位置位于内存的什么地址,因此我们在内存中存放一下这个地址,这个地址暂时就放在0xfe8。
console.c节选:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{ (略)
if (finfo != 0) {/* 找到文件的情况 */
p = (char *) memman_alloc_4k(memman, finfo->size);
*((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);
farcall(0, 1003 * 8);
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
/* 没有找到文件的情况 */
return 0;
}void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{ int cs_base = *((int *) 0xfe8);
/* 地址在这 */
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);
} else if (edx == 2) {cons_putstr0(cons, (char *) ebx + cs_base);
} else if (edx == 3) {cons_putstr1(cons, (char *) ebx + cs_base, ecx);
}
return;
}
make run一下——
文章图片
用C语言来编写应用程序 我们想要用C语言来编写应用程序,这里需要应用程序创建一个api_putchar函数。C语言来api_putchar函数,api_putchar函数里可以调用INT 0x31。
a_nask.nas:
[FORMAT "WCOFF"];
生成对象文件的模式
[INSTRSET "i486p"];
表示使用486兼容指令集
[BITS 32];
生成32位模式机器语言
[FILE "a_nask.nas"];
源文件名信息GLOBAL _api_putchar[SECTION .text]_api_putchar: ;
void api_putchar(int c);
MOVEDX,1
MOVAL,[ESP+4];
c
INT0x31
RET
在程序结束后,能够返回命令行,需要在代码中添加返回HariMain地址的程序。
console.c节选:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{ (略)
if (finfo != 0) {/* 找到文件的情况 */
p = (char *) memman_alloc_4k(memman, finfo->size);
*((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);
if (finfo->size >= 8 && strncmp(p + 4, "Hari", 4) == 0) {p[0] = 0xe8;
p[1] = 0x16;
p[2] = 0x00;
p[3] = 0x00;
p[4] = 0x00;
p[5] = 0xcb;
}
farcall(0, 1003 * 8);
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
/* 没有找到文件的情况 */
return 0;
}
make run 一下——
文章图片
保护操作系统 平时使用操作系统时,也会遇到一些恶意应用程序。比如某些病毒吧,当操作系统中招了之后,就没法使用了。
比如下面的应用程序:
crack1.c文件:
void HariMain(void)
{ *((char *) 0x00102600) = 0;
return;
}
这个应用程序的功能是向内存的0x00102600地址写一个0,看似简单但对操作系统的破坏却是巨大的。
我们运行一下看看:
文章图片
执行了crack1命令后,操作系统已经坏掉了,我们再输入dir命令就毫无反应。
那么怎么避免这样的应用程序破坏操作系统呢?
我们要为应用程序分配自己的内存空间,就在自己的一亩三分田上好好工作,别跑到隔壁家瞎捣乱。创建应用程序专用的数据段,将DS和SS指向该段地址。不过如果有应用程序用汇编语言直接将操作系统的段地址存入DS,又会对操作系统产生破坏了。比如下面的应用程序:
crack2.nas:
[INSTRSET "i486p"]
[BITS 32]
MOVEAX,1*8;
OS用的段号
MOVDS,AX;
将其存入DS
MOVBYTE [0x102600],0
MOVEDX,4
INT0x31
解决的这个问题可以在段定义的地方加上访问权限,将段设置为应用程序可用,这里访问权限设置为0x60。当CS中的段地址为应用程序的段地址时,若存入操作系统的段地址则会产生异常。使用这种方法,在启动应用程序的时候,需要操作系统CALL应用程序。而根据x86规则,这样操作会产生异常。这里我们可以使用RETF,RETF的本质就是从栈中将地址POP出来,然后JMP到这个地址。
naskfunc.nas节选:
_start_app:;
void start_app(int eip, int cs, int esp, int ds, int *tss_esp0);
PUSHAD;
将32位寄存器的值全部保存下来
MOVEAX,[ESP+36] ;
应用程序用EIP
MOVECX,[ESP+40] ;
应用程序用CS
MOVEDX,[ESP+44] ;
应用程序用ESP
MOVEBX,[ESP+48] ;
应用程序用DS/SS
MOVEBP,[ESP+52] ;
tss.esp0的地址
MOV[EBP],ESP;
保存操作系统用ESP
MOV[EBP+4],SS;
保存操作系统用SS
MOVES,BX
MOVDS,BX
MOVFS,BX
MOVGS,BX
;
下面调整栈,以免用RETF跳转到应用程序
ORECX,3;
将应用程序用段号和3进行OR运算
OREBX,3;
将应用程序用段号和3进行OR运算
PUSH EBX;
应用程序的SS
PUSH EDX;
应用程序的ESP
PUSH ECX;
应用程序的CS
PUSH EAX;
应用程序的EIP
RETF
;
应用程序结束后不会回到这里
最后加入对异常的支持,我们对操作系统产生破坏、捣乱的应用程序强制结束。在x86架构规范中,当应用程序破坏操作系统时,会产生0x0d中断,这个中断也称为异常。在中断号0x0d中注册一个_asm_inthandler0d函数,将函数注册到IDT中。
dsctbl.c节选:
void init_gdtidt(void)
{ (略) /* IDT的设置 */
set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32);
/* 将函数注册到IDT中 */
set_gatedesc(idt + 0x20, (int) asm_inthandler20, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x21, (int) asm_inthandler21, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x27, (int) asm_inthandler27, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);
set_gatedesc(idt + 0x31, (int) asm_hrb_api,2 * 8, AR_INTGATE32);
return;
}
执行make run,看一下效果——
文章图片
crack1和crack2都被强制结束了,并报了一般保护异常的错误信息。
注:本文参照《30天自制操作系统》制作,感谢各位的持续关注。跟着作者思路制作起来,实在非常有趣,在此仅做学习分享。
最近看到有不少小伙伴也在尝试制作做操作系统,实感喜悦。大家可以一起动手尝试看看,乐趣满满的把知识学到了才是最重要不是么。
【操作系统|30天自制操作系统——第二十天保护操作系统】另对原著感兴趣的想要电子版的评论区留邮箱或私信我,原著源码在上一篇有分享。
推荐阅读
- 操作系统|[译]从内部了解现代浏览器(1)
- 你的口红,真的很美!
- 30天健身笔记(45)
- 【V课会】第3季-30天小学思维导图实战营
- 苹果手机如何利用库乐队自制铃声
- 2021-05-06节俭生活第30天,活得不如一条狗通透
- 用了30天整理的一些GO语言学习资料,2019请你加油
- 小程序|【自制壁纸生成器】2022新年壁纸领取,换一张手机壁纸,迎接2022叭~
- 【都市】生日|【都市】生日 [01] 【87】 一鸣30天中篇小说挑战营第二期
- 自制海苔芝麻--孩子拌饭吃