今天主要的内容是LDT与库的制作。加油加油😊
我们接下来进行修改bootpack.c程序。我们需要修改两个地方,第一个就是当我们按下X按钮后,就先隐藏起命令行的窗口,之所以这样子做是因为由于关闭的一整个命令行窗口的步骤比较多,因此还需要花费一点时间去处理,这样子可能会让用户觉得有点久,所以如果我们直接隐藏起来,这样子就可以让用户觉得我们已经关闭成功了!!
if(i == 256 + 0x3b && key_shift != 0 && key_win != 0){//shift + F1强制关闭
task = key_win->task; //获取当前处于的图层
if(task != 0 && task->tss.ss0 != 0)
{
cons_putstr0(task->cons, "\nBreak(key):\n");
io_cli();//不能再改变寄存器的值时候进行切换
task->tss.eax = (int) &(task->tss.esp0);
task->tss.eip = (int) asm_end_app;
io_sti();
task_run(task, -1, 0);//为了确实执行结束处理,需要把处于休眠状态的这个任务唤醒
}
}
略
else if (2024 <= i && i <= 2279){
sht2 = shtctl->sheets0 + (i -2024);
memman_free_4k(memman, (int) sht2->buf, 256 * 165);
sheet_free(sht2);
}
然后,我们还要修改console.c这个程序,以便对接收fifo缓冲区发来的数据进行进一步的处理:
在这个 console_task函数里,主要我们就是将sheet换成cons.sheet。对于平常情况下两者没有太大差别,但是当我们的命令行窗口关闭后cons.sht会被置为0,而sheet则不变!
然后,还要修改键盘里的API:
等待键盘输入的过程中,如果FIFO缓冲区接收到4,则表示关闭命令行窗口的信号,此时取消定时器,并发出清理图层的信息,然后将sht->cons置为0!
主要思路就是,我们利用命令行打开了一个程序后,此时我们对于该命令行的键盘输入都会由该程序进行接收,因此,当该程序接收到鼠标点击的关闭按钮后,系统就会像程序的fifo缓冲区发送4,然后调用API 15 进行接收处理,然后再向主函数发送对应的关闭命令行的fifo缓冲数据。
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
略
struct FIFO32 *sys_fifo = (struct FIFO32 *) *((int *) 0xfec);
略
else if (edx == 15){
for(;;){
略
if(i == 4){//只关闭命令行窗口
timer_cancel(cons->timer);
io_cli();
fifo32_put(sys_fifo, cons->sht - shtctl->sheets0 + 20240);
cons->sht = 0;
io_sti();
}
略
运行,看看:
我们前面已经做了一个操作系统的保护功能了,那么试想一下,如果我们呢利用应用程序访问应用程序段,并向里面写入奇怪的数据的话是不是也会发生异常?(毕竟我们制作了不能越权访问操作系统段的程序而已)
我们制作下面这个程序:
[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "crack7.nas"]
GLOBAL _HariMain
[SECTION .text]
_HariMain:
MOV AX,1005*8
MOV DS,AX
CMP DWORD [DS:0x0004],'Hari'
JNE fin ; 不是应用程序,因此不执行任何操作
MOV ECX,[DS:0x0000] ; 读取该应用程序的段
MOV AX,2005*8
MOV DS,AX
crackloop: ; 填入数据
ADD ECX,-1
MOV BYTE [DS:ECX],123
CMP ECX,0
JNE crackloop
fin: ; 结束
MOV EDX,4
INT 0x40
然后,我们打开系统,先运行一个程序,然后再运行我们的破坏程序,最后移动一下鼠标就会出现下图:
以下呢,让我们进行分析一下:
首先呢,我们所分配的段是按顺序进行分配的,也就是说:
当然数据也是类似的,从2003 ~ 2006号段这样子。 因此,我们的破坏程序,一开始就先越权读取了color2的代码段,检查是否是程序(即有无hari)。然后检测到以后,就设置将随意的数据写入到color2的数据段里也就是2005中,以此我们就可以进行覆盖了原来的数据内容了。这样子当然就可以搞破坏了!
那么我们想要解决的思路起始就很简单,只要将我们运行的应用程序段进行隔离,不允许其他应用程序越权访问即可。 好在cpu已经为我们提供了一个较好的解决方案,即LDT ( 局部段管理表)。与GDT不同的是,GDT是全局的,其设置的段是所有任务通用的;但是LDT所设置的段只对某个程序有用而已。因此,我们将某个应用程序段设置再LDT中,其他任务就无法使用该LDT段了。
LDT的内存地址是通过我们再GDT中设置的LDT段来告知cpu的。那么以下我们先进行设置LDT的段属性编号:
bootpack.h:
/* dsctbl.c */
#define AR_LDT 0x0082
/* 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了。
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)
{
略
task->tss.fs = 0;
task->tss.gs = 0;
task->tss.iomap = 0x40000000; //删掉原来的ldtr = 0
task->tss.ss0 = 0 ;
return task;//找到就返回一个!
略
}
接下来,还需要修改一下console.c函数:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
略
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);
略
start_app(0x1b,0 * 8 +4, esp, 1 * 8 + 4, &(task->tss.esp0));
略
}
首先,在start这里我们+4是为了告诉cpu我们这里不是GDT中段号,而是使用的LDT的段号。其次,由于每个任务的LDT都不一样因此这里不用担心会出现多个程序共用一段内存的情况!
接着,我们make run:
看吧,这里直接就出现异常了!!因为此时的1005不再是应用程序的段了,使用操作系统默认该段是操作系统内的段,因此出现了访问异常的。
那么,如果我们将段改成我们设置的4 和 12的话会发生什么? 不用想这肯定是自己破坏自己,因为应用程序的ldr仅有自己而已:
看,确实没啥影响了吧!
在前面我们制作的任何程序,都是直接与a_nask进行相连的,但是我们可能程序并不会全部用到这里面所有存储的api,这就会使得整个程序异常的臃肿,于是乎我们接下来打算将所有api都单独进行存储拆分:
即,一个API单独成一个函数!! 然后由于bim2hrb这个连接器有着筛选功能(也就是说其连接的所有.OBJ文件里如果本应用程序并没有使用到该OBJ文件里的任一函数就会直接丢弃,不进行连接) ,因此我们就可以利用这个极大的简化了我们程序的大小。(makeflie文件里的内容就自行查看了,这里难度不大,就是普通的连接而已)
现在让我们看看未简化前的程序大小:
看到了吧,差距还挺明显的!
这里为了以后方便进行统一的管理以及调用,我们这里将使用库 .lib 的文件将这些.obj文件进行打包成一个文件进行统一的管理。:
makeflie:
GOLIB = $(TOOLPATH)golib00.exe
apilib.lib : Makefile $(OBJS_API)
$(GOLIB) $(OBJS_API) out:apilib.lib
然后,我们就可以吧这些api全部放入库函数中!!程序连接时只需与库函数进行连接即可:
除此,之外为了方便调用函数,我们还制作了一个头文件:
apilin.h:
void api_putchar(int c);
void api_putstr0(char *s);
void api_putstr1(char *s, int l);
void api_end(void);
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_initmalloc(void);
char *api_malloc(int size);
void api_free(char *addr, int size);
void api_point(int win, int x, int y, int col);
void api_refreshwin(int win, int x0, int y0, int x1, int y1);
void api_linewin(int win, int x0, int y0, int x1, int y1, int col);
void api_closewin(int win);
int api_getkey(int mode);
int api_alloctimer(void);
void api_inittimer(int timer, int data);
void api_settimer(int timer, int time);
void api_freetimer(int timer);
void api_beep(int tone);
这样子,我们的应用程序只需写一个include就足以了:
后续的make整理就参考源文件吧,以及书本上的没啥好说的。
唯一要注意的是,这里我们将操作系统的内核和程序的makfile分开了,因此以后就不用每新增一个应用程序就重新生成以便了。其中app_make.txt 就是控制应用程序的生成的。
最后,我们做完后直接在day27的文件夹下,打开运行的窗口,然后先src_only_full 全部清理一下后,在run_full 全部重新生成一下就好了。那么下面看看我们制作的程序的小全家福吧:
以上呢,就是今天关于LDT和库的所有内容,明天将进行很难的文字显示部分的内容啊,搞完明天我们的任务也就差不多完成了!!!!