Day22 用C语言编译应用程序

文章目录

      • [1. 保护操作系统5(harib19a)](#1. 保护操作系统5(harib19a))
      • [2. 帮助发现bug(harib19b)](#2. 帮助发现bug(harib19b))
      • [3. 强制结束应用程序(harib19c)](#3. 强制结束应用程序(harib19c))
      • [4. 用C语言显示字符串(harib19e)](#4. 用C语言显示字符串(harib19e))
      • [5. 显示窗口(harib19f)](#5. 显示窗口(harib19f))

1. 保护操作系统5(harib19a)

上一章节已经对"应用软件在系统内存上动手脚"这一case做了防护。不过定时器上还是存在安全漏洞的,接下来crack3的目的是使光标闪烁变慢,使任务切换的速度变慢。

shell 复制代码
# 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

但这并不会针对性的影响到定时器,因为直接再应用程序中使用IN和OUT指令的话,会产生一般保护异常,然后就在屏幕上显示"General Protected Exception"。

简单粗暴的crack4,目的是直接禁止中断,那么就会不再产生定时器中断,任务切换也会停止,键盘鼠标中断也会停止。

shell 复制代码
# crack4.nas 文件
[INSTRSET "i486p"]
[BITS 32]
		CLI
fin:
		HLT
		JMP		fin

但其实,应用程序中也不能直接使用CLI, STI和HLT指令,这些也会产生一般保护异常。可以尝试CALL操作系统函数(_io_cli)的地址,通过查看map文件可以该函数地址。

shell 复制代码
# crack5.nas 文件
[INSTRSET "i486p"]
[BITS 32]
		CALL	2*8:0xac1
		MOV		EDX,4
		INT		0x40

但是还忽略了一点,应用程序的地址的CALL并不是任意的,只能CALL设置好的地址。所以crack5程序还是会产生一般保护异常。应用程序期望调用操作系统只能才做INT 0x40的方式。

临时修改hrb_api,使它可以处理一个临时的edx用于破坏操作系统,并在应用程序中INT 0x40,但是对定时器或键盘鼠标中断下手,确实并不简单。

c 复制代码
// console.c 文件
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);
	} else if (edx == 123456789) {
		*((char *) 0x00102600) = 0;
	}
	return 0;
}
shell 复制代码
# crack6.nas 文件
[INSTRSET "i486p"]
[BITS 32]
		MOV		EDX,123456789
		INT		0x40
		MOV		EDX,4
		INT		0x40

2. 帮助发现bug(harib19b)

CPU的异常处理功能,除了可以保护操作系统免遭应用程序的破坏,还可以帮助在编写程序时及时发现bug,例如数组越界访问等。

当一个局部数组变量被越界访问时,就有可能产生栈异常。栈异常的中断号为0x0c。0x0c的中断函数类似0x0d,只是改一下异常时的信息打印就好。

shell 复制代码
# naskfunc.nas 文件
_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 0x0d 
		IRETD
c 复制代码
// console.c 文件
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);	/* 强制结束程序 */
}
c 复制代码
// dsctbl.c 文件
void init_gdtidt(void)
{
	set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);	// 注册给idt
}

在inthandler0c函数中,企图打印esp[11],即EIP,会将下一条将要被执行的指令在内存中的地址打印出来。因此inthandler0c的含义就是在发生异常时,把异常产生的指令地址打印出来。然后再根据.map文件和.lst文件就能定位到产生异常的具体位置。

3. 强制结束应用程序(harib19c)

如果一个应用程序中存在一个毫不间歇的死循环,那么系统整体的速度就会变慢。为了防止类似死循环这种程序,可以将某一个按键作为强制结束键,按一下就可以结束程序。

将强制结束任务写在bootpack.c文件的HariMain函数的键盘中断处理中:

c 复制代码
// bootpack.c 文件
void HariMain(void)
{
	/* 省略 */
	i = fifo32_get(&fifo);
	io_sti();
	if (256 <= i && i <= 511)
	{
		if (i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0) {	/* Shift+F1 */
			cons = (struct CONSOLE *) *((int *) 0x0fec);
			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();
		}
	}
	/* 省略 */
}
shell 复制代码
# naskfunc.nas 文件
# asm_end_app是end_app函数的修改
_asm_end_app:
#	EAX为tss.esp0的地址
		MOV		ESP,[EAX]
		MOV		DWORD [EAX+4],0
		POPAD
		RET		# 返回cmd_app

当 按下强制结束键时,改写命令行窗口任务的寄存器值,并goto到asm_end_app函数,如此便可以强制结束程序。

c 复制代码
// mtask.c 文件
struct TASK *task_alloc(void)
{
	/* 省略 */
	for (i = 0; i < MAX_TASKS; i++) {
		if (taskctl->tasks0[i].flags == 0) {
			task = &taskctl->tasks0[i];
			/* 省略 */
			task->tss.ss0 = 0;
			return task;
		}
	}
	return 0;
}

还需要确保task_cons->tss.ss0不为0时采取调用结束程序,为防止应用程序还未运行(task_cons->tss.ss0为0)进行调用。

4. 用C语言显示字符串(harib19e)

第一个供c语言调用的api:

shell 复制代码
# 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

但此时如果直接在c语言中调用api_putstr0函数,应该是无法在屏幕上显示参数s的。

调试api之前,先解释一下可执行程序.hrb文件的内容。可执行程序.hrb文件由两部分组成:代码部分和数据部分。其中,数据部分在位于代码部分的开头一块区域中。当应用程序启动时,数据部分被传送到应用程序使用的数据段中。

对于.hrb文件:

0x0000 存放的是应用程序启动后数据段的大小,当前固定大小为64KB。

0x0004 存放的是"Hari"这四个字符,读取文件的时候会先判断这四个字节,判断一致才会当作可执行程序处理。

0x0008 存放的是数据段预备空间的大小。暂时没有使用。

0x000c 存放的是应用程序启动时,ESP的初始值。也就是说,0x000c位置的内容表示栈指针,调用api_putstr0的时候所传入的参数s就会放在0x000c的内容所代表的内存地址处。例如,可执行程序hello4.hrb被执行时char *参数s的地址是0x0400,就是文件hello4.hrb的0x000c处写的0x0400。而这个值之前的部分会作为栈来使用,也就是1024字节(十六进制就是0x0400),这个值在编译的时候已经确定好了。

shell 复制代码
# Makefile 文件
hello4.bim : hello4.obj a_nask.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:hello4.bim stack:1k map:hello4.map \
		hello4.obj a_nask.obj

0x0010和0x0014 的目的是当.hrb文件被加载时,告诉操作系统该可执行文件的数据大小和在文件中的位置。

0x0018 在内存中的放置应该是"00 00 00 E9",其中E9是JMP指令的机器码,这就相当于在0x001b位置存放了一个JMP指令。

0x001c 的程序入口结合0x001b的JMP指令,就可以运行可执行文件了。(但是程序入口地址-0x20是为啥,不太清楚)

0x0020 存放对应用程序使用的malloc函数调用之后的返回地址。

根据以上内容需要修改console.c文件。

c 复制代码
// console.c 文件
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
	/* 省略 */
	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);	// 根据hrb文件确定数据段大小
			*((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++) {
				// 执行程序前把数据部分拷贝到数据段
				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 {
			// 找不到Hari则报错
			cons_putstr0(cons, ".hrb file format error.\n");
		}
		memman_free_4k(memman, (int) p, finfo->size);
		cons_newline(cons);
		return 1;
	}
	/* 未找到文件 */
	return 0;
}

为啥将"Hari"字符放在文件的0x0004位置,而不是0x0000位置呢?

0x0000位置存放的是应用程序启动后数据段的大小,bim2hrb会自动将该内容调整为4KB的倍数,所以0x0000位置的字节必定是00。一个普通文件的0x0000位置却很小可能是00,但是却有可能是某个字母,所以将"Hari"字符放在文件的0x0004位置可以提高访问的安全性。

5. 显示窗口(harib19f)

现在期望实现一个用于显示窗口的api,暂时这样设计:

调用后返回值:

但是当前有个问题,如何将返回值传递给应用程序。中断服务函数会通过IRETD安全的返回应用程序,并且在返回之前会把8个32位寄存器POPAD出去,所以只能依赖POPAD和IRETD。

两次PUSHAD之后的栈是这样的:

高地址

EAX <--- 1st

ECA

EDX

EBX

ESO

EBP

ESI

EDI

EAX <--- 2nd, hrb_api使用到的就是这里及以下

ECA

EDX

EBX

ESO

EBP

ESI

EDI

c 复制代码
// console.c 文件
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	int ds_base = *((int *) 0xfe8);		// 来自于bootpack.c的HariMain函数中的shtctl变量
	struct TASK *task = task_now();
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
	struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
	struct SHEET *sht;
	int *reg = &eax + 1;	/* 参照栈空间可知, &eax+1可以获取到系统EDI */
		/* 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 == 5) {
		sht = sheet_alloc(shtctl);
		sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);
		make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
		sheet_slide(sht, 100, 50);
		sheet_updown(sht, 3);	/* 背景层高度3,位于task_a之上 */
		reg[7] = (int) sht;	// 把sht赋值给EAX,_asm_hrb_api中POPAD的时候会弹出栈
	}
	return 0;
}

再写一个测试程序:

shell 复制代码
# 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
c 复制代码
// winhello.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, "hello");
	api_end();
}
相关推荐
g_i_a_o_giao2 小时前
Android8 binder源码学习分析笔记(四)——ServiceManager启动
笔记·学习·binder
听情歌落俗2 小时前
MATLAB3-1变量-台大郭彦甫
开发语言·笔记·算法·matlab·矩阵
Naiva3 小时前
ESP32-C3 入门09:基于 ESP-IDF + LVGL + ST7789 的 1.54寸 WiFi 时钟(SquareLine Studio 移植)
ide·笔记·vscode
..过云雨3 小时前
03.【Linux系统编程】基础开发工具1(yum软件安装、vim编辑器、编辑器gcc/g++)
linux·c语言·笔记·学习
肥肠可耐的西西公主4 小时前
后端(FastAPI)学习笔记(CLASS 3):Tortoise ORM
笔记·学习·fastapi
PigeonGuan4 小时前
C盘扩容笔记
笔记
JasmineX-16 小时前
数据结构——顺序表(c语言笔记)
c语言·开发语言·数据结构·笔记
夜流冰7 小时前
工程师 - Onion Architecture in Software Development
笔记
一位搞嵌入式的 genius8 小时前
前端开发核心技术与工具全解析:从构建工具到实时通信
前端·笔记