第22天 用C语言编写应用程序
2020.4.26
1. 保护操作系统(5)(harib19a)
-
把OS的段地址存入DS和访问OS管理的内存空间这两招已经不能用了。
-
试试在定时器上做手脚:这样,光标闪烁就会变得异常,任务切换的速度也会变慢。
-
编写恶意应用程序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
- 原本定时器的设定值是11932=0x2e9c:
io_out8(PIT_CTRL, 0x34); io_out8(PIT_CNT0, 0x9c); io_out8(PIT_CNT0, 0x2e);
- 现在设定值变成了0xffff=65535,显然,这样光标闪烁就会变慢。
- 原本定时器的设定值是11932=0x2e9c:
-
make
后用VMware运行:
- 显示了一般保护性异常。
- 当以应用程序模式运行时,执行IN指令和OUT指令都会产生一般保护性异常。当然,通过修改CPU设置,可以允许应用程序使用IN指令和OUT指令,但这样会留下隐患。
-
继续编写恶意应用程序crack4.nas:
[INSTRSET "i486p"] [BITS 32] CLI fin: HLT JMP fin
- 先CLI再HLT,这样电脑就死机了吧?
-
make
后用VMware运行:
- 再次显示了一般保护性异常。
- 当以应用程序模式运行时,执行CLI、STI和HLT这些指令都会产生异常。因为中断是由OS来管理的,应用程序不可以随便进行控制。
- 不能执行HLT,应用程序就无法省电,不过可以通过调用任务休眠的API来实现,而不能由应用程序自己来执行HLT。
- 此外,在多任务下,调用休眠API还可以让OS将CPU时间分配给其他任务。
-
OS代码中有io_cli函数,如果应用程序far-CALL这个函数呢?找到bootpack.map文件中io_cli的地址:
0x00000AC1 : _io_cli
-
编写恶意应用程序crack5.nas:
[INSTRSET "i486p"] [BITS 32] CALL 2*8:0xac1 MOV EDX,4 INT 0x40
-
make
后用VMware运行试试:
- 再次产生一般保护性异常。
- 如果应用程序可以CALL任何地址的话,那crack5.nas就成功了。因此,CPU规定除了设置好的地址以外,禁止应用程序CALL其他的地址。因此,此OS中应用程序调用OS只能采用INT 40的方法。
-
既然应用程序只能调用API,那么把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); } else if (edx == 123456789) { *((char *) 0x00102600) = 0; } return 0; }
-
编写恶意应用程序crack6.nas:
[INSTRSET "i486p"] [BITS 32] MOV EDX,123456789 INT 0x40 MOV EDX,4 INT 0x40
-
make
后用VMware运行:
- OS崩溃了。
- OS之所以崩溃是因为出现了“内鬼API”——123456789。
2. 帮助发现bug(harib19b)
-
CPU的异常处理功能,除了可以保护OS免遭应用程序的破坏,还可以帮助我们在编写应用程序时及早发现bug。
-
编写有bug的应用程序bug1.c:
void api_putchar(int c); void api_end(void); void HariMain(void) { char a[100]; a[10] = 'A'; /* 这句代码没有问题 */ api_putchar(a[10]); a[102] = 'B'; /* 这句代码有问题 */ api_putchar(a[102]); a[123] = 'C'; /* 这句代码有问题 */ api_putchar(a[123]); api_end(); }
- 这个应用程序有明显的bug,即超出数组边界。
-
make
后用VMware运行试试:
- 这应该是产生了没有设置过的异常所导致的。
-
顺便提一下,保护OS暂时结束了,因而把“内鬼API”删掉,也把
crack[1-6].[nas|c]
删除。 -
由于a数组是保存在栈中的,因此数组越界产生了栈异常。
- 栈异常的中断号是
0x0c
。 - 根据CPU的说明书,从0x00到0x1f都是异常所使用的中断,所以,IRQ的中断号都是从0x20开始的。
- 其他比较有用的异常有:
- 0x00号:除零异常,当试图除以0时产生。
- 0x06号:非法指令异常,当试图执行CPU无法理解的机器语言指令时产生,比如当试图执行一段数据时可能产生。
- 栈异常的中断号是
-
编写asm_inthandler0c函数:
_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
- 当产生0x0c号中断时,必须强制结束应用程序。
-
编写inthandler0c函数(console.c文件中,inthandler0c和inthandler0d都在console.c中):
int *inthandler0c(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); /* 强制结束应用程序 */ }
- inthandler0c和inthandler0d函数的区别只是显示信息不同。
-
在IDT中注册:
void init_gdtidt(void) { …… /* IDT设置 */ set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32); /*注册0x0c号中断*/ 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, (