java开发操作系统:一个程序向另一个程序伸出的咸猪手

安全,是一个操作系统必须具备的根本特性。我们的系统发展到现在,安全性能上当然不可能与专业系统同日而语,但该做到的,系统内核都应该努力完善。前几期课程,我们给系统内核增加了中断处理,于是当应用程序妄图执行特权指令,想要染指内核运行时,中断会把程序强行切断,内核从中断中重新获得CPU的执行权限。

虽说恶意用户程序难以攻击内核,但是系统当前还存在一个漏洞,使得恶意程序能取攻击另一个程序,我们看看这个问题到底是怎么实现的。我们先在内核C语言部分做简单修改,把原来的cmd_hlt函数改为cmd_execute_program:

nt show_pos = 179;
void cmd_execute_program(char* file) {
    io_cli();

    struct Buffer *appBuffer = (struct Buffer*)memman_alloc(memman, 16);
    struct TASK *task = task_now();
    task->pTaskBuffer = appBuffer;

    file_loadfile(file, appBuffer);
    struct SEGMENT_DESCRIPTOR *gdt =(struct SEGMENT_DESCRIPTOR *)get_addr_gdt();
    //select is multiply of 8, divided by 8 get the original value
    int code_seg = 21 + (task->sel - first_task_cons_selector) / 8;
    //change here
    int mem_seg = 30 + (task->sel - first_task_cons_selector) / 8;//22;
    char *p = intToHexStr(mem_seg);
    showString(shtctl, sht_back, 0, show_pos, COL8_FFFFFF, p); 
    show_pos += 16;

    set_segmdesc(gdt + code_seg, 0xfffff, (int) appBuffer->pBuffer, 0x409a + 0x60);
    //new memory 
    char *q = (char *) memman_alloc_4k(memman, 64*1024);
    appBuffer->pDataSeg = (unsigned char*)q;
    set_segmdesc(gdt + mem_seg, 64 * 1024 - 1,(int) q ,0x4092 + 0x60);
    task->tss.esp0 = 0;
    io_sti();

    start_app(0, code_seg*8,64*1024, mem_seg*8, &(task->tss.esp0));
    io_cli();
    memman_free_4k(memman,(unsigned int) appBuffer->pBuffer, appBuffer->length);
    memman_free_4k(memman, (unsigned int) q, 64 * 1024);
    memman_free(memman,(unsigned int)appBuffer, 16);
    task->pTaskBuffer = 0;
    io_sti();

}

void console_task(struct SHEET *sheet, int memtotal) {
    ....
    for(;;) { 
    ....
    else if (i == KEY_RETURN) {
    ....
    } else if (strcmp(cmdline, "hlt") == 1) {
        //change here
        cmd_execute_program("abc.exe");
    }
    ....
    }
    ...
}

原来的cmd_hlt函数默认加载并执行软盘中的abc.exe程序,现在我们把cmd_hlt改名为cmd_execute_program,并且函数需要传入一个字符串,用于表明要加载执行的程序名字。在该函数的代码实现中,我们使用showString函数把被加载执行的用户进程数据段所对应的全局描述符号给显示到桌面上,上面代码执行后情况如下:

我们看到,在控制台中执行hlt命令后,内核加载了用户进程,同时在控制台下方输出了一个字符串,也就是0x1E,这个数值对应的就是当前运行用户进程其数据段对应的全局描述符号。一旦有这个信息之后,另一个进程就可以有机可乘了。

接着我们在本地目录创建一个新文件叫crack.c,其内容如下:

void main() {
    char *p = (char*)0x123;
    p[0] = 'c';
    p[1] = 'r';
    p[2] = 'a';
    p[3] = 'c';
    p[4] = 'k';
    p[5] = 0;
}

它的目的简单,就是针对内存地址0x123处写入字符串”crack”.接着我们修改一下makefile,使得内核编译时,能把crack.c编译成二进制文件:

CFLAGS=-fno-stack-protector
ckernel : ckernel_u.asm app_u.asm crack_u.asm 
    cp ckernel_u.asm win_sheet.h win_sheet.c mem_util.h mem_util.c write_vga_desktop.c timer.c timer.h global_define.h global_define.c multi_task.c multi_task.h app_u.asm app.c crack_u.asm crack.c  makefile '/media/psf/Home/Documents/操作系统/文档/19/OS-kernel-win-sheet/'
ckernel_u.asm : ckernel.o

....

crack_u.asm : crack.o
    ./objconv -fnasm crack.o crack_u.asm
crack.o : crack.c
    gcc -m32 -fno-stack-protector -fno-asynchronous-unwind-tables -s -c -o crack.o crack.c

然后我们在本地目录下,把api_call.asm拷贝一份,并命名为crack_call.asm,后者内容与前者完全相同,只不过稍微有那么一点点改变,例如:

BITS 32
mov AX, 30 * 8
mov DS, ax
call main

mov  edx, 4  ;返回内核
int  02Dh

....

这里需要注意,语句:

mov AX, 30 * 8
mov DS, ax

其中30对应的就是前面显示的0x1E,这两句汇编的作用是,把程序crack的数据段设置成下标为30的全局描述符所指向的内存段一致。这就意味着crack进程所使用的数据段就跟hlt启动的进程所使用的数据段一致了!于是在crack.c中,它对内存地址为0x123的地方写入字符串”crack”,那就意味着对hlt加载用户进程的内存空间写入对应字符串!

完成上面代码后,我们在java项目中,增加代码,一是用来编译crack进程,而是把crack代码写入虚拟磁盘。在OperatingSystem.java中,将代码做如下添加:

 public void makeFllopy()   {
    writeFileToFloppy("kernel.bat", false, 1, 1);
    ....
    header = new FileHeader();
    header.setFileName("crack");
    header.setFileExt("exe");
    file = new File("crack.bat");
    in = null;
    try {
        in = new FileInputStream(file);
        long len = file.length();

        int count = 0;
        while (count < file.length()) {
            bbuf[count] = (byte) in.read();
            count++;
        }

        in.close();
    }catch(IOException e) {
        e.printStackTrace();
        return;
    }
    header.setFileContent(bbuf);
    fileSys.addHeader(header);
    ....
}

public static void main(String[] args) {
        CKernelAsmPrecessor kernelPrecessor = new CKernelAsmPrecessor();
        kernelPrecessor.process();
        kernelPrecessor.createKernelBinary();

        CKernelAsmPrecessor appPrecessor = new CKernelAsmPrecessor("hlt.bat", "app_u.asm", "app.asm", "api_call.asm");
        appPrecessor.process();
        appPrecessor.createKernelBinary();

        CKernelAsmPrecessor crackPrecessor = new CKernelAsmPrecessor("crack.bat", "crack_u.asm", "crack.asm", "crack_call.asm");
        crackPrecessor.process();
        crackPrecessor.createKernelBinary();


        OperatingSystem op = new OperatingSystem("boot.bat");
        op.makeFllopy();
    }

在main函数中,我们把crack.c及其附属汇编文件结合在一起,编译成二进制文件crack.bat,在makeFllopy中,我们把编译后的crack.bat二进制数据读入,并把它写入到虚拟磁盘中,当系统运行起来后,可以把crack.bat二进制内容作为进程加载执行。

完成上面代码后,回到内核的C语言部分,也就是write_vga_desktop.c做一些修改,在kernel_api函数中,修改如下:

int* kernel_api(int edi, int esi, int ebp, int esp,
                int ebx, int edx, int ecx, int eax) {
    ....
    else if (edx == 14) {
        sheet_free(shtctl, (struct SHEET*)ebx);
        //change here
        cons_putstr((char*)(task->pTaskBuffer->pDataSeg + 0x123));
    }
    ....
}

void console_task(struct SHEET *sheet, int memtotal) {
    ....
    for(;;) {
    ....
    else if (i == KEY_RETURN) {
    ....
    else if (strcmp(cmdline, "crack") == 1) {
          cmd_execute_program("crack.exe");
    }
    ....
    }
    ....
}

在kernel_api中,if(edx == 14)对应的api调用是api_closewin,也就是当用户进程关闭窗口时,我们把进程数据偏移0x123处的数据当做字符串打印到控制台窗口上,在console_task控制台进程主函数中,我们增加了对命令crack的响应,当用户在控制台上输入命令”crack”时,将crack代码加载到内核中运行。上面代码完成后,编译内核,然后用虚拟机将内核加载,系统启动后,我们现在一个控制台中输入hlt,先启动用户进程。然后点击”shift + w”,启动另一个控制台窗口,在其中输入crack,运行crack程序:

接着把点击tab键,把焦点恢复到窗口task_a,然后用鼠标点击运行hlt命令的窗口,把输入焦点切换到该控制台,然后再次点击tab键,把执行权限提交给运行hlt命令的控制台,此时点击回车,介绍用户进程启动的窗口,结果情况如下:

此时我们可以看到,运行hlt命令,执行用户进程的控制台窗口居然输出了字符串”crack”,而这个字符串正是crack.c在执行时,写入地址0x123的字符串。这就意味着一个恶意进程成功修改了另一个进程的内存数据,也相当于一个流氓程序把一只咸猪手伸到其他用户进程的裙底,蹂躏一番后留下了猥琐的证据。

那么如何防范恶意进程对其他程序的非法入侵呢,这就得使用CPU提供的LDT机制,也就是局部描述符表,该机制的使用,我们将在下一节详细讲解。

原文发布于微信公众号 - Coding迪斯尼(gh_c9f933e7765d)

原文发表时间:2017-12-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏finleyMa

SSH 技巧

SSH 是 Linux 下进行远程连接的基本工具。 一般情况下我们可以通过 ssh username@xxx.xxx.xxx.xxx 登录远程服务器,如果要管...

29930
来自专栏linux运维学习

linux学习第四十二篇:限定某个目录禁止解析php, 限制user_agent,PHP相关配置

限定某个目录禁止解析php 虚拟主机配置文件添加的核心配置内容: <Directory /data/wwwroot/111.com/upload> ...

26190
来自专栏FreeBuf

一款短小精致的SSH后门分析

0x00. 引言 在《利用系统特性伪装成一个免密登陆后门》一文中,我介绍过利用系统特性伪装成一个ssh系统后门,不过,这个后门需要新开一个端口,而本文介绍的这个...

35540
来自专栏性能与架构

一个小巧实用的User-Agent解析库

UAParser.js 有时我们需要判断客户端的一些信息,如浏览器的类型、型号等,然后进行相应处理,例如,如果是使用微信访问的,就怎么怎么处理。 这就需要从 U...

81170
来自专栏Java后端技术

GitLab配置ssh key

  当前很多公司都选择git作为代码版本控制工具,然后自己公司搭建私有的gitlab来管理代码,我们在clone代码的时候可以选择http协议,当然我们亦可以选...

13130
来自专栏开源优测

AutoLine源码之RobotFramework运行器

在AutoLine中我们自定义实现了RobotFramework的运行器,其路径如下图所示:

13730
来自专栏醉生梦死

MySQL客户端连接工具 mysql

73460
来自专栏wblearn

package.json文件快速入门详解

相信入门nodejs或者npm的同学会对package.json这个文件有疑惑,对这个文件的作用不是很清晰,但搭建自己的博客每每用到node,npm这个文件又必...

15310
来自专栏游戏杂谈

rtx登录内网系统

公司内部使用rtx进行沟通和交流,经常遇到订餐的问题,用php写了一个订餐系统,实现rtx上点击链接打开系统就自动登录了,无需再次输入用户名和密码。

37310
来自专栏地方网络工作室的专栏

Shell 命令行实现将一个站点页面全部下载到本地并替换其中链接的脚本

Shell 命令行实现将一个站点页面全部下载到本地并替换其中链接的脚本 不知道为什么,我总想用 Shell 脚本来实现把一个站点内容给下载下来。但是下载什么站点...

21250

扫码关注云+社区

领取腾讯云代金券