1.3 Linux 基础

常用基础命令

ls                  用来显示目标列表

cd [path]           用来切换工作目录

pwd                 以绝对路径的方式显示用户当前工作目录

man [command]       查看Linux中的指令帮助、配置文件帮助和编程帮助等信息

apropos [whatever]  在一些特定的包含系统命令的简短描述的数据库文件里查找关键字

echo [string]       打印一行文本,参数“-e”可激活转义字符

cat [file]          连接文件并打印到标准输出设备上

less [file]         允许用户向前或向后浏览文字档案的内容

mv [file1] [file2]  用来对文件或目录重新命名,或者将文件从一个目录移到另一个目录中

cp [file1] [file2]  用来将一个或多个源文件或者目录复制到指定的目的文件或目录

rm [file]           可以删除一个目录中的一个或多个文件或目录,也可以将某个目录及其下属的所有文件及其子目录均删除掉

ps                  用于报告当前系统的进程状态

top                 实时查看系统的整体运行情况

kill                杀死一个进程

ifconfig            查看或设置网络设备

ping                查看网络上的主机是否工作

netstat             显示网络连接、路由表和网络接口信息

nc(netcat)          建立 TCP 和 UDP 连接并监听

su                  切换当前用户身份到其他用户身份

touch [file]        创建新的空文件

mkdir [dir]         创建目录

chmod               变更文件或目录的权限

chown               变更某个文件或目录的所有者和所属组

nano / vim / emacs  字符终端的文本编辑器

exit                退出 shell

使用变量:

Bash 快捷键

更多细节请查看:Bash Keyboard Shortcuts

根目录结构

由于不同的发行版会有略微的不同,我们这里使用的是基于 Arch 的发行版 Manjaro,以上就是根目录下的内容,我们介绍几个重要的目录:

  • /bin/sbin:链接到 /usr/bin,存放 Linux 一些核心的二进制文件,其包含的命令可在 shell 上运行。

  • /boot:操作系统启动时要用到的程序。

  • /dev:包含了所有 Linux 系统中使用的外部设备。需要注意的是这里并不是存放外部设备的驱动程序,而是一个访问这些设备的端口。

  • /etc:存放系统管理时要用到的各种配置文件和子目录。

  • /etc/rc.d:存放 Linux 启动和关闭时要用到的脚本。

  • /home:普通用户的主目录。

  • /lib/lib64:链接到 /usr/lib,存放系统及软件需要的动态链接共享库。

  • /mnt:这个目录让用户可以临时挂载其他的文件系统。

  • /proc:虚拟的目录,是系统内存的映射。可直接访问这个目录来获取系统信息。

  • /root:系统管理员的主目录。

  • /srv:存放一些服务启动之后需要提取的数据。

  • /sys:该目录下安装了一个文件系统 sysfs。该文件系统是内核设备树的一个直观反映。当一个内核对象被创建时,对应的文件和目录也在内核对象子系统中被创建。

  • /tmp:公用的临时文件存放目录。

  • /usr:应用程序和文件几乎都在这个目录下。

  • /usr/src:内核源代码的存放目录。

  • /var:存放了很多服务的日志信息。

进程管理

  • top

    • 可以实时动态地查看系统的整体运行情况。

  • ps

    • 用于报告当前系统的进程状态。可以搭配 kill 指令随时中断、删除不必要的程序。

    • 查看某进程的状态:$ ps -aux | grep [file],其中返回内容最左边的数字为进程号(PID)。

  • kill

    • 用来删除执行中的程序或工作。

    • 删除进程某 PID 指定的进程:$ kill [PID]

UID 和 GID

Linux 是一个支持多用户的操作系统,每个用户都有 User ID(UID) 和 Group ID(GID),UID 是对一个用户的单一身份标识,而 GID 则对应多个 UID。知道某个用户的 UID 和 GID 是非常有用的,一些程序可能就需要 UID/GID 来运行。可以使用 id 命令来查看:

UID 为 0 的 root 用户类似于系统管理员,它具有系统的完全访问权。我自己新建的用户 firmy,其 UID 为 1000,是一个普通用户。GID 的关系存储在 /etc/group 文件中:

所有用户的信息(除了密码)都保存在 /etc/passwd 文件中,而为了安全起见,加密过的用户密码保存在 /etc/shadow 文件中,此文件只有 root 权限可以访问。

由于普通用户的权限比较低,这里使用 sudo 命令可以让普通用户以 root 用户的身份运行某一命令。使用 su 命令则可以切换到一个不同的用户:

whoami 用于打印当前有效的用户名称,shell 中普通用户以 $ 开头,root 用户以 # 开头。在输入密码后,我们已经从 firmy 用户转换到 root 用户了。

权限设置

在 Linux 中,文件或目录权限的控制分别以读取、写入、执行 3 种一般权限来区分,另有 3 种特殊权限可供运用。

使用 ls -l [file] 来查看某文件或目录的信息:

第一栏从第二个字母开始就是权限字符串,权限表示三个为一组,依次是所有者权限、组权限、其他人权限。每组的顺序均为 rwx,如果有相应权限,则表示成相应字母,如果不具有相应权限,则用 - 表示。

  • r:读取权限,数字代号为 “4”

  • w:写入权限,数字代号为 “2”

  • x:执行或切换权限,数字代号为 “1”

通过第一栏的第一个字母可知,第一行是一个链接文件 (l),第二行是个目录(d),第三行是个普通文件(-)。

用户可以使用 chmod 指令去变更文件与目录的权限。权限范围被指定为所有者(u)、所属组(g)、其他人(o)和所有人(a)。

  • -R:递归处理,将指令目录下的所有文件及子目录一并处理;

  • <权限范围>+<权限设置>:开启权限范围的文件或目录的该选项权限设置

    • $ chmod a+r [file]:赋予所有用户读取权限

  • <权限范围>-<权限设置>:关闭权限范围的文件或目录的该选项权限设置

    • $ chmod u-w [file]:取消所有者写入权限

  • <权限范围>=<权限设置>:指定权限范围的文件或目录的该选项权限设置;

    • $ chmod g=x [file]:指定组权限为可执行

    • $ chmod o=rwx [file]:制定其他人权限为可读、可写和可执行

img

字节序

目前计算机中采用两种字节存储机制:大端(Big-endian)和小端(Little-endian)。

MSB (Most Significan Bit/Byte):最重要的位或最重要的字节。

LSB (Least Significan Bit/Byte):最不重要的位或最不重要的字节。

Big-endian 规定 MSB 在存储时放在低地址,在传输时放在流的开始;LSB 存储时放在高地址,在传输时放在流的末尾。Little-endian 则相反。常见的 Intel 处理器使用 Little-endian,而 PowerPC 系列处理器则使用 Big-endian,另外 TCP/IP 协议和 Java 虚拟机的字节序也是 Big-endian。

例如十六进制整数 0x12345678 存入以 1000H 开始的内存中:

img

我们在内存中实际地看一下,在地址 0xffffd584 处有字符 1234,在地址 0xffffd588 处有字符 5678

输入输出

  • 使用命令的输出作为可执行文件的输入参数

    • $ ./vulnerable `your_command_here`

    • $ ./vulnerable $(your_command_here)

  • 使用命令作为输入

    • $ your_command_here | ./vulnerable

  • 将命令行输出写入文件

    • $ your_command_here > filename

  • 使用文件作为输入

    • $ ./vulnerable < filename

文件描述符

在 Linux 系统中一切皆可以看成是文件,文件又分为:普通文件、目录文件、链接文件和设备文件。文件描述符(file descriptor)是内核管理已被打开的文件所创建的索引,使用一个非负整数来指代被打开的文件。

标准文件描述符如下:

文件描述符
用途
stdio 流

0

标准输入

stdin

1

标准输出

stdout

2

标准错误

stderr

当一个程序使用 fork() 生成一个子进程后,子进程会继承父进程所打开的文件表,此时,父子进程使用同一个文件表,这可能导致一些安全问题。如果使用 vfork(),子进程虽然运行于父进程的空间,但拥有自己的进程表项。

核心转储

当程序运行的过程中异常终止或崩溃,操作系统会将程序当时的内存、寄存器状态、堆栈指针、内存管理信息等记录下来,保存在一个文件中,这种行为就叫做核心转储(Core Dump)。

会产生核心转储的信号

Signal
Action
Comment

SIGQUIT

Core

Quit from keyboard

SIGILL

Core

Illegal Instruction

SIGABRT

Core

Abort signal from abort

SIGSEGV

Core

Invalid memory reference

SIGTRAP

Core

Trace/breakpoint trap

开启核心转储

  • 输入命令 ulimit -c,输出结果为 0,说明默认是关闭的。

  • 输入命令 ulimit -c unlimited 即可在当前终端开启核心转储功能。

  • 如果想让核心转储功能永久开启,可以修改文件 /etc/security/limits.conf,增加一行:

修改转储文件保存路径

  • 通过修改 /proc/sys/kernel/core_uses_pid,可以使生成的核心转储文件名变为 core.[pid] 的模式。

  • 还可以修改 /proc/sys/kernel/core_pattern 来控制生成核心转储文件的保存位置和文件名格式。

    此时生成的文件保存在 /tmp/ 目录下,文件名格式为 core-[filename]-[pid]-[time]

使用 gdb 调试核心转储文件

例子

调用约定

函数调用约定是对函数调用时如何传递参数的一种约定。关于它的约定有许多种,下面我们分别从内核接口和用户接口介绍 32 位和 64 位 Linux 的调用约定。

内核接口

x86-32 系统调用约定:Linux 系统调用使用寄存器传递参数。eax 为 syscall_number,ebxecxedxesiebp 用于将 6 个参数传递给系统调用。返回值保存在 eax 中。所有其他寄存器(包括 EFLAGS)都保留在 int 0x80 中。

x86-64 系统调用约定:内核接口使用的寄存器有:rdirsirdxr10r8r9。系统调用通过 syscall 指令完成。除了 rcxr11rax,其他的寄存器都被保留。系统调用的编号必须在寄存器 rax 中传递。系统调用的参数限制为 6 个,不直接从堆栈上传递任何参数。返回时,rax 中包含了系统调用的结果。而且只有 INTEGER 或者 MEMORY 类型的值才会被传递给内核。

用户接口

x86-32 函数调用约定:参数通过栈进行传递。最后一个参数第一个被放入栈中,直到所有的参数都放置完毕,然后执行 call 指令。这也是 Linux 上 C 语言函数的方式。

x86-64 函数调用约定:x86-64 下通过寄存器传递参数,这样做比通过栈有更高的效率。它避免了内存中参数的存取和额外的指令。根据参数类型的不同,会使用寄存器或传参方式。如果参数的类型是 MEMORY,则在栈上传递参数。如果类型是 INTEGER,则顺序使用 rdirsirdxrcxr8r9。所以如果有多于 6 个的 INTEGER 参数,则后面的参数在栈上传递。

环境变量

环境变量字符串都是 name=value 这样的形式。大多数 name 由大写字母加下划线组成,一般把 name 部分叫做环境变量名,value 部分则是环境变量的值,而且 value 需要以 '\0' 结尾。

环境变量定义了该进程的运行环境。

分类

  • 按照生命周期划分

    • 永久环境变量:修改相关配置文件,永久生效。

    • 临时环境变量:使用 export 命令,在当前终端下生效,关闭终端后失效。

  • 按照作用域划分

    • 系统环境变量:对该系统中所有用户生效。

    • 用户环境变量:对特定用户生效。

设置方法

  • 在文件 /etc/profile 中添加变量,这种方法对所有用户永久生效。如:

    添加后执行命令 source /etc/profile 使其生效。

  • 在文件 ~/.bash_profile 中添加变量,这种方法对当前用户永久生效。其余同上。

  • 直接运行命令 export 定义变量,这种方法只对当前终端临时生效。

常用变量

使用命令 echo 打印变量:

使用命令 env 可以打印出所有环境变量:

使用命令 set 可以打印出所有本地定义的 shell 变量:

使用命令 unset 可以清除变量:

LD_PRELOAD

该环境变量可以定义在程序运行前优先加载的动态链接库。在 pwn 题目中,我们可能需要一个特定的 libc,这时就可以定义该变量:

一个例子:

注意,在加载动态链接库时需要使用 ld.so 进行重定位,通常被符号链接到 /lib64/ld-linux-x86-64.so 中。动态链接库在编译时隐式指定 ld.so 的搜索路径,并写入 ELF Header 的 INTERP 字段中。从其他发行版直接拷贝已编译的 .so 文件可能会引发 ld.so 搜索路径不正确的问题。相似的,在版本依赖高度耦合的发行版中(如 ArchLinux),版本相差过大也会引发 ld.so 的运行失败。

本地同版本编译后通常不会出现问题。如果有直接拷贝已编译版本的需要,可以对比 interpreter 确定是否符合要求,但是不保证不会失败。

上面的例子中两个 libc 是这样的:

都是 interpreter /lib64/ld-linux-x86-64.so.2,所以可以替换。

而下面的例子是在 Arch Linux 上使用一个 Ubuntu 的 libc,就会出错:

一个在 interpreter /usr/lib/ld-linux-x86-64.so.2,而另一个在 interpreter /lib64/ld-linux-x86-64.so.2

environ

libc 中定义的全局变量 environ 指向环境变量表。而环境变量表存在于栈上,所以通过 environ 指针的值就可以泄露出栈地址。

procfs

procfs 文件系统是 Linux 内核提供的虚拟文件系统,为访问系统内核数据的操作提供接口。之所以说是虚拟文件系统,是因为它不占用存储空间,而只是占用了内存。用户可以通过 procfs 查看有关系统硬件及当前正在运行进程的信息,甚至可以通过修改其中的某些内容来改变内核的运行状态。

/proc/cmdline

在启动时传递给内核的相关参数信息,通常由 lilo 或 grub 等启动管理工具提供:

/proc/cpuinfo

记录 CPU 相关的信息:

/proc/crypto

已安装的内核所使用的密码算法及算法的详细信息:

/proc/devices

已加载的所有块设备和字符设备的信息,包含主设备号和设备组(与主设备号对应的设备类型)名:

/proc/interrupts

X86/X86_64 系统上每个 IRQ 相关的中断号列表,多路处理器平台上每个 CPU 对于每个 I/O 设备均有自己的中断号:

/proc/kcore

系统使用的物理内存,以 ELF 核心文件(core file)格式存储:

/proc/meminfo

系统中关于当前内存的利用状况等的信息:

/proc/mounts

每个进程自身挂载名称空间中的所有挂载点列表文件的符号链接:

/proc/modules

当前装入内核的所有模块名称列表,可以由 lsmod 命令使用。其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态:Live(已经装入)、Loading(正在装入)和 Unloading(正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量:

/proc/slabinfo

保存着监视系统中所有活动的 slab 缓存的信息:

/proc/[pid]

在 /proc 文件系统下,还有一些以数字命名的目录,这些数字是进程的 PID 号,而这些目录是进程目录。目录下的所有文件如下,然后会介绍几个比较重要的:

/proc/[pid]/cmdline

启动当前进程的完整命令:

/proc/[pid]/exe

指向启动当前进程的可执行文件的符号链接:

/proc/[pid]/root

当前进程运行根目录的符号链接:

/proc/[pid]/mem

当前进程所占用的内存空间,由open、read和lseek等系统调用使用,不能被用户读取。但可通过下面的 /proc/[pid]/maps 查看。

/proc/[pid]/maps

这个文件大概是最常用的,用于显示进程的内存区域映射信息:

/proc/[pid]/stack

这个文件表示当前进程的内核调用栈信息,只有在内核编译启用 CONFIG_STACKTRACE 选项,才会生成该文件:

/proc/[pid]/auxv

该文件包含了传递给进程的解释器信息,即 auxv(AUXiliary Vector),每一项都是由一个 unsigned long 长度的 ID 加上一个 unsigned long 长度的值构成:

每个值具体是做什么的,可以用下面的办法显示出来,对比看一看,更详细的可以查看 /usr/include/elf.hman ld.so

值得一提的是,AT_SYSINFO_EHDR 所对应的值是一个叫做的 VDSO(Virtual Dynamic Shared Object) 的地址。在 ret2vdso 漏洞利用方法中会用到(参考章节6.1.6)。

/proc/[pid]/environ

该文件包含了进程的环境变量:

/proc/[pid]/fd

该文件包含了进程打开文件的情况:

/proc/[pid]/status

该文件包含了进程的状态信息:

/proc/[pid]/task

一个目录,包含当前进程的每一个线程的相关信息,每个线程的信息分别放在一个由线程号(tid)命名的目录中:

/proc/[pid]/syscall

该文件包含了进程正在执行的系统调用:

第一个值是系统调用号,后面跟着是六个参数,最后两个值分别是堆栈指针和指令计数器的值。

参考资料

Last updated

Was this helpful?