Generated Raw By Deepseek
你说得对!我确实没有直接解释 read() 为什么用 send() 不会阻塞这个问题。让我直接回答:
核心答案:read() 和 gets() 的根本区别
from pwn import *
# 情况1:目标程序使用 read() 读取
io = process('./program_using_read')
io.send(b'admin') # ✅ 立即返回,不阻塞
io.recv()
# 情况2:目标程序使用 gets() 读取
io = process('./program_using_gets')
io.send(b'admin') # ❌ 程序卡住,必须用 sendline()为什么 read() 用 send() 不会阻塞?
1. read() 的行为特性
当 read() 从文件描述符读取时:
// 目标程序代码
char buffer[100];
ssize_t n = read(0, buffer, 100); // 从标准输入读取read() 的返回条件:
- ✅ 有数据就立即返回(哪怕只有1字节)
- ✅ 读到EOF返回0
- ✅ 错误返回-1
- ❌ 不等待换行符
- ❌ 不等待缓冲区满
2. 实际数据流演示
# 使用 send() 发送 "admin"(无换行符)
io.send(b'admin')数据流向:
Python send() → 内核 → 终端设备 → read() 系统调用
当 read() 被调用时:
- 终端设备收到 “admin” 这5个字节
read()发现缓冲区有数据- 立即返回5,将这5字节复制到程序缓冲区
- 程序继续执行
关键点:read() 不会问”这是完整的行吗?“或”够100字节了吗?“,它只是说”这里有5字节,给你,我返回了”。
3. gets() 为什么不同?
char buffer[100];
gets(buffer); // 这不是系统调用,是库函数gets() 的内部实现:
// gets() 的简化实现
char* gets(char* s) {
int c;
while ((c = getchar()) != '\n' && c != EOF) {
*s++ = c;
}
return s;
}gets() 会:
- 调用
getchar()逐个读取字符 - 一直循环,直到遇到
\n - 即使有数据,也要继续等待换行符
4. 实验验证
测试程序 A:使用 read()
// test_read.c
#include <unistd.h>
#include <stdio.h>
int main() {
char buffer[100];
printf("Input: ");
fflush(stdout);
ssize_t n = read(0, buffer, 100);
printf("read() returned: %ld bytes\n", n);
printf("Content: %.*s\n", (int)n, buffer);
return 0;
}测试程序 B:使用 gets()
// test_gets.c
#include <stdio.h>
int main() {
char buffer[100];
printf("Input: ");
fflush(stdout);
gets(buffer);
printf("gets() returned: %s\n", buffer);
return 0;
}PwnTools 测试
from pwn import *
import time
# 测试 read() 程序
print("=== Testing read() ===")
p1 = process('./test_read')
p1.recvuntil(b'Input: ') # 等待提示符
p1.send(b'admin') # 无换行符
time.sleep(0.1) # 给程序一点时间
output = p1.recv()
print(output) # 立即看到输出
# 测试 gets() 程序
print("\n=== Testing gets() ===")
p2 = process('./test_gets')
p2.recvuntil(b'Input: ')
p2.send(b'admin') # 无换行符
time.sleep(1) # 等待1秒
try:
output = p2.recv(timeout=0.1)
print(output) # 不会执行到这里
except:
print("Program stuck! No output received.")
p2.sendline(b'') # 发送换行符让程序继续
print(p2.recv()) # 现在能看到输出输出:
=== Testing read() ===
read() returned: 5 bytes
Content: admin
=== Testing gets() ===
Program stuck! No output received.
gets() returned: admin
为什么会有这种差异?
层次对比
应用层程序
↓
┌─────────────────────────────────────────┐
│ gets() │ read() │
│ (库函数) │ (系统调用) │
│ 需要换行符才返回 │ 有数据就返回 │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ C 标准库缓冲 │
│ (stdio 的行缓冲) │
└─────────────────────────────────────────┘
↓
┌─────────────────────────────────────────┐
│ 终端驱动(规范模式) │
│ 行缓冲:需要 \n 才传递 │
└─────────────────────────────────────────┘
关键:
read()是系统调用,直接绕过 C 标准库缓冲gets()通过 C 标准库,有额外的行缓冲层- 两者都可能受终端驱动影响,但
read()更”原始”
特殊情况:read() 也会阻塞?
虽然 read() 通常有数据就返回,但以下情况会阻塞:
1. 完全没有数据时
# 程序先调用 read(),然后才 send()
p.sendline(b'start') # 触发程序执行 read()
# 程序执行到 read() 时,如果还没收到数据,会阻塞
p.send(b'data') # 数据到达,read() 立即返回2. 读取普通文件(非终端)
int fd = open("file.txt", O_RDONLY);
read(fd, buffer, 100); // 会读到 EOF 或满100字节3. 设置了特殊标志
// 要求读满指定字节
recv(sock, buffer, 100, MSG_WAITALL); // 会阻塞直到满100字节最终答案
read() 使用 send() 不会阻塞的原因:
read()是系统调用,直接从内核缓冲区读取,不经过 C 标准库的行缓冲read()的语义是”最多读取 N 字节”,而非”必须读取 N 字节”- 只要有数据到达,
read()就立即返回,不等待换行符或缓冲区满 - 发送端
send()发送的数据(即使没有换行符)会立即被内核接收,read()发现数据存在就返回
对比 gets():
gets()是库函数,内部使用getchar()循环gets()的逻辑是”一直读直到遇到\n”- 即使有数据但没有换行符,
gets()也会继续等待
所以,当目标程序使用 read() 时,send() 就够了;当使用 gets() 时,必须用 sendline() 或手动添加 \n。