20169210《Linux内核原理与分析》第十一周作业

第17章 设备与模块

关于设备驱动和设备管理,讨论四种内核成分。

  • 设备类型:在所有的linux系统中为了统一普遍设备的操作所分的类。
  • 模块:Linux内核中用于按需加载和卸载目标码的机制。
  • 内核对象:内核数据机构中支持面向对象的简单操作,还支持维护对象间的父子关系。
  • sysfs:表示系统中设备树的一个文件系统

一、设备类型

在Linux以及所有Unix系统中,设备被分为一下三种类型:

  • 块设备
  • 字符设备
  • 网络设备
  1. 块设备通常缩写为blkdev,它是可寻址的,寻址以块为单位,块大小随设备不同而不同;通常支持重定位操作,也就是对数据的随机访问。
  2. 字符设备通常缩写为cdev,它是不可寻址的,仅提供数据的流式访问。
  3. 网络设备最常见的类型有时也以以太网设备来称呼,他提供了对网络的访问,这是通过一个物理适配器和一种特定的协议进行的。网络设备是通过套接字API这样的特殊接口来访问的。

二、模块

尽管Linux是“单块内核”的操作系统,这是说整个系统内核都运行于一个单独的保护域中,但是Linux内核是模块化组成的,它允许内核在运行时动态地向其中插入或从中删除代码。这些代码被一并组合在一个单独的二进制镜像中,即所谓的可装载内核模块中,或简称模块。支持模块的好处是基本内核镜像可以尽可能的小,因为可选的功能和驱动程序可以利用模块形式再提供。模块允许我们方便地删除和重新载入内核代码,也方便了调试工作。

三、设备模型

  1. 设备模型的核心部分就是kobject,类似于c#或Java这些面向对象语言中的对象类,提供了诸如引用计数、名称和父指针等字段,可以创建对象的层次结构。
  2. kobject对象被关联到一种特殊的类型,即ktype。ktype的存在是为了描述一族kobject所具有的普遍特性。如此一来,不再需要每个kobject都分别定义自己的特性,而是将这些普遍特性在ktype结构中一次定义,然后所有同类的kobject都能共享一样的特性。
  3. kset是kobject对象的集合体。kset可把kobject集中到一个集合中,而ktype描述相关类型kobject所共有的特性,它们之间的重要区别在于:具有相同的ktype的kobject可以被分组到不同的kset。就是说,在Linux内核中,只有少数的ktype,却有多个kset。

四、sysfs

sys文件系统是一个处于内存中的虚拟文件系统,它为我们提供了kobject对象层次结构的视图。
sysfs的诀窍是把kobject对象与目录项紧密联系起来,这点是通过kobject对象中的denty字段实现的。

第19章 可移植性

关于字长和数据类型的一些准则:

  • ANSIC标准规定,一个char的长度一定是1字节
  • 尽管没有规定int类型的长度是32位,但在Linux当前支持的体系结构中,它都是32位的。
  • short类型也类似,在当前所有支持的体系结构中,虽然没有明文规定,但是它都是16位的。
  • 绝对不应该假定指针和long的长度,在linux当前支持的体系结构中,它们可以在32位和64位中变化。
  • 对于不同的体系结构long的长度不同,决不应该假设sizeof(int)=sizeof(long)。
  • 类似的,也不要假设指针和int长度相等。

要想写出移植性好、简洁、合适的内核代码,要注意以下两点:

  1. 编码尽量选取最大公因子:假定任何事情都可能发生,任何潜在的约束都可能存在。

  2. 编码尽量选取最小公约数:不要假定给定的内核特性是可用的,仅仅需要最小的体系结构功能。

    编写可移植性的代码需要考虑很多问题:字长、数据类型、填充、对齐、字节次序、符号、字节顺序、页大小以及处理器的加载/存储排序等。对于绝大多数的内核开发者来说,可能主要考虑的问题就是保证正确使用数据类型。

第20章 补丁,开发和社区

Linux编码风格:

  • 缩进——缩进风格是用制表位每次缩进8个字符长度。
  • switch语句——switch语句下属的case标记应该缩进到和switch声明对齐,这样有助于减少8个字符的tab键带来的排版缩进。
  • 空格——Linux编码风格规定,空格放在关键字周围,函数名和圆括号之间无空格。
  • 花括号——内核选定的风格是左括号紧跟在语句的最后,与语句在相同的一行。而右括号要新起一行,作为该行的第一个字符。
  • 每行代码的长度——源代码中要尽可能地保证每行代码长度不超过80个字符,因为这样做可能使代码最合适在标准的80*24的终端上显示。
  • 命名规范——命名中不允许使用骆驼拼写法、Studly Caps或者其他混合的大小写字符。
  • 函数——根据经验,函数的代码长度不应该超过两屏,局部变量不应超过10个。一个函数应该功能单一而且实现精确。
  • 注释——一般情况下应该描述的是你的代码要做什么和为什么要这样做,而不是具体通过什么方式实现的。
  • typedef——使用typedef要谨慎,只有在确实需要的时候再使用它。
  • 多用现成的东西——请勿闭门造车。内核本身就提供了字符串操作函数,压缩函数和一个链表接口,所以请使用他们。
  • 在源码中减少使用ifdef——不赞成在源码中使用ifdef预处理指令。
  • 结构初始化——结构初始化的时候必须在他的成员前加上结构标识符。
  • 代码的事后修正——indent是一个在大多数Linux系统中都能找到的好工具,它可以按照指定的方式对源代码进行格式化。

实验部分

2014年9月24日,Bash中发现了一个严重漏洞shellshock,该漏洞可用于许多系统,并且既可以远程也可以在本地触发。

什么是shellshock:Shellshock,又称Bashdoor,是在Unix中广泛使用的Bash shell中的一个安全漏洞,首次于2014年9月24日公开。许多互联网守护进程,如网页服务器,使用bash来处理某些命令,从而允许攻击者在易受攻击的Bash版本上执行任意代码。Bash (GNU Bourne-Again Shell) 是许多Linux发行版的默认Shell。这可使攻击者在未授权的情况下访问计算机系统。

实验准备

以root权限安装4.1版bash

下载:

# wget http://labfile.oss.aliyuncs.com/bash-4.1.tar.gz

安装:

# tar xf bash-4.1.tar.gz
# cd bash-4.1
# ./configure
# make & make install

./configure的作用是检测系统配置,生成makefile文件,以便你可以用make和make install来编译和安装程序。

链接:

# rm /bin/bash
# ln -s /usr/local/bin/bash /bin/bash


到这里就安装完了,接下来检测是否存在shellshock漏洞。

$ env x='() { :;}; echo vulnerable' bash -c "echo this is a test "

-c参数,则bash从字符串中读入命令。bash -c "echo this is a test"在执行的时候存在一个环境变量x,而x呢等于 () { :;}; echo vulnerable ,我们都知道环境变量在使用前都会被初始化,那么 () { :;}; echo vulnerable 就自然而然的被执行了。 () { :;}; 定义了一个函数(function)并且它什么也不做,而后的 echo vulnerable 被解析后得到了执行权。那么 echo vulnerable 换成其他更具破坏性的代码后果就不堪设想了。

输出vulnerable的话,说明bash有漏洞。

最后,让/bin/sh 指向/bin/bash.

$ sudo ln -sf /bin/bash /bin/sh

-s是创建软连接,-f是强制创建,软连接相当于一个快捷方式。

了解bash自定义函数,只需要函数名就能够调用该函数。

$ foo() { echo bar; } 
$ foo
> bar

这个时候的Bash的环境变量:

KEY = foo
VALUE = () { echo bar; }

用$ echo 查看环境变量。
来看看ShellShock漏洞的真身:

export foo=’() { :; }; echo Hello World’ 
bash
>Hello World

export设置环境变量。

为什么调用bash的时候输出Hello World了呢?瞧瞧他内部的情况:

KEY = foo
VALUE = () { :; }; echo Hello World

bash读取了环境变量,在定义foo之后直接调用了后面的函数。 一旦调用bash,自定义的语句就直接触发。

攻击Set-UID程序
本实验中,我们通过攻击Set-UID程序来获得root权限。Set-UID 是Unix系统中的一个重要的安全机制。当一个Set-UID程序运行的时候,它被假设为具有拥有者的权限。例如,如果程序的拥有者是root,那么任何人运行这个程序时都会获得程序拥有者的权限。 一个文件都有一个所有者, 表示该文件是谁创建的。同时, 该文件还有一个组编号, 表示该文件所属的组, 一般为文件所有者所属的组。setuid: 设置使文件在执行阶段具有文件所有者的权限。典型的文件是 /usr/bin/passwd. 如果一般用户执行该文件, 则在执行过程中, 该文件可以获得root权限, 从而可以更改用户的密码。首先,确保安装了带有漏洞的bash版本,并让/bin/sh 指向/bin/bash.

$ sudo ln -sf /bin/bash /bin/sh

请编译下面这段代码,并设置其为Set-UID程序,保证它的所有者是root。我们知道system()函数将调用"/bin/sh -c" 来运行指定的命令, 这也意味着/bin/bash 会被调用。

#include <stdio.h>
void main()
{
    setuid(geteuid()); // make real uid = effective uid.
    system("/bin/ls -l");
}

system:运行外部函数。real UID是标记谁调用了该可执行文件;effective UID表示该可执行程序所具有的权限的用户;默认情况下real UID和effective UID一样,但是当使用了setUID之后,两者有可能不一样。例如passwd程序,当你执行这个程序的时候,它的real UID就是调用passwd的用户,这个用户可能是root,也可能是任何普通用户;但是它的effective UID是root,只有这样passwd程序才能修改/etc/passwd文件。

我们注意到这里使用了setuid(geteuid()) 来使real uid = effective uid。

如果 setuid(geteuid()) 语句被去掉了,再试试看攻击,我们还能够拿到权限么?

#include <stdio.h>
void main()
{
    system("/bin/ls -l");
}

失败了!这就说明如果 real uid 和 effective uid 相同的话,定义在环境变量中的内容在该程序内有效,那样shellshock漏洞就能够被利用了。但是如果两个uid不同的话,环境变量失效,就无法发动攻击了。

posted @ 2016-12-04 13:10  刘翠杰  阅读(205)  评论(0编辑  收藏  举报