4 保护应用程序(2)(harib24d)
到底该怎样阻止应用程序攻击别的应用程序呢?我们倒是有一个办法,就是通过改写GDT的设置,只将正在运行的那个程序的段设置为应用程序用,其他的应用程序段都暂时设置为操作系统用。不过,用这个方法的话,需要在每次任务切换时都改写GDT的设置,话说我们现在每个任务也就只有两个应用程序段,这样看来这个方法也并不是不可行。
不过其实CPU已经为我们准备好了这个问题的解决方案,那就是LDT。难得有这么好的功能,我们当然要充分利用啦。
GDT是“global (segment) descriptor table”的缩写,LDT则是“local (segment) descriptor table”的缩写。相对于global代表“全局”,local则代表“局部”的意思,即GDT中的段设置是供所有任务通用的,而LDT中的段设置则只对某个应用程序有效。
如果将应用程序段设置在LDT中,其他的任务由于无法使用该LDT,也就不用担心它们来搞破坏了。
■■■■■
和GDT一样,LDT的容量也是64KB(可容纳设置8,192个段),不过在“纸娃娃系统”中我们现在只需要设置两个段,所以只使用了其中的16个字节,我们把这16个字节的信息放在struct TASK中。
我们可以通过GDTR这个寄存器将GDT的内存地址告知CPU,而LDT的内存地址则是通过在GDT中创建LDT段来告知CPU的。也就是说,在GDT中我们可以设置多个LDT(当然,不能同时使用两个以上的LDT),这和TSS非常相似。
下面我们在bootpack.h中添加用于设置LDT的段属性编号。
本次的bootpack.h节选
/* dsctbl.c */
(中略)
#define ADR_IDT 0x0026f800
#define LIMIT_IDT 0x000007ff
#define ADR_GDT 0x00270000
#define LIMIT_GDT 0x0000ffff
#define ADR_BOTPAK 0x00280000
#define LIMIT_BOTPAK 0x0007ffff
#define AR_DATA32_RW 0x4092
#define AR_CODE32_ER 0x409a
#define AR_LDT 0x0082 /*这里!*/
#define AR_TSS32 0x0089
#define AR_INTGATE32 0x008e
/* mtask.c */
(中略)
struct TASK {
int sel, flags; /* sel代表GDT编号*/
int level, priority;
struct FIFO32 fifo;
struct TSS32 tss;
struct SEGMENT_DESCRIPTOR ldt[2]; /*这里!*/
struct CONSOLE *cons;
int ds_base, cons_stack;
};
接下来我们修改mtask.c以便设置LDT。我们可以将LDT编号写入tss.ldtr,这样在创建TSS时就顺便在GDT中设置了LDT,CPU也就知道这个任务应该使用哪个LDT了。
本次的mtask.c节选
struct TASK *task_init(struct MEMMAN *memman)
{
(中略)
for (i = 0; i < MAX_TASKS; i++) {
taskctl->tasks0[i].flags = 0;
taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
taskctl->tasks0[i].tss.ldtr = (TASK_GDT0 + MAX_TASKS + i) * 8; /*这里!*/
set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
set_segmdesc(gdt + TASK_GDT0 + MAX_TASKS + i, 15, (int) taskctl->tasks0[i].ldt, AR_LDT);
/*这里!*/
}
(中略)
}
struct TASK *task_alloc(void)
{
(中略)
for (i = 0; i < MAX_TASKS; i++) {
if (taskctl->tasks0[i].flags == 0) {
(中略)
task->tss.fs = 0;
task->tss.gs = 0;
/*删掉原来的task->tss.ldtr = 0;*/
task->tss.iomap = 0x40000000;
task->tss.ss0 = 0;
return task;
}
}
return 0; /*已经全部正在使用*/
}
最后我们来修改console.c,使得应用程序段创建在LDT中。
本次的console.c节选
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
(中略)
if (finfo !=s 0) {
/*找到文件的情况*/
(中略)
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
(中略)
set_segmdesc(task->ldt + 0, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60); /*这里!*/
set_segmdesc(task->ldt + 1, segsiz - 1, (int) q, AR_DATA32_RW + 0x60); /*这里!*/
for (i = 0; i < datsiz; i++) {
q[esp + i] = p[dathrb + i];
}
start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0)); /*这里!*/
(中略)
} else {
cons_putstr0(cons, ".hrb file format error.\n");
}
(中略)
}
(中略)
}
在start_app的地方,我们指定的段号是4(=0×8+4)和12(=1×8+4),这里乘以8的部分和GDT是一样的,但不一样的是还加上了4,这是代表“该段号不是GDT中的段号,而是LDT内的段号”的意思。
不过如果用这样的写法,在多个应用程序同时运行时,应用程序用的代码段号就都为4,数据段号都为12,这不就跟我们之前遇到的一个bug差不多了嘛(参阅25.7节)。其实不然,由于这里我们使用的是LDT的段号,而每个任务都有自己专用的LDT,因此这样写完全没有问题。耶!
于是我们总共只修改了8行代码就完成了对LDT的支持,赶紧来测试一下吧。
■■■■■
我们来“make run”,当然,之前能实现的功能现在也完全没问题。
然后我们运行lines.hrb,再运行crack7.hrb。哦哦,crack7产生异常了,这是因为1005和2005号段现在并不是应用程序用的代码段和数据段了。
crack7.hrb产生异常了哦
那么如果我们将crack7.nas中的段号从10058和20058改为4和12会怎么样呢?
修改的crack7.nas节选
_HariMain:
MOV AX,4 ; 这里!
MOV DS,AX
CMP DWORD [DS:0x0004],'Hari'
JNE fin ; 不是应用程序,因此不执行任何操作
MOV ECX,[DS:0x0000] ; 读取该应用程序数据段的大小
MOV AX,12 ; 这里!
MOV DS,AX
这样的话就变成自己攻击自己了,对lines.hrb没有任何影响,所以坏人被我们打败了。
不过对于坏人来说还有一个漏洞可以利用,那就是CPU中的LLDT指令,用这个指令可以改变LDTR寄存器(用于保存当前正在运行的应用程序所使用的LDT的寄存器)的值,这样的话就可以切换到其他应用程序的LDT,从而引发问题。但是大家别担心,因为这个指令是系统专用指令,位于应用程序段内的程序是无法执行的,即时要强行执行这个指令,也会像执行CLI指令那样产生异常(参阅22.1节),捣乱的程序就会被强制结束。
于是我们终于成功地保卫了“世界和平”,可喜可贺呀。