除了存放在物理内存中的文件外Linux还提供了一系列虚拟文件系统来访问系统资源 其中/proc可以访问一个进程的所有信息 可以用来读取一些敏感程序信息 使用/proc/<pid>/来访问pid所指进程的资源 详细信息参考Linux内核文档
各个子文件
| File | Content |
|---|---|
| clear_refs | Clears page referenced bits shown in smaps output |
| cmdline | Command line arguments |
| cpu | Current and last cpu in which it was executed (2.4)(smp) |
| cwd | Link to the current working directory |
| environ | Values of environment variables |
| exe | Link to the executable of this process |
| fd | Directory, which contains all file descriptors |
| maps | Memory maps to executables and library files (2.4) |
| mem | Memory held by this process |
| root | Link to the root directory of this process |
| stat | Process status |
| statm | Process memory status information |
| status | Process status in human readable form |
| wchan | Present with CONFIG_KALLSYMS=y: it shows the kernel function symbol the task is blocked in - or “0” if not blocked. |
| pagemap | Page table |
| stack | Report full stack trace, enable via CONFIG_STACKTRACE |
| smaps | An extension based on maps, showing the memory consumption of each mapping and flags associated with it |
| smaps_rollup | Accumulated smaps stats for all mappings of the process. This can be derived from smaps, but is faster and more convenient |
| numa_maps | An extension based on maps, showing the memory locality and binding policy as well as mem usage (in pages) of each mapping. |
/proc/self/cmdline
作用:存储进程的命令行参数 以
\0作为分隔符(不像ps aux那样以空格分隔)查看方式:
1
cat /proc/self/cmdline
如果当前进程是
1
./myprogram arg1 arg2
那么内容类似:
1
./myprogramarg1arg2
/proc/self/environ
作用:存储进程的环境变量 每个变量以
\0作为分隔符查看方式:
1
cat /proc/self/environ | tr '\0' '\n'
示例输出:
1
2
3PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin
SHELL=/bin/bash
USER=root
/proc/self/exe
作用:指向进程当前执行的可执行文件
查看方式:
1
ls -l /proc/self/exe
示例输出:
1
lrwxrwxrwx 1 root root 0 Mar 3 10:00 /proc/self/exe -> /usr/bin/ls
获取可执行路径:
1
readlink /proc/self/exe
输出示例:
1
/usr/bin/bash
/proc/self/fd/
作用:存储当前进程打开的所有文件描述符(类似
ls -l /proc/self/fd/)查看打开的文件:
1
ls -l /proc/self/fd/
示例输出:
1
2
3
4
5total 0
lrwx------ 1 user user 64 Mar 3 10:00 0 -> /dev/pts/1
lrwx------ 1 user user 64 Mar 3 10:00 1 -> /dev/pts/1
lrwx------ 1 user user 64 Mar 3 10:00 2 -> /dev/pts/1
lr-x------ 1 user user 64 Mar 3 10:00 3 -> /var/log/syslog0= 标准输入(stdin)1= 标准输出(stdout)2= 标准错误(stderr)3及以上 = 进程打开的其他文件
/proc/self/maps
作用:显示当前进程的内存映射 包括代码段、堆、栈、动态库等
查看方式:
1
cat /proc/self/maps
示例输出:
1
2
3
4
5
6555555554000-555555556000 r-xp 00000000 08:01 123456 /usr/bin/myprogram
555555756000-555555757000 rw-p 00002000 08:01 123456 /usr/bin/myprogram
7ffff7dcf000-7ffff7df1000 r-xp 00000000 08:01 654321 /lib/x86_64-linux-gnu/libc.so.6
7ffff7ff8000-7ffff7ffa000 r--p 00000000 00:00 0 [vvar]
7ffff7ffa000-7ffff7ffc000 r-xp 00000000 00:00 0 [vdso]
7fffffffde000-7ffffffff000 rw-p 00000000 00:00 0 [stack]主要映射区域:
- 可执行文件代码段(
r-xp) - 可执行文件数据段(
rw-p) - 共享库(如 libc.so.6)
- 堆(heap)
- 栈(stack)
vdso、vvar等特殊段
- 可执行文件代码段(
/proc/self/mem
作用:映射当前进程的整个内存(可用
lseek + read读取)示例:读取特定地址:
1
2
3int fd = open("/proc/self/mem", O_RDONLY);
lseek(fd, (off_t)0x555555554000, SEEK_SET);
read(fd, buffer, sizeof(buffer));- 只能读取已映射区域 否则
read()失败
- 只能读取已映射区域 否则
/proc/self/stat
作用:进程的详细状态信息(数值型)
查看方式:
1
cat /proc/self/stat
示例输出:
1
1234 (myprogram) R 567 567 567 0 -1 4194560 250 0 0 0 0 0 0 0 20 0 1 0 12345678 12345678 1000 0 0 0 0 18446744073709551615 4194304 1234 0 0 0 0 0 0 17 0 0 0 0 0 0
第 1 项:PID
第 2 项:进程名
第 3 项:进程状态(
R= 运行S= 睡眠)其他信息如父进程 PID、优先级、进程时间等
/proc/self/status
作用:与
stat类似 但更可读查看方式:
1
cat /proc/self/status
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Name: cat
Umask: 0022
State: R (running)
Tgid: 107428
Ngid: 0
Pid: 107428
PPid: 64836
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 64
Groups: 4 20 24 25 27 29 30 44 46 100 101 113 116 129 136 1000
NStgid: 107428
NSpid: 107428
NSpgid: 107428
NSsid: 64836
...包含进程名称、状态、PID、内存占用、线程数等信息
应用举例
[攻防世界] house of grey
主函数:
1 | void __fastcall __noreturn main(int a1, char **a2, char **a3) |
初始化函数中设置了几个沙箱规则 seccomp-tools反汇编看一下

禁掉了execve 意味着主线程不可能get shell
回到主函数 mmap了一块 0x10000000 bytes 的空间 然后从随机数发生器中读取一个0x10对齐的随机数作为将fn()映射到mmap出来的空间后使用的栈地址 然后启用子线程执行fn()
主线程执行的函数基本可以排除泄露flag的可能 只有4字节的输入并且通过exit()退出 接下来看看fn:
1 | int menu() |
子线程也加载了沙箱规则 调试时dump出来反汇编一下:

只能使用fn要用到的功能 所以子线程也是不可能get shell的 只可能对flag orw回显内容
回到fn函数 一个明显的漏洞点就是buf长度是24字节 但是可以读0x28字节覆盖掉v8 而功能4可以向v8储存的地址写入0x200字节的数据 也就是可以简单地实现任意地址写
问题是fn函数同样用exit()退出 所以不可能通过修改fn的返回地址来读出flag 而fn中调用的具有输入功能的函数有menu() 和 read() menu中无法利用AAW 而功能4中read函数可以修改自己的返回地址调用ROP链伪造fn的栈帧将[rbp+buf]修改为’flag’ 然后直接返回到open(buf, 0)来绕过检查并利用功能3打印出flag
现在唯一的问题是fn的栈帧地址是随机的 我们只能通过上文提到的特殊文件系统/proc/self/maps来泄露各个段的基址 然后再通过/proc/self/mem读取100000字节的数据 检查其中是否有fn栈帧中的数据来定位fn栈帧的地址
由于fn中的功能最多只能使用30次 而100000 * 30字节的数据在mmap出的0x10000000字节下也只占了很小的一段 所以还不一定能在一次远程连接中找到栈帧并进行后续操作 而且前置工作和后续打ROP都需要占用几次功能的使用
本地调试几次取个平均值加到泄露出的段基址上 然后通过功能2 fseek()定位到此处 通过功能3读取数据并检查 当检测到特定数据存在就计算这个数据所在的地址 再计算read函数的返回地址存放的位置即可做到在一次read后伪造栈帧并写入ROP
经过调试 栈帧结构如下:
1 | LOW |
要检查的特定数据就设定为b'/proc'
完整exp:
1 | from pwn import * |
其中本地拿到flag不需要在写入ROP和b'flag'时进一步覆盖掉v8 而远程拿到flag需要将v8地址覆盖成一个可读可写的地址