# ressources
https://zq99299.github.io/linux-tutorial/tutorial-basis/# 内容导航
# Linux 常用操作
# 用户
# 添加用户
-m
新建 home 目录
sudo useradd -m username |
# 加入 sudoer
sudo usermod -aG sudo username |
# sudo 不需要密码
输入 visudo
编辑 sudo 配置文件,加入
yourname ALL=(ALL) NOPASSWD:ALL |
如果 visudo 是 nano,可以这样改成 vim
在 .bashrc
里面加入
export EDITOR=vim |
然后 source 一下,然后用
sudo -E visudo |
继承环境变量
# SSH
# 安装
连接虚拟机,先把网络调成桥接模式
sudo apt install openssh-server | |
sudo systemctl enable ssh | |
sudo systemctl start ssh |
# 更改默认 shell
有时候默认 shell 是 /bin/sh
很不好用,可以这样改成 bash
chsh -s /bin/bash yourname |
# window 设置 ssh alias
打开 windows 的 powershell,编辑 $PROFILE
code $PROFILE |
然后加入
function sshgermany { | |
ssh fyind@217.154.10.231 | |
} |
这样就可以添加 alias
# ssh 无密码登录
先创建密钥
ssh-keygen -t ed25519 -C "your_email@example.com" |
在 windows 里面,可以在 powershell 里这样打开
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub |
然后登录服务器,在家目录的 .ssh
目录里,创建文件 authorized_keys
把公钥粘贴进去保存,然后就可以无密码登录了
# APT
# ppa release 问题
error text
E: The repository 'https://ppa.launchpadcontent.net/alexlarsson/flatpak/ubuntu jammy Release' does not have a Release file. |
solution:
sudo add-apt-repository --remove ppa:alexlarsson/flatpak |
# Docker
# 安装
sudo apt-get install docker.io docker-compose |
# build
在一个有 Dockerfile
的目录下
docker build -t my-ctf-bot . |
# run
docker run -p 8802:8802 my-ctf-bot |
# 检查容器状态
docker-compose ps |
还有
docker ps -a |
# 运行容器
在 docker-compose.yml
文件目录下
docker-compose up (-d) # -d 运行在后台 |
# 检查网络
docker network ls |
删除自定义网络
docker network prune |
# Errors
Error: short-name "vaultwarden/server:latest" did not resolve to an alias and no unqualified-search registries are defined in "/etc/containers/registries.conf" |
解决:
sudo vim /etc/containers/registries.conf |
然后修改 (21 行)
unqualified-search-registries = ["docker.io"] |
# VPN 网络配置
# 安装 wireguard
sudo apt install wireguard |
# 生成服务器密钥
cd ~/.ssh | |
wg genkey | tee server_private.key | wg pubkey > server_public.key |
# 编辑 wireguard 服务器配置
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 转发
echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf | |
sudo sysctl -p |
启动 wireguard
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
[Peer] | |
PublicKey = <客户端公钥> | |
AllowedIPs = 10.0.0.2/32 |
然后在客户端配置, windows 可以下载 wireguard 客户端
[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
# 检查
首先可以连接上服务器
ping 10.0.0.1 |
然后检查可以 dns 服务
ping 8.8.8.8 |
然后看一下返回 ip
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 里可以用
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 有任务控制的特性,可以创建多个进程和管道
ls -l | sort -k5n | less |
这些进程会被放到一个进程组里。里面每个进程有相同的进程组标识符(其中某个进程组长的 PID)。
# 会话
一组进程组。会话里面所有进程有相同的会话标识符。(会话首进程的 PID)
打开控制终端会让会话首进程变成终端的控制进程。断开终端的时候,控制进程收到 SIGHUP 信号
会话有一个前台任务,可以从终端中输入数据。
- CTRL+C 是中断信号
- CTRL+Z 是挂起信号
&
结尾,可以创建后台任务。
# 伪终端
telnet 或者 ssh
# /proc
文件系统
虚拟文件系统,提供指向内核的数据结构接口。比如 /proc/1
是进程 1 的运行状态。
# 系统编程概念
# 系统调用
- 将处理器从用户态切换到内核态,以便 CPU 访问受保护的内核内存
- 每个系统调用有唯一的标识
例子:
- 程序调用 C 函数 Wrapper,把 syscall 的编号复制到 eax 里
- 执行
int0x80
切换到内核态,执行中断矢量的代码,(或者 sysenter)- 响应中断,调用
system_call()
, inarch/i386.entry.S
- 在 stack 保存寄存器
- 查找
sys_call_table
, 执行服务例程 ((arch/x86/kernel/proccess_32.c)
),返回给system_call()
- 返回到外壳函数
# 标准 C 语言库函数 glibc
GNU C 是 Linux 上最常用的实现
# 确定系统的 glibc 版本
/lib/libc.so.6 |
(在自己 linux 上没有成功):有些 linux 路径不一样,查找方式是,对与 glibc 动态连接的可执行文件用 ldd 程序查找
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>
有对错误编号的定义。
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 的错误信息
fd = open(pathname, flags, mode); | |
if (fd == -1) { | |
perror("open") | |
exit(EXIT_FAILURE); | |
} |
# 命令行选项
可以用 getopt()
解析命令行选项。
# 系统数据类型
在 <sys/types.h>
里面
# 文件 IO
标准文件描述符
0
stdin1
stdout2
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 , 每个字符串又有一个值。新进程会继承父进村的环境变量值。
SHELL=/bin/shell | |
export SHELL # Put in process's environment list |
显示当前环境变量
printenv |
C 语言可以用 char **environ
访问环境列表
可以用 putenv, setenv
来修改环境变量
# 长跳转
setjump
和 longjump
可以跳转出函数
# 内存分配
# 堆上分配内存
改变堆的大小: brk
和 sbrk()
系统调用
分配内存: malloc, free
# malloc
扫描之前由 free 释放的列表,找到尺寸要求的空闲块,如果没有的话,调用 brk 申请更多内存
# 能力
超级用户的权限被分成不同的单元,这个单元就是能力。
# 进程和文件的能力
每个进程有 3 个能力集:许可的,有效的,可继承的。
每个文件也有 3 个一样名字的。
- 许可的:进程可以使用的能力
- 有效的:用于权限检查的能力
- 可继承的:执行程序的时候带入这些权限。