0%
毅种循环

模块 7 - Windows API 简介

恶意软件开发课程 - Windows API 简介


模块 7 - Windows API 简介#

[!IMPORTANT] 本知识库声明

本知识库由本人整理自互联网 MalDev Academy 泄露资源,并由本人手动翻译为中文,过程中增加了大量关键技术提示实践心得

  1. 内容完整性:并未修改任何核心代码与技术逻辑,仅做汉化与注释加强。
  2. 版权归属:原始知识产权归原作者/官方所有。
  3. 支持正版:本仓库仅供内部学习交流,如果您有经济能力,请务必支持正版课程
  4. 权利申诉:如相关内容侵犯了您的权益,请联系我,我将立即核实并删除。

Windows API 简介#

简介#

Windows API 为开发者提供了一种让其应用程序与 Windows 操作系统交互的方式。例如,如果应用程序需要在屏幕上显示内容、修改文件或查询注册表,所有这些操作都可以通过 Windows API 来完成。Windows API 由 Microsoft 提供了非常详细的文档,可以在这里查看。

💡 初学者提示:什么是 API?

API (Application Programming Interface - 应用程序编程接口) 就像餐厅的菜单:

  • 你不需要知道厨房如何做菜(内部实现)
  • 你只需要点菜(调用 API)
  • 厨房给你做好的菜(返回结果)

Windows API 的作用:

  • 你的程序想创建文件 → 调用 CreateFile() API
  • 你的程序想分配内存 → 调用 VirtualAlloc() API
  • 你的程序想创建进程 → 调用 CreateProcess() API

为什么需要 API?

  1. 安全性:不让程序直接操作硬件,都通过系统控制
  2. 标准化:所有程序用相同的方式做相同的事
  3. 简化:微软帮你写好复杂的底层代码,你直接调用

Windows 数据类型#

Windows API 有许多超出常见数据类型(例如 int、float)的数据类型。这些数据类型都有详细的文档,可以在这里查看。

以下是一些常见的数据类型:

  • DWORD - 一个 32 位无符号整数,无论是在 32 位系统还是 64 位系统上,都表示从 0 到 (2^32 - 1) 的值。
DWORD dwVariable = 42;
c
  • size_t - 用于表示对象的大小。在 32 位系统上,它是一个 32 位无符号整数,表示从 0 到 (2^32 - 1) 的值;在 64 位系统上,它是一个 64 位无符号整数,表示从 0 到 (2^64 - 1) 的值。
SIZE_T sVariable = sizeof(int);
c
  • VOID - 表示没有特定的数据类型。
void* pVariable = NULL; // 这与 PVOID 相同
c
  • PVOID - 在 32 位系统上是一个 32 位或 4 字节的指针,指向任何数据类型;在 64 位系统上是一个 64 位或 8 字节的指针,指向任何数据类型。
PVOID pVariable = &SomeData;
c
  • HANDLE - 一个值,指定操作系统管理的特定对象(例如:文件、进程、线程)。
HANDLE hFile = CreateFile(...);
c

💡 初学者提示:什么是 HANDLE(句柄)?

HANDLE 就像银行存单号

你去银行存钱:
1. 存入1000元 → Windows创建一个对象(如文件、进程)
2. 银行给你存单号:12345 → 返回一个HANDLE
3. 你用存单号取钱 → 用HANDLE操作对象
4. 销户 → CloseHandle()
plaintext

为什么不直接给你对象地址?

  • 安全性:你不能直接访问别人的”存款”(对象)
  • 灵活性:系统可以移动对象,但存单号不变
  • 权限控制:同一个文件,不同HANDLE可以有不同权限

常见的HANDLE类型:

API函数返回的HANDLE关闭函数
CreateFile()文件句柄CloseHandle()
OpenProcess()进程句柄CloseHandle()
CreateThread()线程句柄CloseHandle()
LoadLibrary()模块句柄(HMODULE)FreeLibrary()

重要规则:

// ✅ 正确:使用完后关闭句柄
HANDLE hFile = CreateFile(...);
// ... 使用文件
CloseHandle(hFile);

// ❌ 错误:忘记关闭 → 资源泄漏!
HANDLE hFile = CreateFile(...);
// ... 使用文件
// 忘记CloseHandle()
c
  • HMODULE - 一个模块的句柄。它是模块在内存中的基地址。模块的示例可以是 DLL 或 EXE 文件。
HMODULE hModule = GetModuleHandle(...);
c

📚 知识扩展:HMODULE vs HANDLE

HMODULE 是特殊的 HANDLE

  • HMODULE = Module的HANDLE
  • 实际上就是DLL/EXE在内存中的基地址
HMODULE hKernel32 = GetModuleHandle(L"kernel32.dll");
// hKernel32 现在包含 kernel32.dll 在内存中的地址
// 例如:0x7FF9A0000000

// 可以用这个地址做什么?
// 1. 查找函数地址
FARPROC pCreateFile = GetProcAddress(hKernel32, "CreateFileW");

// 2. 读取PE头信息
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hKernel32;
c
  • LPCSTR/PCSTR - 一个指向常量以 null 结尾的 8 位 Windows 字符串(ANSI)的指针。字母 “L” 来自于 16 位 Windows 编程时期的”long”,虽然今天它不再影响数据类型,但命名约定仍然存在;字母 “C” 表示常量 (const) 或只读变量。两者等同于 const char*
LPCSTR  lpcString   = "Hello, world!";
PCSTR   pcString    = "Hello, world!";
c
  • LPSTR/PSTR - 与 LPCSTRPCSTR 相同,唯一的不同是 LPSTRPSTR 不指向常量变量,而是指向可读写的字符串。两者等同于 char*
LPSTR   lpString    = "Hello, world!";
PSTR    pString     = "Hello, world!";
c
  • LPCWSTR/PCWSTR - 一个指向常量以 null 结尾的 16 位 Windows Unicode 字符串(Unicode)的指针。两者等同于 const wchar_t*
LPCWSTR lpwcString  = L"Hello, world!";
PCWSTR  pcwString   = L"Hello, world!";
c
  • PWSTR/LPWSTR - 与 LPCWSTRPCWSTR 相同,唯一的不同是 ‘PWSTR’ 和 ‘LPWSTR’ 不指向常量变量,而是指向可读写的字符串。两者等同于 wchar_t*
LPWSTR  lpwString   = L"Hello, world!";
PWSTR   pwString    = L"Hello, world!";
c
  • wchar_t - 与 wchar 相同,用于表示宽字符 (Wide Character)。
wchar_t  wChar      = L'A';
wchar_t* wcString   = L"Hello, world!";
c

💡 初学者提示:Windows数据类型命名规则

前缀含义:

前缀含义示例
PPointer(指针)PDWORD = DWORD*
LPLong Pointer(长指针,历史遗留)LPSTR = char*
CConst(常量)LPCSTR = const char*
WWide(宽字符,Unicode)LPWSTR = wchar_t*
值类型DWORD

解码示例:

LPCWSTR 的含义:
LP  → Long Pointer  → 指针
C   → Const         → 常量
W   → Wide          → 宽字符(Unicode)
STR → String        → 字符串

完整含义:指向常量Unicode字符串的指针
C语言等价:const wchar_t*
plaintext

快速对照表:

Windows类型C语言等价说明
LPSTRchar*ANSI字符串指针
LPCSTRconst char*常量ANSI字符串指针
LPWSTRwchar_t*Unicode字符串指针
LPCWSTRconst wchar_t*常量Unicode字符串指针
PDWORDDWORD*DWORD指针
PVOIDvoid*通用指针
  • ULONG_PTR - 表示一个无符号整数,其大小与特定架构的指针大小相同。这意味着在 32 位系统上,ULONG_PTR 是 32 位大小,而在 64 位系统上是 64 位大小。在本课程中,ULONG_PTR 将用于操作包含指针的算术表达式(例如:PVOID)。在执行任何算术操作之前,指针将会进行类型转换为 ULONG_PTR,此方法用于避免直接操作指针,从而避免编译错误。
PVOID Pointer = malloc(100);
// Pointer = Pointer + 10; // 不允许 - 编译错误
Pointer = (ULONG_PTR)Pointer + 10; // 允许 - 正确的指针算术
c

📚 知识扩展:为什么需要 ULONG_PTR?

问题:指针大小在不同架构下不同

32位系统:指针 = 4字节
64位系统:指针 = 8字节
plaintext

ULONG_PTR 会自动适配:

// 32位系统
sizeof(ULONG_PTR) == 4 字节

// 64位系统  
sizeof(ULONG_PTR) == 8 字节
c

实际应用场景:

场景1:指针算术

PVOID baseAddr = VirtualAlloc(...);

// ❌ 错误:不能直接对PVOID做算术
PVOID newAddr = baseAddr + 0x1000;  // 编译错误!

// ✅ 正确:转换为ULONG_PTR
PVOID newAddr = (PVOID)((ULONG_PTR)baseAddr + 0x1000);
c

场景2:存储地址为数值

PVOID someAddr = GetProcAddress(...);

// 存储为整数用于计算
ULONG_PTR addrValue = (ULONG_PTR)someAddr;

// 进行计算
ULONG_PTR offset = addrValue - imageBase;

// 转回指针
PVOID result = (PVOID)offset;
c

场景3:ROP链构建

// 在栈上布置ROP gadgets(高级技术)
ULONG_PTR ropChain[] = {
    (ULONG_PTR)gadget1,  // pop rax; ret
    0x4141414141414141,   // 值
    (ULONG_PTR)gadget2,  // pop rcx; ret
    ...
};
c

数据类型指针#

Windows API 允许开发者声明数据类型或数据类型的指针。这在数据类型名称中有所体现,其中以 “P” 开头的数据类型表示指向实际数据类型的指针,而不以 “P” 开头的数据类型表示实际的数据类型本身。

当使用 Windows API 时,这种区别尤为重要,特别是函数参数是指向某个数据类型的指针。以下例子展示了 “P” 数据类型如何与其非指针版本相对应。

  • PHANDLEHANDLE* 相同。
  • PSIZE_TSIZE_T* 相同。
  • PDWORDDWORD* 相同。

💡 初学者提示:P前缀规则

简单记忆:P = Pointer(指针)

DWORD value = 42;        // DWORD:值类型
PDWORD pValue = &value;  // PDWORD:指向DWORD的指针

// 等价于:
DWORD* pValue = &value;
c

为什么Windows要定义这些?

  • 提高代码可读性
  • 类型安全
  • 统一命名规范

实际使用示例:

// 错误:类型不匹配
DWORD value;
GetExitCodeProcess(hProcess, value);  // ❌ 需要指针!

// 正确:使用指针
DWORD value;
GetExitCodeProcess(hProcess, &value); // ✅ 

// 或者用P类型更清晰:
DWORD value;
PDWORD pValue = &value;
GetExitCodeProcess(hProcess, pValue); // ✅
c

ANSI 与 Unicode 函数#

大多数 Windows API 函数都有两个版本,分别以 “A” 或 “W” 结尾。例如,有 CreateFileACreateFileW。以 “A” 结尾的函数表示 “ANSI”,而以 “W” 结尾的函数表示 Unicode 或 “Wide”(宽字符)。

主要区别在于,ANSI 函数将接受 ANSI 数据类型作为参数,而 Unicode 函数将接受 Unicode 数据类型作为参数。例如,CreateFileA 的第一个参数是 LPCSTR,即一个指向常量以 null 结尾的 8 位 Windows ANSI 字符串的指针。而 CreateFileW 的第一个参数是 LPCWSTR,即一个指向常量以 null 结尾的 16 位 Unicode 字符串的指针。

此外,所需的字节数会因使用的版本不同而有所不同。

char str1[] = "maldev";     // 7 字节(maldev + null字节)
wchar_t str2[] = L"maldev"; // 14 字节,每个字符占 2 字节(null字节也是 2 字节)
c

📚 知识扩展:ANSI vs Unicode 深度对比

历史背景:

  • ANSI:早期Windows使用,每个字符1字节
  • Unicode:现代标准,支持全世界所有语言

详细对比表:

特性ANSI (A函数)Unicode (W函数)
后缀A (CreateFileA)W (CreateFileW)
字符大小1字节2字节(UTF-16)
数据类型LPSTR, LPCSTRLPWSTR, LPCWSTR
字符串前缀L"文本"
支持语言仅英文和部分西欧语言全世界所有语言
Windows内部需要转换原生使用
性能稍慢(需转换)稍快
推荐使用❌ 不推荐✅ 推荐

实际示例:

为什么推荐使用 Unicode (W)?

  1. Windows 内部全部使用 Unicode
  2. ANSI函数会先转换成Unicode再调用W函数(多一层开销)
  3. 支持国际化(中文、日文、阿拉伯文等)
  4. 未来兼容性更好

自动选择版本(高级):

// Visual Studio 中,可以用宏自动选择
#ifdef UNICODE
    #define CreateFile CreateFileW
    #define TCHAR wchar_t
#else
    #define CreateFile CreateFileA
    #define TCHAR char
#endif

// 然后就可以这样写:
HANDLE hFile = CreateFile(TEXT("C:\\test.txt"), ...);
// 根据项目配置自动选择 A 或 W 版本
c

输入与输出参数#

Windows API 有 inout 参数。IN 参数是传入函数并用于输入的参数,而 OUT 参数是用于将值返回给函数调用者的参数。输出参数通常通过指针传递,以引用的方式。

例如,以下代码片段展示了一个 HackTheWorld 函数,该函数接受一个整数指针并将其值设置为 123。由于这个参数正在返回一个值,因此它是一个输出参数。

请记住,使用 OUTIN 关键字旨在帮助开发者理解函数期望的参数以及如何处理这些参数。然而,值得注意的是,省略这些关键字并不会影响参数是输入还是输出参数。

💡 初学者提示:IN 和 OUT 参数

理解输入输出参数:

函数就像一个工厂:
IN参数  → 原材料(输入)
OUT参数 → 成品(输出)
plaintext

三种参数模式:

1. IN - 只输入

void PrintNumber(IN int num) {
    printf("%d\n", num);
    // num 只被读取,不被修改
}
c

2. OUT - 只输出

void GetNumber(OUT int* pNum) {
    *pNum = 42;
    // pNum 只被写入,不被读取
}
c

3. IN OUT - 输入又输出

void DoubleNumber(IN OUT int* pNum) {
    *pNum = *pNum * 2;
    // pNum 先被读取,再被修改
}
c

Windows API 实际示例:

关键规则:

  • IN 参数:传值或传指针(只读)
  • OUT 参数:必须传指针(函数需要写入)
  • IN OUT 参数:传指针(读取后修改)

常见错误:

// ❌ 错误:OUT参数没有初始化
PROCESS_INFORMATION pi;  // 未初始化,包含垃圾数据
CreateProcess(..., &pi);

// ✅ 正确:OUT参数应该初始化为零
PROCESS_INFORMATION pi = {0};
CreateProcess(..., &pi);
c

Windows API 示例#

现在,Windows API 的基本概念已经介绍完毕,接下来我们将通过 CreateFileW 函数来演示如何使用 Windows API。

查找 API 文档#

如果不确定函数的作用或所需参数,始终要参考文档。总是先阅读函数的描述,评估该函数是否完成了预期的任务。CreateFileW 的文档可以在这里查看。

🔧 技术提示:如何阅读MSDN文档

查找API文档的方法:

  1. Google搜索:CreateFileW MSDN
  2. 直接访问:https://learn.microsoft.com/en-us/windows/win32/api/
  3. Visual Studio:光标放在函数上按 F1

阅读文档的顺序:

1. 函数描述(Description)

- 这个函数是干什么的?
- 是否符合我的需求?
plaintext

2. 函数签名(Syntax)

HANDLE CreateFileW(
  [in]           LPCWSTR               lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  ...
);
c

3. 参数说明(Parameters)

  • 每个参数的含义
  • 可以传递哪些值
  • 哪些参数是可选的(optional)

4. 返回值(Return Value)

  • 成功返回什么
  • 失败返回什么
  • 如何判断成功/失败

5. 备注(Remarks)

  • 重要的使用注意事项
  • 常见陷阱

6. 示例代码(Examples)

  • 微软提供的使用示例

分析返回类型与参数#

接下来查看函数的参数以及返回的数据类型。文档中指出如果函数成功,返回值是指定文件、设备、命名管道或邮件槽的打开句柄,因此 CreateFileW 返回的是一个 HANDLE 数据类型,表示指定的已创建项。

此外,注意到函数的所有参数都是 in 参数。这意味着该函数不会从参数返回任何数据,因为它们都是输入参数。请记住,方括号中的关键字,例如 inoutoptional,仅供开发者参考,并不会对函数的行为产生实际影响。

HANDLE CreateFileW(
  [in]           LPCWSTR               lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);
c

📚 知识扩展:CreateFileW 参数详解

完整参数说明表:

参数类型说明常用值
lpFileNameLPCWSTR文件路径L"C:\\test.txt"
dwDesiredAccessDWORD访问权限GENERIC_READ, GENERIC_WRITE, GENERIC_ALL
dwShareModeDWORD共享模式0(独占), FILE_SHARE_READ, FILE_SHARE_WRITE
lpSecurityAttributesLPSECURITY_ATTRIBUTES安全属性NULL(默认)
dwCreationDispositionDWORD创建方式CREATE_NEW, CREATE_ALWAYS, OPEN_EXISTING, OPEN_ALWAYS
dwFlagsAndAttributesDWORD文件属性FILE_ATTRIBUTE_NORMAL, FILE_FLAG_OVERLAPPED
hTemplateFileHANDLE模板文件NULL(通常)

参数详细说明:

1. dwDesiredAccess(访问权限):

GENERIC_READ          // 读取
GENERIC_WRITE         // 写入
GENERIC_READ | GENERIC_WRITE  // 读写
GENERIC_ALL           // 所有权限
c

2. dwCreationDisposition(创建方式):

CREATE_NEW        // 创建新文件,如果存在则失败
CREATE_ALWAYS     // 总是创建,存在则覆盖
OPEN_EXISTING     // 打开已存在的文件,不存在则失败
OPEN_ALWAYS       // 打开文件,不存在则创建
TRUNCATE_EXISTING // 打开并清空文件,不存在则失败
c

实际使用对照:

使用该函数#

以下示例代码展示了如何使用 CreateFileW。它将在当前用户的桌面上创建一个名为 maldev.txt 的文本文件。

💡 初学者提示:完整的文件操作示例

常见错误和解决方案:

错误1:路径问题

// ❌ 错误:单反斜杠
L"C:\Users\test.txt"  // \U 和 \t 会被解释为转义字符

// ✅ 正确:双反斜杠
L"C:\\Users\\test.txt"

// ✅ 也正确:正斜杠(Windows也支持)
L"C:/Users/test.txt"
c

错误2:忘记检查返回值

// ❌ 危险:没检查就使用
HANDLE hFile = CreateFileW(...);
WriteFile(hFile, ...);  // 如果hFile无效会崩溃!

// ✅ 正确:先检查
HANDLE hFile = CreateFileW(...);
if (hFile != INVALID_HANDLE_VALUE) {
    WriteFile(hFile, ...);
    CloseHandle(hFile);
}
c

错误3:忘记关闭句柄

// ❌ 资源泄漏
void BadExample() {
    HANDLE hFile = CreateFileW(...);
    // ... 使用文件
    // 忘记 CloseHandle(hFile)
}  // 句柄泄漏!

// ✅ 正确:总是关闭
void GoodExample() {
    HANDLE hFile = CreateFileW(...);
    if (hFile != INVALID_HANDLE_VALUE) {
        // 使用文件...
        CloseHandle(hFile);  // 清理资源
    }
}
c

Windows API 错误调试#

当函数失败时,通常会返回一个非详细的错误。例如,如果 CreateFileW 失败,它会返回 INVALID_HANDLE_VALUE,表示无法创建文件。要更深入了解文件未能创建的原因,必须使用 GetLastError 函数来获取错误代码。

获取代码后,您可以在 Windows 系统错误代码列表 中查找错误代码。以下是一些常见的错误代码及其翻译:

  • 5 - ERROR_ACCESS_DENIED(拒绝访问)
  • 2 - ERROR_FILE_NOT_FOUND(文件未找到)
  • 87 - ERROR_INVALID_PARAMETER(无效参数)

📚 知识扩展:Windows错误处理完全指南

错误处理的完整流程:

常见错误代码速查表:

错误代码常量名称含义常见原因
2ERROR_FILE_NOT_FOUND文件未找到路径错误、文件不存在
3ERROR_PATH_NOT_FOUND路径未找到目录不存在
5ERROR_ACCESS_DENIED拒绝访问权限不足、文件被保护
32ERROR_SHARING_VIOLATION共享冲突文件被其他程序打开
87ERROR_INVALID_PARAMETER无效参数参数值错误
122ERROR_INSUFFICIENT_BUFFER缓冲区不足提供的缓冲区太小
183ERROR_ALREADY_EXISTS已存在CREATE_NEW时文件已存在

获取错误描述字符串:

实用的错误处理宏:

Windows 原生 API 错误调试#

回顾 Windows 架构 模块,NTAPI 通常从 ntdll.dll 导出。与 Windows API 不同,这些函数不能通过 GetLastError 获取错误代码。相反,它们会直接返回错误代码,并以 NTSTATUS 数据类型表示。

NTSTATUS 用于表示系统调用或函数的状态,它被定义为一个 32 位无符号整数值。成功的系统调用将返回 STATUS_SUCCESS,即 0;而如果调用失败,则返回一个非零值。要进一步调查问题原因,必须查阅 Microsoft 的 NTSTATUS 值文档

以下代码片段展示了如何进行系统调用的错误检查。

NTSTATUS STATUS = NativeSyscallExample(...);
if (STATUS != STATUS_SUCCESS){
    // 以无符号整数十六进制格式打印错误
    printf("[!] NativeSyscallExample 调用失败,状态:0x%0.8X \n", STATUS); 
}

// NativeSyscallExample 成功
c

NT_SUCCESS 宏#

另一种检查 NTAPI 返回值的方法是通过 NT_SUCCESS 宏,该宏返回 TRUE 表示函数成功,FALSE 表示失败。

#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
c

以下是使用该宏的示例:

NTSTATUS STATUS = NativeSyscallExample(...);
if (!NT_SUCCESS(STATUS)){
    // 以无符号整数十六进制格式打印错误
    printf("[!] NativeSyscallExample 调用失败,状态:0x%0.8X \n", STATUS); 
}

// NativeSyscallExample 成功
c

📚 知识扩展:NTSTATUS错误处理详解

NTSTATUS vs GetLastError():

特性Windows APINative API (NTAPI)
错误获取GetLastError()函数直接返回
错误类型DWORDNTSTATUS
成功值0STATUS_SUCCESS (0)
错误值正整数负数(最高位为1)
检查宏无标准宏NT_SUCCESS()

理解 NTSTATUS 格式:

常见NTSTATUS错误代码:

十六进制常量名称含义
0x00000000STATUS_SUCCESS成功
0xC0000005STATUS_ACCESS_VIOLATION访问违规
0xC0000008STATUS_INVALID_HANDLE无效句柄
0xC000000DSTATUS_INVALID_PARAMETER无效参数
0xC0000022STATUS_ACCESS_DENIED拒绝访问
0xC0000034STATUS_OBJECT_NAME_NOT_FOUND对象名未找到
0xC0000035STATUS_OBJECT_NAME_COLLISION对象名冲突
0xC0000043STATUS_SHARING_VIOLATION共享违规

实际使用示例:

为什么使用 Native API?

  1. 绕过 Hooks:很多 EDR 只 hook Win32 API,不hook Native API
  2. 更多功能:某些功能只有 Native API 提供
  3. 性能:减少调用层次

警告:

  • Native API 未正式文档化,可能随时改变
  • 使用难度更高
  • 建议只在必要时使用

🎯 本模块学习要点

必须掌握的概念:

  1. ✅ Windows 数据类型命名规则(P前缀、LP前缀、C前缀等)
  2. ✅ HANDLE 的概念和正确使用
  3. ✅ ANSI vs Unicode(A函数 vs W函数)
  4. ✅ IN/OUT 参数的区别
  5. ✅ 如何阅读 MSDN 文档
  6. ✅ Windows API 错误处理(GetLastError)
  7. ✅ Native API 错误处理(NTSTATUS)

关键技能:

  • 正确检查 API 返回值
  • 使用 GetLastError() 调试问题
  • 总是关闭 HANDLE 避免泄漏
  • 理解 ULONG_PTR 用于指针算术

常见数据类型速查:

Windows类型C等价用途
DWORDunsigned long32位无符号整数
HANDLEvoid*对象句柄
PVOIDvoid*通用指针
LPSTRchar*ANSI字符串指针
LPWSTRwchar_t*Unicode字符串指针
LPCWSTRconst wchar_t*常量Unicode字符串指针

实践建议:

  1. 阅读文档:每次使用新API前先看MSDN
  2. 检查返回值:永远不要假设API调用成功
  3. 错误处理:使用GetLastError()定位问题
  4. 资源管理:用完HANDLE立即关闭
  5. 优先Unicode:使用W函数而不是A函数

调试技巧:

// 创建一个通用错误处理包装器
void CheckAPIResult(BOOL result, const char* apiName) {
    if (!result) {
        DWORD error = GetLastError();
        printf("[!] %s 失败,错误码:%d (0x%08X)\n", 
               apiName, error, error);
    }
}

// 使用示例
BOOL result = WriteFile(hFile, data, size, &written, NULL);
CheckAPIResult(result, "WriteFile");
c

下一步: 学习 PE 文件格式和 DLL 的使用