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函数中,企图打印esp11,即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();
}
相关推荐
小小筱筱8 分钟前
LabVIEW笔记:解决周立功CAN卡驱动缺失导致的启动报错问题
笔记·labview
Wang's Blog27 分钟前
PostgreSQL笔记4: 市场地位、生态全景与行业应用实践
数据库·笔记·postgresql
RainCity41 分钟前
Java Swing 自定义组件库分享(十六)
java·笔记·后端
蒸蒸yyyyzwd2 小时前
cpp选手秋招准备 学习笔记day7 webserver
笔记·学习
Chen—LSN4 小时前
C语言——数据在内存中的存储
c语言·开发语言·经验分享·笔记
bitbrowser15 小时前
giffgaff突然停用?先分清闲置停用和长期境外断开
笔记
九硕智慧建筑一体化厂家15 小时前
数字化管控落地,直流照明构筑安全照明体系
运维·人工智能·笔记·安全·智慧城市
AI云海15 小时前
天幕红尘笔记
笔记
青山是哪个青山18 小时前
LangChain 学习笔记(三):LangSmith 调试与监控
笔记·学习·langchain
蒸蒸yyyyzwd20 小时前
cpp选手备战后端学习笔记day6 秋招笔试题
笔记·学习