【读书笔记-《30天自制操作系统》-20】Day21

news2024/11/18 3:45:33

本篇的内容主要是操作系统的保护,涉及到x86 CPU的一些机制,以及操作系统的异常处理。

在这里插入图片描述

1. 字符显示API问题解决

首先来解决一下上一篇内容中字符串显示API没有生效的问题。

void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	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);
	} else if (edx == 3) {
		cons_putstr1(cons, (char *) ebx, ecx);
	}
	return;
}

在显示单个字符的时候,用[CS:ECX]的方式指定了CS的值,可以成功读取到显示的内容;而显示字符串时,无法指定段地址,传入的ebx地址,程序按照DS从错误的地址中读取了内容,恰好内容为0,因此什么都没有显示出来。

从cmd_app到hrb_api的数据传递,仍然需要借助内存。这里我们用了0xfe8这个地址:

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; //传递段地址到0xfe8
		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;
}

这样修改之后再运行程序,就可以正常显示字符串了。

在这里插入图片描述
2. 操作系统保护

操作系统上运行着很多应用程序。有些应用程序可能会因为bug而对操作系统造成影响,有的恶意应用则会故意对操作系统进行破坏,造成运行异常等。对此操作系统需要提供足够的保护,防止这些有意无意的破坏。实际上x86架构的CPU就提供了保护操作系统的功能。

2.1 破坏程序1:直接篡改操作系统的内存

首先来模拟一种破坏方式——在应用程序中直接篡改操作系统的内存内容。

void HariMain(void)
{
	*((char *) 0x00102600) = 0;
	return;
}

将以上代码编译为crack1应用程序并进行运行。可以看到输入crack1后运行,虽然没有任何输出,但接下来输入dir也无法正常响应了。

在这里插入图片描述
这里应用程序直接操作了属于操作系统管理的内存空间。操作系统和应用程序之间可谓是泾渭分明,应用程序只能通过允许的接口调用操作系统功能。对于这种非法的操作,必须要在其生效之前强行终止。我们需要为应用程序创建专用的数据段,在应用程序运行期间将DS和SS指向该地址段,而不允许应用程序访问其他的内存空间。

  • 操作系统用代码段 2*8
  • 操作系统用数据段 1*8
  • 应用程序用代码段 1003*8
  • 应用程序用数据段 1004*8

(3*8 - 1002*8为TSS所使用的段)

	if (finfo != 0) {
		/* 找到文件的情况 */
		p = (char *) memman_alloc_4k(memman, finfo->size);
		q = (char *) memman_alloc_4k(memman, 64 * 1024); // 为应用程序分配内存
		*((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);
		// 设置内存段
		set_segmdesc(gdt + 1004, 64 * 1024 - 1,   (int) q, AR_DATA32_RW);
		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;
		}
		start_app(0, 1003 * 8, 64 * 1024, 1004 * 8);
		memman_free_4k(memman, (int) p, finfo->size);
		// 使用完毕之后释放内存
		memman_free_4k(memman, (int) q, 64 * 1024);
		cons_newline(cons);
		return 1;
	}
	/* 没有找到文件的情况 */
	return 0;

其中start_app用于启动应用程序。之前启动应用程序只是执行了far-CALL,下面还需要设置ESP和DS,SS。

接下来作者用一大段汇编语言程序来展示了上面提到的需求。老实说确实没太理解,不过这些汇编语言的功能已经由x-86的CPU实现了,这里跳过也不影响后面的内容。

通过汇编语言增加了以上功能,虽然可以阻止有问题的程序,但还没有实现强制终止其功能。虽然在QEMU上执行已经正常(可能是QEMU自身的bug),但在真机上运行仍然会有问题。

对于这种破坏性的操作,我们应该在程序生效之前强行结束,就是异常处理。
在x86架构中,当应用程序试图破坏操作系统或违背操作系统设置时,会自动产生0xd中断。这样我们只需在中断号0xd注册一个中断处理函数,强制结束程序即可。

_asm_inthandler0d函数如下:

_asm_inthandler0d:
		STI
		PUSH	ES
		PUSH	DS
		PUSHAD
		MOV		AX,SS
		CMP		AX,1*8
		JNE		.from_app
;	操作系统活动时产生中断的情况和之前差不多
		MOV		EAX,ESP
		PUSH	SS				; 保存中断时的SS
		PUSH	EAX				; 保存中断时的ESP
		MOV		AX,SS
		MOV		DS,AX
		MOV		ES,AX
		CALL	_inthandler0d
		ADD		ESP,8
		POPAD
		POP		DS
		POP		ES
		ADD		ESP,4			; INT 0x0d中需要这一句
		IRETD
.from_app:
;	应用程序运行时产生中断
		CLI
		MOV		EAX,1*8
		MOV		DS,AX			; 先仅将DS设置为操作系统用
		MOV		ECX,[0xfe4]		; 操作系统的ESP
		ADD		ECX,-8
		MOV		[ECX+4],SS		; 保存产生中断时的SS
		MOV		[ECX  ],ESP		; 保存产生中断时的ESP
		MOV		SS,AX
		MOV		ES,AX
		MOV		ESP,ECX
		STI
		CALL	_inthandler0d
		CLI
		CMP		EAX,0
		JNE		.kill
		POP		ECX
		POP		EAX
		MOV		SS,AX			; 将SS恢复为应用程序用
		MOV		ESP,ECX			; 将ESP恢复为应用程序用
		POPAD
		POP		DS
		POP		ES
		ADD		ESP,4			; INT 0x0d需要这一句
		IRETD
.kill:
;	将应用程序强制终止
		MOV		EAX,1*8			; 操作系统用的DS/SS
		MOV		ES,AX
		MOV		SS,AX
		MOV		DS,AX
		MOV		FS,AX
		MOV		GS,AX
		MOV		ESP,[0xfe4]		; 强制返回start_app时的ESP
		STI			; 切换完成后恢复中断请求
		POPAD	; 恢复事先保存的寄存器值
		RET
int inthandler0d(int *esp)
{
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
	cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
	return 1; /* 返回1,强制结束程序 */
}

在_asm_inthandler0d中,首先通过段号来判断中断是操作系统产生的还是应用程序产生的。如果是应用程序产生的,则跳转到.from_app。from_app中,首先保存寄存器的值,然后调用_inthandler0d函数。在_inthandler0d函数中,会显示一段提示信息"General Protected Exception.\n",即一般保护异常,然后返回1。
在from_app接下来的处理中,判断如果EAX不为0,则跳转到.kill,kill强制返回到了执行应用程序前的start_app。

2.1 破坏程序2:篡改DS寄存器

不能直接篡改操作系统管理的内存了,但在操作系统运行应用程序时,会指定应用程序使用的DS。破坏者可以通过篡改DS寄存器为操作系统的段地址,间接篡改操作系统的内存,如下:

[INSTRSET "i486p"]
[BITS 32]
		MOV		EAX,1*8			; OS使用的段号
		MOV		DS,AX			; 将其存入DS
		MOV		BYTE [0x102600],0
		RETF

运行一下crack2,同样发现dir命令没有响应了。

在这里插入图片描述

防止这种破坏也很简单,只需要让应用程序无法使用操作系统的段地址就可以了。而x86的CPU恰好实现了这一功能。

在定义段时,将访问权限加上0x60,就可以将当前段设置为应用程序用。当CS中的段地址为应用程序用的段地址时,CPU会认为当前正在运行应用程序,这时如果存入操作系统用的段地址就会产生异常。

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
……
	char name[18], *p, *q;
	struct TASK *task = task_now(); // 获取当前任务
……
	if (finfo == 0 && name[i - 1] != '.') {
		name[i    ] = '.';
		name[i + 1] = 'H';
		name[i + 2] = 'R';
		name[i + 3] = 'B';
		name[i + 4] = 0;
		finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	}

	if (finfo != 0) {
		/* 找到文件的情况 */
……
		// 设置应用程序段的权限加0x60
		set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
		set_segmdesc(gdt + 1004, 64 * 1024 - 1,   (int) q, AR_DATA32_RW + 0x60);
		……
		start_app(0, 1003 * 8, 64 * 1024, 1004 * 8, &(task->tss.esp0));
		memman_free_4k(memman, (int) p, finfo->size);
		memman_free_4k(memman, (int) q, 64 * 1024);
		cons_newline(cons);
		return 1;
	}
	/* 没有找到文件的情况 */
	return 0;
}

这样的话就需要在TSS中注册操作系统用的段地址和ESP,因此在start_app中增加传入了注册的地址。

接下来还需要在操作系统运行应用程序的时候执行far-CALL。但在x-86 CPU中,操作系统向应用程序用的段执行far-CALL或far-JMP都是被禁止的。(跟Intel的设计有关) 这样我们只能使用RETF来实现这一功能。事先将地址PUSH到栈中,再执行RETF,就可以实现启动应用程序了。

_start_app:		; void start_app(int eip, int cs, int esp, int ds, int *tss_esp0);
		PUSHAD		;32位寄存器的值全部保存下来
		MOV		EAX,[ESP+36]	; 应用程序用EIP
		MOV		ECX,[ESP+40]	; 应用程序用CS
		MOV		EDX,[ESP+44]	; 应用程序用ESP
		MOV		EBX,[ESP+48]	; 应用程序用DS/SS
		MOV		EBP,[ESP+52]	; tss.esp0的地址
		MOV		[EBP  ],ESP		; 保存操作系统用的ESP
		MOV		[EBP+4],SS		; 保存操作系统用的SS
		MOV		ES,BX
		MOV		DS,BX
		MOV		FS,BX
		MOV		GS,BX
;	调整栈,使RETF跳转到应用程序
		OR		ECX,3			; 将应用程序用段号和3进行OR运算
		OR		EBX,3			; 将应用程序用段号和3进行OR运算
		PUSH	EBX				; 应用程序的SS
		PUSH	EDX				; 应用程序的ESP
		PUSH	ECX				; 应用程序的CS
		PUSH	EAX				; 应用程序的EIP
		RETF
;	应用程序结束后不会回到这里

由于不是使用far-CALL指令调用应用程序,应用程序无法以RETF方式结束并返回,需要通过其他方式来替代。

接受API调用的_asm_hrb_api也需要进行修改:

_asm_hrb_api:
		STI
		PUSH	DS
		PUSH	ES
		PUSHAD		; 用于保存的PUSH
		PUSHAD		; 用于向hrb_api传递参数的PUSH
		MOV		AX,SS
		MOV		DS,AX		; 将操作系统用段地址存入DS和ES
		MOV		ES,AX
		CALL	_hrb_api
		CMP		EAX,0		; 当EAX不为0时程序结束
		JNE		end_app
		ADD		ESP,32
		POPAD
		POP		ES
		POP		DS
		IRETD
end_app:
;	EAX为tss.esp0的地址
		MOV		ESP,[EAX]
		POPAD
		RET					; 返回cmd_app

因为不能用RETF来结束应用程序,这里做了一个结束应用程序的API。结束程序的API分配到EDX=4,对应修改后的hrb_api如下:

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	int cs_base = *((int *) 0xfe8);
	struct TASK *task = task_now();
	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);
	} else if (edx == 4) {
		return &(task->tss.esp0);
	}
	return 0;
}

还需要修改一下IDT的设置。我们已经清楚地区分了操作系统段和应用程序段,这时候如果应用程序试图调用未经操作系统授权的中断时,CPU会产生异常。这里需要将INT 0x40设置为"可供应用程序作为API来调用的中断",其实也就是在注册到IDT时在访问权限编码上加上0x60。

……
	set_gatedesc(idt + 0x0d, (int) asm_inthandler0d, 2 * 8, AR_INTGATE32);
	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 + 0x40, (int) asm_hrb_api,      2 * 8, AR_INTGATE32 + 0x60);

	return;

这样我们需要修改应用程序中原来使用RETF的地方,改为调用api_end来结束程序。
运行crack2,可以成功抛出异常:
在这里插入图片描述
下一篇内容中将进入用C语言编写应用程序。敬请期待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2136659.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

为什么H.266未能普及?EasyCVR视频编码技术如何填补市场空白

H.266,也被称为Versatile Video Coding(VVC),是近年来由MPEG(Moving Picture Experts Group)和ITU(International Telecommunication Union)联合开发并发布的新一代国际视频编码标准…

[每周一更]-(第114期):介绍GitLab不同角色对应的权限

文章目录 GitLab 角色及其权限项目级别角色组级别角色 使用场景示例 工作中一直使用Gitlab搭建了公司内网的代码管理工具,但是不同的用户会分配相应的权限,来管理不同用户及角色的权限信息,我们来介绍下角色的信息,方便我们管理公…

演示:基于WPF的自绘的中国地铁轨道控件

一、目的:演示一个基于WPF的自绘的中国地铁轨道控件 二、效果演示 北京地铁 成都地铁 上海地铁 深圳地铁 南京地铁 长春地铁 哈尔滨地铁 武汉地铁 厦门地铁 香港地铁 三、功能 支持平移、缩放等操作 鼠标悬停显示线路信息和站点信息 按表格显示,按纸张…

传知代码-融合经典与创新的图像分类新途径

代码以及视频讲解 本文所涉及所有资源均在传知代码平台可获取 概述 在当前的深度学习领域,构建兼具高性能与灵活性的卷积神经网络(CNN)已成为计算机视觉研究的核心课题。本文介绍了一种全新的卷积神经网络架构,该网络巧妙地结合…

MacOS Sonoma(14.x) 大写模式或中文输入法下的英文模式,光标下方永远会出现的CapsLock箭头Icon的去除办法

如图,MacOS Sonoma(14.x) 大写模式或中文输入法下的英文模式下,光标下方永远会出现一个CapsLock箭头Icon。此Icon挡住视野,还容易误触导致切换大小写状态,带来的收益远远小于带来的困扰。 解决办法 打开终端,输入以下…

Go协程及并发锁应用指南

概念 协程(Goroutine)是Go语言独有的并发体,是一种轻量级的线程,也被称为用户态线程。相对于传统的多线程编程,协程的优点在于更加轻量级,占用系统资源更少,切换上下文的速度更快,不…

Vue:使用v-model绑定的textarea在光标处插入指定文本

一、问题描述 使用v-model绑定的textarea如果需要改变其内容,一般只要改变v-model对应的变量即可,但如果需要在textarea的当前光标位置插入指定文本,那就需要操作DOM了。于是我们写了一段js: const insertTextAtCursor (text) …

聊天组件 Vue3-beautiful-chat

前言 最近很多公司都在搞大模型,类似于 chatgpt 的功能;而 chatgpt 的界面其实就是个对话框。今天就介绍一个不错的对话框组件 Vue3-beautiful-chat 项目框架 vite vue3 TS Vue3-beautiful-chat 使用流程 1、引用三方件 npm install Vue3-beaut…

【大模型专栏—进阶篇】语言模型创新大总结——“三派纷争”

大模型专栏介绍 😊你好,我是小航,一个正在变秃、变强的文艺倾年。 🔔本文为大模型专栏子篇,大模型专栏将持续更新,主要讲解大模型从入门到实战打怪升级。如有兴趣,欢迎您的阅读。 &#x1f4…

ChatGPT对话训练数据采集渠道有哪些

ChatGPT是人工智能技术驱动的自然语言处理工具,它可以生成逼真的自然语言回复,被广泛应用于聊天机器人、智能助理等领域。ChatGPT本身需要依赖大量的训练对话数据和算法运行,其所依赖的对话数据,需要专业的数据采集标注处理流程才…

20 递归算法精髓解析:基准、性质、案例(阶乘、斐波拉契、猴子吃桃、汉诺塔等)、与循环的对比

目录 1 概述 2 递归的基本组成部分 2.1 基准情况 2.2 递归步骤 2.3 案例:循环实现阶乘的计算 2.4 案例:递归函数实现阶乘的计算 3 递归的性质 3.1 自我调用 3.2 栈的使用 3.3 问题分解 3.4 性能考虑 3.5 案例:递归的回溯 4 综合…

WPF DataGrid 列表中,DataGrid.Columns 列根据不同的值显示不同内容

需求&#xff1a;在WPF DataGrid 控件中&#xff0c;有以下列&#xff0c;绑定了一个LogType&#xff0c;值分别是0,1,2&#xff0c;根据不同的值&#xff0c;显示不同的内容以及背景 <DataGrid ItemsSource"{Binding EventLog}"><DataGrid.Columns><…

力扣之1777.每家商店的产品价格

文章目录 1. 1777.每家商店的产品价格1.1 题干1.2 建表1.3 题解1.4 结果截图 1. 1777.每家商店的产品价格 1.1 题干 表&#xff1a;Products -------------------- | Column Name | Type | -------------------- | product_id | int | | store | enum | | price | int | ---…

猜数-while-python

题目要求&#xff1a; 设置一个范围1-100的随机整数变量&#xff0c;通过while循环&#xff0c;诶和input语句&#xff0c;判断输入的数字是否等于随机数 无限次机会&#xff0c;直到猜中为止每一次不猜中都&#xff0c;会提示大了小了猜完数字后&#xff0c;提示裁了几次 imp…

K8s 之Pod的定义及详细资源调用案例

资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通过操作资源来管理kubernetes。kubernetes的本质上就是一个集群系统&#xff0c;用户可以在集群中部署各种服务所谓的部署服务&#xff0c;其实就是在kubernetes集群中运行一个个的容器&a…

Day25_0.1基础学习MATLAB学习小技巧总结(25)——四维图形的可视化

利用空闲时间把碎片化的MATLAB知识重新系统的学习一遍&#xff0c;为了在这个过程中加深印象&#xff0c;也为了能够有所足迹&#xff0c;我会把自己的学习总结发在专栏中&#xff0c;以便学习交流。 参考书目&#xff1a; 1、《MATLAB基础教程 (第三版) (薛山)》 2、《MATL…

开发后台管理系统-开发环境搭建

文章目录 需求设计环境搭建创建项目工程测试结果 安装Element Plus安装路由安装Vue Router配置Vue Router 测试 需求 开发一个后台管理系统 这里以CDN后台管理系统为例 设计 参照 CDN后台管理系统功能说明文档 环境搭建 确保已经安装了Node.js和npm 执行 npm install -g vu…

【极限、数学】 NOIP 2018 提高组初赛试题 第 7 题详解(线段长度期望)

在一条长度为 1 1 1 的线段上随机取两个点&#xff0c;则以这两个点为端点的线段的期望长度是&#xff08; &#xff09;。 考虑将一个线段上平均分布有 n ( n ≥ 2 ) n(n\geq 2) n(n≥2) 个节点&#xff0c;其中首尾均有一个节点&#xff0c;那么我们就将一个线段均分为 n…

SSMP+ajax实现广告系统的分页效果

文章目录 1.案例需求2.编程思路3.案例源码4.小结 1.案例需求 使用SSMPajax实现广告系统的分页效果&#xff0c;效果图如下&#xff1a; 2.编程思路 mapper层&#xff1a;定义一个接口&#xff0c;继承自BaseMapper&#xff0c;指定泛型为AdvInfo&#xff0c;这样MyBatis Pl…

Idea springboot项目热部署

使用 spring-boot-devtools spring-boot-devtools 是 Spring Boot 提供的开发工具模块&#xff0c;它可以自动检测到代码的变化并重启应用&#xff0c;实现热部署。 配置步骤&#xff1a; 添加依赖&#xff1a; 在项目的 pom.xml 中加入 spring-boot-devtools 依赖&#xff1…