第10节-从逆向角度看API和syscall
第 10 节:从逆向角度看 API 和 syscall
所属课程:操作系统自学路线:面向网络安全、逆向工程与漏洞分析
所属周次:第 5 周
课程主题:系统调用
本节目标:从逆向工程和安全分析角度理解库函数、API、系统调用之间的调用链,掌握 Linux 下ltrace/strace的区别,以及 Windows 下 Win32 API、Native API、ntdll.dll和 syscall 的基本关系。
1. 本节课你要学会什么
学完这一节,你应该能回答下面几个问题:
- 库函数、API、系统调用之间是什么关系?
- 为什么逆向分析常从 API 调用入手?
- Linux 下 libc 在系统调用前做了什么?
ltrace和strace有什么区别?- Windows 下
kernel32.dll、KernelBase.dll、ntdll.dll分别大致是什么角色? CreateFileW和NtCreateFile有什么关系?- 什么是 syscall stub?
- 为什么恶意代码行为经常被描述成 API 序列?
- 为什么安全软件会监控 API 和系统调用?
- 如何用工具观察程序的 API / syscall 行为?
本节课的主线是:
1 | |
上一节我们从操作系统角度学习了系统调用;这一节重点从逆向和安全分析角度看这些调用链。
2. 为什么逆向要关注 API 和 syscall
逆向工程中,单看普通计算指令往往很难快速判断程序目的。
例如你看到:
1 | |
这只能说明程序在计算。
但如果你看到:
1 | |
你马上可以推测程序可能有:
- 文件操作
- 进程创建
- 网络通信
- 内存分配
- 动态执行代码
API 和系统调用是程序和操作系统交互的边界。
所以它们能反映程序行为。
逆向分析时常常先问:
1 | |
3. 行为比单条指令更重要
从安全分析角度,单个 API 不一定说明问题。
例如:
1 | |
普通软件也会创建文件。
1 | |
普通程序也会分配内存。
1 | |
浏览器、聊天软件、更新程序都会联网。
真正重要的是:
1 | |
例如:
1 | |
可能表示:
1 | |
再例如:
1 | |
在 Windows 安全分析中就非常值得关注,因为它可能和进程注入有关。
所以逆向时要关注行为链,而不是孤立 API。
4. Linux 下的调用链
Linux 下一个普通 C 程序调用文件写入时,可能是:
1 | |
例如:
1 | |
可能经历:
1 | |
如果直接写:
1 | |
调用链会短一些:
1 | |
但即使你调用的是 write,通常也不是你自己直接写 syscall 指令,而是调用 libc 提供的封装。
5. libc 是什么
libc 是 C 标准库和系统接口的重要实现。
Linux 上常见的是 glibc。
它提供很多函数,例如:
1 | |
这些函数并不都直接进入内核。
可以粗略分为三类:
5.1 纯用户态函数
例如:
1 | |
它们通常只处理用户态内存,不需要进入内核。
5.2 可能触发系统调用的库函数
例如:
1 | |
它们内部可能根据情况调用系统调用。
例如 malloc 可能通过:
1 | |
向内核申请内存区域。
5.3 系统调用封装函数
例如:
1 | |
这些函数更接近系统调用。
它们负责按照 ABI 准备参数,执行进入内核的指令,并处理返回值和 errno。
6. syscall wrapper 是什么
系统调用封装函数可以理解成 syscall wrapper。
它负责:
- 接收 C 函数参数
- 把参数放到系统调用约定要求的寄存器
- 设置系统调用号
- 执行
syscall指令 - 读取返回值
- 如果出错,设置
errno - 返回给调用者
例如用户写:
1 | |
libc 内部大致会准备:
1 | |
你通常不需要自己写这些底层细节。
libc 帮你封装好了。
7. ltrace 和 strace 的区别
Linux 下常用两个观察工具:
1 | |
它们关注层次不同。
| 工具 | 主要观察对象 | 典型输出 |
|---|---|---|
| ltrace | 用户态库函数调用 | printf(...)、strcmp(...)、malloc(...) |
| strace | 系统调用 | write(...)、openat(...)、mmap(...) |
简单理解:
1 | |
例如程序调用:
1 | |
ltrace 可能看到:
1 | |
strace 可能看到:
1 | |
8. 为什么 ltrace 不一定总能看到
ltrace 主要跟踪动态链接库函数调用。
如果程序:
- 静态链接
- 使用内联函数
- 被优化
- 直接系统调用
- 被加壳或混淆
- 不通过 PLT 调用库函数
ltrace 可能看不到预期内容。
所以分析时不要只依赖一个工具。
常见组合是:
1 | |
不同工具提供不同层次证据。
9. Linux 实验:对比 ltrace 和 strace
创建 trace_demo.c:
1 | |
编译:
1 | |
用 ltrace:
1 | |
输入:
1 | |
你可能看到:
1 | |
用 strace:
1 | |
你会看到系统调用,例如:
1 | |
思考:
1 | |
10. strcmp 为什么对逆向很有用
很多简单验证程序会使用:
1 | |
如果用 ltrace 运行,可能直接看到:
1 | |
这对逆向分析非常有帮助。
因为你不用先读复杂汇编,就能看到程序把输入和哪个字符串比较。
但真实程序可能会:
- 自己实现比较函数
- 加密字符串
- 运行时解密
- 使用哈希比较
- 混淆控制流
所以 ltrace 对入门题和简单程序很有用,但不能依赖它解决所有逆向问题。
11. strace 看不到什么
strace 只看系统调用层。
如果一个程序在用户态内部做了大量计算,strace 可能看不到细节。
例如:
1 | |
这是用户态计算。
它不需要进入内核。
所以 strace 不会显示每次计算。
它只能看到:
- 读输入
- 写输出
- 文件操作
- 网络操作
- 进程创建
- 内存映射
- 退出
因此:
1 | |
算法逻辑需要用:
- GDB
- Ghidra
- IDA
- 反汇编
- 反编译
来分析。
12. Linux 动态链接和 PLT/GOT 的直觉
动态链接程序调用外部库函数时,通常涉及 PLT/GOT。
你现在不需要完全掌握细节,但需要建立直觉。
例如程序调用:
1 | |
可执行文件里并不一定直接写死 libc 中 puts 的最终地址。
因为 libc 运行时加载地址可能变化。
所以 ELF 会通过动态链接机制解析函数地址。
常见结构:
1 | |
简化理解:
1 | |
逆向时看到:
1 | |
大致表示:
1 | |
后面 ELF 文件格式章节会更详细讲 PLT/GOT。
13. Windows 下的 API 调用层次
Windows 下 API 层次更复杂一些。
常见调用链可以简化为:
1 | |
例如文件创建:
1 | |
注意:
Windows 版本不同,内部实现可能变化。
但逆向时常见思路是:
1 | |
14. kernel32.dll 是什么
kernel32.dll 是 Windows 用户态核心 DLL 之一。
很多常见 Win32 API 都从这里导出,或者经由它转发。
常见 API:
1 | |
现代 Windows 中,一些实现可能转到:
1 | |
所以你在调试器里可能看到:
1 | |
逆向时,不要因为中间多跳了一层就困惑。
这只是 Windows API 实现层次的一部分。
15. ntdll.dll 是什么
ntdll.dll 是 Windows 用户态和内核态之间非常关键的一层。
它包含:
- Native API / NTAPI 的用户态入口
- 系统调用 stub
- 运行时支持函数
- 异常处理相关机制
常见 Native API:
1 | |
许多 Win32 API 最终会调用 ntdll 中的 Native API。
例如:
1 | |
这就是为什么 Windows 逆向中经常看到 ntdll.dll。
16. syscall stub 是什么
syscall stub 是用户态中一小段进入内核的代码。
在 Windows x64 中,ntdll 的某些函数内部可能类似:
1 | |
简化理解:
1 | |
系统调用号会随 Windows 版本变化。
所以不要随便硬编码系统调用号。
逆向和安全分析中,看到这样的模式,可以推测它是系统调用入口。
17. CreateFileW 到 NtCreateFile 的直觉
应用程序可能调用:
1 | |
它是 Win32 API,语义比较友好。
底层可能转换成更底层的 NT 风格参数,然后调用:
1 | |
NtCreateFile 再进入内核。
所以同一个文件创建行为,在不同工具中可能显示不同层次:
| 工具/层次 | 可能看到 |
|---|---|
| 源代码 | CreateFileW |
| x64dbg 高层断点 | CreateFileW |
| 更底层调试 | NtCreateFile |
| Process Monitor | CreateFile 操作事件 |
| 内核跟踪 | 文件系统/对象管理器相关事件 |
它们描述的是同一类行为的不同层次。
18. Windows API 参数观察
Windows x64 调用约定中,前四个整数/指针参数通常是:
1 | |
所以在 x64dbg 中,如果你在 CreateFileW 下断点,可以观察:
rcx:文件名指针rdx:访问权限r8:共享模式r9:安全属性指针- 栈上:后续参数
如果 rcx 是宽字符串指针,可以在 Dump 中按 Unicode 查看。
这对分析 Windows 程序非常有用。
例如你可以看到程序实际打开了哪个路径。
19. Process Monitor 看到的是 API 吗
Process Monitor 展示的是系统行为事件。
例如:
1 | |
这些名称不一定等同于应用程序直接调用的 API 名。
它们是 Process Monitor 以易读方式展示的操作类型。
例如程序可能调用:
1 | |
Process Monitor 可能显示:
1 | |
所以要理解:
1 | |
20. API Hook 的基本概念
安全软件、沙箱、调试工具可能会 Hook API。
Hook 可以理解为:
在函数调用路径上插入自己的监控或修改逻辑。
例如监控:
1 | |
当程序调用这些 API 时,监控组件可以记录:
- 调用者是谁
- 参数是什么
- 返回值是什么
- 调用时间
- 调用栈
这对安全防护有用。
但恶意代码也可能检测 Hook 或绕过用户态 Hook。
这就是为什么会出现直接系统调用等技术。
本课程只从防御和分析角度理解这些概念。
21. 为什么恶意代码行为常被描述为 API 序列
因为 API 序列能很好描述行为。
例如:
1 | |
可能表示:
1 | |
例如:
1 | |
可能表示:
1 | |
例如:
1 | |
可能表示:
1 | |
API 序列比单个 API 更能表达意图。
22. API 序列分析的注意事项
不要看到某个 API 就直接判定恶意。
例如:
1 | |
很多正常程序都会使用。
1 | |
安装程序、更新程序、终端、IDE 都会使用。
分析时要结合:
- 父进程是谁
- 程序路径是否可疑
- 命令行参数是什么
- 是否在临时目录
- 是否隐藏窗口
- 是否连接陌生网络
- 是否写入启动项
- 是否修改其他进程内存
- 行为是否符合业务逻辑
安全分析不是只看 API 名称,而是看上下文。
23. Linux API / syscall 行为链示例
Linux 程序行为也可以用系统调用序列描述。
例如文件复制:
1 | |
例如执行命令:
1 | |
例如网络连接:
1 | |
例如修改内存权限:
1 | |
看到这些序列,你就能推断程序行为。
24. 从逆向角度看导入表
在 Windows PE 中,Import Table 会列出程序导入的 DLL 和函数。
例如:
1 | |
这些导入函数是静态线索。
它们说明程序可能使用相关功能。
但注意:
- 导入了不一定真的调用
- 没导入也不代表不会用
- 程序可能动态解析 API
- 程序可能加壳隐藏导入表
所以导入表是线索,不是最终结论。
25. 动态解析 API
有些程序不会在导入表中直接显示敏感 API。
它可能运行时调用:
1 | |
动态获取 API 地址。
例如:
1 | |
这样静态导入表里可能看不到 VirtualAlloc。
恶意代码常用这种方式隐藏行为。
分析时可以:
- 观察字符串
- 对
LoadLibrary/GetProcAddress下断点 - 动态调试返回的函数地址
- 监控后续 API 行为
26. Linux 中的动态加载
Linux 也有动态加载机制。
常见函数:
1 | |
例如程序运行时加载某个 .so:
1 | |
这和 Windows 的 LoadLibrary / GetProcAddress 类似。
逆向时,如果看到:
1 | |
说明程序可能运行时解析函数。
27. Linux 实验:ltrace 看库函数
创建 api_trace_demo.c:
1 | |
编译:
1 | |
运行:
1 | |
输入:
1 | |
观察:
printfscanfstrcmpputsmallocstrcpyfree
思考:
1 | |
28. Linux 实验:strace 看系统调用
继续使用 api_trace_demo。
执行:
1 | |
或者过滤:
1 | |
观察:
- 输入对应的
read - 输出对应的
write - 内存相关的
brk/mmap
对比 ltrace:
1 | |
这说明两者关注层次不同。
29. Linux 实验:API 行为链分析
创建 behavior_demo.c:
1 | |
编译:
1 | |
观察:
1 | |
你应该能得到类似行为链:
1 | |
用自然语言描述:
1 | |
这就是把系统调用序列转化成行为描述。
30. Windows 实验:Process Monitor 看行为链
Windows 下可以准备一个简单程序:
1 | |
用 Process Monitor 观察:
- 过滤进程名
- 运行程序
- 查看文件事件
你可能看到:
1 | |
用自然语言描述:
1 | |
31. Windows 实验:x64dbg 看 API 参数
用 x64dbg 打开上面的 Windows 程序。
尝试:
- 在
CreateFileW下断点 - 运行程序
- 命中断点后查看寄存器
- 查看
rcx指向的宽字符串 - 继续运行到
WriteFile - 查看写入缓冲区
- 查看
rax返回值
重点:
1 | |
如果你只知道调用了 CreateFileW,信息还不够。
你还要知道:
- 创建了哪个路径
- 访问权限是什么
- 创建方式是什么
- 是否成功
32. 从漏洞分析角度看 API/syscall
漏洞分析时,API 和 syscall 能帮你定位输入和危险操作。
例如:
1 | |
可能表示:
1 | |
例如:
1 | |
可能表示:
1 | |
例如:
1 | |
可能表示:
1 | |
你要逐渐形成数据流意识:
1 | |
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 | |
可能表示程序在准备可执行内存。
34. 本节重点总结
你需要记住这些核心结论:
- API 和系统调用是程序与操作系统交互的重要边界。
- 逆向分析中,API 调用比普通计算指令更容易体现程序行为。
- Linux 下 libc 提供大量库函数和系统调用封装。
ltrace主要观察库函数调用,strace主要观察系统调用。strace适合看系统行为,不适合看纯用户态算法细节。- Windows 下常见调用链是 Win32 API -> KernelBase/kernel32 -> ntdll -> syscall -> 内核。
ntdll.dll中包含 Native API 和 syscall stub。- Process Monitor 展示的是系统行为事件,不一定等于源码中的 API 名称。
- 恶意代码行为常被描述为 API 序列,因为 API 序列能表达程序意图。
- 分析 API/syscall 时必须结合参数、返回值和上下文。
35. 本节课后作业
作业 1:对比 ltrace 和 strace
写 trace_demo.c:
1 | |
执行:
1 | |
提交内容:
1 | |
作业 2:分析文件行为链
写 behavior_demo.c,创建并写入文件。
执行:
1 | |
提交内容:
1 | |
作业 3:库函数和系统调用分类
根据 api_trace_demo.c 的 ltrace 和 strace 输出,列出:
1 | |
思考:
1 | |
作业 4:Windows API 行为观察
如果你有 Windows 环境:
- 写一个调用
CreateFileW/WriteFile/CloseHandle的程序 - 用 Process Monitor 观察文件行为
- 用 x64dbg 在
CreateFileW下断点 - 查看参数和返回值
提交内容:
1 | |
作业 5:导入表分析
找一个简单 Windows exe 或 Linux ELF。
Windows:
- 用 PE-bear / Ghidra 查看 Imports
Linux:
1 | |
提交内容:
1 | |
36. 自测题
题 1
ltrace 和 strace 的主要区别是什么?
题 2
为什么 strcmp 通常能被 ltrace 看到,但不会被 strace 看到?
题 3
Linux 下 libc 的 syscall wrapper 大致做什么?
题 4
Windows 下 CreateFileW 和 NtCreateFile 大致是什么关系?
题 5
ntdll.dll 为什么在 Windows 逆向中重要?
题 6
为什么 API 序列比单个 API 更能表达程序行为?
题 7
导入表能告诉我们什么?它有什么局限?
题 8
为什么分析 API/syscall 时必须看参数和返回值?
37. 自测题参考答案
答 1
ltrace 主要跟踪用户态动态库函数调用,例如 printf、strcmp、malloc;strace 主要跟踪系统调用,例如 read、write、openat、mmap。
答 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 Header
- Program Header
- Section Header
.text、.data、.bss、.rodata.plt、.got- 动态链接和静态链接
- 如何用
readelf、objdump观察 ELF 结构