本文记录了 pwnable.kr 中 fd 题目的解题过程。这道题核心考察 Linux 系统中的文件描述符(File Descriptor)机制 。通过代码审计发现,程序利用 read(fd, buf, 32) 获取输入与目标字符串比对。为了能够从终端手动输入 LETMEWIN 字符串,我们需要让 fd 的值等于 0(即标准输入 stdin)。由于程序中 fd = atoi(argv[1]) - 0x1234,解题的关键就是将十六进制 0x1234 转换为十进制的 4660 作为参数传入,从而成功控制 read 函数读取标准输入,顺利拿下 flag! 本文记录了 pwnable.kr 中 fd 题目的解题过程。这道题核心考察 Linux 系统中的文件描述符(File Descriptor)机制 。通过代码审计发现,程序利用 read(fd, buf, 32) 获取输入与目标字符串比对。为了能够从终端手动输入 LETMEWIN 字符串,我们需要让 fd 的值等于 0(即标准输入 stdin)。由于程序中 fd = atoi(argv[1]) - 0x1234,解题的关键就是将十六进制 0x1234 转换为十进制的 4660 作为参数传入,从而成功控制 read 函数读取标准输入,顺利拿下 flag!
FD

fd是编译好的可执行二进制文件。
fd.c 是源文件查看:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
int atoi(const char *str)可以查看此文章
就是将输入的值取整。该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
0x1234 是十六进制,因为有0x或0X。
read() 这行代码调用的是 Linux/Unix 系统提供的 系统调用 read,原型如下:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
从文件描述符 fd 所指向的“文件”(可以是普通文件、管道、socket、终端等)中尝试读取最多 count 个字节,放入用户态缓冲区 buf 中。
0=stdin, 1=stdout, 2=stderr, 3+=open()返回
不同 fd 时的具体行为(重点):
| fd 值 | 对应的资源 | read() 行为举例 |
|---|---|---|
| 0 | 标准输入 (stdin) | 通常连接到终端键盘 → 没输入时阻塞等待 → 用户输入后回车 → 返回实际字节数(含 \n) → 用户按 Ctrl+D → 返回 0(EOF) |
| 1 | 标准输出 (stdout) | 几乎永远不能读,返回 -1,errno=EBADF(坏的文件描述符) |
| 2 | 标准错误 (stderr) | 同上,不能读 |
| 3+ | open() 返回的 fd | 取决于打开的是什么文件/管道/socket |
所以解题思路:
当!strcmp("LETMEWIN\n", buf)为0时,也就是buf=LETMEWIN\n时,才能得到flag
len = read(fd, buf, 32);fd要等于0才能从终端输入字符。
所以fd = atoi( argv[1] ) - 0x1234;所以atoi( argv[1] ) = 0x1234
./fd 4660
LETMEWINFD

fd是编译好的可执行二进制文件。
fd.c 是源文件查看:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
if(argc<2){
printf("pass argv[1] a number\n");
return 0;
}
int fd = atoi( argv[1] ) - 0x1234;
int len = 0;
len = read(fd, buf, 32);
if(!strcmp("LETMEWIN\n", buf)){
printf("good job :)\n");
setregid(getegid(), getegid());
system("/bin/cat flag");
exit(0);
}
printf("learn about Linux file IO\n");
return 0;
}
int atoi(const char *str)可以查看此文章
就是将输入的值取整。该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
0x1234 是十六进制,因为有0x或0X。
read() 这行代码调用的是 Linux/Unix 系统提供的 系统调用 read,原型如下:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
从文件描述符 fd 所指向的“文件”(可以是普通文件、管道、socket、终端等)中尝试读取最多 count 个字节,放入用户态缓冲区 buf 中。
0=stdin, 1=stdout, 2=stderr, 3+=open()返回
不同 fd 时的具体行为(重点):
| fd 值 | 对应的资源 | read() 行为举例 |
|---|---|---|
| 0 | 标准输入 (stdin) | 通常连接到终端键盘 → 没输入时阻塞等待 → 用户输入后回车 → 返回实际字节数(含 \n) → 用户按 Ctrl+D → 返回 0(EOF) |
| 1 | 标准输出 (stdout) | 几乎永远不能读,返回 -1,errno=EBADF(坏的文件描述符) |
| 2 | 标准错误 (stderr) | 同上,不能读 |
| 3+ | open() 返回的 fd | 取决于打开的是什么文件/管道/socket |
所以解题思路:
当!strcmp("LETMEWIN\n", buf)为0时,也就是buf=LETMEWIN\n时,才能得到flag
len = read(fd, buf, 32);fd要等于0才能从终端输入字符。
所以fd = atoi( argv[1] ) - 0x1234;所以atoi( argv[1] ) = 0x1234
./fd 4660
LETMEWIN