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() 被调用时:

  1. 终端设备收到 “admin” 这5个字节
  2. read() 发现缓冲区有数据
  3. 立即返回5,将这5字节复制到程序缓冲区
  4. 程序继续执行

关键点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() 会:

  1. 调用 getchar() 逐个读取字符
  2. 一直循环,直到遇到 \n
  3. 即使有数据,也要继续等待换行符

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() 不会阻塞的原因

  1. read() 是系统调用,直接从内核缓冲区读取,不经过 C 标准库的行缓冲
  2. read() 的语义是”最多读取 N 字节”,而非”必须读取 N 字节”
  3. 只要有数据到达,read() 就立即返回,不等待换行符或缓冲区满
  4. 发送端 send() 发送的数据(即使没有换行符)会立即被内核接收,read() 发现数据存在就返回

对比 gets()

  • gets() 是库函数,内部使用 getchar() 循环
  • gets() 的逻辑是”一直读直到遇到 \n
  • 即使有数据但没有换行符,gets() 也会继续等待

所以,当目标程序使用 read() 时,send() 就够了;当使用 gets() 时,必须用 sendline() 或手动添加 \n