今天,我们将继续再完善一下保护操作系统的内容,以及进一步的利用c语言显示字符串!
首先,让我们来继续验证一下昨天的应用程序的保护力度吧:
先总结一下:
我们尝试对定时器进行破坏:
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

crack4:
[INSTRSET "i486p"]
[BITS 32]
CLI
fin:
HLT
JMP fin

crack5:
[INSTRSET "i486p"]
[BITS 32]
CALL 2*8:0xac1
MOV EDX,4
INT 0x40

很好,能够很有效的进行了防范!但是呢,如果我们使用的api里含有严重的写入内存的bug的化,还是有可能会产生漏洞的。因为api这一个本身就是在操作系统内部运行的,而非应用程序段运行的,应用程序只是简单进行调用而已!!
那么,我们接下来再试一下:
bug1.c:
void api_putchar(int c);
void api_end(void);
void HariMain(void)
{
char a[100];
a[10] = 'Y';
api_putchar(a[10]);
a[102] = 'U';
api_putchar(a[102]);
a[120] = 'A';
api_putchar(a[120]);
api_end();
}
然后,运行一下,嘿!果然出问题了,这里我在VMware中运行时,首先弹出YU然后系统马上就重启了。(重启是因为产生了没有设置过的异常导致的)
这里,由于a数组是存于栈空间的,因此这里很明显的异常就是栈溢出! 那么在进行下一步的更正前我先简介一下cpu的中断有哪些:
接下来,让我们开始进行设置吧:
naskfun:
_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
console:
int *inthandler0d(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); /*强制结束程序*/
}
dsctbl:
set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);

喔!貌似成功了,但是这里还有一点点小问题需要解释一下。即我们的U这里,也是明显超过了数组a的界限,为什么还能显示出来呢?
其实这里,0x0c这个异常仅仅只判断是否超过了应用程序分配的数据段的边界。也就是说我们的A是超过了分配的边界,而U虽然也超过了数组a的边界是个bug,但是他这里还没超过系统为应用程序分配的数据段的边界,因此这里不报错!
那么我们做一个定位的小程序吧:
int *inthandler0d(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct TASK *task = task_now();
char s[30];
cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
sprintf(s, "EIP = %08X\n", esp[11]);
cons_putstr0(cons, s);
return &(task->tss.esp0); /*强制结束程序*/
}
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); /*强制结束程序*/
}
这里就是☞将esp的第十一号元素显示出来:

呃呃呃,图片里有我画的笔记,嘿嘿嘿别介意!! 然后我们来看看结果:

这里,显示了eip = 0042,那么我们来看看map和list:
list

map:

这里,list是还链接时的地址,因此暂时用0000代替。而map是连接后各个的地址。首先从map中可以看出我们的异常地址位于主函数中,然后看看主函数里的代码长度,发现了 0x24 + 0x1e = 0x42 因此,我们就定位到了a[120] = ‘A’ 处!

假设我们有如下bug:
void api_putchar(int c);
void api_end(void);
void HariMain(void)
{
for (;;) {
api_putchar('a');
}
}
如果我们,不能强制关闭的话,那么这个程序会一直循环导致程序一直占用系统资源的!
那么接下来我们就制作一个强制结束的程序,主要利用前面的end_app这个函数,不过这里我们要稍作修改:
bootpack.c:
if(i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0){//shift + F1强制关闭
cons = (struct CONSOLE *) *((int *) 0xfec);
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();
}
naskfunc.nas:
_asm_end_app:
; EAX为tss.esp0的地址
MOV ESP,[EAX]
MOV DWORD [EAX+4],0
POPAD
RET ; 返回cmd_app
mtask.c:
struct TASK *task_alloc(void)
{
略
task->tss.iomap = 0x40000000;
task->tss.ss0 = 0 ;
略
}
运行看看:

首先,我们写入可供调用的api:
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文件:
void api_putstr0(char *s);
void api_end(void);
void HariMain(void)
{
api_putstr0("hello, world\n");
api_end();
}
这里,还有一点需要修改的就是,由于我们之前已经启用了x86架构对于异常保护的特性,因此不能利用retf来结束应用程序,那么我们之前对于"Hari" 开头这里也需要修改一下:
start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
可以直接,指定应用程序的hari的起始地址来代替前面的替换。好了让我们试试看?
哎!这里貌似有点小问题,我尝试了几次,有时是显现不出来,有时是乱码。这是怎么一回事呢? 那由于我们再前面用的是EBX进行传递字符串地址的,那现在让我们看看EBX寄存器里保存的值是多少吧:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
char s[12];
略
else if (edx == 2) {
cons_putstr0(cons, (char *) ebx + cs_base);
sprintf(s,"%08X\n", ebx);
cons_putstr0(cons, s);
}
略
}
然后,运行一下看看,发现ebx的值为0x00000400,也就是1024字节,可是这大小远远超过了我们程序段的范围了呀!
那让我们分析一下hrb这个文件吧:
由bim2hrb这个生成的文件有两部分:
当程序中没有使用字符串和外部变量时候,就会生成不包含数据部分的hrb文件。不过,我们之前很少考虑到数据部分这个位置的。而我们上述的程序在连接时候bim2hrb这个程序直接就认为了我们的字符串应该是在0x400这个部分。(具体的应该是编译器的设计问题吧)。 那么既然我们要开始考虑这部分的内容了,就让我们看一下hrb文件开头有哪些内容吧:

在0x0018这里, e9这个在汇编语言里的意思就是jmp。结合后面的就是jmp 应用程序入口。
至此,我们来修改一下程序:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
int i, segsiz, datsiz, esp, dathrb;
略
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);
*((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++){//将hrb文件的数据部分复制到数据段中后再启动app
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{
cons_putstr0(cons, ".hrb file format error.\n");
}
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
return 0;
}
本次,修改的地方主要针对以下3点:
然后,我们再写一个nas的程序看看:
hello5.nas
[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "hello5.nas"]
GLOBAL _HariMain
[SECTION .text]
_HariMain:
MOV EDX,2
MOV EBX,msg
INT 0x40
MOV EDX,4
INT 0x40
[SECTION .data]
msg:
DB "hello, world", 0x0a, 0
这里的section就是指将代码段和数据段进行区分开来。除此之外,一般可执行文件最后都要加上一些类似"Hari”这个标志,比如说exe开头也有MZ这个标志,以此来进行标识的作用。

好了,现在让我们make run看看:

成功了!嘿嘿嘿,很好继续加油。
接下来,让我们调用API来显示一个窗口吧,具体我们主要做如下的设置:
于是按照这个思路进行如下改写:
console.c:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
char s[12];
int ds_base = *((int *) 0xfe8);
struct TASK *task = task_now();
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);//bootpack中的值
struct SHEET *sht;
int *reg = &eax + 1;//指向eax后面的地址,是因为asm_hrb_api中push了两次一模一样的寄存器的值
/* 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 == 1) {
cons_putchar(cons, eax & 0xff, 1);
} else if (edx == 2) {
cons_putstr0(cons, (char *) ebx + ds_base);
sprintf(s,"%08X\n", ebx);
cons_putstr0(cons, s);
} else if (edx == 3) {
cons_putstr1(cons, (char *) ebx + ds_base, ecx);
} else if (edx == 4) {
return &(task->tss.esp0);
}else if (edx == 5){
sht = sheet_alloc(shtctl);
sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);//设置x、y轴和透明色
make_window8((char *)ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
sheet_slide(sht, 100, 50);
sheet_updown(sht, 3);//高度高于task_a
reg[7] = (int) sht;//方便后续向应用程序的返回值动手脚
}
return 0;
}
bootpack.c:
task_run(task_a, 1, 2);
*((int *) 0x0fe4) = (int) shtctl;
测试程序:
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
winhelo.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, "Yuan-os");
api_end();
}
然后,运行:

哦耶!!🤞
那么我们继续进一步的对窗口增加一些功能:
窗口中显示字符API:
窗口中显示方块的API:
接下来就开始描写吧:
console.c:
else if (edx == 6) {
sht = (struct SHEET *) ebx;
putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);
sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);
} else if (edx == 7) {
sht = (struct SHEET *) ebx;
boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
}
a_nask.nas:
_api_putstrwin: ; void api_putstrwin(int win, int x, int y, int col, int len, char *str);
PUSH EDI
PUSH ESI
PUSH EBP
PUSH EBX
MOV EDX,6
MOV EBX,[ESP+20] ; win
MOV ESI,[ESP+24] ; x
MOV EDI,[ESP+28] ; y
MOV EAX,[ESP+32] ; col
MOV ECX,[ESP+36] ; len
MOV EBP,[ESP+40] ; str
INT 0x40
POP EBX
POP EBP
POP ESI
POP EDI
RET
_api_boxfilwin: ; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
PUSH EDI
PUSH ESI
PUSH EBP
PUSH EBX
MOV EDX,7
MOV EBX,[ESP+20] ; win
MOV EAX,[ESP+24] ; x0
MOV ECX,[ESP+28] ; y0
MOV ESI,[ESP+32] ; x1
MOV EDI,[ESP+36] ; y1
MOV EBP,[ESP+40] ; col
INT 0x40
POP EBX
POP EBP
POP ESI
POP EDI
RET
程序:
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);
char buf[150 * 50];
void HariMain(void)
{
int win;
win = api_openwin(buf, 150, 50, -1, "hello");
api_boxfilwin(win, 8, 36, 141, 43, 3 /* 黄色*/);
api_putstrwin(win, 28, 28, 0 /* 黑色*/, 12, "hello, world");
api_end();
}
然后运行看看:

呼呼,终于完成了,说实话,这一天的内容我实际上花了好久,因为身心以及难度来说,都令我感到疲倦了。不过休整了一下明天继续加油,还有几天我们就可以大功告成了!!