# ressources

https://zq99299.github.io/linux-tutorial/tutorial-basis/# 内容导航

# Linux 常用操作

# 用户

# 添加用户

  • -m 新建 home 目录
l
sudo useradd -m username

# 加入 sudoer

l
sudo usermod -aG sudo username

# sudo 不需要密码

输入 visudo 编辑 sudo 配置文件,加入

l
yourname ALL=(ALL) NOPASSWD:ALL

如果 visudo 是 nano,可以这样改成 vim

.bashrc 里面加入

l
export EDITOR=vim

然后 source 一下,然后用

l
sudo -E visudo

继承环境变量

# SSH

# 安装

连接虚拟机,先把网络调成桥接模式

l
sudo apt install openssh-server
sudo systemctl enable ssh
sudo systemctl start ssh

# 更改默认 shell

有时候默认 shell 是 /bin/sh 很不好用,可以这样改成 bash

l
chsh -s /bin/bash yourname

# window 设置 ssh alias

打开 windows 的 powershell,编辑 $PROFILE

l
code $PROFILE

然后加入

l
function sshgermany {
    ssh fyind@217.154.10.231
}

这样就可以添加 alias

# ssh 无密码登录

先创建密钥

l
ssh-keygen -t ed25519 -C "your_email@example.com"

在 windows 里面,可以在 powershell 里这样打开

l
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub

然后登录服务器,在家目录的 .ssh 目录里,创建文件 authorized_keys

把公钥粘贴进去保存,然后就可以无密码登录了

# APT

# ppa release 问题

error text

t
E: The repository 'https://ppa.launchpadcontent.net/alexlarsson/flatpak/ubuntu jammy Release' does not have a Release file.

solution:

l
sudo add-apt-repository --remove ppa:alexlarsson/flatpak

# Docker

# 安装

l
sudo apt-get install docker.io docker-compose

# build

在一个有 Dockerfile 的目录下

l
docker build -t my-ctf-bot .

# run

l
docker run -p 8802:8802 my-ctf-bot

# 检查容器状态

l
docker-compose ps

还有

l
docker ps -a

# 运行容器

docker-compose.yml 文件目录下

l
docker-compose up (-d) # -d 运行在后台

# 检查网络

l
docker network ls

删除自定义网络

l
docker network prune

# Errors

l
Error: short-name "vaultwarden/server:latest" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf"

解决:

l
sudo vim /etc/containers/registries.conf

然后修改 (21 行)

l
unqualified-search-registries = ["docker.io"]

# VPN 网络配置

# 安装 wireguard

l
sudo apt install wireguard

# 生成服务器密钥

l
cd ~/.ssh
wg genkey | tee server_private.key | wg pubkey > server_public.key

# 编辑 wireguard 服务器配置

l
sudo vim /etc/wireguard/wg0.conf

加入, eth0 要改成在用的网卡

[Interface]
PrivateKey = 服务器私钥
Address = 10.0.0.1/24
ListenPort = 51820

PostUp = iptables -A FORWARD -i wg0 -j ACCEPT; iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT; iptables -t nat -D POSTROUTING -o eth0 -j MASQUERADE

启动 ip 转发

l
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

启动 wireguard

l
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
sudo wg-quick down wg0
sudo wg-quick up wg0
sudo wg show # 检查

# 生成客户端公钥

把客户端公钥加入服务器配置文件 /etc/wireguard/wg0.conf

l
[Peer]
PublicKey = <客户端公钥>
AllowedIPs = 10.0.0.2/32

然后在客户端配置, windows 可以下载 wireguard 客户端

l
[Interface]
PrivateKey = <客户端私钥>
Address = 10.0.0.2/24
DNS = 1.1.1.1
[Peer]
PublicKey = <服务器公钥>
Endpoint = <你的服务器IP>:51820
AllowedIPs = 0.0.0.0/0
PersistentKeepalive = 25

然后在防火墙开发端口 UDP, 51820

# 检查

首先可以连接上服务器

l
ping 10.0.0.1

然后检查可以 dns 服务

l
ping 8.8.8.8

然后看一下返回 ip

l
curl https://icanhazip.com

如果显示服务器 ip 就成功了

# Linux 系统编程手册

# 进程

进程的内存布局

  • 文本:程序的指令
  • 数据:静态变量
  • 堆:动态分配的内存
  • 栈:函数调用,局部变量

# 创建进程

通过系统调用 fork() 来创建新进程。调用 fork() 的是父进程,新的是子进程。

内核会复制父进程当前状态,并且之后修改不会影响父进程。

# 执行程序

用系统调用 execve() (也就是常说的 exec() )加载执行一个全新的程序,会销毁现有的文本,数据,堆栈,并且创建新的。

# 进程 ID

每个进程有一个整数的进程 ID,是 PID, 还有一个 PPID,是父进程的 PID

# 终止进程

  • 可以用 _exit() 来终止进程。
  • 向进程传信号,将它杀死

进程终止会产生 终止状态 ,是一个非负小整数,来让父进程的 wait() 检测。保存在 $? shell 变量里。

  • 0 是正常退出

# 进程的用户

每个进程有一组 UID 和 GID

  • 真实 UID,GID:进程所属的用户和组。fork 出来的会继承父进程的
  • 有效 UID,GID:访问受保护资源时候确定权限。
  • 补充 GID:进程所属的额外 GID。fork 也会继承这个

# 特权进程

有效 UID 是 0 的进程。

成为特权进程的方式可以是 set-user-ID ,可以改变进程的有效 UID

# 能力 Capabilities

赋予某进程部分能力,可以进行某些特权操作

# init 进程

系统引导时候,内核创建的 init 特殊进程。它是所有进程的父进程。对应的文件是 /sbin/init

  • init 进程号是 1, 以 super user 权限运行
  • 谁都不能杀死 init, 只有关闭系统才可以
  • init 任务:创建,监控系统运行的一系列进程

# 守护进程

有特殊用途的进程

  • 通常在系统引导时启动,直到关闭前都在
  • 在后台运行

比如 syslogd 系统日志, httpd

# 环境列表

每个进程都有一个环境列表,也就是在用户空间内存维护的环境变量。

  • fork 会继承父亲的环境副本

  • exec 要么继承老环境,要么在参数中指定新环境

shell 里可以用

l
export MYVAR='Hello world'

来设置环境变量

C 语言用 char **environ 来访问环境。

shell 定义的环境变量

  • HOME 用户登录的路径名
  • PATH 用户输入命令后查找的路径

# 资源限制

资源是:打开文件,内存,CPU 时间

fork 会继承资源限制

系统调用 setrlimit() 可以设置资源消耗的上限。

  • 硬限制
  • 软限制:非特权进程可以调整为 0 到硬限制直接的

ulimit 可以调整 shell 的资源限制

# 内存映射

系统调用 mmap() 会在虚拟地址中创建一个新的内存映射。

  • 文件映射:把文件的部分映射到调用进程的虚拟内存
  • 匿名映射:内容初始化为 0

进程映射可以和其他进程共享。

# 静态库和共享库

库是一种文件:一组函数代码编译。

# 静态库

使用静态库中的函数要链接。链接器会复制静态库里的函数到可执行文件。

问题:内存浪费,修改后需要重新编译

# 共享库

共享库不会复制,而是写入一条记录。运行是,动态连接器会找到并且载入内存。运行时,共享库代码只用保存一次。

# 进程间通信

  • signal 信号
  • 管道和 FIFO
  • socket
  • 文件锁定
  • 消息队列
  • 信号量
  • 共享内存

# 信号

信号有称为软件中断。是整数,名字是 SIGxxx 的类型。

内核,进程都可以对进程发送信号。

内核发送信号的情况:

  • Ctrl + C
  • 子进程终止
  • 进程设定的计时器到期
  • 进程访问无效的内存地址

在 shell 里可以用 kill 向进程发送信号。

进程收到信号后

  • 忽略信号
  • 被信号杀死
  • 先挂起,然后再被唤醒

程序可以建立信号处理器。

# 线程

每个进程可以执行多个进程。线程共享同一个虚拟内存,代码,共享数据区域和堆。每个线程有自己的栈。

线程直接可以共享全局变量通信。

线程的优点是:数据共享更容易。

# 进程组

shell 有任务控制的特性,可以创建多个进程和管道

l
ls -l | sort -k5n | less

这些进程会被放到一个进程组里。里面每个进程有相同的进程组标识符(其中某个进程组长的 PID)。

# 会话

一组进程组。会话里面所有进程有相同的会话标识符。(会话首进程的 PID)

打开控制终端会让会话首进程变成终端的控制进程。断开终端的时候,控制进程收到 SIGHUP 信号

会话有一个前台任务,可以从终端中输入数据。

  • CTRL+C 是中断信号
  • CTRL+Z 是挂起信号

& 结尾,可以创建后台任务。

# 伪终端

telnet 或者 ssh

# /proc 文件系统

虚拟文件系统,提供指向内核的数据结构接口。比如 /proc/1 是进程 1 的运行状态。

# 系统编程概念

# 系统调用

  • 将处理器从用户态切换到内核态,以便 CPU 访问受保护的内核内存
  • 每个系统调用有唯一的标识

例子:

  1. 程序调用 C 函数 Wrapper,把 syscall 的编号复制到 eax 里
  2. 执行 int0x80 切换到内核态,执行中断矢量的代码,(或者 sysenter)
  3. 响应中断,调用 system_call() , in arch/i386.entry.S
    1. 在 stack 保存寄存器
    2. 查找 sys_call_table , 执行服务例程 ( (arch/x86/kernel/proccess_32.c) ),返回给 system_call()
    3. 返回到外壳函数

# 标准 C 语言库函数 glibc

GNU C 是 Linux 上最常用的实现

# 确定系统的 glibc 版本

l
/lib/libc.so.6

(在自己 linux 上没有成功):有些 linux 路径不一样,查找方式是,对与 glibc 动态连接的可执行文件用 ldd 程序查找

l
ldd myprog | grep libc

libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007943ccc00000)

或者在程序里用常量 __GLIBC__, __GLIBC_MINOR__ , or gnu_get_libc_version() 返回指向版本号的指针.

# 处理函数错误

系统调用和库函数有返回值的话一定要检查

syscall 失败会设置 errno 全局变量, 在 <errno.h> 有对错误编号的定义。

c
cnt = read(fd, buf, numbytes);
if (cnt == -1) {
	if (errno == EINTER) {
        fprintf(stderr, "read was interrupted by a signal.\n");
    } else {
		//...
    }
}

如果调用成功 errno 不会重置为 0. perror 可以输出 syscall 的错误信息

c
fd = open(pathname, flags, mode);
if (fd == -1) {
    perror("open")
    exit(EXIT_FAILURE);
}

# 命令行选项

可以用 getopt() 解析命令行选项。

# 系统数据类型

<sys/types.h> 里面

# 文件 IO

标准文件描述符

  • 0 stdin
  • 1 stdout
  • 2 stderr

# 通用 IO

  • /dev/tty 终端文件
  • /dev/pts/16 其他终端

# open

flag 有很多,比如 O_RDONLY 是只读

# create

创建新文件的 syscall

# lseek

每打开文件,系统会记录偏移量,以后 read,write 会自动调整

lseek 可以调整文件偏移量

# ioctl

为执行文件和设备操作提供多用途机制

# 深入文件 IO

# 原子操作

syscall 是原子操作,不会被其他进程打断。

# fcntl

对一个打开的文件描述符执行控制操作,可以访问或者修改访问模式

# 文件描述符和打开文件

  • 进程的文件描述符表:对每个进程,内核维护打开文件的文件描述符,flag,句柄应用
  • 系统级打开文件表:内核对所有打开的文件有描述表格
  • 文件系统:文件系统对所有文件建立 i-node 表格,包括文件类型,访问权限,锁的指针,其他属性

# 复制文件描述符

ls -l > output.txt 2>&1

Shell 分析过程:

Token 类型 说明
ls 命令 要执行的命令
-l 参数 传给 ls 的选项
> 重定向标记 表示 stdout 重定向
output.txt 重定向目标 stdout 重定向到这个文件
2>&1 重定向标记 stderr 重定向到 stdout 的位置

2>&1 这里不是传给 ls 的参数,而是 Shell 内部解释后执行的重定向操作,在执行 ls 之前就完成了

# dup

复制打开的文件描述符

# pread, pwrite

和 read,write 类似,但可以在特定位置读写

# readv

readv 是一个比 read 更高效的系统调用,可以一次从文件或 socket 中读取数据到多个缓冲区,而不是多次调用 read

# truncate

设置文件大小

# 访问大文件

需要调用 open64() 系统调用

# dev fd 目录

对于每个进程,内核提供特殊目录 /dev/fd/n , n 是 pid

# mkstemp

这个函数用于生成临时文件

# 进程

# 进程内存布局

每个进程分配的内存分成很多部分,称为段 segment

  • 文本段
  • 初始化数据段
  • 未初始化数据段 bss
  • stack
  • heap

# 虚拟内存管理

虚拟内存将每个程序使用的内存切割成小型的,固定大小的页 page, 把 RAM 分成许多页帧。任意时刻,程序只有部分页载入内存。这些页构成驻留集 (resident set).

程序未使用的页保存在 swap area,如果进程访问的页不在物理内存里,就发生 page fault,内核挂起进程,从磁盘把页载入内存

内核对那个进村维护一个 page table,描述了每个 page 在虚拟内存地址空间的位置

# argv

linux 可以通过 /proc/PID/cmdline 读取进程的命令参数

# 环境列表

每个进程有一个 environment list , 每个字符串又有一个值。新进程会继承父进村的环境变量值。

l
SHELL=/bin/shell
export SHELL # Put in process's environment list

显示当前环境变量

l
printenv

C 语言可以用 char **environ 访问环境列表

可以用 putenv, setenv 来修改环境变量

# 长跳转

setjumplongjump 可以跳转出函数

# 内存分配

# 堆上分配内存

改变堆的大小: brksbrk() 系统调用

分配内存: malloc, free

# malloc

扫描之前由 free 释放的列表,找到尺寸要求的空闲块,如果没有的话,调用 brk 申请更多内存

# 能力

超级用户的权限被分成不同的单元,这个单元就是能力。

# 进程和文件的能力

每个进程有 3 个能力集:许可的,有效的,可继承的。

每个文件也有 3 个一样名字的。

  • 许可的:进程可以使用的能力
  • 有效的:用于权限检查的能力
  • 可继承的:执行程序的时候带入这些权限。