今天我们再接再励,主要完成保护操作系统的这个功能!
在这之前,我们都是使用的汇编语言进行编写的。但是为了方便吧,我们今天尝试用c语言来编写一个应用程序看看。
编写一个程序名为:
a.c:
void api_putchar(int c);
void HariMain(void)
{
api_putchar('A');
return;
}
这里的harimain与操作系统的能够bootpack虽然都是指主函数,但是这可和前面的不一样。我们的a.c是一个应用程序,是单独独立出来的。
此外,我们还要创建一个nas文件,里面写入api_putchar这个函数,以供我们程序的调用:
a_nask.nas:
[FORMAT "WCOFF"] ; 生成对象文件的模式
[INSTRSET "i486p"] ; 使用486兼容模式
[BITS 32] ; 使用32位模式机器语言
[FILE "a_nask.nas"] ; 源文件信息名
GLOBAL _api_putchar
[SECTION .text]
;调用cons_putchar这个模式!!
_api_putchar: ; void api_putchar(int c);
MOV EDX,1
MOV AL,[ESP+4] ; c
INT 0x40
RET
然后继续修改一下makefile文件:
a.bim : a.obj a_nask.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:a.bim map:a.map a.obj a_nask.obj
a.hrb : a.bim Makefile
$(BIM2HRB) a.bim a.hrb 0
haribote.sys : asmhead.bin bootpack.hrb Makefile
copy /B asmhead.bin+bootpack.hrb haribote.sys
haribote.img : ipl10.bin haribote.sys hello.hrb hello2.hrb \
a.hrb Makefile
$(EDIMG) imgin:../z_tools/fdimg0at.tek \
wbinimg src:ipl10.bin len:512 from:0 to:0 \
copy from:haribote.sys to:@: \
copy from:ipl10.nas to:@: \
copy from:make.bat to:@: \
copy from:hello.hrb to:@: \
copy from:hello2.hrb to:@: \
copy from:a.hrb to:@: \
imgout:haribote.img
然后,运行看看?
哎!!我们输入a貌似一bug了。那么我们先试着修改一个地方看看吧:
打开a.hrb,我们将前6个字节修改以下:修改位E8 16 00 00 00 CB
然后保存,在运行看看:
喔!成功了。下面呢我来解释解释。我们输入的那6个字节用汇编语言表示就是:
[BITS 32]
CALL 0x1b
RETF
那么这里的0x1b是指啥呢?让我们来看看之前的asmhead:
在asmhead中也有这一句。实际上,0x1b大致指向的就是.hrb文件中的HariMain函数。在asmhead中就是利用这个调用的bootpack.hrb的主函数的。因此这里我们也需要利用这一个进行调用!!
当然了,这里我们总不可能有一个程序就改一次吧!于是,我们直接再操作系统中进行对应的设置:
cmd_app{
略
if (finfo != 0) {
/*找到文件的情况*/
p = (char *) memman_alloc_4k(memman, finfo->size);
*((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);
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;
}
farcall(0, 1003 * 8);
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
return;
}
凡是通过bim2hrb生成的hrb文件,其第4~7字节一定位“Hari” , 因此,这里我们就可以利用这个特性进行检查,然后进行对应的修改即可!
hello3:
void api_putchar(int c);
void HariMain(void)
{
api_putchar('h');
api_putchar('e');
api_putchar('l');
api_putchar('l');
api_putchar('o');
return;
}
耶耶,完美运行了!
我们之所以要讲这一部分,主要原因在于我们是想要防止某些应用程序编程导致的系统bug或者说某些应用程序中含有严重的病毒导致整个系统的异常运行和崩溃的出现!
我们使用的x86架构cpu已经为我们提供了良好的保护操作系统的功能,我们以下就要利用此来有效的进行保护我们的系统。
首先,编写一个捣蛋的程序:
crack1.c:
void HariMain(void)
{
*((char *) 0x00102600) = 0;
return;
}
然后运行,就会发现bug了:
那么,我们接下来自制一个保护操作系统的程序吧。前面的crack1这个程序之所以能够产生破坏,其主要原因在于私自访问了本该由操作系统进行管理的内存。因此,想要防范,我们可以规划出一片应用程序专用的地址空间,并且禁止应用程序访问其他的段:
操作系统用的数据段: 1 * 8
操作系统用的代码段: 2 * 8
TSS专用段 : 3 * 8~1002 * 8
应用程序代码段:1003 * 8
应用程序的数据段:1004 * 8
然后以下我们将利用x86架构的cpu系统保护功能;来进行实现。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
略:
char name[18], *p, *q;
struct TASK *task = task_now();
略
if (finfo != 0) {
/*找到文件的情况*/
p = (char *) memman_alloc_4k(memman, finfo->size);
q = (char *) memman_alloc_4k(memman, 64 * 1024);//分配64KB给应用程序
*((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 + 0x60);
set_segmdesc(gdt + 1004, 64 * 1024 -1, (int) q, AR_DATA32_RW + 0x60);
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, &(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;
}
这里我们多建立了一个函数start_app,用于定位到对应的应用程序的段地址空间中。除此之外,我们还设置了一个task->tss.esp0 用于将操作系统用的段地址和ESP传入。
;此时还在操作系统内部
_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的地址,可参考task的定义
MOV [EBP ],ESP ; 保存操作系统用ESP,[ebp] 指向的是task->tss.esp
MOV [EBP+4],SS ; 保存操作系统用SS,[ebp+4] 指向的是task->tss.ss
MOV ES,BX ;将DS/SS进行复制,且以防万一,也给其他寄存器值进行赋值
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 ;取得CS和EIP,然后跳转到应用程序的地址
; 应用程序结束后不会回到这里,此时还剩与esp、ss、pushad的各个值
然后,我们还需要修改接收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
当hrb_api返回的值是0时继续运行应用程序,若非零值时当作tss.esp0,此时cpu就会识别到这是操作系统的段,就会说哦!应用程序你越界了,我会马上终止你的!
//用edx来指向是哪一号的子功能
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;
}
_asm_inthandler0d:
STI
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler0d
CMP EAX,0
JNE end_app
POP EAX
POPAD
POP DS
POP ES
ADD ESP,4 ; INT 0x0d需要这句
IRETD
console.c:
int *inthandler0d(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct TASK *task = task_now();
cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
return &(task->tss.esp0); /*强制结束程序*/
}
dsctbl.c:
set_gatedesc(idt + 0x40, (int) asm_hrb_api, 2 * 8, AR_INTGATE32 + 0x60);
然后为了验证破坏性,我们尝试再写一个破坏程序:
[INSTRSET "i486p"]
[BITS 32]
MOV EAX,1*8 ; OS用的段号
MOV DS,AX ; 将其存入DS
MOV BYTE [0x102600],0
MOV EDX,4
INT 0x40
注意,上面我们已经不能使用irted之类的进行返回了,因为cpu已经禁止了。所以我们使用最后两行这种进行返回!!
让我们试试看吧:
喔喔,很棒。成功拦截了
以上就是今天主要写的对于操作系统保护的内容,明天还要继续加油加油呀!