burpow
第10节-从逆向角度看API和syscall

第10节-从逆向角度看API和syscall

第 10 节:从逆向角度看 API 和 syscall

所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 5 周
课程主题:系统调用
本节目标:从逆向工程和安全分析角度理解库函数、API、系统调用之间的调用链,掌握 Linux 下 ltrace / strace 的区别,以及 Windows 下 Win32 API、Native API、ntdll.dll 和 syscall 的基本关系。


1. 本节课你要学会什么

学完这一节,你应该能回答下面几个问题:

  1. 库函数、API、系统调用之间是什么关系?
  2. 为什么逆向分析常从 API 调用入手?
  3. Linux 下 libc 在系统调用前做了什么?
  4. ltracestrace 有什么区别?
  5. Windows 下 kernel32.dllKernelBase.dllntdll.dll 分别大致是什么角色?
  6. CreateFileWNtCreateFile 有什么关系?
  7. 什么是 syscall stub?
  8. 为什么恶意代码行为经常被描述成 API 序列?
  9. 为什么安全软件会监控 API 和系统调用?
  10. 如何用工具观察程序的 API / syscall 行为?

本节课的主线是:

1
源代码调用函数 -> 用户态库/API -> 更底层封装 -> syscall 入口 -> 内核处理 -> 返回用户态

上一节我们从操作系统角度学习了系统调用;这一节重点从逆向和安全分析角度看这些调用链。


2. 为什么逆向要关注 API 和 syscall

逆向工程中,单看普通计算指令往往很难快速判断程序目的。

例如你看到:

1
2
3
mov eax, DWORD PTR [rbp-0x4]
add eax, 1
mov DWORD PTR [rbp-0x4], eax

这只能说明程序在计算。

但如果你看到:

1
2
3
4
5
6
7
CreateFileW
WriteFile
CreateProcessW
connect
send
recv
VirtualAlloc

你马上可以推测程序可能有:

  • 文件操作
  • 进程创建
  • 网络通信
  • 内存分配
  • 动态执行代码

API 和系统调用是程序和操作系统交互的边界。

所以它们能反映程序行为。

逆向分析时常常先问:

1
2
3
4
这个程序调用了哪些系统 API?
这些 API 的参数是什么?
调用顺序是什么?
返回结果是什么?

3. 行为比单条指令更重要

从安全分析角度,单个 API 不一定说明问题。

例如:

1
CreateFileW

普通软件也会创建文件。

1
VirtualAlloc

普通程序也会分配内存。

1
connect

浏览器、聊天软件、更新程序都会联网。

真正重要的是:

1
API 序列 + 参数 + 上下文

例如:

1
CreateFileW -> WriteFile -> CreateProcessW

可能表示:

1
写出一个文件并执行它

再例如:

1
VirtualAlloc -> WriteProcessMemory -> CreateRemoteThread

在 Windows 安全分析中就非常值得关注,因为它可能和进程注入有关。

所以逆向时要关注行为链,而不是孤立 API。


4. Linux 下的调用链

Linux 下一个普通 C 程序调用文件写入时,可能是:

1
2
3
4
5
6
7
main
-> printf / fwrite / write
-> libc
-> syscall wrapper
-> syscall 指令
-> Linux 内核
-> 文件系统 / 设备驱动

例如:

1
printf("hello\n");

可能经历:

1
2
3
4
5
printf
-> libc 缓冲处理
-> write
-> syscall
-> 内核

如果直接写:

1
write(1, "hello\n", 6);

调用链会短一些:

1
2
3
4
write
-> libc syscall wrapper
-> syscall
-> 内核

但即使你调用的是 write,通常也不是你自己直接写 syscall 指令,而是调用 libc 提供的封装。


5. libc 是什么

libc 是 C 标准库和系统接口的重要实现。

Linux 上常见的是 glibc。

它提供很多函数,例如:

1
2
3
4
5
6
7
8
9
10
11
printf
malloc
free
open
read
write
fopen
strcpy
strlen
fork
execve

这些函数并不都直接进入内核。

可以粗略分为三类:

5.1 纯用户态函数

例如:

1
2
3
strlen
strcmp
memcpy

它们通常只处理用户态内存,不需要进入内核。


5.2 可能触发系统调用的库函数

例如:

1
2
3
4
5
printf
malloc
fopen
fread
fwrite

它们内部可能根据情况调用系统调用。

例如 malloc 可能通过:

1
2
brk
mmap

向内核申请内存区域。


5.3 系统调用封装函数

例如:

1
2
3
4
5
6
read
write
open
close
execve
fork

这些函数更接近系统调用。

它们负责按照 ABI 准备参数,执行进入内核的指令,并处理返回值和 errno


6. syscall wrapper 是什么

系统调用封装函数可以理解成 syscall wrapper。

它负责:

  1. 接收 C 函数参数
  2. 把参数放到系统调用约定要求的寄存器
  3. 设置系统调用号
  4. 执行 syscall 指令
  5. 读取返回值
  6. 如果出错,设置 errno
  7. 返回给调用者

例如用户写:

1
write(1, "hello\n", 6);

libc 内部大致会准备:

1
2
3
4
5
rax = write 的系统调用号
rdi = 1
rsi = 字符串地址
rdx = 6
syscall

你通常不需要自己写这些底层细节。

libc 帮你封装好了。


7. ltracestrace 的区别

Linux 下常用两个观察工具:

1
2
ltrace
strace

它们关注层次不同。

工具 主要观察对象 典型输出
ltrace 用户态库函数调用 printf(...)strcmp(...)malloc(...)
strace 系统调用 write(...)openat(...)mmap(...)

简单理解:

1
2
ltrace 看库函数层
strace 看系统调用层

例如程序调用:

1
printf("hello\n");

ltrace 可能看到:

1
puts("hello")

strace 可能看到:

1
write(1, "hello\n", 6)

8. 为什么 ltrace 不一定总能看到

ltrace 主要跟踪动态链接库函数调用。

如果程序:

  • 静态链接
  • 使用内联函数
  • 被优化
  • 直接系统调用
  • 被加壳或混淆
  • 不通过 PLT 调用库函数

ltrace 可能看不到预期内容。

所以分析时不要只依赖一个工具。

常见组合是:

1
strings -> ltrace -> strace -> objdump/Ghidra -> gdb

不同工具提供不同层次证据。


9. Linux 实验:对比 ltracestrace

创建 trace_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
char input[32];

printf("input: ");
scanf("%31s", input);

if (strcmp(input, "secret") == 0) {
printf("success\n");
} else {
printf("fail\n");
}

write(1, "done\n", 5);
return 0;
}

编译:

1
gcc -O0 trace_demo.c -o trace_demo

ltrace

1
ltrace ./trace_demo

输入:

1
secret

你可能看到:

1
2
3
4
5
printf("input: ")
__isoc99_scanf("%31s", ...)
strcmp("secret", "secret")
printf("success\n")
write(1, "done\n", 5)

strace

1
strace ./trace_demo

你会看到系统调用,例如:

1
2
3
read(0, ...)
write(1, ...)
exit_group(0)

思考:

1
2
ltrace 更容易看到 strcmp 的语义
strace 更容易看到程序和内核的交互

10. strcmp 为什么对逆向很有用

很多简单验证程序会使用:

1
strcmp(input, "secret")

如果用 ltrace 运行,可能直接看到:

1
strcmp("user_input", "secret")

这对逆向分析非常有帮助。

因为你不用先读复杂汇编,就能看到程序把输入和哪个字符串比较。

但真实程序可能会:

  • 自己实现比较函数
  • 加密字符串
  • 运行时解密
  • 使用哈希比较
  • 混淆控制流

所以 ltrace 对入门题和简单程序很有用,但不能依赖它解决所有逆向问题。


11. strace 看不到什么

strace 只看系统调用层。

如果一个程序在用户态内部做了大量计算,strace 可能看不到细节。

例如:

1
2
3
for (...) {
hash = hash * 131 + input[i];
}

这是用户态计算。

它不需要进入内核。

所以 strace 不会显示每次计算。

它只能看到:

  • 读输入
  • 写输出
  • 文件操作
  • 网络操作
  • 进程创建
  • 内存映射
  • 退出

因此:

1
strace 适合看系统行为,不适合看纯算法逻辑。

算法逻辑需要用:

  • GDB
  • Ghidra
  • IDA
  • 反汇编
  • 反编译

来分析。


12. Linux 动态链接和 PLT/GOT 的直觉

动态链接程序调用外部库函数时,通常涉及 PLT/GOT。

你现在不需要完全掌握细节,但需要建立直觉。

例如程序调用:

1
puts("hello");

可执行文件里并不一定直接写死 libc 中 puts 的最终地址。

因为 libc 运行时加载地址可能变化。

所以 ELF 会通过动态链接机制解析函数地址。

常见结构:

1
2
PLT:Procedure Linkage Table
GOT:Global Offset Table

简化理解:

1
2
PLT 像跳板
GOT 保存最终解析出的函数地址

逆向时看到:

1
call puts@plt

大致表示:

1
调用动态链接的 puts 函数

后面 ELF 文件格式章节会更详细讲 PLT/GOT。


13. Windows 下的 API 调用层次

Windows 下 API 层次更复杂一些。

常见调用链可以简化为:

1
2
3
4
5
6
7
应用程序
-> Win32 API
-> KernelBase.dll / kernel32.dll
-> ntdll.dll
-> Native API / NTAPI
-> syscall stub
-> Windows 内核

例如文件创建:

1
2
3
4
5
CreateFileW
-> KernelBase.dll / kernel32.dll
-> NtCreateFile
-> ntdll.dll syscall stub
-> 内核处理

注意:

Windows 版本不同,内部实现可能变化。

但逆向时常见思路是:

1
2
3
高层 Win32 API 表达语义
底层 NTAPI 更接近内核入口
ntdll.dll 中有进入内核的 syscall 代码

14. kernel32.dll 是什么

kernel32.dll 是 Windows 用户态核心 DLL 之一。

很多常见 Win32 API 都从这里导出,或者经由它转发。

常见 API:

1
2
3
4
5
6
7
CreateFileW
ReadFile
WriteFile
CreateProcessW
VirtualAlloc
GetLastError
CloseHandle

现代 Windows 中,一些实现可能转到:

1
KernelBase.dll

所以你在调试器里可能看到:

1
kernel32.dll -> KernelBase.dll -> ntdll.dll

逆向时,不要因为中间多跳了一层就困惑。

这只是 Windows API 实现层次的一部分。


15. ntdll.dll 是什么

ntdll.dll 是 Windows 用户态和内核态之间非常关键的一层。

它包含:

  • Native API / NTAPI 的用户态入口
  • 系统调用 stub
  • 运行时支持函数
  • 异常处理相关机制

常见 Native API:

1
2
3
4
5
6
7
NtCreateFile
NtReadFile
NtWriteFile
NtCreateProcess
NtAllocateVirtualMemory
NtProtectVirtualMemory
NtCreateThreadEx

许多 Win32 API 最终会调用 ntdll 中的 Native API。

例如:

1
2
3
CreateFileW -> NtCreateFile
VirtualAlloc -> NtAllocateVirtualMemory
WriteFile -> NtWriteFile

这就是为什么 Windows 逆向中经常看到 ntdll.dll


16. syscall stub 是什么

syscall stub 是用户态中一小段进入内核的代码。

在 Windows x64 中,ntdll 的某些函数内部可能类似:

1
2
3
4
mov r10, rcx
mov eax, 系统调用号
syscall
ret

简化理解:

1
2
3
准备系统调用号和参数
执行 syscall 指令
从内核返回

系统调用号会随 Windows 版本变化。

所以不要随便硬编码系统调用号。

逆向和安全分析中,看到这样的模式,可以推测它是系统调用入口。


17. CreateFileWNtCreateFile 的直觉

应用程序可能调用:

1
CreateFileW(L"test.txt", ...);

它是 Win32 API,语义比较友好。

底层可能转换成更底层的 NT 风格参数,然后调用:

1
NtCreateFile

NtCreateFile 再进入内核。

所以同一个文件创建行为,在不同工具中可能显示不同层次:

工具/层次 可能看到
源代码 CreateFileW
x64dbg 高层断点 CreateFileW
更底层调试 NtCreateFile
Process Monitor CreateFile 操作事件
内核跟踪 文件系统/对象管理器相关事件

它们描述的是同一类行为的不同层次。


18. Windows API 参数观察

Windows x64 调用约定中,前四个整数/指针参数通常是:

1
rcx, rdx, r8, r9

所以在 x64dbg 中,如果你在 CreateFileW 下断点,可以观察:

  • rcx:文件名指针
  • rdx:访问权限
  • r8:共享模式
  • r9:安全属性指针
  • 栈上:后续参数

如果 rcx 是宽字符串指针,可以在 Dump 中按 Unicode 查看。

这对分析 Windows 程序非常有用。

例如你可以看到程序实际打开了哪个路径。


19. Process Monitor 看到的是 API 吗

Process Monitor 展示的是系统行为事件。

例如:

1
2
3
4
5
6
CreateFile
ReadFile
WriteFile
RegSetValue
Process Create
TCP Connect

这些名称不一定等同于应用程序直接调用的 API 名。

它们是 Process Monitor 以易读方式展示的操作类型。

例如程序可能调用:

1
2
CreateFileW
NtCreateFile

Process Monitor 可能显示:

1
CreateFile

所以要理解:

1
工具展示的是某一层抽象,不一定是源码中的函数名。

20. API Hook 的基本概念

安全软件、沙箱、调试工具可能会 Hook API。

Hook 可以理解为:

在函数调用路径上插入自己的监控或修改逻辑。

例如监控:

1
2
3
4
CreateFileW
WriteFile
CreateProcessW
connect

当程序调用这些 API 时,监控组件可以记录:

  • 调用者是谁
  • 参数是什么
  • 返回值是什么
  • 调用时间
  • 调用栈

这对安全防护有用。

但恶意代码也可能检测 Hook 或绕过用户态 Hook。

这就是为什么会出现直接系统调用等技术。

本课程只从防御和分析角度理解这些概念。


21. 为什么恶意代码行为常被描述为 API 序列

因为 API 序列能很好描述行为。

例如:

1
URLDownloadToFileW -> CreateProcessW

可能表示:

1
下载文件并执行

例如:

1
OpenProcess -> VirtualAllocEx -> WriteProcessMemory -> CreateRemoteThread

可能表示:

1
向其他进程写入代码并创建远程线程

例如:

1
RegOpenKeyExW -> RegSetValueExW

可能表示:

1
修改注册表

API 序列比单个 API 更能表达意图。


22. API 序列分析的注意事项

不要看到某个 API 就直接判定恶意。

例如:

1
VirtualAlloc

很多正常程序都会使用。

1
CreateProcessW

安装程序、更新程序、终端、IDE 都会使用。

分析时要结合:

  • 父进程是谁
  • 程序路径是否可疑
  • 命令行参数是什么
  • 是否在临时目录
  • 是否隐藏窗口
  • 是否连接陌生网络
  • 是否写入启动项
  • 是否修改其他进程内存
  • 行为是否符合业务逻辑

安全分析不是只看 API 名称,而是看上下文。


23. Linux API / syscall 行为链示例

Linux 程序行为也可以用系统调用序列描述。

例如文件复制:

1
openat -> read -> openat -> write -> close

例如执行命令:

1
clone -> execve -> wait4

例如网络连接:

1
socket -> connect -> sendto -> recvfrom

例如修改内存权限:

1
mmap -> mprotect

看到这些序列,你就能推断程序行为。


24. 从逆向角度看导入表

在 Windows PE 中,Import Table 会列出程序导入的 DLL 和函数。

例如:

1
2
3
kernel32.dll: CreateFileW, WriteFile, CreateProcessW
ws2_32.dll: socket, connect, send, recv
advapi32.dll: RegSetValueExW

这些导入函数是静态线索。

它们说明程序可能使用相关功能。

但注意:

  1. 导入了不一定真的调用
  2. 没导入也不代表不会用
  3. 程序可能动态解析 API
  4. 程序可能加壳隐藏导入表

所以导入表是线索,不是最终结论。


25. 动态解析 API

有些程序不会在导入表中直接显示敏感 API。

它可能运行时调用:

1
2
LoadLibrary
GetProcAddress

动态获取 API 地址。

例如:

1
2
LoadLibraryA("kernel32.dll")
GetProcAddress(handle, "VirtualAlloc")

这样静态导入表里可能看不到 VirtualAlloc

恶意代码常用这种方式隐藏行为。

分析时可以:

  • 观察字符串
  • LoadLibrary / GetProcAddress 下断点
  • 动态调试返回的函数地址
  • 监控后续 API 行为

26. Linux 中的动态加载

Linux 也有动态加载机制。

常见函数:

1
2
3
dlopen
dlsym
dlclose

例如程序运行时加载某个 .so

1
2
dl_handle = dlopen("libexample.so", RTLD_NOW);
func = dlsym(dl_handle, "target_func");

这和 Windows 的 LoadLibrary / GetProcAddress 类似。

逆向时,如果看到:

1
2
dlopen
dlsym

说明程序可能运行时解析函数。


27. Linux 实验:ltrace 看库函数

创建 api_trace_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main() {
char input[32];

printf("password: ");
scanf("%31s", input);

if (strcmp(input, "secret123") == 0) {
puts("success");
} else {
puts("fail");
}

char *p = malloc(16);
if (p) {
strcpy(p, "done");
puts(p);
free(p);
}

return 0;
}

编译:

1
gcc -O0 api_trace_demo.c -o api_trace_demo

运行:

1
ltrace ./api_trace_demo

输入:

1
secret123

观察:

  • printf
  • scanf
  • strcmp
  • puts
  • malloc
  • strcpy
  • free

思考:

1
2
哪些调用是纯用户态库函数?
哪些调用可能进一步触发系统调用?

28. Linux 实验:strace 看系统调用

继续使用 api_trace_demo

执行:

1
strace ./api_trace_demo

或者过滤:

1
strace -e trace=read,write,brk,mmap,munmap ./api_trace_demo

观察:

  • 输入对应的 read
  • 输出对应的 write
  • 内存相关的 brk / mmap

对比 ltrace

1
2
ltrace 能看到 strcmp、malloc、free
strace 能看到 read、write、mmap、brk

这说明两者关注层次不同。


29. Linux 实验:API 行为链分析

创建 behavior_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main() {
int fd = open("behavior.txt", O_CREAT | O_WRONLY | O_TRUNC, 0644);
if (fd < 0) {
perror("open");
return 1;
}

write(fd, "hello behavior\n", 15);
close(fd);
return 0;
}

编译:

1
gcc behavior_demo.c -o behavior_demo

观察:

1
strace -e trace=file,write,close ./behavior_demo

你应该能得到类似行为链:

1
openat -> write -> close

用自然语言描述:

1
程序创建 behavior.txt,写入 hello behavior,然后关闭文件。

这就是把系统调用序列转化成行为描述。


30. Windows 实验:Process Monitor 看行为链

Windows 下可以准备一个简单程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <windows.h>

int main() {
HANDLE h = CreateFileW(
L"behavior.txt",
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);

if (h != INVALID_HANDLE_VALUE) {
DWORD written;
WriteFile(h, "hello behavior\r\n", 16, &written, NULL);
CloseHandle(h);
}

return 0;
}

用 Process Monitor 观察:

  1. 过滤进程名
  2. 运行程序
  3. 查看文件事件

你可能看到:

1
2
3
CreateFile
WriteFile
CloseFile

用自然语言描述:

1
程序创建 behavior.txt,并写入数据。

31. Windows 实验:x64dbg 看 API 参数

用 x64dbg 打开上面的 Windows 程序。

尝试:

  1. CreateFileW 下断点
  2. 运行程序
  3. 命中断点后查看寄存器
  4. 查看 rcx 指向的宽字符串
  5. 继续运行到 WriteFile
  6. 查看写入缓冲区
  7. 查看 rax 返回值

重点:

1
API 名称 + 参数 + 返回值 = 行为证据

如果你只知道调用了 CreateFileW,信息还不够。

你还要知道:

  • 创建了哪个路径
  • 访问权限是什么
  • 创建方式是什么
  • 是否成功

32. 从漏洞分析角度看 API/syscall

漏洞分析时,API 和 syscall 能帮你定位输入和危险操作。

例如:

1
read -> strcpy -> crash

可能表示:

1
从外部读取输入后,没有边界检查就复制到缓冲区

例如:

1
recv -> memcpy -> heap corruption

可能表示:

1
网络输入影响堆内存

例如:

1
open -> read -> parse -> crash

可能表示:

1
文件解析漏洞

你要逐渐形成数据流意识:

1
2
3
4
输入从哪里来?
经过哪些函数?
写到哪里?
在哪里崩溃?

API/syscall 是追踪输入来源的重要线索。


33. 从恶意代码分析角度看 API/syscall

恶意代码分析中,API/syscall 更是核心。

常见行为和 API 对应:

行为 Windows 常见 API Linux 常见 syscall/API
创建文件 CreateFileW openat
写文件 WriteFile write
创建进程 CreateProcessW fork/execve/clone
网络连接 connect / WinHttp socket/connect
分配内存 VirtualAlloc mmap/brk
改内存权限 VirtualProtect mprotect
创建线程 CreateThread clone/pthread_create
注册表修改 RegSetValueExW Linux 无直接注册表概念
动态加载库 LoadLibraryW dlopen
获取函数地址 GetProcAddress dlsym

分析时要把底层调用翻译成行为。

例如:

1
mmap -> write data -> mprotect(PROT_EXEC)

可能表示程序在准备可执行内存。


34. 本节重点总结

你需要记住这些核心结论:

  1. API 和系统调用是程序与操作系统交互的重要边界。
  2. 逆向分析中,API 调用比普通计算指令更容易体现程序行为。
  3. Linux 下 libc 提供大量库函数和系统调用封装。
  4. ltrace 主要观察库函数调用,strace 主要观察系统调用。
  5. strace 适合看系统行为,不适合看纯用户态算法细节。
  6. Windows 下常见调用链是 Win32 API -> KernelBase/kernel32 -> ntdll -> syscall -> 内核。
  7. ntdll.dll 中包含 Native API 和 syscall stub。
  8. Process Monitor 展示的是系统行为事件,不一定等于源码中的 API 名称。
  9. 恶意代码行为常被描述为 API 序列,因为 API 序列能表达程序意图。
  10. 分析 API/syscall 时必须结合参数、返回值和上下文。

35. 本节课后作业

作业 1:对比 ltracestrace

trace_demo.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main() {
char input[32];

printf("input: ");
scanf("%31s", input);

if (strcmp(input, "secret") == 0) {
printf("success\n");
} else {
printf("fail\n");
}

write(1, "done\n", 5);
return 0;
}

执行:

1
2
3
gcc -O0 trace_demo.c -o trace_demo
ltrace ./trace_demo
strace ./trace_demo

提交内容:

1
2
3
4
5
1. ltrace 中你看到的关键库函数
2. strace 中你看到的关键系统调用
3. strcmp 的参数是什么
4. write 的参数是什么
5. ltrace 和 strace 的区别

作业 2:分析文件行为链

behavior_demo.c,创建并写入文件。

执行:

1
strace -e trace=file,write,close ./behavior_demo

提交内容:

1
2
3
4
5
1. 系统调用序列
2. 创建的文件名
3. 写入的内容
4. 返回值是否成功
5. 用自然语言描述程序行为

作业 3:库函数和系统调用分类

根据 api_trace_demo.cltracestrace 输出,列出:

1
2
3
1. 纯用户态库函数
2. 可能触发系统调用的库函数
3. 实际观察到的系统调用

思考:

1
2
为什么 strcmp 不会出现在 strace 里?
为什么 printf 可能最终体现为 write?

作业 4:Windows API 行为观察

如果你有 Windows 环境:

  1. 写一个调用 CreateFileW / WriteFile / CloseHandle 的程序
  2. 用 Process Monitor 观察文件行为
  3. 用 x64dbg 在 CreateFileW 下断点
  4. 查看参数和返回值

提交内容:

1
2
3
4
5
1. Process Monitor 中的行为事件
2. x64dbg 中 CreateFileW 的参数
3. 文件路径在哪里看到
4. API 返回值是什么
5. Process Monitor 事件和 API 调用有什么对应关系

作业 5:导入表分析

找一个简单 Windows exe 或 Linux ELF。

Windows:

  • 用 PE-bear / Ghidra 查看 Imports

Linux:

1
2
readelf -s program
objdump -T program

提交内容:

1
2
3
4
5
1. 程序导入了哪些库
2. 至少 5 个导入函数
3. 这些函数暗示了哪些可能行为
4. 导入函数是否一定会被调用
5. 如果程序动态解析 API,导入表可能有什么局限

36. 自测题

题 1

ltracestrace 的主要区别是什么?

题 2

为什么 strcmp 通常能被 ltrace 看到,但不会被 strace 看到?

题 3

Linux 下 libc 的 syscall wrapper 大致做什么?

题 4

Windows 下 CreateFileWNtCreateFile 大致是什么关系?

题 5

ntdll.dll 为什么在 Windows 逆向中重要?

题 6

为什么 API 序列比单个 API 更能表达程序行为?

题 7

导入表能告诉我们什么?它有什么局限?

题 8

为什么分析 API/syscall 时必须看参数和返回值?


37. 自测题参考答案

答 1

ltrace 主要跟踪用户态动态库函数调用,例如 printfstrcmpmallocstrace 主要跟踪系统调用,例如 readwriteopenatmmap

答 2

因为 strcmp 通常是用户态库函数,只在进程自己的地址空间中比较内存,不需要进入内核;strace 只观察系统调用层,所以通常看不到 strcmp

答 3

它接收 C 函数参数,按照系统调用约定把系统调用号和参数放到指定寄存器,执行 syscall 指令进入内核,处理返回值和错误码,再返回给调用者。

答 4

CreateFileW 是高层 Win32 API,语义更友好;它底层可能经过 KernelBase/kernel32 转换参数后调用 ntdll 中的 NtCreateFile,再通过 syscall 进入内核。

答 5

因为 ntdll.dll 包含许多 Native API 和进入内核的 syscall stub,是 Windows 用户态到内核态的重要边界,很多底层行为和反调试/安全分析都会涉及它。

答 6

单个 API 可能出现在正常程序中,不能直接说明意图;API 序列结合顺序和上下文更能表达行为,例如写文件后创建进程、分配内存后修改权限等。

答 7

导入表能显示程序静态导入的库和函数,帮助推测可能行为。局限是导入了不一定调用,没导入也可能通过动态解析 API 使用相关功能,加壳或混淆也可能隐藏导入。

答 8

因为 API 名称只能说明操作类型,参数和返回值才能说明具体对象和结果。例如 CreateFileW 必须看文件路径、访问权限和返回句柄,才能判断它实际做了什么以及是否成功。


38. 下一节预告

下一节课会讲:

1
ELF 文件格式

你会学习:

  • ELF Header
  • Program Header
  • Section Header
  • .text.data.bss.rodata
  • .plt.got
  • 动态链接和静态链接
  • 如何用 readelfobjdump 观察 ELF 结构
隐藏
换装
本文作者:burpow
本文链接:https://youthfulnesszxx.github.io/2026/05/28/第10节-从逆向角度看API和syscall/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可