• 自制操作系统日志——第二十二天


    自制操作系统日志——第二十二天

    今天,我们将继续再完善一下保护操作系统的内容,以及进一步的利用c语言显示字符串!



    一、保护操作系统3

    首先,让我们来继续验证一下昨天的应用程序的保护力度吧:
    先总结一下:

    1. 不允许应用程序访问操作系统的段地址空间;
    2. 应用程序运行时,对in、out一异常保护的功能;
    3. 应用程序运行时,指向cli、sti、hlt等都会产生异常;
    4. 不允许应用程序任意使用call命令。

    我们尝试对定时器进行破坏:

    crack3.nas

    [INSTRSET "i486p"]
    [BITS 32]
    		MOV		AL,0x34
    		OUT		0x43,AL
    		MOV		AL,0xff
    		OUT		0x40,AL
    		MOV		AL,0xff
    		OUT		0x40,AL
    
    ; 	上述代码与下面的相同
    ;	io_out8(PIT_CTRL, 0x34);
    ;	io_out8(PIT_CNT0, 0xff);
    ;	io_out8(PIT_CNT0, 0xff);
    
    		MOV		EDX,4
    		INT		0x40
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    crack4:

    [INSTRSET "i486p"]
    [BITS 32]
    		CLI
    fin:
    		HLT
    		JMP		fin
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    crack5:

    [INSTRSET "i486p"]
    [BITS 32]
    		CALL	2*8:0xac1
    		MOV		EDX,4
    		INT		0x40
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    很好,能够很有效的进行了防范!但是呢,如果我们使用的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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    然后,运行一下,嘿!果然出问题了,这里我在VMware中运行时,首先弹出YU然后系统马上就重启了。(重启是因为产生了没有设置过的异常导致的)

    这里,由于a数组是存于栈空间的,因此这里很明显的异常就是栈溢出! 那么在进行下一步的更正前我先简介一下cpu的中断有哪些:

    • 0x00~0x1f 都是异常所使用的中断,通常有0x00除数异常;0x06非法指令异常;0x0c栈异常;0x0d应用程序异常
    • 0x20以后就是我们IRQ设置的部分了!!

    接下来,让我们开始进行设置吧:
    naskfun:

    _asm_inthandler0c:
            STI
    		PUSH 	ES
    		PUSH 	DS
    		PUSHAD
    		MOV		EAX,ESP
    		PUSH	EAX
    		MOV		AX,SS
    		MOV		DS,AX
    		MOV		ES,AX
    		CALL	_inthandler0c
    		CMP		EAX,0
    		JNE		end_app	
    		POP		EAX
    		POPAD
    		POP		DS
    		POP		ES
    		ADD		ESP,4		;int 0x0c需要
    		IRETD
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    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);	/*强制结束程序*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    dsctbl:

    set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);
    
    
    • 1
    • 2

    在这里插入图片描述

    喔!貌似成功了,但是这里还有一点点小问题需要解释一下。即我们的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);	/*强制结束程序*/
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    这里就是☞将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');
    	}
    }
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    如果我们,不能强制关闭的话,那么这个程序会一直循环导致程序一直占用系统资源的!
    那么接下来我们就制作一个强制结束的程序,主要利用前面的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();
    				   }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 这里我们选用主函数的原因是因为,当我们输入上述恶意代码后,命令行窗口会被占用,无法输入任何字符,因此也无法进行终止,想来想去,目前只有主函数可以随时传送数据进入;
    2. 上述程序的工作原理是:当我们接收到来自键盘的中断请求后,判断进入缓冲区的是否是shift+F1,并且还需要判断命令行窗口是否运行着程序,如果运行且接收到shif+F1,则:
    3. 改写命令行窗口任务的寄存器值,将asm_end_app的地址放入下一条执行命令里,以便执行该函数里的内容,并且将esp0也就是代码段地址的值送入eax中。
    4. 任何end_app这个函数接收到后进行把该值再赋予esp,任何返回到命令行窗口。

    naskfunc.nas:

    _asm_end_app:
    ;	EAX为tss.esp0的地址
    		MOV		ESP,[EAX]
    		MOV 	DWORD [EAX+4],0
    		POPAD
    		RET			; 返回cmd_app
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    mtask.c:

    struct TASK *task_alloc(void)
    {
    略
    			task->tss.iomap = 0x40000000;
                task->tss.ss0 = 0 ;}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    运行看看:
    在这里插入图片描述

    二、用c语言显示字符串

    首先,我们写入可供调用的api:
    a_nask.nas:

    _api_putstr0:  ; void api_putstr0(char *s);
            PUSH	EBX
    		MOV		EDX,2
    		MOV		EBX,[ESP+8]		;S
    		INT		0x40
    		POP		EBX
    		RET
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后编写一下c文件:

    void api_putstr0(char *s);
    void api_end(void);
    
    void HariMain(void)
    {
    	api_putstr0("hello, world\n");
    	api_end();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里,还有一点需要修改的就是,由于我们之前已经启用了x86架构对于异常保护的特性,因此不能利用retf来结束应用程序,那么我们之前对于"Hari" 开头这里也需要修改一下:

    start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
    
    • 1

    可以直接,指定应用程序的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);
    		}}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后,运行一下看看,发现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文件的数据部分复制到数据段中后再启动app
    				q[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}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    本次,修改的地方主要针对以下3点:

    • 文件中找不到"Hari"标志就报错;
    • 数据段大小根据.hrb文件中指定的值进行分配;
    • 将hrb文件的数据部分复制到数据段后再启动程序。

    然后,我们再写一个nas的程序看看:
    hello5.nas

    [FORMAT "WCOFF"]
    [INSTRSET "i486p"]
    [BITS 32]
    [FILE "hello5.nas"]
    
    		GLOBAL	_HariMain
    
    [SECTION .text]
    
    _HariMain:
    		MOV		EDX,2
    		MOV		EBX,msg
    		INT		0x40
    		MOV		EDX,4
    		INT		0x40
    
    [SECTION .data]
    
    msg:
    		DB	"hello, world", 0x0a, 0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    这里的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_a
    		reg[7] = (int) sht;//方便后续向应用程序的返回值动手脚
    	}
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    bootpack.c:

    	task_run(task_a, 1, 2);
    	*((int *) 0x0fe4) = (int) shtctl;
    
    • 1
    • 2

    测试程序:
    a_nask.nas:

    _api_openwin:	; int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
    		PUSH	EDI
    		PUSH	ESI
    		PUSH	EBX
    		MOV		EDX,5
    		MOV		EBX,[ESP+16]	; buf
    		MOV		ESI,[ESP+20]	; xsiz
    		MOV		EDI,[ESP+24]	; ysiz
    		MOV		EAX,[ESP+28]	; col_inv
    		MOV		ECX,[ESP+32]	; title
    		INT		0x40
    		POP		EBX
    		POP		ESI
    		POP		EDI
    		RET
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    然后,运行:
    在这里插入图片描述

    哦耶!!🤞

    那么我们继续进一步的对窗口增加一些功能:

    窗口中显示字符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);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    a_nask.nas:

    
    _api_putstrwin:	; void api_putstrwin(int win, int x, int y, int col, int len, char *str);
    		PUSH	EDI
    		PUSH	ESI
    		PUSH	EBP
    		PUSH	EBX
    		MOV		EDX,6
    		MOV		EBX,[ESP+20]	; win
    		MOV		ESI,[ESP+24]	; x
    		MOV		EDI,[ESP+28]	; y
    		MOV		EAX,[ESP+32]	; col
    		MOV		ECX,[ESP+36]	; len
    		MOV		EBP,[ESP+40]	; str
    		INT		0x40
    		POP		EBX
    		POP		EBP
    		POP		ESI
    		POP		EDI
    		RET
    
    _api_boxfilwin:	; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
    		PUSH	EDI
    		PUSH	ESI
    		PUSH	EBP
    		PUSH	EBX
    		MOV		EDX,7
    		MOV		EBX,[ESP+20]	; win
    		MOV		EAX,[ESP+24]	; x0
    		MOV		ECX,[ESP+28]	; y0
    		MOV		ESI,[ESP+32]	; x1
    		MOV		EDI,[ESP+36]	; y1
    		MOV		EBP,[ESP+40]	; col
    		INT		0x40
    		POP		EBX
    		POP		EBP
    		POP		ESI
    		POP		EDI
    		RET
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    程序:

    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();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    然后运行看看:

    在这里插入图片描述


    总结

    呼呼,终于完成了,说实话,这一天的内容我实际上花了好久,因为身心以及难度来说,都令我感到疲倦了。不过休整了一下明天继续加油,还有几天我们就可以大功告成了!!

  • 相关阅读:
    CentOS 7.2 配置mysql5.7
    【毕业设计】推荐系统构建和应用 毕业设计能做哪些推荐系统
    wflow模型sbm
    vue 把echarts封装成一个方法 并且从后端读取数据 +转换数据格式 =动态echarts 联动echarts表
    文件I/O_03PageCache和Mmap
    Docker极简入门:使用Docker-Compose 运行网站浏览量统计Demo
    【货干】IP 配置出现意外。
    AMD EPYC(霄龙)Genoa服务器 | 综合评测
    国密是什么意思?属于商密还是普密?
    2023CCF中国开源大会 | 麒麟信安作为首批合作伙伴入驻全国信创开源广场
  • 原文地址:https://blog.csdn.net/qq_43696276/article/details/126185242