ReadFile

CreateFile,现在说的 ReadFile 就是它的“搭档”——用来读取文件或设备的数据的 Win32 API。
它非常常见,也非常实用。下面我用通俗、生活化的方式讲清楚它。


🧭 一、函数原型(简化版)

c
BOOL ReadFile(
  HANDLE hFile,                // 文件句柄
  LPVOID lpBuffer,             // 数据读到哪里去
  DWORD  nNumberOfBytesToRead, // 想读多少字节
  LPDWORD lpNumberOfBytesRead, // 实际读了多少字节
  LPOVERLAPPED lpOverlapped    // 异步用的,一般填 NULL
);

返回值:

  • 成功:返回 TRUE
  • 失败:返回 FALSE(可用 GetLastError() 查看原因)

🧩 二、参数通俗解释

参数 含义 通俗比喻
hFile 文件句柄 你手上拿到的“书”或“文件”对象,之前 CreateFile() 打开的那个
lpBuffer 数据缓冲区 你准备好的“碗”,用来装读出来的内容
nNumberOfBytesToRead 计划读取的字节数 想读多少?(比如读 100 个字节)
lpNumberOfBytesRead 实际读取的字节数 实际读到了多少?(可能比预期少)
lpOverlapped 异步参数 一般不用,普通读文件时填 NULL

🧠 三、简单例子(读文件)

✅ 例1:读取一个文本文件

假设磁盘上有个文件:C:\test\data.txt,内容是:

Hello World!

我们写个程序去读它👇

c
#include <windows.h>
#include <stdio.h>

int main() {
    HANDLE hFile = CreateFile(
        "C:\\test\\data.txt",      // 文件路径
        GENERIC_READ,              // 只读
        FILE_SHARE_READ,           // 可共享读
        NULL,                      // 默认安全
        OPEN_EXISTING,             // 文件必须存在
        FILE_ATTRIBUTE_NORMAL,     // 普通文件
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        printf("打开文件失败: %d\n", GetLastError());
        return 1;
    }

    char buffer[100] = {0};        // 准备一个缓冲区
    DWORD bytesRead = 0;

    BOOL success = ReadFile(
        hFile,                     // 文件句柄
        buffer,                    // 存放读取的数据
        sizeof(buffer) - 1,        // 想读多少字节
        &bytesRead,                // 实际读了多少
        NULL                       // 同步读取
    );

    if (success) {
        printf("读取成功:%d 字节\n", bytesRead);
        printf("内容是:%s\n", buffer);
    } else {
        printf("读取失败: %d\n", GetLastError());
    }

    CloseHandle(hFile);
    return 0;
}

📘 输出结果:

读取成功:12 字节
内容是:Hello World!

🧩 四、类比理解(生活版)

可以把 CreateFileReadFile 想象成这样的生活过程:

动作 类比
CreateFile 打开一本书
ReadFile 从书上读内容到你的笔记本里
WriteFile 把笔记写回书上

你先“打开文件”(拿到句柄),再“读内容”(复制到缓冲区),最后“关闭文件”(放下书)。


🧱 五、重要细节

  1. 读取位置

    • ReadFile 每次从当前文件指针位置开始读。
    • 读完后,文件指针会自动往后移动。
    • 可以用 SetFilePointer() 改变读的位置。
  2. 实际读取字节数可能少于期望值

    • 比如文件只剩 20 个字节,你要读 100 个,结果只读到 20 个。
  3. 异步读取(高级用法)

    • 如果用 FILE_FLAG_OVERLAPPED 打开文件,可以异步读取(不阻塞主线程)。
    • 但普通文件操作中,我们通常用同步方式(lpOverlapped = NULL)。

📘 六、再举几个实际用途例子

✅ 读取二进制文件(比如图片)

c
BYTE buffer[1024];
DWORD bytesRead;
ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
// buffer 里现在就是文件的原始字节内容

✅ 读取设备或管道

ReadFile 不仅能读文件,还能从:

  • 串口 (COM1)
  • 命名管道 (\\.\pipe\mypipe)
  • 磁盘设备 (\\.\PhysicalDrive0) 这些“文件型设备”里读取数据。

🧾 七、总结(口语记忆法)

参数 一句话记忆
hFile 我要从哪个文件读?
lpBuffer 数据放哪?
nNumberOfBytesToRead 想读多少?
lpNumberOfBytesRead 实际读了多少?
lpOverlapped 要不要异步?(普通情况填 NULL)

是否希望我帮你画一张图,展示“CreateFile → ReadFile → CloseHandle”整个读取流程?(文件 → 内存缓冲区 → 输出)
图会让你更容易记住数据是怎么流动的。

lpBuffer 举例说明
Robot Avatar

非常好 👍,你问的这个 lpBufferReadFile 函数中最关键的参数之一。
它决定了读取到的数据要放到哪里
下面我来通俗讲解,并配上例子让你立刻明白。


🧭 一、lpBuffer 是什么?

lpBuffer 全称是 “Long Pointer to Buffer”,意思是:

👉 指向一个**内存区域(缓冲区)**的指针,ReadFile() 读到的数据就会放到这里。

简单说:

  • 文件 → 数据读出来 → 放到你准备的 lpBuffer 这块内存里。

就像你要打水,lpBuffer 就是你拿的“桶”,ReadFile 是“打水的人”,文件是“水源”。


🧩 二、lpBuffer 的常见定义方式

一般是一个 数组内存指针。常见写法如下👇

c
char buffer[100];      // 定义一个 100 字节的缓冲区
ReadFile(hFile, buffer, 100, &bytesRead, NULL);

或者你也可以用动态内存分配:

c
char* buffer = (char*)malloc(100);
ReadFile(hFile, buffer, 100, &bytesRead, NULL);

🧠 三、举几个通俗例子

✅ 例1:读取文本文件

c
char buffer[128] = {0};      // 准备一个128字节的缓冲区
DWORD bytesRead;

ReadFile(hFile, buffer, sizeof(buffer) - 1, &bytesRead, NULL);
printf("文件内容是:%s\n", buffer);

📘 输出:

文件内容是:Hello World!

👉 buffer 就是 lpBuffer
ReadFile 把文件中的 “Hello World!” 拷贝进了 buffer 里。


✅ 例2:读取二进制文件(如图片)

c
BYTE buffer[1024];
DWORD bytesRead;

ReadFile(hFile, buffer, sizeof(buffer), &bytesRead, NULL);
printf("前4个字节是:%02X %02X %02X %02X\n", buffer[0], buffer[1], buffer[2], buffer[3]);

📘 输出:

4个字节是:89 50 4E 47

👉 这四个字节其实是 PNG 图片文件头的标志(89 50 4E 47 = “.PNG”)。


✅ 例3:读取串口数据(设备)

c
char recvData[50];
DWORD bytesRead;
ReadFile(hSerial, recvData, sizeof(recvData), &bytesRead, NULL);
printf("收到的数据:%.*s\n", bytesRead, recvData);

👉 这时 lpBuffer(即 recvData)装的不是文件内容,而是串口传来的原始数据。


📘 四、注意事项

注意点 说明
必须分配足够内存 否则可能导致越界或程序崩溃
lpBuffer 必须可写 因为 ReadFile 要往里写数据
不会自动添加字符串结束符 读文本时记得自己加上 '\0',否则打印会乱码
二进制数据不要用 %s 打印 因为里面可能没有 \0,会乱输出

🧩 五、图示理解

[文件内容] → "Hello World!"

ReadFile(
    hFile,
    buffer,        ← lpBuffer (存放数据的“碗”)
    100,
    &bytesRead,
    NULL
);

执行后:

buffer = "Hello World!"
bytesRead = 12

🧾 六、总结一句话记忆

lpBuffer = 我准备好的“内存碗”,
ReadFile = “把文件内容舀进碗里”的动作。


是否希望我帮你画一个示意图,展示 ReadFile 从文件到 lpBuffer 的数据流?
(那张图会像是“文件 → 内核 → 内存 → 变量”的过程,非常直观。)

在x64dbg中 lpBuffer 记录什么。举例说明
Robot Avatar

在 x64dbg 中调试 ReadFile 时,lpBuffer一个指针,它指向的内存区域会存放从文件或设备读取出来的数据
简单又形象的说法:

lpBuffer 是“碗”,真正的数据装在它指向的那块“内存碗”里。
x64dbg 显示的是这个指针和它指向的内容。


✅ x64dbg 中如何查看 lpBuffer 内容?

假设代码中有:

c
char buffer[100] = {0};
ReadFile(hFile, buffer, 100, &bytesRead, NULL);

当程序运行到 ReadFile 调用之后

  1. 查看寄存器里传参
    在 32 位下调用约定为:

    • 第 2 个参数 lpBuffer 通常在 ESP+8
    • 或者在 x64 中第 2 个参数在寄存器 RDX
  2. 跳转到这个指针指向的内存
    在反汇编界面右键 → Follow in Dump → Memory Address
    就能看到内存中的真实数据内容


🧩 举例:读取文件 “Hello World!”

文件内容:

Hello World!

ReadFile 执行完后,在 x64dbg Dump 窗口会看到类似:

地址 Hex 内容 ASCII 显示
0x00xxxxxx 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 00 … Hello World!

说明:

  • 48 = ‘H’
  • 65 = ‘e’
  • 6C 6C = ‘ll’
  • 看到右边 ASCII 中直观显示文件内容

👉 这块内存就是 lpBuffer 指向的地方
👉 x64dbg 通过 Dump 界面展示它的内容


🧠 再举例:读二进制文件(如 PNG)

读某张图片前 8 字节
x64dbg 中 Dump:

Hex ASCII
89 50 4E 47 0D 0A 1A 0A .PNG….

此时不用 %s 打印,因为没有字符串结束符
x64dbg 可以正确显示二进制原始内容


✨ 重点总结

说明
lpBuffer 是什么 一个内存地址,用于存放读出的数据
在 x64dbg 查看什么 它指向的那段内存的 Hex 和 ASCII 内容
什么时候有数据 ReadFile 调用完成之后

一句话背下来:

lpBuffer 在 x64dbg 里显示的不是变量本身,而是它背后的“内存碗”里的真实数据内容。


如果你愿意,我可以带你操作一个完整示例:
断在 ReadFile 前后 → 查看 lpBuffer 内容变化 → 识别 ASCII/二进制信息。
只要你说一声“来个实操”。 😄

lpBuffer 存放的指针 是开机的地方 还是结束的地方
Robot Avatar

非常好的问题 👍,这个细节很多人一开始都会弄混。


🧭 简答一句话:

lpBuffer 存放的是内存起始地址(开头的地方),不是结束的地方。


📘 详细解释:

lpBuffer 是一个指针,它指向一块内存区域(缓冲区)的开始位置
ReadFile 把文件数据读进来时,是从这个地址开始写入

也就是说:

  • lpBuffer → 缓冲区的第一个字节的地址(起点)
  • lpBuffer + n → 后续第 n 个字节的位置(偏移量)
  • lpBuffer + bytesRead - 1 → 最后一个有效字节的位置(结束点)

🧩 举个形象例子:

假设:

c
char buffer[10];
ReadFile(hFile, buffer, 10, &bytesRead, NULL);

假设 buffer 的地址是 0x1000,文件内容是 "ABCDEF"(6 字节)。

执行完后内存中是这样的👇:

地址 内容 说明
0x1000 41 (‘A’) lpBuffer 起始位置
0x1001 42 (‘B’) 第二个字节
0x1002 43 (‘C’) 第三个字节
0x1003 44 (‘D’) 第四个字节
0x1004 45 (‘E’) 第五个字节
0x1005 46 (‘F’) 第六个字节
0x1006 ?? 未使用

所以:

  • lpBuffer = 0x1000(指向开头)
  • 结束位置 = 0x1000 + bytesRead - 1 = 0x1005

🧠 在 x64dbg 中观察:

  1. 在寄存器窗口中找到 lpBuffer(如 RDX 或栈参数)
    假设值是 0x1000
  2. 右键 → “Follow in Dump → Memory address”
  3. 在 Dump 窗口中你看到的第一行就是从 lpBuffer(开头)开始的内容

所以在 x64dbg 里看到的内存数据第一字节,就是从 lpBuffer 开始的!


✅ 总结:

问题 答案
lpBuffer 是开头还是结尾? ✅ 开头地址
数据存放顺序 从 lpBuffer 开始,往后依次写入
如何找结束地址? lpBuffer + bytesRead - 1
x64dbg Dump 里第一行是什么? 从 lpBuffer 开始的内容

一句话记忆:

lpBuffer 指向“碗的口”,不是“碗的底”。
数据是从这个口往后装进去的。

此条目发表在安全分析分类目录。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注