自制操作系统日志——第二十二天
今天,我们将继续再完善一下保护操作系统的内容,以及进一步的利用c语言显示字符串!
文章目录
- 自制操作系统日志——第二十二天
- 一、保护操作系统3
- 手动强制关闭应用程序
- 二、用c语言显示字符串
- API 显示窗口
- 总结
一、保护操作系统3
首先,让我们来继续验证一下昨天的应用程序的保护力度吧:
先总结一下:
- 不允许应用程序访问操作系统的段地址空间;
- 应用程序运行时,对in、out一异常保护的功能;
- 应用程序运行时,指向cli、sti、hlt等都会产生异常;
- 不允许应用程序任意使用call命令。
我们尝试对定时器进行破坏:
crack3.nas
[INSTRSET "i486p"]
[BITS 32]MOV AL,0x34OUT 0x43,ALMOV AL,0xffOUT 0x40,ALMOV AL,0xffOUT 0x40,AL; 上述代码与下面的相同
; io_out8(PIT_CTRL, 0x34);
; io_out8(PIT_CNT0, 0xff);
; io_out8(PIT_CNT0, 0xff);MOV EDX,4INT 0x40
crack4:
[INSTRSET "i486p"]
[BITS 32]CLI
fin:HLTJMP fin
crack5:
[INSTRSET "i486p"]
[BITS 32]CALL 2*8:0xac1MOV EDX,4INT 0x40
很好,能够很有效的进行了防范!但是呢,如果我们使用的api里含有严重的写入内存的bug的化,还是有可能会产生漏洞的。因为api这一个本身就是在操作系统内部运行的,而非应用程序段运行的,应用程序只是简单进行调用而已!!
那么,我们接下来再试一下:
bug1.c:
void api_putchar(int c);
void api_end(void);void HariMain(void)
{char a[100];a[10] = 'Y';api_putchar(a[10]);a[102] = 'U';api_putchar(a[102]);a[120] = 'A';api_putchar(a[120]);api_end();
}
然后,运行一下,嘿!果然出问题了,这里我在VMware中运行时,首先弹出YU然后系统马上就重启了。(重启是因为产生了没有设置过的异常导致的)
这里,由于a数组是存于栈空间的,因此这里很明显的异常就是栈溢出! 那么在进行下一步的更正前我先简介一下cpu的中断有哪些:
- 0x00~0x1f 都是异常所使用的中断,通常有0x00除数异常;0x06非法指令异常;0x0c栈异常;0x0d应用程序异常
- 0x20以后就是我们IRQ设置的部分了!!
接下来,让我们开始进行设置吧:
naskfun:
_asm_inthandler0c:STIPUSH ESPUSH DSPUSHADMOV EAX,ESPPUSH EAXMOV AX,SSMOV DS,AXMOV ES,AXCALL _inthandler0cCMP EAX,0JNE end_app POP EAXPOPADPOP DSPOP ESADD ESP,4 ;int 0x0c需要IRETD
console:
int *inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");return &(task->tss.esp0); /*强制结束程序*/
}
dsctbl:
set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);
喔!貌似成功了,但是这里还有一点点小问题需要解释一下。即我们的U这里,也是明显超过了数组a的界限,为什么还能显示出来呢?
其实这里,0x0c这个异常仅仅只判断是否超过了应用程序分配的数据段的边界。也就是说我们的A是超过了分配的边界,而U虽然也超过了数组a的边界是个bug,但是他这里还没超过系统为应用程序分配的数据段的边界,因此这里不报错!
那么我们做一个定位的小程序吧:
int *inthandler0d(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();char s[30];cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");sprintf(s, "EIP = %08X\n", esp[11]);cons_putstr0(cons, s);return &(task->tss.esp0); /*强制结束程序*/
}int *inthandler0c(int *esp)
{struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct TASK *task = task_now();char s[30];cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");sprintf(s, "EIP = %08X\n", esp[11]);cons_putstr0(cons, s);return &(task->tss.esp0); /*强制结束程序*/
}
这里就是☞将esp的第十一号元素显示出来:
呃呃呃,图片里有我画的笔记,嘿嘿嘿别介意!! 然后我们来看看结果:
这里,显示了eip = 0042,那么我们来看看map和list:
list
map:
这里,list是还链接时的地址,因此暂时用0000代替。而map是连接后各个的地址。首先从map中可以看出我们的异常地址位于主函数中,然后看看主函数里的代码长度,发现了 0x24 + 0x1e = 0x42 因此,我们就定位到了a[120] = ‘A’ 处!
手动强制关闭应用程序
假设我们有如下bug:
void api_putchar(int c);
void api_end(void);void HariMain(void)
{for (;;) {api_putchar('a');}
}
如果我们,不能强制关闭的话,那么这个程序会一直循环导致程序一直占用系统资源的!
那么接下来我们就制作一个强制结束的程序,主要利用前面的end_app这个函数,不过这里我们要稍作修改:
bootpack.c:
if(i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0){//shift + F1强制关闭cons = (struct CONSOLE *) *((int *) 0xfec);cons_putstr0(cons, "\nBreak(key):\n");io_cli();//不能再改变寄存器的值时候进行切换task_cons->tss.eax = (int) &(task_cons->tss.esp0);task_cons->tss.eip = (int) asm_end_app;io_sti();}
- 这里我们选用主函数的原因是因为,当我们输入上述恶意代码后,命令行窗口会被占用,无法输入任何字符,因此也无法进行终止,想来想去,目前只有主函数可以随时传送数据进入;
- 上述程序的工作原理是:当我们接收到来自键盘的中断请求后,判断进入缓冲区的是否是shift+F1,并且还需要判断命令行窗口是否运行着程序,如果运行且接收到shif+F1,则:
- 改写命令行窗口任务的寄存器值,将asm_end_app的地址放入下一条执行命令里,以便执行该函数里的内容,并且将esp0也就是代码段地址的值送入eax中。
- 任何end_app这个函数接收到后进行把该值再赋予esp,任何返回到命令行窗口。
naskfunc.nas:
_asm_end_app:
; EAX为tss.esp0的地址MOV ESP,[EAX]MOV DWORD [EAX+4],0POPADRET ; 返回cmd_app
mtask.c:
struct TASK *task_alloc(void)
{
略task->tss.iomap = 0x40000000;task->tss.ss0 = 0 ;略}
运行看看:
二、用c语言显示字符串
首先,我们写入可供调用的api:
a_nask.nas:
_api_putstr0: ; void api_putstr0(char *s);PUSH EBXMOV EDX,2MOV EBX,[ESP+8] ;SINT 0x40POP EBXRET
然后编写一下c文件:
void api_putstr0(char *s);
void api_end(void);void HariMain(void)
{api_putstr0("hello, world\n");api_end();
}
这里,还有一点需要修改的就是,由于我们之前已经启用了x86架构对于异常保护的特性,因此不能利用retf来结束应用程序,那么我们之前对于"Hari" 开头这里也需要修改一下:
start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
可以直接,指定应用程序的hari的起始地址来代替前面的替换。好了让我们试试看?
哎!这里貌似有点小问题,我尝试了几次,有时是显现不出来,有时是乱码。这是怎么一回事呢? 那由于我们再前面用的是EBX进行传递字符串地址的,那现在让我们看看EBX寄存器里保存的值是多少吧:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{char s[12];略else if (edx == 2) {cons_putstr0(cons, (char *) ebx + cs_base);sprintf(s,"%08X\n", ebx);cons_putstr0(cons, s);}略}
然后,运行一下看看,发现ebx的值为0x00000400,也就是1024字节,可是这大小远远超过了我们程序段的范围了呀!
那让我们分析一下hrb这个文件吧:
由bim2hrb这个生成的文件有两部分:
- 代码部分;
- 数据部分。
当程序中没有使用字符串和外部变量时候,就会生成不包含数据部分的hrb文件。不过,我们之前很少考虑到数据部分这个位置的。而我们上述的程序在连接时候bim2hrb这个程序直接就认为了我们的字符串应该是在0x400这个部分。(具体的应该是编译器的设计问题吧)。 那么既然我们要开始考虑这部分的内容了,就让我们看一下hrb文件开头有哪些内容吧:
在0x0018这里, e9这个在汇编语言里的意思就是jmp。结合后面的就是jmp 应用程序入口。
至此,我们来修改一下程序:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{int i, segsiz, datsiz, esp, dathrb;略if (finfo != 0) {/*找到文件的情况*/p = (char *) memman_alloc_4k(memman, finfo->size);file_loadfile(finfo->clustno, finfo->size, p, fat, (char *)(ADR_DISKIMG + 0x003e00));if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00){segsiz = *((int *) (p + 0x0000)); //应用程序的数据段大小esp = *((int *) (p + 0x000c));datsiz = *((int *) (p + 0x0010));dathrb = *((int *) (p + 0x0014));q = (char *) memman_alloc_4k(memman, segsiz);*((int *) 0xfe8) = (int) q;set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);set_segmdesc(gdt + 1004, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);for(i = 0; i < datsiz; i++){//将hrb文件的数据部分复制到数据段中后再启动appq[esp + i] = p[dathrb + i];}start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));memman_free_4k(memman, (int) q, segsiz);}else{cons_putstr0(cons, ".hrb file format error.\n");}memman_free_4k(memman, (int) p, finfo->size);cons_newline(cons);return 1;}
return 0;
}
本次,修改的地方主要针对以下3点:
- 文件中找不到"Hari"标志就报错;
- 数据段大小根据.hrb文件中指定的值进行分配;
- 将hrb文件的数据部分复制到数据段后再启动程序。
然后,我们再写一个nas的程序看看:
hello5.nas
[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "hello5.nas"]GLOBAL _HariMain[SECTION .text]_HariMain:MOV EDX,2MOV EBX,msgINT 0x40MOV EDX,4INT 0x40[SECTION .data]msg:DB "hello, world", 0x0a, 0
这里的section就是指将代码段和数据段进行区分开来。除此之外,一般可执行文件最后都要加上一些类似"Hari”这个标志,比如说exe开头也有MZ这个标志,以此来进行标识的作用。
好了,现在让我们make run看看:
成功了!嘿嘿嘿,很好继续加油。
API 显示窗口
接下来,让我们调用API来显示一个窗口吧,具体我们主要做如下的设置:
- EDX = 5;
- EBX = 窗口缓冲区;
- ESI = x轴大小,即窗口宽度;
- EDI = y轴大小,即窗口高度;
- EAX = 透明色;
- ECX = 窗口名称;
调用后,返回值如下: - EAX = 操作窗口的句柄(例如刷新操作)
于是按照这个思路进行如下改写:
console.c:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{char s[12];int ds_base = *((int *) 0xfe8);struct TASK *task = task_now();struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);//bootpack中的值struct SHEET *sht;int *reg = &eax + 1;//指向eax后面的地址,是因为asm_hrb_api中push了两次一模一样的寄存器的值/* reg[0] : EDI, reg[1] : ESI, reg[2] : EBP, reg[3] : ESP *//* reg[4] : EBX, reg[5] : EDX, reg[6] : ECX, reg[7] : EAX */if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);} else if (edx == 2) {cons_putstr0(cons, (char *) ebx + ds_base);sprintf(s,"%08X\n", ebx);cons_putstr0(cons, s);} else if (edx == 3) {cons_putstr1(cons, (char *) ebx + ds_base, ecx);} else if (edx == 4) {return &(task->tss.esp0);}else if (edx == 5){sht = sheet_alloc(shtctl);sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);//设置x、y轴和透明色make_window8((char *)ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);sheet_slide(sht, 100, 50);sheet_updown(sht, 3);//高度高于task_areg[7] = (int) sht;//方便后续向应用程序的返回值动手脚}return 0;
}
bootpack.c:
task_run(task_a, 1, 2);*((int *) 0x0fe4) = (int) shtctl;
测试程序:
a_nask.nas:
_api_openwin: ; int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);PUSH EDIPUSH ESIPUSH EBXMOV EDX,5MOV EBX,[ESP+16] ; bufMOV ESI,[ESP+20] ; xsizMOV EDI,[ESP+24] ; ysizMOV EAX,[ESP+28] ; col_invMOV ECX,[ESP+32] ; titleINT 0x40POP EBXPOP ESIPOP EDIRET
winhelo.c:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_end(void);char buf[150 * 50];void HariMain(void)
{int win;win = api_openwin(buf, 150, 50, -1, "Yuan-os");api_end();
}
然后,运行:
哦耶!!🤞
那么我们继续进一步的对窗口增加一些功能:
窗口中显示字符API:
- EDX = 6
- EBX = 窗口句柄
- ESI = 显示位置的x坐标
- EDI = 显示位置的y坐标
- EAX = 色号
- ECX = 字符串长度
- EBP = 字符串
窗口中显示方块的API:
- EDX = 7
- EBX = 窗口句柄
- EAX = x0
- ECX = y0
- ESI = x1
- EDI = y1
- EBP = 色号
接下来就开始描写吧:
console.c:
else if (edx == 6) {sht = (struct SHEET *) ebx;putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);} else if (edx == 7) {sht = (struct SHEET *) ebx;boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);}
a_nask.nas:
_api_putstrwin: ; void api_putstrwin(int win, int x, int y, int col, int len, char *str);PUSH EDIPUSH ESIPUSH EBPPUSH EBXMOV EDX,6MOV EBX,[ESP+20] ; winMOV ESI,[ESP+24] ; xMOV EDI,[ESP+28] ; yMOV EAX,[ESP+32] ; colMOV ECX,[ESP+36] ; lenMOV EBP,[ESP+40] ; strINT 0x40POP EBXPOP EBPPOP ESIPOP EDIRET_api_boxfilwin: ; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);PUSH EDIPUSH ESIPUSH EBPPUSH EBXMOV EDX,7MOV EBX,[ESP+20] ; winMOV EAX,[ESP+24] ; x0MOV ECX,[ESP+28] ; y0MOV ESI,[ESP+32] ; x1MOV EDI,[ESP+36] ; y1MOV EBP,[ESP+40] ; colINT 0x40POP EBXPOP EBPPOP ESIPOP EDIRET
程序:
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_end(void);char buf[150 * 50];void HariMain(void)
{int win;win = api_openwin(buf, 150, 50, -1, "hello");api_boxfilwin(win, 8, 36, 141, 43, 3 /* 黄色*/);api_putstrwin(win, 28, 28, 0 /* 黑色*/, 12, "hello, world");api_end();
}
然后运行看看:
总结
呼呼,终于完成了,说实话,这一天的内容我实际上花了好久,因为身心以及难度来说,都令我感到疲倦了。不过休整了一下明天继续加油,还有几天我们就可以大功告成了!!